コード例 #1
0
def autodict_decorate(cls,                         # type: Type[T]
                      include=None,                # type: Union[str, Tuple[str]]
                      exclude=None,                # type: Union[str, Tuple[str]]
                      only_known_fields=True,      # type: bool
                      only_public_fields=True,     # type: bool
                      legacy_str_repr=False,       # type: bool
                      only_constructor_args=AUTO,  # type: bool
                      ):
    # type: (...) -> Type[T]
    """
    To automatically generate the appropriate methods so that objects of this class behave like a `dict`,
    manually, without using @autodict decorator.

    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attribute names to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :param only_known_fields: if True (default), only known fields (constructor arguments or pyfields fields) will be
        exposed through the dictionary view, not any other field that would be created in the constructor or
        dynamically. If set to False, the dictionary is a direct view of *all* public object fields. This view can be
        filtered with include/exclude and private fields can be made visible by setting only_public_fields to false
    :param only_public_fields: this parameter is only used when only_constructor_args is set to False. If
        only_public_fields is set to False, all fields are visible. Otherwise (default), class-private fields will be
        hidden
    :param legacy_str_repr: turn this to `True` to get the legacy string representation `{%r: %r, ...}` instead of
        the new default one `(%s=%r, ...)`
    :return:
    """
    if only_constructor_args is not AUTO:
        warn("@autodict: `only_constructor_args` is deprecated and will be removed in a future version, please use "
             "`only_known_fields` instead")
        if only_known_fields is not True:
            raise ValueError("`only_known_fields` is the new name of `only_constructor_args`. Please only set one of "
                             "the two.")
        only_known_fields = only_constructor_args

    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autodict')

    # perform the class mod
    if only_known_fields:
        # retrieve the list of fields from pyfields or constructor signature
        selected_names, source = read_fields(cls, include=include, exclude=exclude, caller="@autodict")

        # add autohash with explicit list
        execute_autodict_on_class(cls, selected_names=selected_names, legacy_str_repr=legacy_str_repr)
    else:
        # no explicit list
        execute_autodict_on_class(cls, include=include, exclude=exclude, public_fields_only=only_public_fields,
                                  legacy_str_repr=legacy_str_repr)

    return cls
コード例 #2
0
def autorepr_decorate(
        cls,  # type: Type[T]
        include=None,  # type: Union[str, Tuple[str]]
        exclude=None,  # type: Union[str, Tuple[str]]
        only_known_fields=True,  # type: bool
        only_public_fields=True,  # type: bool
        curly_string_repr=False,  # type: bool
):
    # type: (...) -> Type[T]
    """
    To automatically generate the appropriate str and repr methods, without using @autoeq decorator.

    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attribute names to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :param only_known_fields: if True (default), only known fields (constructor arguments or pyfields fields) will be
        exposed through the str/repr view, not any other field that would be created in the constructor or
        dynamically. If set to False, the representation is a direct view of *all* public object fields. This view can be
        filtered with include/exclude and private fields can be made visible by setting only_public_fields to false
    :param only_public_fields: this parameter is only used when only_constructor_args is set to False. If
        only_public_fields is set to False, all fields are visible. Otherwise (default), class-private fields will be
        hidden
    :param curly_string_repr: turn this to `True` to get the curly string representation `{%r: %r, ...}` instead of
        the default one `(%s=%r, ...)`
    :return:
    """
    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autorepr')

    # perform the class mod
    if only_known_fields:
        # retrieve the list of fields from pyfields or constructor signature
        selected_names, source = read_fields(cls,
                                             include=include,
                                             exclude=exclude,
                                             caller="@autorepr")

        # add autohash with explicit list
        execute_autorepr_on_class(cls,
                                  selected_names=selected_names,
                                  curly_string_repr=curly_string_repr)
    else:
        # no explicit list
        execute_autorepr_on_class(cls,
                                  include=include,
                                  exclude=exclude,
                                  public_fields_only=only_public_fields,
                                  curly_string_repr=curly_string_repr)

    return cls
コード例 #3
0
def autoeq_decorate(cls,                      # type: Type[T]
                      include=None,             # type: Union[str, Tuple[str]]
                      exclude=None,             # type: Union[str, Tuple[str]]
                      only_known_fields=True,   # type: bool
                      only_public_fields=True,  # type: bool
                      ):
    # type: (...) -> Type[T]
    """
    To automatically generate the appropriate eq methods, without using @autoeq decorator.

    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attribute names to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :param only_known_fields: if True (default), only known fields (constructor arguments or pyfields fields) will be
        used in the eq comparison, not any other field that would be created in the constructor or
        dynamically. If set to False, *all* public object fields will be used. The list can be
        filtered with include/exclude and private fields can be included by setting only_public_fields to false
    :param only_public_fields: this parameter is only used when only_known_fields is set to False. If
        only_public_fields is set to False, all fields are used in the comparison. Otherwise (default), class-private
        fields will not be used
    :return:
    """
    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autoeq')

    # perform the class mod
    if only_known_fields:
        # retrieve the list of fields from pyfields or constructor signature
        selected_names, source = read_fields(cls, include=include, exclude=exclude, caller="@autoeq")

        # add autohash with explicit list
        execute_autoeq_on_class(cls, selected_names=selected_names)
    else:
        # no explicit list
        execute_autoeq_on_class(cls, include=include, exclude=exclude, public_fields_only=only_public_fields)

    return cls
コード例 #4
0
def autoprops_decorate(
    cls,  # type: Type[T]
    include=None,  # type: Union[str, Tuple[str]]
    exclude=None  # type: Union[str, Tuple[str]]
):
    # type: (...) -> Type[T]
    """
    To automatically generate all properties getters and setters from the class constructor manually, without using
    @autoprops decorator.
    * if a @contract annotation exist on the __init__ method, mentioning a contract for a given parameter, the
    parameter contract will be added on the generated setter method
    * The user may override the generated getter and/or setter by creating them explicitly in the class and annotating
    them with @getter_override or @setter_override. Note that the contract will still be dynamically added on the
    setter, even if the setter already has one (in such case a `UserWarning` will be issued)

    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attribute names to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :return:
    """
    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autoprops')

    # retrieve and filter the names
    init_fun = cls.__init__
    selected_names, init_fun_sig = read_fields_from_init(init_fun,
                                                         include=include,
                                                         exclude=exclude,
                                                         caller="@autoprops")

    # perform the class mod
    execute_autoprops_on_class(cls,
                               init_fun=init_fun,
                               init_fun_sig=init_fun_sig,
                               prop_names=selected_names)

    return cls
コード例 #5
0
def autoclass_decorate(cls,               # type: Type[T]
                       include=None,      # type: Union[str, Tuple[str]]
                       exclude=None,      # type: Union[str, Tuple[str]]
                       autoargs=AUTO,     # type: bool
                       autoprops=AUTO,    # type: bool
                       autoinit=AUTO,     # type: bool
                       autodict=True,     # type: bool
                       autorepr=AUTO,     # type: bool
                       autoeq=AUTO,       # type: bool
                       autohash=True,     # type: bool
                       autoslots=False,   # type: bool
                       autofields=False,  # type: bool
                       ):
    # type: (...) -> Type[T]
    """

    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attributes to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :param autoargs: a boolean to enable autoargs on the constructor. By default it is `AUTO` and means "automatic
        configuration". In that case, the behaviour will depend on the class: it will be equivalent to `True` if the
        class defines an `__init__` method and has no `pyfields` fields ; and `False` otherwise.
    :param autoprops: a boolean to enable autoprops on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, the behaviour will depend on the class: it will be equivalent to `True` if the
        class defines an `__init__` method and has no `pyfields` fields ; and `False` otherwise.
    :param autoinit: a boolean to enable autoinit on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, the behaviour will depend on the class: it will be equivalent to `True` if the
        class has `pyfields` fields and does not define an `__init__` method ; and `False` otherwise.
    :param autodict: a boolean to enable autodict on the class (default: True). By default it will be executed with
        `only_known_fields=True`.
    :param autorepr: a boolean to enable autorepr on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, it will be defined as `not autodict`.
    :param autoeq: a boolean to enable autoeq on the class. By default it is `AUTO` and means "automatic
        configuration". In that case, it will be defined as `not autodict`.
    :param autohash: a boolean to enable autohash on the class (default: True). By default it will be executed with
        `only_known_fields=True`.
    :param autoslots: a boolean to enable autoslots on the class (default: False).
    :param autofields: a boolean (default: False) to apply autofields automatically on the class before applying
        `autoclass` (see `pyfields` documentation for details)
    :return:
    """
    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autoclass')

    if autofields:
        if WITH_PYFIELDS:
            cls = apply_autofields(cls)
        else:
            raise ValueError("`autofields=True` can only be used when `pyfields` is installed. Please `pip install "
                             "pyfields`")

    # Get constructor
    init_fun, is_init_inherited = get_constructor(cls)

    # Check for pyfields fields
    if WITH_PYFIELDS:
        all_pyfields = get_fields(cls)
        has_pyfields = len(all_pyfields) > 0  # 'or autofields' ?: cant' find a use case where it would be needed.
    else:
        has_pyfields = False

    # variable and function used below to get the reference list of attributes
    selected_names, init_fun_sig = None, None

    # ------- @autoslots - this replaces the class so do it first
    if autoslots:
        if has_pyfields:
            raise ValueError("autoslots is not available for classes using `pyfields`")
        cls = autoslots_decorate(cls, include=include, exclude=exclude, use_public_names=not autoprops)

    # ------- @autoargs and @autoprops
    if autoargs is AUTO:
        # apply if the init is defined in the class AND if there are no pyfields
        autoargs = (not is_init_inherited) and (not has_pyfields)
    if autoprops is AUTO:
        # apply if there are no pyfields
        autoprops = not has_pyfields

    # a few common variables
    if autoargs or autoprops:
        # retrieve init function signature and filter its parameters according to include/exclude
        # note: pyfields *can* be present, but for explicit @autoargs and @autoprops we do not use it as a reference
        selected_names, init_fun_sig = read_fields_from_init(init_fun, include=include, exclude=exclude,
                                                             caller="@autoclass")

    # apply them
    if autoargs:
        if is_init_inherited:  # no init explicitly defined in the class > error
            raise NoCustomInitError(cls)
        cls.__init__ = _autoargs_decorate(func=init_fun, func_sig=init_fun_sig, att_names=selected_names)
    if autoprops:
        # noinspection PyUnboundLocalVariable
        execute_autoprops_on_class(cls, init_fun=init_fun, init_fun_sig=init_fun_sig, prop_names=selected_names)

    # create a reference list of attribute names and type hints for all subsequent decorators
    if has_pyfields:
        # Use reference list from pyfields now (even if autoargs was executed, it did not use the correct list)
        # noinspection PyUnboundLocalVariable
        all_names = tuple(f.name for f in all_pyfields)
        selected_names = filter_names(all_names, include=include, exclude=exclude, caller="@autoclass")
        selected_fields = tuple(f for f in all_pyfields if f.name in selected_names)

    elif selected_names is None:
        # Create reference list - autoargs was not executed and there are no pyfields: we need something
        selected_names, init_fun_sig = read_fields_from_init(init_fun, include=include, exclude=exclude,
                                                             caller="@autoclass")

    # autoinit
    if autoinit is AUTO:
        # apply if no init is defined in the class AND if there are pyfields
        autoinit = is_init_inherited and has_pyfields

    if autoinit:
        if not has_pyfields:
            raise ValueError("`autoinit` is only available if you class contains `pyfields` fields.")
        # noinspection PyUnboundLocalVariable
        cls.__init__ = make_init(*selected_fields)

    # @autodict or @autorepr
    if autodict:
        if autorepr is not AUTO and autorepr:
            raise ValueError("`autorepr` can not be set to `True` simultaneously with `autodict`. Please set "
                             "`autodict=False`.")
        if autoeq is not AUTO and autoeq:
            raise ValueError("`autoeq` can not be set to `True` simultaneously with `autodict`. Please set "
                             "`autodict=False`.")
        # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
        # Exclude private fields by default to have a consistent behaviour with autodict
        public_names = tuple(n for n in selected_names if not n.startswith('_'))
        execute_autodict_on_class(cls, selected_names=public_names)
    else:
        if autorepr is AUTO or autorepr:
            # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
            execute_autorepr_on_class(cls, selected_names=selected_names)
        if autoeq is AUTO or autoeq:
            # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
            execute_autoeq_on_class(cls, selected_names=selected_names)

    # @autohash
    if autohash:
        # By default execute with the known list of fields, so equivalent of `only_known_fields=True`.
        execute_autohash_on_class(cls, selected_names=selected_names)

    return cls
コード例 #6
0
def autoslots_decorate(cls,                    # type: Type[C]
                       include=None,           # type: Union[str, Tuple[str]]
                       exclude=None,           # type: Union[str, Tuple[str]]
                       use_public_names=True,  # type: bool
                       add_weakref_slot=True,  # type: bool
                       ):
    # type: (...) -> Type[C]
    """



    :param cls: the class on which to execute. Note that it won't be wrapped.
    :param include: a tuple of explicit attributes to include (None means all)
    :param exclude: a tuple of explicit attribute names to exclude. In such case, include should be None.
    :param use_public_names: a boolean (default=True) indicating if the slot names should be the same names than the
        constructor names, or the
    :param add_weakref_slot: a boolean indicating if a `__weakref__` slot should be added (default: True)
    :return:
    """
    # first check that we do not conflict with other known decorators
    check_known_decorators(cls, '@autoslots')

    # retrieve the list of fields from pyfields or constructor signature
    selected_names, source = read_fields(cls, include=include, exclude=exclude, caller="@autoslots")

    # c. Collect the various items in the namespace of the class
    cd = {k: v for k, v in cls.__dict__.items() if k not in ("__dict__", "__weakref__")}

    # Traverse the MRO to check for an existing __weakref__.
    weakref_inherited = False
    base_names = []
    for base_cls in cls.__mro__[1:-1]:
        base_names += getattr(base_cls, "__slots__", ())
        if "__weakref__" in  getattr(base_cls, "__dict__", ()):  # not needed: "in basename"
            weakref_inherited = True
            break

    if use_public_names:
        names = selected_names
    else:
        names = tuple("_%s" % a for a in selected_names)

    # are there slots already on this class ?
    existing_cls_slots = list(cls.__dict__.get("__slots__", ()))

    if add_weakref_slot and not weakref_inherited \
        and "__weakref__" not in names:  # and "__weakref__" not in existing_cls_slots \
        names += ("__weakref__",)

    # Now we augment the namespace of the class to create, with

    # We only add the names of attributes that aren't inherited.
    # Settings __slots__ to inherited attributes wastes memory.
    slot_names = [name for name in names if name not in base_names and name not in existing_cls_slots]
    # add pre-existing slots from the class
    for es in existing_cls_slots:
        slot_names.append(es)
        # remove existing slot descriptor from the class
        del cd[es]
    cd["__slots__"] = tuple(slot_names)

    # __getstate__/__setstate__ need to be overridden for pickle to continue working
    # see https://stackoverflow.com/questions/28665411/odd-behavior-with-slots-and-pickle
    # __weakref__ is not writable.
    state_attr_names = tuple(an for an in slot_names if an != "__weakref__")

    def __getstate__(self):
        """
        Generated by @autoslots
        """
        return tuple(getattr(self, name) for name in state_attr_names)

    def __setstate__(self, state):
        """
        Generated by @autoslots
        """
        for name, value in zip(state_attr_names, state):
            setattr(self, name, value)

    cd["__getstate__"] = __getstate__
    cd["__setstate__"] = __setstate__

    # Finally create the new class
    new_cls = type(cls)(cls.__name__, cls.__bases__, cd)

    # Shameously copied from `attrs` (in particular very tricky code of `set_closure_cell` below)
    # A fix for https://github.com/python-attrs/attrs/issues/102.
    # On Python 3, if a method mentions `__class__` or uses the no-arg super(), the
    # compiler will bake a reference to the class in the method itself
    # as `method.__closure__`.  Since we replace the class with a
    # clone, we rewrite these references so it keeps working.
    for item in cls.__dict__.values():
        if isinstance(item, (classmethod, staticmethod)):
            # Class- and staticmethods hide their functions inside.
            # These might need to be rewritten as well.
            closure_cells = getattr(item.__func__, "__closure__", None)
        else:
            closure_cells = getattr(item, "__closure__", None)

        if not closure_cells:  # Catch None or the empty list.
            continue
        for cell in closure_cells:
            try:
                match = cell.cell_contents is cls
            except ValueError:  # ValueError: Cell is empty
                pass
            else:
                if match:
                    set_closure_cell(cell, new_cls)

    return new_cls