Description
- I am opening a feature request.
- This could be considered a duplicate of How to accept dicts with extra keys? #4617 and TypedDict typecheck fails if extra dict keys present #6223, but the specifics are more flexible.
I would like to define a Django application configuration in a type safe way, and provide tools which supply app dependencies to components of the application, like a service locator pattern or something. I am developing code to do make this happen.
Here is the outline of an app config class:
DependencyBundle = TypeVar("DependencyBundle")
class AppConfig(DjangoAppConfig, Generic[DependencyBundle], ABC):
@classmethod
def bundle(cls: Type["AppConfig"]) -> DependencyBundle:
# ... accessor details omitted
# ... initialization code in `ready()` omitted
Here is an app config instance:
@dataclass
class ContentDependencies:
content_validator: Validator
content_loader: Loader
other_dependency: Any
class ContentConfig(AppConfig[ContentDependencies]):
name = "organization.content"
verbose_name = "Content App"
# ... specifics of bundle initialization omitted
Here is the action base class:
AppConfigType = TypeVar("ConfigType")
ParamType = TypeVar("ParamType")
ReturnType = TypeVar("ReturnType")
class AppAction(Generic[AppConfigType, ParamType, ReturnType], ABC):
config: AppConfigType
@classmethod
def build(cls: Type[ActionType], args: ParamType) -> ActionType:
return cls(app=cls.config.bundle(), args=args)
def __init__(self, *, app: AppConfigType, args: ParamType) -> None:
self.app = app
self.args = args
def execute(self) -> ReturnType:
# ... (skeleton validation code omitted)
Here is an instance of an action. The action class wants to make use of the ContentConfig bundle, but also identify that it depends only on the loader and validator classes, the better to support the Interface Segregation Principle.
class PublishDependencies(Protocol):
content_loader: Loader
content_validator: Validator
class PublishArgs(TypedDict):
url_slug: str
external_content_id: str
class PublishContent(AppAction[PublishDependencies, PublishArgs, None]):
app = ContentConfig
def execute(self) -> None:
content = self.app.loader(content_id=self.args["content_id"])
...
This approach seems to work well so far. However, it is slightly tedious to test this, because we must create an instance of a test-only class that implements the PublishDependencies
protocol:
@dataclass
class BundleForTest:
content_loader: Loader
content_validator: Validator
class TestApp(TestCase):
def test_action(self):
test_action = PublishContent(app=BundleForTest(test_content_loader, test_validator), args={"args": "under test"})
If not for the problem of extra keys being present at runtime, it would be convenient to make both PublishDependencies
and ContentDependencies
into a TypedDict
, instead, which would allow tests to use simple dict
objects here instead:
def test_foo(self):
test_action = PublishContent(app={"content_loader": test_content_loader, "content_validator": test_content_validator}, args={})
I seek some way to identify one typed TypedDict
as a strict subset of another, or else a type-safe way to construct a TypedDict
from a dict which is a strict superset (without duplicating every key in the child dict).