I am creating a decorator that has a similar purpose to that of the builtin dataclass
decorator. Given @jsbueno's answer to this question, it would be reasonable to assume that it is not possible to type-hint such a class. However, VS Code is able to supply type hints for dataclasses. Is this hardcoded into VS Code, or is there a way to use the same method dataclass
uses in order to get type hinting on my class?
My current implementation of this decorator:
def json_object_factory(cls: type) -> Type[JsonObject]:
elements: Dict[str, JsonElement] = {}
# Methods for this object
def __init__(self):
for name, elem_dat in elements.items():
if elem_dat.default is not None:
setattr(self, name, elem_dat.default)
else:
setattr(self, name, elem_dat.default_factory())
def to_json(self) -> dict:
json = {}
for name, elem_dat in elements.items():
value = getattr(self, name)
# Run checks on the value
if elem_dat.expected_type is not None:
type_check(name, value, elem_dat.expected_type,
not elem_dat.must_exist)
if elem_dat.bounds is not None:
low, high = elem_dat.bounds
bounds_check(name, value, low, high)
# Maps the element to its new value
value = elem_dat.mapping(value)
# Adds it to the dict
if elem_dat.alternate_name is not None:
json[elem_dat.alternate_name] = value
else:
json[name] = value
return json
# Any JsonElement class variables are meant to be instance variables of this
# new class.
for name in dir(cls):
field = getattr(cls, name)
# Check if the field is of the type we're interested in
if isinstance(field, JsonElement):
# Add it to the dictionary
elements[name] = field
supers = list(cls.__mro__)[1:] # Slice out the reference to the regular class
if JsonObject not in supers:
supers.append(JsonObject)
return type(cls.__name__, tuple(supers), {
'__init__': __init__,
'to_json': to_json
})
Which uses these classes:
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, Tuple, Type, Union
from my_other_code import type_check, bounds_check
class JsonObject(ABC):
@abstractmethod
def to_json(self):
pass
class JsonElement:
def __init__(
self,
expected_type: type = None,
default: Any = None, *,
default_factory: Callable[[], Any] = None,
mapping: Callable[[Any], Any] = lambda x: x,
bounds: Tuple[Union[int,float], Union[int,float]] = None,
must_exist: bool = False,
alternate_name: str = None
):
self.expected_type = expected_type
if default is not None and default_factory is not None:
raise TypeError('Both default and default_factory cannot be supplied.')
self.default = default
self.default_factory = default_factory
self.mapping = mapping
self.bounds = bounds
self.must_exist = must_exist
self.alternate_name = alternate_name
Now, using this module as below works, but to no one's suprise, the type checker has no idea the type or the attributes of the class.
from above import *
@json_object_factory
class Test:
a = JsonElement(bool, False)
b = JsonElement(str, 'default')
c = JsonElement(list, default_factory=list)
Test().a # Typechecker gives an error: JsonObject has no attribute a
But by some witchcraft, the type checker does understand the below code!
from dataclasses import dataclass, field
@dataclass
class Test:
a: bool = field(default=False)
b: str = field(default='default')
c: list = field(default_factory=list)
Test().a # Typechecker recognizes existance of attribute and type of a.
Is it possible to acquire static type-checking like dataclass
achieves with my json_object_factory
decorator?
dataclass
specially. Statically speaking,a
is just a class attribute, because the decorator hasn't yet been applied. Furthermore, taking only statically available information, the data class wouldn't type check, becausea
is annotated as abool
but assigned afield
value. – chepner