Skip to content

Latest commit

 

History

History
547 lines (401 loc) · 14.4 KB

File metadata and controls

547 lines (401 loc) · 14.4 KB

Interface

Interface Implementation for Python

Create interface-like behavior using metaclasses and enhance your object-oriented programming with clear contracts and abstract method definitions.

⚠️ Educational Project Disclaimer: This Interface metaclass was created as an educational exercise to learn about Python metaclasses and interface patterns. It should NOT be used in production code. For real-world applications, please use Python's built-in abc.ABC (Abstract Base Classes) from the standard library, which is more robust, well-tested, and officially supported.

Browse code here

Purpose

The Interface metaclass provides a way to define interfaces in Python, similar to interfaces in languages like Java or C#. It allows you to create abstract base classes that define a contract for implementing classes, ensuring that all required methods are implemented and providing better code organization and type safety.

Key Features

  • ✅ Interface Definition - Define clear contracts for implementing classes
  • ✅ Method Validation - Automatic validation of required method implementations
  • ✅ Abstract Method Support - Mark methods as abstract to enforce implementation
  • ✅ Inheritance Support - Interfaces can inherit from other interfaces
  • ✅ Type Safety - Enhanced type checking and IDE support
  • ✅ Clear Contracts - Explicit definition of what implementing classes must provide

Usage Examples

Basic Interface Definition

from danielutils import Interface

class DatabaseInterface(metaclass=Interface):
    """Interface for database operations"""
    
    def connect(self) -> bool:
        """Connect to the database"""
        ...
    
    def disconnect(self) -> None:
        """Disconnect from the database"""
        ...
    
    def execute_query(self, query: str) -> list:
        """Execute a database query"""
        ...
    
    def close(self) -> None:
        """Close the database connection"""
        ...

# Implementation
class SQLiteDatabase(DatabaseInterface):
    def __init__(self, db_path: str):
        self.db_path = db_path
        self.connected = False
    
    def connect(self) -> bool:
        print(f"Connecting to SQLite database: {self.db_path}")
        self.connected = True
        return True
    
    def disconnect(self) -> None:
        print("Disconnecting from SQLite database")
        self.connected = False
    
    def execute_query(self, query: str) -> list:
        if not self.connected:
            raise RuntimeError("Database not connected")
        print(f"Executing query: {query}")
        return [{"result": "data"}]
    
    def close(self) -> None:
        self.disconnect()

# Usage
db = SQLiteDatabase("test.db")
db.connect()
results = db.execute_query("SELECT * FROM users")
db.close()

Interface with Abstract Methods

from danielutils import Interface

class PaymentProcessor(metaclass=Interface):
    """Interface for payment processing"""
    
    def process_payment(self, amount: float, currency: str) -> bool:
        """Process a payment"""
        ...
    
    def refund_payment(self, payment_id: str) -> bool:
        """Refund a payment"""
        ...
    
    def get_balance(self) -> float:
        """Get current balance"""
        ...

class CreditCardProcessor(PaymentProcessor):
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.balance = 0.0
    
    def process_payment(self, amount: float, currency: str) -> bool:
        print(f"Processing credit card payment: {amount} {currency}")
        self.balance += amount
        return True
    
    def refund_payment(self, payment_id: str) -> bool:
        print(f"Processing refund for payment: {payment_id}")
        return True
    
    def get_balance(self) -> float:
        return self.balance

class PayPalProcessor(PaymentProcessor):
    def __init__(self, email: str):
        self.email = email
        self.balance = 0.0
    
    def process_payment(self, amount: float, currency: str) -> bool:
        print(f"Processing PayPal payment: {amount} {currency}")
        self.balance += amount
        return True
    
    def refund_payment(self, payment_id: str) -> bool:
        print(f"Processing PayPal refund for payment: {payment_id}")
        return True
    
    def get_balance(self) -> float:
        return self.balance

# Usage
processors = [
    CreditCardProcessor("api_key_123"),
    PayPalProcessor("user@example.com")
]

for processor in processors:
    processor.process_payment(100.0, "USD")
    print(f"Balance: {processor.get_balance()}")

Interface Inheritance

from danielutils import Interface

class Animal(metaclass=Interface):
    """Base interface for animals"""
    
    def make_sound(self) -> str:
        """Make a sound"""
        ...
    
    def move(self) -> str:
        """Move around"""
        ...

class Pet(Animal, metaclass=Interface):
    """Interface for pets that extends Animal"""
    
    def get_name(self) -> str:
        """Get the pet's name"""
        ...
    
    def play(self) -> str:
        """Play with the pet"""
        ...

class Dog(Pet):
    def __init__(self, name: str):
        self.name = name
    
    def make_sound(self) -> str:
        return "Woof!"
    
    def move(self) -> str:
        return "Running on four legs"
    
    def get_name(self) -> str:
        return self.name
    
    def play(self) -> str:
        return f"{self.name} is playing fetch"

class Cat(Pet):
    def __init__(self, name: str):
        self.name = name
    
    def make_sound(self) -> str:
        return "Meow!"
    
    def move(self) -> str:
        return "Walking gracefully"
    
    def get_name(self) -> str:
        return self.name
    
    def play(self) -> str:
        return f"{self.name} is chasing a laser pointer"

# Usage
pets = [Dog("Buddy"), Cat("Whiskers")]

for pet in pets:
    print(f"{pet.get_name()}: {pet.make_sound()}")
    print(f"Movement: {pet.move()}")
    print(f"Activity: {pet.play()}")
    print()

Complex Interface with Properties

from danielutils import Interface
from typing import List, Optional

class UserRepository(metaclass=Interface):
    """Interface for user data access"""
    
    def find_by_id(self, user_id: int) -> Optional[dict]:
        """Find user by ID"""
        ...
    
    def find_all(self) -> List[dict]:
        """Find all users"""
        ...
    
    def save(self, user: dict) -> bool:
        """Save a user"""
        ...
    
    def delete(self, user_id: int) -> bool:
        """Delete a user"""
        ...
    
    def count(self) -> int:
        """Get total number of users"""
        ...

class InMemoryUserRepository(UserRepository):
    def __init__(self):
        self.users = {}
        self.next_id = 1
    
    def find_by_id(self, user_id: int) -> Optional[dict]:
        return self.users.get(user_id)
    
    def find_all(self) -> List[dict]:
        return list(self.users.values())
    
    def save(self, user: dict) -> bool:
        if 'id' not in user:
            user['id'] = self.next_id
            self.next_id += 1
        self.users[user['id']] = user.copy()
        return True
    
    def delete(self, user_id: int) -> bool:
        if user_id in self.users:
            del self.users[user_id]
            return True
        return False
    
    def count(self) -> int:
        return len(self.users)

# Usage
repo = InMemoryUserRepository()

# Add users
user1 = {"name": "Alice", "email": "alice@example.com"}
user2 = {"name": "Bob", "email": "bob@example.com"}

repo.save(user1)
repo.save(user2)

print(f"Total users: {repo.count()}")
print(f"All users: {repo.find_all()}")
print(f"User 1: {repo.find_by_id(1)}")

Interface with Static Methods

from danielutils import Interface
from typing import Type

class Logger(metaclass=Interface):
    """Interface for logging functionality"""
    
    def log(self, message: str, level: str = "INFO") -> None:
        """Log a message"""
        ...
    
    def error(self, message: str) -> None:
        """Log an error message"""
        ...
    
    def warning(self, message: str) -> None:
        """Log a warning message"""
        ...
    
    @classmethod
    def get_instance(cls) -> 'Logger':
        """Get logger instance"""
        ...

class ConsoleLogger(Logger):
    def __init__(self, name: str):
        self.name = name
    
    def log(self, message: str, level: str = "INFO") -> None:
        print(f"[{level}] {self.name}: {message}")
    
    def error(self, message: str) -> None:
        self.log(message, "ERROR")
    
    def warning(self, message: str) -> None:
        self.log(message, "WARNING")
    
    @classmethod
    def get_instance(cls) -> 'ConsoleLogger':
        return cls("DefaultLogger")

class FileLogger(Logger):
    def __init__(self, filename: str):
        self.filename = filename
    
    def log(self, message: str, level: str = "INFO") -> None:
        with open(self.filename, 'a') as f:
            f.write(f"[{level}] {message}\n")
    
    def error(self, message: str) -> None:
        self.log(message, "ERROR")
    
    def warning(self, message: str) -> None:
        self.log(message, "WARNING")
    
    @classmethod
    def get_instance(cls) -> 'FileLogger':
        return cls("app.log")

# Usage
loggers = [ConsoleLogger("App"), FileLogger("debug.log")]

for logger in loggers:
    logger.log("Application started")
    logger.warning("This is a warning")
    logger.error("This is an error")

API Reference

Interface Metaclass

class Interface(type):
    """
    Metaclass for creating interfaces in Python.
    
    Interfaces define a contract that implementing classes must follow.
    All methods in an interface must be implemented by any class that
    uses the interface as a metaclass.
    """

Interface Definition Pattern

class MyInterface(metaclass=Interface):
    """Documentation for the interface"""
    
    def required_method(self, param: str) -> bool:
        """Documentation for the method"""
        ...  # Abstract method (no implementation)
    
    def optional_method(self, param: int) -> str:
        """Optional method with default implementation"""
        return f"Default: {param}"  # Default implementation

Implementation Pattern

class MyImplementation(MyInterface):
    """Implementation of the interface"""
    
    def required_method(self, param: str) -> bool:
        # Must implement this method
        return param == "expected"
    
    # optional_method is inherited with default implementation

Best Practices

1. Clear Interface Design

# Good: Clear, focused interface
class DataProcessor(metaclass=Interface):
    def process(self, data: bytes) -> bytes:
        """Process binary data"""
        ...
    
    def validate(self, data: bytes) -> bool:
        """Validate data format"""
        ...

# Avoid: Too many responsibilities
class DataProcessor(metaclass=Interface):
    def process(self, data: bytes) -> bytes:
        ...
    def validate(self, data: bytes) -> bool:
        ...
    def compress(self, data: bytes) -> bytes:
        ...
    def encrypt(self, data: bytes) -> bytes:
        ...
    def send_email(self, to: str, subject: str) -> bool:
        ...

2. Proper Documentation

class PaymentGateway(metaclass=Interface):
    """Interface for payment processing gateways.
    
    This interface defines the contract that all payment gateways
    must implement to be compatible with the payment system.
    """
    
    def authorize_payment(self, amount: float, card_token: str) -> str:
        """Authorize a payment transaction.
        
        Args:
            amount: Payment amount in currency units
            card_token: Secure token representing the payment method
            
        Returns:
            Transaction ID for the authorized payment
            
        Raises:
            PaymentError: If authorization fails
        """
        ...

3. Type Safety

from typing import List, Optional, Union

class UserService(metaclass=Interface):
    def find_user(self, user_id: int) -> Optional[dict]:
        ...
    
    def find_users(self, criteria: dict) -> List[dict]:
        ...
    
    def create_user(self, user_data: dict) -> Union[dict, None]:
        ...

4. Interface Composition

# Compose interfaces for complex systems
class ReadableRepository(metaclass=Interface):
    def find_by_id(self, id: int) -> Optional[dict]:
        ...
    
    def find_all(self) -> List[dict]:
        ...

class WritableRepository(metaclass=Interface):
    def save(self, entity: dict) -> bool:
        ...
    
    def delete(self, id: int) -> bool:
        ...

class FullRepository(ReadableRepository, WritableRepository, metaclass=Interface):
    """Combines read and write capabilities"""
    pass

Comparison with Other Approaches

Feature Interface abc.ABC Protocol
Explicit contracts ✅ ✅ ❌
Method validation ✅ ✅ ❌
Inheritance support ✅ ✅ ✅
Static analysis ✅ ✅ ✅
Runtime checking ✅ ✅ ✅
Learning curve Low Medium Medium

Common Patterns

1. Repository Pattern

class Repository(metaclass=Interface):
    def find_by_id(self, id: int) -> Optional[dict]:
        ...
    
    def save(self, entity: dict) -> bool:
        ...
    
    def delete(self, id: int) -> bool:
        ...

2. Service Pattern

class UserService(metaclass=Interface):
    def create_user(self, user_data: dict) -> dict:
        ...
    
    def update_user(self, user_id: int, user_data: dict) -> dict:
        ...
    
    def delete_user(self, user_id: int) -> bool:
        ...

3. Factory Pattern

class ConnectionFactory(metaclass=Interface):
    def create_connection(self, config: dict) -> 'Connection':
        ...
    
    def close_connection(self, connection: 'Connection') -> None:
        ...

Note: The Interface metaclass provides a clean and explicit way to define contracts in Python, making your code more maintainable and self-documenting while ensuring that implementing classes follow the defined contract.