2

Would like to add type hinting to def make(self): from in the class AggregateMaker so that the code in the tests test_fruit and test_tea would autocomplete the Fruit or Tea methods/properties rather than returning None

Is this possible in Python 3.10?

from dataclasses import dataclass

@dataclass
class Fruit:
    name: str
    smell: str

@dataclass
class Tea:
    name: str
    hot: bool

class AggregateMaker():
    _fields: dict

    @classmethod
    def new(cls, **fields):
        return cls(fields=None).with_(**fields)

    ###
    #  How to type hint in here to return Fruit or Tea?
    ###
    def make(self):
        return self._make(self._fields)

    def with_(self, **overrides):
        copy = dict(self._fields)
        for name, value in overrides.items():
            copy[name] = value
        return type(self)(copy)

class FruitMaker(AggregateMaker):
    def __init__(self, fields):
        if fields is None:
            fields = {
                "name": None,
                "smell": None,
            }
        self._fields = fields

    def _make(self, fields) -> Fruit:
        return Fruit(**fields)

class TeaMaker(AggregateMaker):
    def __init__(self, fields):
        if fields is None:
            fields = {
                "name": None,
                "hot": None,
            }
        self._fields = fields

    def _make(self, fields) -> Tea:
        return Tea(**fields)

def test_fruit():
    durian = FruitMaker.new().with_(name="Durian").with_(smell="Strong").make()
    assert durian.name == "Durian"
    assert durian.smell == "Strong"
    assert type(durian) is Fruit

def test_tea():
    camomile = TeaMaker.new(name="Camomile", hot=True).make()
    assert type(camomile) is Tea
2
  • That's quite a long code snippet. Are you able to make it more focussed? Commented Jun 25, 2022 at 13:14
  • I've reduced it from 84 lines to 62 lines. I understand it's quite long but thought it would be useful to have the tests to explain how it's supposed to be used. Commented Aug 12, 2022 at 10:43

1 Answer 1

3

I typed as much of it as I felt was reasonable, but there are still gaps.

I feel like it'd usually make sense to replace most of this with, like, prototype objects, and calls to dataclasses.replace.

(From context elsewhere, I know that isn't practical in the near term.)

from dataclasses import dataclass
from typing import Any, Generic, Type, TypeVar

T = TypeVar("T")
TMaker = TypeVar("TMaker", bound="AggregateMaker[Any]")

@dataclass
class Fruit:
    name: str
    smell: str

@dataclass
class Tea:
    name: str
    hot: bool

class AggregateMaker(Generic[T]):
    _fields: dict[str, Any]
    
    def __init__(self, fields: dict[str, Any] | None) -> None:
        ...

    @classmethod
    def new(cls: Type[TMaker], **fields: Any) -> TMaker:
        return cls(fields=None).with_(**fields)

    def make(self) -> T:
        return self._make(self._fields)
        
    def _make(self, fields: dict[str, Any]) -> T:
        ...

    def with_(self: TMaker, **overrides: Any) -> TMaker:
        copy = dict(self._fields)
        for name, value in overrides.items():
            copy[name] = value
        return type(self)(copy)

class FruitMaker(AggregateMaker[Fruit]):
    def __init__(self, fields: dict[str, Any]):
        if fields is None:
            fields = {
                "name": None,
                "smell": None,
            }
        self._fields = fields

    def _make(self, fields: dict[str, Any]) -> Fruit:
        return Fruit(**fields)

class TeaMaker(AggregateMaker[Tea]):
    def __init__(self, fields: dict[str, Any]):
        if fields is None:
            fields = {
                "name": None,
                "hot": None,
            }
        self._fields = fields

    def _make(self, fields: dict[str, Any]) -> Tea:
        return Tea(**fields)

def test_fruit() -> None:
    durian = FruitMaker.new().with_(name="Durian").with_(smell="Strong").make()
    assert durian.name == "Durian"
    assert durian.smell == "Strong"
    assert type(durian) is Fruit

def test_tea() -> None:
    camomile = TeaMaker.new(name="Camomile", hot=True).make()
    assert type(camomile) is Tea
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.