I want a class that encapsulates data with some meta information. The data changes during runtime. I want to safe it for evaluation. The class looks like this:
from dataclasses import dataclass, replace, _DataclassT
@dataclass
class Encapsulated[P: ???]: # <- What to specify here?
type: int
payload: P
def record(self) -> P:
return replace(self.payload)
As you can see, I have no requirements with regard to P except that is has to be a Dataclass that can be used in dataclasses.replace. For example:
@dataclass
class Payload:
val: int = 0
I tried object which fails for obvious reasons. I find _DataclassT inappropriate because of it being private. Also: It doesn't work.
First its best to directly look at the signature of replace() itself:
def replace(obj, /, **changes):
...
This offers no type annotations. In situations like this, you may find that typeshed does contain the annotations you are looking for.
Typeshed contains external type annotations for the Python standard library and Python builtins, as well as third party packages as contributed by people external to those projects.
In typeshed, you find that replace() is defined as:
def replace(obj: _DataclassT, /, **changes: Any) -> _DataclassT: ...
Where _DataclassT is:
_DataclassT = TypeVar("_DataclassT", bound=DataclassInstance)
And DataclassInstance is defined as:
from typing import Any, ClassVar, Protocol
from dataclasses import Field
class DataclassInstance(Protocol):
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
You can access it by doing:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from _typeshed import DataclassInstance
The issue with using _typeshed is that it is not available during runtime and you have to make the import statement inaccessible during runtime.
Furthermore, as it is an internal package to typeshed its API has limited stability guarantees. However, as DataclassInstance is a Protocol, you can copy the above definition into your own code, and the static type tool will be happy. ie.
from dataclasses import dataclass, replace, Field
from typing import Any, ClassVar, Protocol, reveal_type
class DataclassInstance(Protocol):
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
@dataclass
class Point:
x: int
y: int
@dataclass
class Encapsulated[P: DataclassInstance]:
type: int
payload: P
def record(self) -> P:
return replace(self.payload)
x = Encapsulated(0, Point(0, 0))
y = x.record()
reveal_type(y) # note: Revealed type is "mymod.Point"