Exemplo n.º 1
0
def messagecls(
    cls_=None,
    *,
    msg_meta: Type[Metadata] = Metadata,
    init=True,
    repr=True,
    eq=True,
    order=False,
    unsafe_hash=False,
    frozen=False,
) -> Type[Message]:
    """Decorator used to build a custom Message type, with the ability to bind
    a custom Metadata class with additional fields. When these instances are built,
    serialized, or de-serialized from the database all the correct fields will be
    filled out with no interference on in-editor linters.

    The parameters for this decorator copy @dataclass with the addition of ``msg_meta``
     which allows the definition to have a custom Metadata class assigned to it.

    All @messagecls decorated classes behave like normal dataclasses.
    """
    def wrap(cls):
        # turn the wrapped class into a dataclass
        kls = dataclass(
            cls,
            init=init,
            repr=repr,
            eq=eq,
            order=order,
            unsafe_hash=unsafe_hash,
            frozen=frozen,
        )
        # extract all the field names and types from the new class definition
        m_fields = {f.name: f.type for f in fields(msg_meta)}
        # re-create the msg_meta class on the `metadata` attribute for this Message
        #  object. We attach the new (and old) fields into the metadata flag for
        # this field so we don't have to process those values every time an instance
        #  is de-serialized from the database.
        return make_dataclass(
            cls.__name__,
            fields=[
                (
                    'metadata',
                    msg_meta,
                    field(
                        init=False,
                        default_factory=msg_meta,
                        metadata=m_fields,
                    ),
                ),
            ],
            bases=(
                kls,
                Message,
            ),
        )

    # ensure this class definition follows basic guidelines
    if not hasattr(msg_meta, '__dataclass_fields__'):
        raise ValueError('custom message metadata class must be a @dataclass')

    if not issubclass(msg_meta, Metadata):
        raise ValueError(
            'custom message metadata class must inherit eventide.Metadata')

    # "wrap" the Metadata class with @dataclass so we don't have to on its definition
    msg_meta = _process_class(msg_meta, True, False, True, False, False, False)

    # mimic @dataclass functionality
    if cls_ is None:
        return wrap
    return wrap(cls_)
Exemplo n.º 2
0
def _process_class(
    _cls: AnyType,
    init: bool,
    repr: bool,
    eq: bool,
    order: bool,
    unsafe_hash: bool,
    frozen: bool,
    config: Optional[Type[Any]],
) -> "DataclassType":
    post_init_original = getattr(_cls, "__post_init__", None)
    if post_init_original and post_init_original.__name__ == "_pydantic_post_init":
        post_init_original = None
    if not post_init_original:
        post_init_original = getattr(_cls, "__post_init_original__", None)

    post_init_post_parse = getattr(_cls, "__post_init_post_parse__", None)

    def _pydantic_post_init(self: "DataclassType", *initvars: Any) -> None:
        if post_init_original is not None:
            post_init_original(self, *initvars)
        d, _, validation_error = validate_model(self.__pydantic_model__,
                                                self.__dict__,
                                                cls=self.__class__)
        if validation_error:
            raise validation_error
        object.__setattr__(self, "__dict__", d)
        object.__setattr__(self, "__initialised__", True)
        if post_init_post_parse is not None:
            post_init_post_parse(self, *initvars)

    _cls.__post_init__ = _pydantic_post_init
    cls = dataclasses._process_class(_cls, init, repr, eq, order, unsafe_hash,
                                     frozen)  # type: ignore

    fields: Dict[str, Any] = {}
    for field in dataclasses.fields(cls):

        if field.default != dataclasses.MISSING:
            field_value = field.default
        # mypy issue 7020 and 708
        elif field.default_factory != dataclasses.MISSING:  # type: ignore
            field_value = field.default_factory()  # type: ignore
        else:
            field_value = Required

        fields[field.name] = (field.type, field_value)

    validators = gather_all_validators(cls)
    cls.__pydantic_model__ = create_model(
        cls.__name__,
        __config__=config,
        __module__=_cls.__module__,
        __validators__=validators,
        **fields,
    )

    cls.__initialised__ = False
    cls.__validate__ = classmethod(_validate_dataclass)
    cls.__get_validators__ = classmethod(_get_validators)
    if post_init_original:
        cls.__post_init_original__ = post_init_original

    if cls.__pydantic_model__.__config__.validate_assignment and not frozen:
        cls.__setattr__ = setattr_validate_assignment

    return cls
Exemplo n.º 3
0
 def wrap(cls):
     setattr(cls, "__post_init__", BaseValidator.__post_init__)
     setattr(cls, "to_dict", BaseValidator.to_dict)
     return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen)
Exemplo n.º 4
0
    def _add_into(data_type: 'DataType', data_type_dict, **kwargs):
        """You can use the prefix `dataclass_` with a dataclass parameter to
        configure it. They can be found in this link:
        https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass

        For example:

        .. code:: python

            from panda_core_data.model import Model

            class ModelName(Model, dataclass_init=False):
                out: int

                def __init__(self, num):
                    self.out = num ** num

        Will make the library not create a `__init__` method and use yours
        instead.

        :param data_type: class type to be added
        :param template_name: The name of the template, if not supplied, the
                              class name is used.
        :type template_name: None or str
        :param dependency_list: :class:`Template` to be used as dependency.
        :type dependency_list: list[str]"""
        from .data_core_bases import GroupWrapper

        if not hasattr(data_type, "dataclass_args"):
            generate_dataclass_args(data_type, **kwargs)
            data_type = _process_class(data_type, **data_type.dataclass_args)

            def new_init(self,
                         *init_args,
                         db_file: Optional[str] = None,
                         default_table: str = DataType.DEFAULT_TABLE,
                         **init_kwargs):
                from .model import Model
                if db_file:
                    self.load_db(db_file,
                                 *init_args,
                                 default_table=default_table,
                                 **init_kwargs)
                elif self.original_init:
                    self.original_init(*init_args, **init_kwargs)

                if isinstance(self, Model):
                    self.wrapper.instances.append(self)
                else:
                    self.wrapper.instances = self

                if hasattr(self, "__post_init__"):
                    self.__post_init__(*init_args, **init_kwargs)

            if (hasattr(data_type, "__init__")
                    and data_type.__init__ is not new_init):
                if data_type.__init__ is not TinyDB.__init__:
                    data_type.original_init = data_type.__init__

                data_type.__init__ = new_init

        replace = kwargs.pop("replace", False)
        data_name = kwargs.pop("data_name", data_type.__name__)
        data_type.dependencies = kwargs.pop("dependencies", [])

        data_type.data_name = data_name
        data_type.data_type_dict = data_type_dict
        data_type.wrapper = GroupWrapper(data_type)

        if data_name not in data_type_dict or replace:
            data_type_dict[data_name] = data_type
        else:
            raise PCDDuplicatedTypeName(
                f"There's already a {type(data_type)} with the name "
                f"{data_name}")