Skip to content

There should be some way to type a dict as a strict subset of some other TypedDict #7299

Closed
@tomoyoirl

Description

@tomoyoirl

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).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions