Skip to content

Enum Practical Patterns

Real-world enum patterns that solve common application needs: state machines, configuration, and domain modeling.


State Machine Pattern

from enum import Enum
from typing import Optional

class OrderState(Enum):
    PENDING = "pending"
    PAID = "paid"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

    def can_transition_to(self, next_state) -> bool:
        '''Check if transition is allowed'''
        valid_transitions = {
            OrderState.PENDING: {OrderState.PAID, OrderState.CANCELLED},
            OrderState.PAID: {OrderState.SHIPPED, OrderState.CANCELLED},
            OrderState.SHIPPED: {OrderState.DELIVERED},
            OrderState.DELIVERED: set(),
            OrderState.CANCELLED: set()
        }
        return next_state in valid_transitions.get(self, set())

order_state = OrderState.PENDING
print(order_state.can_transition_to(OrderState.PAID))      # True
print(order_state.can_transition_to(OrderState.SHIPPED))   # False

Configuration with Enums

from enum import Enum
from dataclasses import dataclass

class Environment(Enum):
    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"

    @property
    def config(self):
        '''Get configuration for environment'''
        configs = {
            Environment.DEVELOPMENT: {
                'debug': True,
                'log_level': 'DEBUG',
                'database_url': 'sqlite:///:memory:'
            },
            Environment.STAGING: {
                'debug': False,
                'log_level': 'INFO',
                'database_url': 'postgresql://staging-db'
            },
            Environment.PRODUCTION: {
                'debug': False,
                'log_level': 'WARNING',
                'database_url': 'postgresql://prod-db'
            }
        }
        return configs[self]

env = Environment.PRODUCTION
config = env.config
print(f"Debug: {config['debug']}, DB: {config['database_url']}")

HTTP Method and Status Codes

from enum import Enum, IntEnum

class HttpMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"
    PATCH = "PATCH"

    def is_idempotent(self) -> bool:
        '''Check if method is idempotent'''
        idempotent = {HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE}
        return self in idempotent

class HttpStatus(IntEnum):
    OK = 200
    CREATED = 201
    BAD_REQUEST = 400
    UNAUTHORIZED = 401
    NOT_FOUND = 404
    SERVER_ERROR = 500

    @property
    def is_success(self) -> bool:
        '''Check if status indicates success'''
        return 200 <= self.value < 300

    @property
    def is_client_error(self) -> bool:
        '''Check if status indicates client error'''
        return 400 <= self.value < 500

method = HttpMethod.PUT
print(f"PUT is idempotent: {method.is_idempotent()}")  # True

status = HttpStatus.CREATED
print(f"201 is success: {status.is_success}")  # True

User Roles and Permissions

from enum import Enum

class UserRole(Enum):
    GUEST = 0
    USER = 1
    MODERATOR = 2
    ADMIN = 3

    def can_perform(self, action: str) -> bool:
        '''Check if role can perform action'''
        permissions = {
            'view_content': {UserRole.GUEST, UserRole.USER, UserRole.MODERATOR, UserRole.ADMIN},
            'edit_own': {UserRole.USER, UserRole.MODERATOR, UserRole.ADMIN},
            'edit_others': {UserRole.MODERATOR, UserRole.ADMIN},
            'delete_content': {UserRole.MODERATOR, UserRole.ADMIN},
            'manage_users': {UserRole.ADMIN}
        }
        return self in permissions.get(action, set())

admin = UserRole.ADMIN
user = UserRole.USER

print(f"Admin can manage users: {admin.can_perform('manage_users')}")  # True
print(f"User can manage users: {user.can_perform('manage_users')}")    # False
print(f"User can edit own: {user.can_perform('edit_own')}")            # True

Notification Types and Handling

from enum import Enum
from typing import Callable, Dict

class NotificationType(Enum):
    EMAIL = "email"
    SMS = "sms"
    PUSH = "push"
    WEBHOOK = "webhook"

    def get_handler(self) -> Callable:
        '''Get handler function for notification type'''
        handlers = {
            NotificationType.EMAIL: self._send_email,
            NotificationType.SMS: self._send_sms,
            NotificationType.PUSH: self._send_push,
            NotificationType.WEBHOOK: self._send_webhook
        }
        return handlers[self]

    @staticmethod
    def _send_email(message: str) -> bool:
        print(f"Sending email: {message}")
        return True

    @staticmethod
    def _send_sms(message: str) -> bool:
        print(f"Sending SMS: {message}")
        return True

    @staticmethod
    def _send_push(message: str) -> bool:
        print(f"Sending push notification: {message}")
        return True

    @staticmethod
    def _send_webhook(message: str) -> bool:
        print(f"Posting webhook: {message}")
        return True

notif_type = NotificationType.EMAIL
handler = notif_type.get_handler()
handler("Hello, World!")

File Type and Handler

from enum import Enum

class FileType(Enum):
    JSON = ".json"
    CSV = ".csv"
    XML = ".xml"
    YAML = ".yaml"

    @property
    def parser_module(self) -> str:
        '''Get module name for parsing this file type'''
        modules = {
            FileType.JSON: "json",
            FileType.CSV: "csv",
            FileType.XML: "xml.etree.ElementTree",
            FileType.YAML: "yaml"
        }
        return modules[self]

    def validate_content(self, content: str) -> bool:
        '''Basic validation for file type'''
        validators = {
            FileType.JSON: lambda c: c.strip().startswith(('{', '[')),
            FileType.CSV: lambda c: True,  # Minimal validation
            FileType.XML: lambda c: c.strip().startswith('<'),
            FileType.YAML: lambda c: True
        }
        return validators[self](content)

file_type = FileType.JSON
print(f"JSON uses: {file_type.parser_module}")      # json
print(f"Valid JSON: {file_type.validate_content('{}')}")  # True

Color and Formatting

from enum import Enum

class ColorCode(Enum):
    RED = 31
    GREEN = 32
    YELLOW = 33
    BLUE = 34

    def format_text(self, text: str) -> str:
        '''Format text with ANSI color code'''
        return f"[{self.value}m{text}"

text = "Important!"
print(ColorCode.RED.format_text(text))      # Displays in red
print(ColorCode.GREEN.format_text("Good"))  # Displays in green

Time Period Enums

from enum import Enum
from datetime import timedelta

class TimePeriod(Enum):
    HOURLY = timedelta(hours=1)
    DAILY = timedelta(days=1)
    WEEKLY = timedelta(days=7)
    MONTHLY = timedelta(days=30)
    YEARLY = timedelta(days=365)

    def get_seconds(self) -> int:
        '''Get period duration in seconds'''
        return int(self.value.total_seconds())

period = TimePeriod.WEEKLY
print(f"Weekly period: {period.get_seconds()} seconds")  # 604800

Runnable Example: poker_game_example.py

"""
OOP Case Study: Poker Card Game with Enumerations

A practical OOP example combining Enum, classes, properties,
magic methods, and composition to build a card game simulator.

Topics covered:
- Enum with @unique for card suits
- Dunder methods: __str__, __repr__, __lt__
- @property for computed attributes
- Composition: Poker has Cards, Player has Cards
- List comprehensions with nested loops
- random.shuffle for shuffling

Based on concepts from Python-100-Days example14 and ch06/enum materials.
"""

import random
from enum import Enum, unique


# =============================================================================
# Example 1: Card Suit Enumeration
# =============================================================================

@unique
class Suit(Enum):
    """Card suits as an enumeration.

    @unique ensures no duplicate values.
    Custom __lt__ enables sorting by suit order.
    """
    SPADE = 0
    HEART = 1
    CLUB = 2
    DIAMOND = 3

    def __lt__(self, other):
        if self.__class__ is other.__class__:
            return self.value < other.value
        return NotImplemented

    @property
    def symbol(self) -> str:
        symbols = {
            Suit.SPADE: 'S', Suit.HEART: 'H',
            Suit.CLUB: 'C', Suit.DIAMOND: 'D',
        }
        return symbols[self]


# =============================================================================
# Example 2: Card Class
# =============================================================================

class Card:
    """A playing card with suit and face value.

    Face values: 1=Ace, 2-10, 11=Jack, 12=Queen, 13=King
    """

    FACE_NAMES = {
        1: 'A', 11: 'J', 12: 'Q', 13: 'K'
    }

    def __init__(self, suit: Suit, face: int):
        self.suit = suit
        self.face = face

    def __str__(self):
        face_str = self.FACE_NAMES.get(self.face, str(self.face))
        return f'{self.suit.symbol}{face_str}'

    def __repr__(self):
        return self.__str__()

    def __lt__(self, other):
        """Sort by suit first, then by face value."""
        if self.suit != other.suit:
            return self.suit < other.suit
        return self.face < other.face


# =============================================================================
# Example 3: Deck Class (Composition)
# =============================================================================

class Deck:
    """A standard 52-card deck.

    Demonstrates:
    - List comprehension with nested loops
    - @property for state checking
    - Iterator-like dealing interface
    """

    def __init__(self):
        self._index = 0
        self._cards = [
            Card(suit, face)
            for suit in Suit
            for face in range(1, 14)
        ]

    def shuffle(self) -> None:
        """Shuffle the deck and reset the deal position."""
        self._index = 0
        random.shuffle(self._cards)

    def deal(self) -> Card:
        """Deal the next card from the deck.

        Raises:
            IndexError: If no more cards to deal.
        """
        if not self.has_cards:
            raise IndexError("No more cards in deck")
        card = self._cards[self._index]
        self._index += 1
        return card

    @property
    def has_cards(self) -> bool:
        """Check if there are cards remaining to deal."""
        return self._index < len(self._cards)

    @property
    def remaining(self) -> int:
        """Number of cards remaining in deck."""
        return len(self._cards) - self._index

    def __len__(self):
        return len(self._cards)


# =============================================================================
# Example 4: Player Class
# =============================================================================

class Player:
    """A card game player who can receive and organize cards."""

    def __init__(self, name: str):
        self.name = name
        self.hand: list[Card] = []

    def receive(self, card: Card) -> None:
        """Add a card to the player's hand."""
        self.hand.append(card)

    def sort_hand(self) -> None:
        """Sort cards in hand by suit and face value."""
        self.hand.sort()

    def show_hand(self) -> str:
        """Display the player's hand."""
        return f"{self.name}: {self.hand}"

    def __repr__(self):
        return f"Player('{self.name}', {len(self.hand)} cards)"


# =============================================================================
# Example 5: Game Simulation
# =============================================================================

def deal_game(num_players: int = 4, cards_each: int = 13) -> None:
    """Simulate dealing cards to players."""
    player_names = ['North', 'East', 'South', 'West']

    deck = Deck()
    deck.shuffle()

    players = [Player(name) for name in player_names[:num_players]]

    print(f"=== Dealing {cards_each} cards to {num_players} players ===")
    print(f"Deck size: {len(deck)} cards")
    print()

    # Deal cards round-robin
    for _ in range(cards_each):
        for player in players:
            if deck.has_cards:
                player.receive(deck.deal())

    # Sort and display each player's hand
    for player in players:
        player.sort_hand()
        print(player.show_hand())

    print(f"\nRemaining in deck: {deck.remaining}")


# =============================================================================
# Example 6: Enum Iteration and Access Patterns
# =============================================================================

def demo_enum_features():
    """Demonstrate Enum features used in this example."""
    print("\n=== Enum Features ===")

    # Iteration
    print("All suits:", [s.name for s in Suit])
    print("All values:", [s.value for s in Suit])

    # Access by name and value
    print(f"By name: Suit['HEART'] = {Suit['HEART']}")
    print(f"By value: Suit(2) = {Suit(2)}")

    # Comparison
    print(f"SPADE < HEART: {Suit.SPADE < Suit.HEART}")

    # Identity
    print(f"Suit.SPADE is Suit(0): {Suit.SPADE is Suit(0)}")


# =============================================================================
# Main
# =============================================================================

if __name__ == '__main__':
    deal_game()
    demo_enum_features()