in chz/data_model.py [0:0]
def chz_make_class(cls, version: str | None, typecheck: bool | None) -> type:
if cls.__class__ is not type:
if cls.__class__ is typing._ProtocolMeta:
if typing_extensions.is_protocol(cls):
raise TypeError("chz class cannot itself be a Protocol)")
else:
import abc
if cls.__class__ is not abc.ABCMeta:
raise TypeError("Cannot use custom metaclass")
user_module = cls.__module__
cls_annotations = cls.__dict__.get("__annotations__", {})
fields: dict[str, Field] = {}
# Collect fields from parent classes
for b in reversed(cls.__mro__):
if hasattr(b, "__dataclass_fields__"):
raise ValueError("Cannot mix chz with dataclasses")
# Only process classes that have been processed by our decorator
base_fields: dict[str, Field] | None = getattr(b, "__chz_fields__", None)
if base_fields is None:
continue
for f in base_fields.values():
if (
f.logical_name in cls.__dict__
and f.logical_name not in cls_annotations
and not _is_property_like(getattr(cls, f.logical_name))
):
# Do an LSP check against parent fields (for non-property-like members)
raise ValueError(
f"Cannot override field {f.logical_name!r} with a non-field member; "
f"maybe you're missing a type annotation?"
)
else:
fields[f.logical_name] = f
# Collect fields from the current class
for name, annotation in cls_annotations.items():
if _is_classvar_annotation(annotation):
continue
# Find the field specification from the class __dict__
value = cls.__dict__.get(name, MISSING)
if value is MISSING:
field = Field(name=name, raw_type=annotation)
elif isinstance(value, Field):
field = value
field._name = name
field._raw_type = annotation
delattr(cls, name)
else:
if _is_property_like(value) or (
isinstance(value, types.FunctionType)
and value.__name__ != "<lambda>"
and value.__qualname__.startswith(cls.__qualname__)
):
# It's problematic to redefine the field in the same class, because it means we
# lose any field specification or default value
raise ValueError(f"Field {name!r} is clobbered by {type_repr(type(value))}")
field = Field(name=name, raw_type=annotation, default=value)
delattr(cls, name)
field._user_module = user_module
# Do a basic LSP check for new fields
parent_value = getattr(cls, name, MISSING) # note the delattr above
if parent_value is not MISSING and not (
field.logical_name in fields and isinstance(parent_value, init_property)
):
raise ValueError(
f"Cannot define field {name!r} because it conflicts with something defined on a "
f"superclass: {parent_value!r}"
)
other_name = field.logical_name if name != field.logical_name else field.x_name
parent_value = getattr(cls, other_name, MISSING)
if (
parent_value is not MISSING
and not (field.logical_name in fields and isinstance(parent_value, init_property))
and other_name not in cls.__dict__
):
raise ValueError(
f"Cannot define field {name!r} because it conflicts with something defined on a "
f"superclass: {parent_value!r}"
)
if (
name == field.logical_name
and name not in cls.__dict__
and name in fields
and fields[name]._name != name
):
raise ValueError(
"I'm a little unsure of what the semantics should be here. "
"See test_conflicting_superclass_x_field_in_base. "
"Please let @shantanu know if you hit this. "
f"You can also just rename the field in the subclass to X_{name}."
)
# Create a default init_property for the field that accesses the raw X_ field
munger: Any = field.get_munger()
if munger is not None:
if field.logical_name in cls.__dict__:
raise ValueError(
f"Cannot define {field.logical_name!r} in class when the associated field "
f"has a munger"
)
munger.__name__ = field.logical_name
munger = init_property(munger)
munger.__set_name__(cls, field.logical_name)
setattr(cls, field.logical_name, munger)
if (
# but don't clobber existing definitions...
field.logical_name not in cls.__dict__ # ...if something is already there in class
and field.logical_name not in fields # ...if a parent has defined the field
):
fn: Any = lambda self, x_name=field.x_name: getattr(self, x_name)
fn.__name__ = field.logical_name
fn = init_property(fn)
fn.__set_name__(cls, field.logical_name)
setattr(cls, field.logical_name, fn)
fields[field.logical_name] = field
for name, value in cls.__dict__.items():
if isinstance(value, Field) and name not in cls_annotations:
raise TypeError(f"{name!r} has no type annotation")
# Mark the class as having been processed by our decorator
cls.__chz_fields__ = fields
if "__init__" in cls.__dict__:
raise ValueError("Cannot define __init__ on a chz class. " + _INIT_ALTERNATIVES)
if "__post_init__" in cls.__dict__:
raise ValueError("Cannot define __post_init__ on a chz class. " + _INIT_ALTERNATIVES)
cls.__init__ = _synthesise_init(fields.values(), sys.modules[user_module].__dict__)
cls.__init__.__qualname__ = f"{cls.__qualname__}.__init__"
cls.__chz_validate__ = __chz_validate__
cls.__chz_init_property__ = __chz_init_property__
if "__setattr__" in cls.__dict__:
raise ValueError("Cannot define __setattr__ on a chz class")
cls.__setattr__ = __setattr__
if "__delattr__" in cls.__dict__:
raise ValueError("Cannot define __delattr__ on a chz class")
cls.__delattr__ = __delattr__
if "__repr__" not in cls.__dict__:
cls.__repr__ = __repr__
if "__eq__" not in cls.__dict__:
cls.__eq__ = __eq__
if "__hash__" not in cls.__dict__:
cls.__hash__ = __hash__
if "_repr_pretty_" not in cls.__dict__:
# Special-cased by IPython
cls._repr_pretty_ = _repr_pretty_
if "__chz_pretty__" not in cls.__dict__:
cls.__chz_pretty__ = __chz_pretty__
if version is not None:
import json
# Hash all the fields and check the version matches
expected_version = version.split("-")[0]
key = [f.versioning_key() for f in sorted(fields.values(), key=lambda f: f.x_name)]
key_bytes = json.dumps(key, separators=(",", ":")).encode()
actual_version = hashlib.sha1(key_bytes).hexdigest()[:8]
if actual_version != expected_version:
raise ValueError(f"Version {version!r} does not match {actual_version!r}")
if typecheck is not None:
import chz.validators as chzval
if typecheck:
chzval._ensure_chz_validators(cls)
if chzval._decorator_typecheck not in cls.__chz_validators__:
cls.__chz_validators__.append(chzval._decorator_typecheck)
else:
if chzval._decorator_typecheck in getattr(cls, "__chz_validators__", []):
raise ValueError("Cannot disable typecheck; all validators are inherited")
return cls