I have Python data class created from JSON (quite a lot of them actually). I want to have a method for creating class instance from JSON.
I have something like this:
class FromJSONMixin:
@staticmethod
@abstractmethod
def from_json(json: Union[Dict, TypedDict], **kwargs):
raise NotImplementedError
class PatientJSON(TypedDict):
ID: str
Name: str
Description: str
BirthDate: str
@dataclass
class Patient(FromJSONMixin):
name: str
birth_date: str
description: str
@staticmethod
def from_json(json: PatientJSON, **kwargs) -> Patient:
return Patient(
name=json["Name"],
birth_date=json["BirthDate"],
description=raw_data["Description"])
I want to create Patient
objects from PatientJSON
(the structure is related to the existing database, I have to integrate with it; it also does a few name-attribute translations, as you can see above). I created the FromJSONMixin
to explicitly mark classes that can be created from related classes for JSONs (like PatientJSON
).
Question: I get an error with the -> Patient:
part, Unresolved reference 'Patient'
. Why? I can't type class objects in methods in the same class? Do I have to just give up on typing the return type?
This is a common problems when creating modules with good type annotations. The problem is when the python interpreter is parsing the code for creating the class Patient. The return type annotation of method Patient.from_json
references class Patient which is in the middle of parsing and has not yet been created. To solve this problem you usually enclose the class name in return annotation with quotes so it becomes a string. But now there is a problem with MyPy and other type checkers. they do not allow string return annotations so here is a good solution for it:
class MyClass(SomeOtherClass):
def __init__(self, param_a):
self.attr_a = param_a
def foo(self, bar: MyClass) -> MyClass:
return MyClass(self.attr_a + 1)
which will raise a Unresolved Reference error.
to fix this you can enclose the method return annotation with quotes
class MyClass(SomeOtherClass):
def __init__(self, param_a):
self.attr_a = param_a
def foo(self, bar: 'MyClass') -> 'MyClass':
return MyClass(self.attr_a + bar.attr_a)
this will work for readability but not for type checkers such as MyPy. So for checkers such as MyPy you can make a TypeVar.
from typing import TypeVar, Type
MyClassT = TypeVar('MyClassT', bound='MyClass')
class MyClass(SomeOtherClass):
def __init__(self, param_a):
self.attr_a = param_a
def foo(self, bar: Type[MyClassT]) -> MyClassT:
return MyClass(self.attr_a + bar.attr_a)
With the introduction of Self
type in python version 3.11, the above example can be written as:
from typing import Self
class MyClass(SomeOtherClass):
def __init__(self, param_a):
self.attr_a = param_a
def foo(self, bar: Self) -> Self:
return MyClass(self.attr_a + bar.attr_a)