Home > Mobile >  Annotating specific dataclass subclass in a signature
Annotating specific dataclass subclass in a signature

Time:02-01

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.

  1. Ignore static typint by using an explicit getattr call

     class AProductRequestSubmitter:
         def __call__(self, job_id: int, cmd: ProductSubmissionCommand):
             ...
             getattr(cmd.product, 'a_specific_id')
    
  2. Use Generic to specialize ProductSubmissionCommand on 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
    
  •  Tags:  
  • Related