Skip to main content

A Python library for explicit interface implementations with compile-time safety

Project description

Explicit Implementation

PyPI version Python Support License: MIT

A Python library that provides explicit interface implementations with compile-time safety and clear separation between interface contracts and their implementations.

Features

  • Explicit Interface Declarations: Define clear interface contracts using abstract methods
  • Concrete Method Support: Include concrete methods in interfaces with default implementations
  • Flexible Implementation: Allow partial implementations by default, enforce completeness with concrete=True
  • Compile-time Safety: Catch implementation errors at class definition time, not runtime
  • Multiple Interface Support: Implement multiple interfaces in a single class
  • Interface Access Control: Access implementations through specific interface views
  • Type Safety: Full typing support with generics for better IDE experience
  • Diamond Inheritance: Proper handling of complex inheritance patterns

Installation

pip install explicit-implementation

Quick Start

from explicit_implementation import Interface, abstractmethod, implements

# Define an interface
class IDrawable(Interface):
    @abstractmethod
    def draw(self) -> str:
        ...

class IPrintable(Interface):
    @abstractmethod
    def print_info(self) -> str:
        ...

# Implement the interfaces explicitly
class Document(IDrawable, IPrintable):
    def __init__(self, content: str):
        self.content = content
    
    @implements(IDrawable.draw)
    def render_document(self) -> str:
        return f"Drawing: {self.content}"
    
    @implements(IPrintable.print_info)
    def document_info(self) -> str:
        return f"Document: {self.content}"

# Use the implementation
doc = Document("Hello World")

# Access through specific interfaces
drawable = doc.as_interface(IDrawable)
printable = doc.as_interface(IPrintable)

print(drawable.draw())        # "Drawing: Hello World"
print(printable.print_info()) # "Document: Hello World"

Key Concepts

Interface Declaration

Interfaces are defined by inheriting from Interface and using @abstractmethod:

class IRepository(Interface):
    @abstractmethod
    def save(self, data: dict) -> bool:
        ...
    
    @abstractmethod
    def load(self, id: str) -> dict:
        ...

Explicit Implementation

Use the @implements decorator to explicitly map interface methods to implementation methods:

class DatabaseRepository(IRepository):
    @implements(IRepository.save)
    def save_to_database(self, data: dict) -> bool:
        # Implementation here
        return True
    
    @implements(IRepository.load)  
    def load_from_database(self, id: str) -> dict:
        # Implementation here
        return {"id": id}

Interface Access

Access implementations through specific interface views:

repo = DatabaseRepository()

# Access through the IRepository interface
repository_interface = repo.as_interface(IRepository)
repository_interface.save({"name": "example"})
repository_interface.load("123")

Concrete Methods in Interfaces

Interfaces can include concrete (non-abstract) methods that provide default implementations. These methods can be accessed directly from implementing classes without explicit implementation:

class IService(Interface):
    @abstractmethod
    def process(self, data: str) -> str:
        ...
    
    def log(self, message: str) -> None:
        """Concrete method with default implementation."""
        print(f"[LOG] {message}")
    
    @classmethod
    def get_version(cls) -> str:
        """Concrete class method."""
        return "1.0.0"

class MyService(IService):
    @implements(IService.process)
    def process_data(self, data: str) -> str:
        return f"Processed: {data}"

service = MyService()

# Access concrete methods directly
service.log("Starting process")          # Works directly
service.get_version()                    # Works directly

# Also accessible through interface casting
interface = service.as_interface(IService)
interface.log("Through interface")       # Also works
interface.get_version()                  # Also works

Concrete Classes

By default, partial implementations are allowed at class definition time. To enforce complete implementation:

# This is allowed - partial implementation class definition
class PartialRepository(IRepository):
    @implements(IRepository.save)
    def save_to_database(self, data: dict) -> bool:
        return True
    # Missing IRepository.load implementation - class definition succeeds

# However, you still can't instantiate classes with unimplemented abstract methods
# partial = PartialRepository()  # Raises TypeError at instantiation

# This will raise TypeError at class definition time
class ConcreteRepository(IRepository, concrete=True):
    @implements(IRepository.save)
    def save_to_database(self, data: dict) -> bool:
        return True
    # Missing IRepository.load implementation - TypeError at class definition!

Advanced Usage

Multiple Interface Implementation

class IValidator(Interface):
    @abstractmethod
    def validate(self, data: str) -> bool:
        ...

class IFormatter(Interface):
    @abstractmethod
    def format(self, data: str) -> str:
        ...

class DataProcessor(IValidator, IFormatter):
    @implements(IValidator.validate)
    def check_data(self, data: str) -> bool:
        return len(data) > 0
    
    @implements(IFormatter.format)
    def format_data(self, data: str) -> str:
        return data.upper()

Interface Inheritance

class IBasic(Interface):
    @abstractmethod
    def basic_method(self) -> str:
        ...

class IExtended(IBasic):
    @abstractmethod
    def extended_method(self) -> int:
        ...

class Implementation(IExtended):
    @implements(IBasic.basic_method)
    def basic_impl(self) -> str:
        return "basic"
    
    @implements(IExtended.extended_method)
    def extended_impl(self) -> int:
        return 42

Diamond Inheritance Patterns

In diamond inheritance, each interface path requires explicit implementation:

class IBase(Interface):
    @abstractmethod
    def base_method(self) -> str:
        ...

class ILeft(IBase):
    @abstractmethod
    def left_method(self) -> int:
        ...

class IRight(IBase):
    @abstractmethod
    def right_method(self) -> bool:
        ...

class Diamond(ILeft, IRight):
    # Can only implement base_method once for IBase
    @implements(IBase.base_method)
    def base_impl(self) -> str:
        return "base"
    
    @implements(ILeft.left_method)
    def left_impl(self) -> int:
        return 42
    
    @implements(IRight.right_method)
    def right_impl(self) -> bool:
        return True

# Access base_method only through IBase interface
diamond = Diamond()
base_interface = diamond.as_interface(IBase)
print(base_interface.base_method())  # "base"

Error Handling

The library provides clear error messages for common mistakes:

Missing Implementation

By default, partial implementations are allowed:

class Incomplete(IDrawable):
    pass  # Missing @implements for IDrawable.draw
    # This is allowed by default - no error raised

To enforce complete implementation, use concrete=True:

class Concrete(IDrawable, concrete=True):
    pass  # Missing @implements for IDrawable.draw
    # Raises: TypeError at class definition time

Invalid Implementation Target

class Invalid(IDrawable):
    @implements(IPrintable.print_info)  # Wrong interface method
    def some_method(self) -> str:
        return "invalid"
    # Raises: TypeError - method not in base interfaces

Accessing Unimplemented Interface

class Partial(IDrawable):
    @implements(IDrawable.draw)
    def draw_impl(self) -> str:
        return "drawn"

partial = Partial()
# This will raise TypeError:
printable = partial.as_interface(IPrintable)

Type Safety

The library provides full typing support:

from typing import Protocol

def use_drawable(drawable: IDrawable) -> str:
    return drawable.draw()

def process_document(doc: Document) -> tuple[str, str]:
    drawable = doc.as_interface(IDrawable)  # Type: IDrawable
    printable = doc.as_interface(IPrintable)  # Type: IPrintable
    
    return drawable.draw(), printable.print_info()

Comparison with ABC

Feature ABC Explicit Implementation
Method Names Must match interface Can be different
Interface Access Direct method calls Through .as_interface()
Multiple Interfaces Name conflicts possible Clean separation
Implementation Clarity Implicit Explicit with @implements
Error Detection Runtime Compile-time

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

0.1.1

  • Bug Fix: Fixed access to concrete (non-abstract) methods when using as_interface()
  • Concrete methods in interfaces can now be properly accessed both directly and through interface casting
  • Added comprehensive test coverage for concrete method access patterns
  • Improved documentation with concrete method usage examples

0.1.0

  • Initial release
  • Basic interface and implementation functionality
  • Support for multiple interface implementation
  • Diamond inheritance pattern support
  • Full typing support
  • Comprehensive test coverage

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

explicit_implementation-0.1.1.tar.gz (33.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

explicit_implementation-0.1.1-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file explicit_implementation-0.1.1.tar.gz.

File metadata

  • Download URL: explicit_implementation-0.1.1.tar.gz
  • Upload date:
  • Size: 33.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for explicit_implementation-0.1.1.tar.gz
Algorithm Hash digest
SHA256 b9eaac84f2d43781b95e56ffc40e09bacda8565dcf6826ec15561cbcb84a7481
MD5 7e33db3610263f051e5b1338ff422fd2
BLAKE2b-256 73ac2b143ce7b6056064c7dddc1757eb1a7da58f4fb803aa6dcc81bb4a5bb74e

See more details on using hashes here.

File details

Details for the file explicit_implementation-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for explicit_implementation-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 296be882bb291f287a4a41804dda2cf9bc372ba88065bc3c785752bb4e80f459
MD5 86364340e16ff74a03df599e021ef2cc
BLAKE2b-256 2d803c66955ced8fb680500c34e9a7414407630142311496b03713291032a3ab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page