A Python library for explicit interface implementations with compile-time safety
Project description
Explicit Implementation
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9eaac84f2d43781b95e56ffc40e09bacda8565dcf6826ec15561cbcb84a7481
|
|
| MD5 |
7e33db3610263f051e5b1338ff422fd2
|
|
| BLAKE2b-256 |
73ac2b143ce7b6056064c7dddc1757eb1a7da58f4fb803aa6dcc81bb4a5bb74e
|
File details
Details for the file explicit_implementation-0.1.1-py3-none-any.whl.
File metadata
- Download URL: explicit_implementation-0.1.1-py3-none-any.whl
- Upload date:
- Size: 7.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
296be882bb291f287a4a41804dda2cf9bc372ba88065bc3c785752bb4e80f459
|
|
| MD5 |
86364340e16ff74a03df599e021ef2cc
|
|
| BLAKE2b-256 |
2d803c66955ced8fb680500c34e9a7414407630142311496b03713291032a3ab
|