Repository navigation
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()` omittedHere 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 omittedHere 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).
Activity
ilevkivskyi commentedon Aug 12, 2019
I am not sure I completely understand the issue. Could you please give a simplified example of code with typed dicts that currently passes but should fail in your opinion or vice versa (fails but should pass in your opinion).
tomoyoirl commentedon Aug 12, 2019
To highlight the feature without the use case, then, it is well and right that this should fail:
However, there should be a way to write something similar that succeeds, in the way that this succeeds:
ilevkivskyi commentedon Aug 13, 2019
The code you posted actually passes mypy, because typed dicts follow structural subtyping (exactly as protocols do). Maybe you were bitten by some failures with
total=False, but then this is a known problem, see #6577