Interface Stubs
Imagine you are building two separate modules, but within each one you declare two different dataclasses as such:
@dataclass
class Order:
user: User
item: str
@dataclass
class User:
name: str
def create_order(self, item: str) -> Order:
return Order(self, item)
However, the only problem is that, if these are in different modules, you'll have a circular import issue due to each class relying on definitions present in the other.
To resolve this, create interface stubs with abstract classes for the core
data structures and then use them as type hints for the actual concrete
class. For the above example, you may create a new file interface.py which contains this implementation:
import abc
class IOrder(metaclass=abc.ABCMeta):
user: 'IUser'
item: str
class IUser(metaclass=abc.ABCMeta):
name: str
@abc.abstractmethod
def create_order(self, item: str) -> IOrder:
...
Then you can have the actual concrete class implementations to use said
interfaces to allow for modularization in separate files. First, define Order as:
from dataclasses import dataclass
from interface import IOrder, IUser
@dataclass
class Order(IOrder):
user: IUser
item: str
Then define the User in a separate file as such:
from dataclasses import dataclass
from interface import IUser, IOrder
@dataclass
class User(IUser):
name: str
def create_order(self, item: str) -> IOrder:
return IOrder(self, item)
Here are a list of benefits:
- Breaks circular dependencies
- Improves testability with mocking interfaces
- Reduces dependency bloat
- More clearly documented + modularized
This pattern is particularly useful in larger codebases where you have complex relationships between modules. By defining abstract interfaces, you create a contract that both modules can depend on without directly importing each other. This makes your code more maintainable and easier to test, as you can mock the interfaces during unit testing.
Questions or feedback? Feel free to reach out!