from dataclasses import dataclass
@dataclass
class BaseProduct:
...
@dataclass
class ProductA(BaseProduct):
a_specific_id: int
@dataclass
class ProductSubmissionCommand:
product_id: str
product: BaseProduct
class AProductRequestSubmitter:
def __call__(self, job_id: int, cmd: ProductSubmissionCommand):
...
cmd.product.a_specific_id
mypy error is:
error: "BaseProduct" has no attribute "a_specific_id"
How to annotate AProductRequestSubmitter.__call__ properly? This class is specific for AProduct, there are other submitters for different product types.
Is it possible to use Generic types or Literal values somehow? Or maybe cast or assert is an only way to go?
CodePudding user response:
There are multiple ways to achieve what you are trying to, I will show the ones that pop out the top of my head right now.
Ensure correct type at runtime
There may be multiple children having BaseProduct as their parent and there is no way for the type checker to know for sure, you will never pass a ProductSubmissionCommand instance that has product attribute of a type that is a child of BaseProduct that does not have a_specific_id attribute. In order to make sure this can never happen, you could start your function call with a type assertion, such as:
class AProductRequestSubmitter:
def __call__(self, job_id: int, cmd: ProductSubmissionCommand):
if not isinstance(cmd.product, ProductA):
raise TypeError(f"Only 'ProductA' instances are allowed, not '{cmd.product.__class__.__name__}'")
...
cmd.product.a_specific_id
Pycharm (or any other static type checker) should now be quiet, because the program will never reach the part of the code, where a_specific_id is accessed unless the correct type is in use.
Subclass ProductSubmissionCommand
If you really plan on using AProductRequestSubmitter for instances, where ProductSubmissionCommand has product of type ProductA, you may subclass your ProductSubmissionCommand and typehint its product attribute as a ProductA instance.
@dataclass
class ProductASubmissionCommand(ProductSubmissionCommand):
product: ProductA
class AProductRequestSubmitter:
def __call__(self, job_id: int, cmd: ProductASubmissionCommand):
...
cmd.product.a_specific_id
Again, all clever typecheckers should now understand, that cmd will always have product.a_specific_id attribute.
Use Protocol
Using:
class ProductASubmissionCommand(Protocol):
product: ProductA
class AProductRequestSubmitter:
def __call__(self, job_id: int, cmd: ProductASubmissionCommand):
...
cmd.product.a_specific_id
You are basically saying "accept anything, that has an attribute named product of a type ProductA". This is a little hacky and makes you create a protocol subclass only for the sake of typehinting this single method call, but, again, should work with all static type checkers.
CodePudding user response:
You have 2 possible ways.
Ignore static typint by using an explicit
getattrcallclass AProductRequestSubmitter: def __call__(self, job_id: int, cmd: ProductSubmissionCommand): ... getattr(cmd.product, 'a_specific_id')Use
Genericto specializeProductSubmissionCommandon a static typing point of view:T = TypeVar("T", bound=BaseProduct) @dataclass class ProductSubmissionCommand(Generic[T]): product_id: str product: T class AProductRequestSubmitter: def __call__(self, job_id: int, cmd: ProductSubmissionCommand[ProductA]): pass
