Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_make.py3407 linesDownload Raw Back to attr
1# SPDX-License-Identifier: MIT2 3from __future__ import annotations4 5import abc6import contextlib7import copy8import enum9import inspect10import itertools11import linecache12import sys13import types14import unicodedata15import weakref16 17from collections.abc import Callable, Mapping18from functools import cached_property19from typing import Any, NamedTuple, TypeVar20 21# We need to import _compat itself in addition to the _compat members to avoid22# having the thread-local in the globals here.23from . import _compat, _config, setters24from ._compat import (25    PY_3_10_PLUS,26    PY_3_11_PLUS,27    PY_3_13_PLUS,28    _AnnotationExtractor,29    _get_annotations,30    get_generic_base,31)32from .exceptions import (33    DefaultAlreadySetError,34    FrozenInstanceError,35    NotAnAttrsClassError,36    UnannotatedAttributeError,37)38 39 40# This is used at least twice, so cache it here.41_OBJ_SETATTR = object.__setattr__42_INIT_FACTORY_PAT = "__attr_factory_%s"43_CLASSVAR_PREFIXES = (44    "typing.ClassVar",45    "t.ClassVar",46    "ClassVar",47    "typing_extensions.ClassVar",48)49# we don't use a double-underscore prefix because that triggers50# name mangling when trying to create a slot for the field51# (when slots=True)52_HASH_CACHE_FIELD = "_attrs_cached_hash"53 54_EMPTY_METADATA_SINGLETON = types.MappingProxyType({})55 56# Unique object for unequivocal getattr() defaults.57_SENTINEL = object()58 59_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate)60 61 62class _Nothing(enum.Enum):63    """64    Sentinel to indicate the lack of a value when `None` is ambiguous.65 66    If extending attrs, you can use ``typing.Literal[NOTHING]`` to show67    that a value may be ``NOTHING``.68 69    .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False.70    .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant.71    """72 73    NOTHING = enum.auto()74 75    def __repr__(self):76        return "NOTHING"77 78    def __bool__(self):79        return False80 81 82NOTHING = _Nothing.NOTHING83"""84Sentinel to indicate the lack of a value when `None` is ambiguous.85 86When using in 3rd party code, use `attrs.NothingType` for type annotations.87"""88 89 90class _CacheHashWrapper(int):91    """92    An integer subclass that pickles / copies as None93 94    This is used for non-slots classes with ``cache_hash=True``, to avoid95    serializing a potentially (even likely) invalid hash value. Since `None`96    is the default value for uncalculated hashes, whenever this is copied,97    the copy's value for the hash should automatically reset.98 99    See GH #613 for more details.100    """101 102    def __reduce__(self, _none_constructor=type(None), _args=()):  # noqa: B008103        return _none_constructor, _args104 105 106def attrib(107    default=NOTHING,108    validator=None,109    repr=True,110    cmp=None,111    hash=None,112    init=True,113    metadata=None,114    type=None,115    converter=None,116    factory=None,117    kw_only=None,118    eq=None,119    order=None,120    on_setattr=None,121    alias=None,122):123    """124    Create a new field / attribute on a class.125 126    Identical to `attrs.field`, except it's not keyword-only.127 128    Consider using `attrs.field` in new code (``attr.ib`` will *never* go away,129    though).130 131    ..  warning::132 133        Does **nothing** unless the class is also decorated with134        `attr.s` (or similar)!135 136 137    .. versionadded:: 15.2.0 *convert*138    .. versionadded:: 16.3.0 *metadata*139    .. versionchanged:: 17.1.0 *validator* can be a ``list`` now.140    .. versionchanged:: 17.1.0141       *hash* is `None` and therefore mirrors *eq* by default.142    .. versionadded:: 17.3.0 *type*143    .. deprecated:: 17.4.0 *convert*144    .. versionadded:: 17.4.0145       *converter* as a replacement for the deprecated *convert* to achieve146       consistency with other noun-based arguments.147    .. versionadded:: 18.1.0148       ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``.149    .. versionadded:: 18.2.0 *kw_only*150    .. versionchanged:: 19.2.0 *convert* keyword argument removed.151    .. versionchanged:: 19.2.0 *repr* also accepts a custom callable.152    .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.153    .. versionadded:: 19.2.0 *eq* and *order*154    .. versionadded:: 20.1.0 *on_setattr*155    .. versionchanged:: 20.3.0 *kw_only* backported to Python 2156    .. versionchanged:: 21.1.0157       *eq*, *order*, and *cmp* also accept a custom callable158    .. versionchanged:: 21.1.0 *cmp* undeprecated159    .. versionadded:: 22.2.0 *alias*160    .. versionchanged:: 25.4.0161       *kw_only* can now be None, and its default is also changed from False to162       None.163    """164    eq, eq_key, order, order_key = _determine_attrib_eq_order(165        cmp, eq, order, True166    )167 168    if hash is not None and hash is not True and hash is not False:169        msg = "Invalid value for hash.  Must be True, False, or None."170        raise TypeError(msg)171 172    if factory is not None:173        if default is not NOTHING:174            msg = (175                "The `default` and `factory` arguments are mutually exclusive."176            )177            raise ValueError(msg)178        if not callable(factory):179            msg = "The `factory` argument must be a callable."180            raise ValueError(msg)181        default = Factory(factory)182 183    if metadata is None:184        metadata = {}185 186    # Apply syntactic sugar by auto-wrapping.187    if isinstance(on_setattr, (list, tuple)):188        on_setattr = setters.pipe(*on_setattr)189 190    if validator and isinstance(validator, (list, tuple)):191        validator = and_(*validator)192 193    if converter and isinstance(converter, (list, tuple)):194        converter = pipe(*converter)195 196    return _CountingAttr(197        default=default,198        validator=validator,199        repr=repr,200        cmp=None,201        hash=hash,202        init=init,203        converter=converter,204        metadata=metadata,205        type=type,206        kw_only=kw_only,207        eq=eq,208        eq_key=eq_key,209        order=order,210        order_key=order_key,211        on_setattr=on_setattr,212        alias=alias,213    )214 215 216def _compile_and_eval(217    script: str,218    globs: dict[str, Any] | None,219    locs: Mapping[str, object] | None = None,220    filename: str = "",221) -> None:222    """223    Evaluate the script with the given global (globs) and local (locs)224    variables.225    """226    bytecode = compile(script, filename, "exec")227    eval(bytecode, globs, locs)228 229 230def _linecache_and_compile(231    script: str,232    filename: str,233    globs: dict[str, Any] | None,234    locals: Mapping[str, object] | None = None,235) -> dict[str, Any]:236    """237    Cache the script with _linecache_, compile it and return the _locals_.238    """239 240    locs = {} if locals is None else locals241 242    # In order of debuggers like PDB being able to step through the code,243    # we add a fake linecache entry.244    count = 1245    base_filename = filename246    while True:247        linecache_tuple = (248            len(script),249            None,250            script.splitlines(True),251            filename,252        )253        old_val = linecache.cache.setdefault(filename, linecache_tuple)254        if old_val == linecache_tuple:255            break256 257        filename = f"{base_filename[:-1]}-{count}>"258        count += 1259 260    _compile_and_eval(script, globs, locs, filename)261 262    return locs263 264 265def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type:266    """267    Create a tuple subclass to hold `Attribute`s for an `attrs` class.268 269    The subclass is a bare tuple with properties for names.270 271    class MyClassAttributes(tuple):272        __slots__ = ()273        x = property(itemgetter(0))274    """275    attr_class_name = f"{cls_name}Attributes"276    body = {}277    for i, attr_name in enumerate(attr_names):278 279        def getter(self, i=i):280            return self[i]281 282        body[attr_name] = property(getter)283    return type(attr_class_name, (tuple,), body)284 285 286# Tuple class for extracted attributes from a class definition.287# `base_attrs` is a subset of `attrs`.288class _Attributes(NamedTuple):289    attrs: type290    base_attrs: list[Attribute]291    base_attrs_map: dict[str, type]292 293 294def _is_class_var(annot):295    """296    Check whether *annot* is a typing.ClassVar.297 298    The string comparison hack is used to avoid evaluating all string299    annotations which would put attrs-based classes at a performance300    disadvantage compared to plain old classes.301    """302    annot = str(annot)303 304    # Annotation can be quoted.305    if annot.startswith(("'", '"')) and annot.endswith(("'", '"')):306        annot = annot[1:-1]307 308    return annot.startswith(_CLASSVAR_PREFIXES)309 310 311def _has_own_attribute(cls, attrib_name):312    """313    Check whether *cls* defines *attrib_name* (and doesn't just inherit it).314    """315    return attrib_name in cls.__dict__316 317 318def _collect_base_attrs(319    cls, taken_attr_names320) -> tuple[list[Attribute], dict[str, type]]:321    """322    Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.323    """324    base_attrs = []325    base_attr_map = {}  # A dictionary of base attrs to their classes.326 327    # Traverse the MRO and collect attributes.328    for base_cls in reversed(cls.__mro__[1:-1]):329        for a in getattr(base_cls, "__attrs_attrs__", []):330            if a.inherited or a.name in taken_attr_names:331                continue332 333            a = a.evolve(inherited=True)  # noqa: PLW2901334            base_attrs.append(a)335            base_attr_map[a.name] = base_cls336 337    # For each name, only keep the freshest definition i.e. the furthest at the338    # back.  base_attr_map is fine because it gets overwritten with every new339    # instance.340    filtered = []341    seen = set()342    for a in reversed(base_attrs):343        if a.name in seen:344            continue345        filtered.insert(0, a)346        seen.add(a.name)347 348    return filtered, base_attr_map349 350 351def _collect_base_attrs_broken(cls, taken_attr_names):352    """353    Collect attr.ibs from base classes of *cls*, except *taken_attr_names*.354 355    N.B. *taken_attr_names* will be mutated.356 357    Adhere to the old incorrect behavior.358 359    Notably it collects from the front and considers inherited attributes which360    leads to the buggy behavior reported in #428.361    """362    base_attrs = []363    base_attr_map = {}  # A dictionary of base attrs to their classes.364 365    # Traverse the MRO and collect attributes.366    for base_cls in cls.__mro__[1:-1]:367        for a in getattr(base_cls, "__attrs_attrs__", []):368            if a.name in taken_attr_names:369                continue370 371            a = a.evolve(inherited=True)  # noqa: PLW2901372            taken_attr_names.add(a.name)373            base_attrs.append(a)374            base_attr_map[a.name] = base_cls375 376    return base_attrs, base_attr_map377 378 379def _transform_attrs(380    cls,381    these,382    auto_attribs,383    kw_only,384    collect_by_mro,385    field_transformer,386) -> _Attributes:387    """388    Transform all `_CountingAttr`s on a class into `Attribute`s.389 390    If *these* is passed, use that and don't look for them on the class.391 392    If *collect_by_mro* is True, collect them in the correct MRO order,393    otherwise use the old -- incorrect -- order.  See #428.394 395    Return an `_Attributes`.396    """397    cd = cls.__dict__398    anns = _get_annotations(cls)399 400    if these is not None:401        ca_list = list(these.items())402    elif auto_attribs is True:403        ca_names = {404            name405            for name, attr in cd.items()406            if attr.__class__ is _CountingAttr407        }408        ca_list = []409        annot_names = set()410        for attr_name, type in anns.items():411            if _is_class_var(type):412                continue413            annot_names.add(attr_name)414            a = cd.get(attr_name, NOTHING)415 416            if a.__class__ is not _CountingAttr:417                a = attrib(a)418            ca_list.append((attr_name, a))419 420        unannotated = ca_names - annot_names421        if unannotated:422            raise UnannotatedAttributeError(423                "The following `attr.ib`s lack a type annotation: "424                + ", ".join(425                    sorted(unannotated, key=lambda n: cd.get(n).counter)426                )427                + "."428            )429    else:430        ca_list = sorted(431            (432                (name, attr)433                for name, attr in cd.items()434                if attr.__class__ is _CountingAttr435            ),436            key=lambda e: e[1].counter,437        )438 439    fca = Attribute.from_counting_attr440    no = ClassProps.KeywordOnly.NO441    own_attrs = [442        fca(443            attr_name,444            ca,445            kw_only is not no,446            anns.get(attr_name),447        )448        for attr_name, ca in ca_list449    ]450 451    if collect_by_mro:452        base_attrs, base_attr_map = _collect_base_attrs(453            cls, {a.name for a in own_attrs}454        )455    else:456        base_attrs, base_attr_map = _collect_base_attrs_broken(457            cls, {a.name for a in own_attrs}458        )459 460    if kw_only is ClassProps.KeywordOnly.FORCE:461        own_attrs = [a.evolve(kw_only=True) for a in own_attrs]462        base_attrs = [a.evolve(kw_only=True) for a in base_attrs]463 464    attrs = base_attrs + own_attrs465 466    # Resolve default field alias before executing field_transformer, so that467    # the transformer receives fully populated Attribute objects with usable468    # alias values.469    for a in attrs:470        if not a.alias:471            # Evolve is very slow, so we hold our nose and do it dirty.472            _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))473            _OBJ_SETATTR.__get__(a)("alias_is_default", True)474 475    if field_transformer is not None:476        attrs = tuple(field_transformer(cls, attrs))477 478    # Check attr order after executing the field_transformer.479    # Mandatory vs non-mandatory attr order only matters when they are part of480    # the __init__ signature and when they aren't kw_only (which are moved to481    # the end and can be mandatory or non-mandatory in any order, as they will482    # be specified as keyword args anyway). Check the order of those attrs:483    had_default = False484    for a in (a for a in attrs if a.init is not False and a.kw_only is False):485        if had_default is True and a.default is NOTHING:486            msg = f"No mandatory attributes allowed after an attribute with a default value or factory.  Attribute in question: {a!r}"487            raise ValueError(msg)488 489        if had_default is False and a.default is not NOTHING:490            had_default = True491 492    # Resolve default field alias for any new attributes that the493    # field_transformer may have added without setting an alias.494    for a in attrs:495        if not a.alias:496            _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))497            _OBJ_SETATTR.__get__(a)("alias_is_default", True)498 499    # Create AttrsClass *after* applying the field_transformer since it may500    # add or remove attributes!501    attr_names = [a.name for a in attrs]502    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)503 504    return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map)505 506 507def _make_cached_property_getattr(cached_properties, original_getattr, cls):508    lines = [509        # Wrapped to get `__class__` into closure cell for super()510        # (It will be replaced with the newly constructed class after construction).511        "def wrapper(_cls):",512        "    __class__ = _cls",513        "    def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):",514        "         func = cached_properties.get(item)",515        "         if func is not None:",516        "              result = func(self)",517        "              _setter = _cached_setattr_get(self)",518        "              _setter(item, result)",519        "              return result",520    ]521    if original_getattr is not None:522        lines.append(523            "         return original_getattr(self, item)",524        )525    else:526        lines.extend(527            [528                "         try:",529                "             return super().__getattribute__(item)",530                "         except AttributeError:",531                "             if not hasattr(super(), '__getattr__'):",532                "                 raise",533                "             return super().__getattr__(item)",534                "         original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",535                "         raise AttributeError(original_error)",536            ]537        )538 539    lines.extend(540        [541            "    return __getattr__",542            "__getattr__ = wrapper(_cls)",543        ]544    )545 546    unique_filename = _generate_unique_filename(cls, "getattr")547 548    glob = {549        "cached_properties": cached_properties,550        "_cached_setattr_get": _OBJ_SETATTR.__get__,551        "original_getattr": original_getattr,552    }553 554    return _linecache_and_compile(555        "\n".join(lines), unique_filename, glob, locals={"_cls": cls}556    )["__getattr__"]557 558 559def _frozen_setattrs(self, name, value):560    """561    Attached to frozen classes as __setattr__.562    """563    if isinstance(self, BaseException) and name in (564        "__cause__",565        "__context__",566        "__traceback__",567        "__suppress_context__",568        "__notes__",569    ):570        BaseException.__setattr__(self, name, value)571        return572 573    raise FrozenInstanceError574 575 576def _frozen_delattrs(self, name):577    """578    Attached to frozen classes as __delattr__.579    """580    if isinstance(self, BaseException) and name == "__notes__":581        BaseException.__delattr__(self, name)582        return583 584    raise FrozenInstanceError585 586 587def evolve(*args, **changes):588    """589    Create a new instance, based on the first positional argument with590    *changes* applied.591 592    .. tip::593 594       On Python 3.13 and later, you can also use `copy.replace` instead.595 596    Args:597 598        inst:599            Instance of a class with *attrs* attributes. *inst* must be passed600            as a positional argument.601 602        changes:603            Keyword changes in the new copy.604 605    Returns:606        A copy of inst with *changes* incorporated.607 608    Raises:609        TypeError:610            If *attr_name* couldn't be found in the class ``__init__``.611 612        attrs.exceptions.NotAnAttrsClassError:613            If *cls* is not an *attrs* class.614 615    .. versionadded:: 17.1.0616    .. deprecated:: 23.1.0617       It is now deprecated to pass the instance using the keyword argument618       *inst*. It will raise a warning until at least April 2024, after which619       it will become an error. Always pass the instance as a positional620       argument.621    .. versionchanged:: 24.1.0622       *inst* can't be passed as a keyword argument anymore.623    """624    try:625        (inst,) = args626    except ValueError:627        msg = (628            f"evolve() takes 1 positional argument, but {len(args)} were given"629        )630        raise TypeError(msg) from None631 632    cls = inst.__class__633    attrs = fields(cls)634    for a in attrs:635        if not a.init:636            continue637        attr_name = a.name  # To deal with private attributes.638        init_name = a.alias639        if init_name not in changes:640            changes[init_name] = getattr(inst, attr_name)641 642    return cls(**changes)643 644 645class _ClassBuilder:646    """647    Iteratively build *one* class.648    """649 650    __slots__ = (651        "_add_method_dunders",652        "_attr_names",653        "_attrs",654        "_base_attr_map",655        "_base_names",656        "_cache_hash",657        "_cls",658        "_cls_dict",659        "_delete_attribs",660        "_frozen",661        "_has_custom_setattr",662        "_has_post_init",663        "_has_pre_init",664        "_is_exc",665        "_on_setattr",666        "_pre_init_has_args",667        "_repr_added",668        "_script_snippets",669        "_slots",670        "_weakref_slot",671        "_wrote_own_setattr",672    )673 674    def __init__(675        self,676        cls: type,677        these,678        auto_attribs: bool,679        props: ClassProps,680        has_custom_setattr: bool,681    ):682        attrs, base_attrs, base_map = _transform_attrs(683            cls,684            these,685            auto_attribs,686            props.kw_only,687            props.collected_fields_by_mro,688            props.field_transformer,689        )690 691        self._cls = cls692        self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}693        self._attrs = attrs694        self._base_names = {a.name for a in base_attrs}695        self._base_attr_map = base_map696        self._attr_names = tuple(a.name for a in attrs)697        self._slots = props.is_slotted698        self._frozen = props.is_frozen699        self._weakref_slot = props.has_weakref_slot700        self._cache_hash = (701            props.hashability is ClassProps.Hashability.HASHABLE_CACHED702        )703        self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))704        self._pre_init_has_args = False705        if self._has_pre_init:706            # Check if the pre init method has more arguments than just `self`707            # We want to pass arguments if pre init expects arguments708            pre_init_func = cls.__attrs_pre_init__709            pre_init_signature = inspect.signature(pre_init_func)710            self._pre_init_has_args = len(pre_init_signature.parameters) > 1711        self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))712        self._delete_attribs = not bool(these)713        self._is_exc = props.is_exception714        self._on_setattr = props.on_setattr_hook715 716        self._has_custom_setattr = has_custom_setattr717        self._wrote_own_setattr = False718 719        self._cls_dict["__attrs_attrs__"] = self._attrs720        self._cls_dict["__attrs_props__"] = props721 722        if props.is_frozen:723            self._cls_dict["__setattr__"] = _frozen_setattrs724            self._cls_dict["__delattr__"] = _frozen_delattrs725 726            self._wrote_own_setattr = True727        elif self._on_setattr in (728            _DEFAULT_ON_SETATTR,729            setters.validate,730            setters.convert,731        ):732            has_validator = has_converter = False733            for a in attrs:734                if a.validator is not None:735                    has_validator = True736                if a.converter is not None:737                    has_converter = True738 739                if has_validator and has_converter:740                    break741            if (742                (743                    self._on_setattr == _DEFAULT_ON_SETATTR744                    and not (has_validator or has_converter)745                )746                or (self._on_setattr == setters.validate and not has_validator)747                or (self._on_setattr == setters.convert and not has_converter)748            ):749                # If class-level on_setattr is set to convert + validate, but750                # there's no field to convert or validate, pretend like there's751                # no on_setattr.752                self._on_setattr = None753 754        if props.added_pickling:755            (756                self._cls_dict["__getstate__"],757                self._cls_dict["__setstate__"],758            ) = self._make_getstate_setstate()759 760        # tuples of script, globs, hook761        self._script_snippets: list[762            tuple[str, dict, Callable[[dict, dict], Any]]763        ] = []764        self._repr_added = False765 766        # We want to only do this check once; in 99.9% of cases these767        # exist.768        if not hasattr(self._cls, "__module__") or not hasattr(769            self._cls, "__qualname__"770        ):771            self._add_method_dunders = self._add_method_dunders_safe772        else:773            self._add_method_dunders = self._add_method_dunders_unsafe774 775    def __repr__(self):776        return f"<_ClassBuilder(cls={self._cls.__name__})>"777 778    def _eval_snippets(self) -> None:779        """780        Evaluate any registered snippets in one go.781        """782        script = "\n".join([snippet[0] for snippet in self._script_snippets])783        globs = {}784        for _, snippet_globs, _ in self._script_snippets:785            globs.update(snippet_globs)786 787        locs = _linecache_and_compile(788            script,789            _generate_unique_filename(self._cls, "methods"),790            globs,791        )792 793        for _, _, hook in self._script_snippets:794            hook(self._cls_dict, locs)795 796    def build_class(self):797        """798        Finalize class based on the accumulated configuration.799 800        Builder cannot be used after calling this method.801        """802        self._eval_snippets()803        if self._slots is True:804            cls = self._create_slots_class()805            self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)806        else:807            cls = self._patch_original_class()808            if PY_3_10_PLUS:809                cls = abc.update_abstractmethods(cls)810 811        # The method gets only called if it's not inherited from a base class.812        # _has_own_attribute does NOT work properly for classmethods.813        if (814            getattr(cls, "__attrs_init_subclass__", None)815            and "__attrs_init_subclass__" not in cls.__dict__816        ):817            cls.__attrs_init_subclass__()818 819        return cls820 821    def _patch_original_class(self):822        """823        Apply accumulated methods and return the class.824        """825        cls = self._cls826        base_names = self._base_names827 828        # Clean class of attribute definitions (`attr.ib()`s).829        if self._delete_attribs:830            for name in self._attr_names:831                if (832                    name not in base_names833                    and getattr(cls, name, _SENTINEL) is not _SENTINEL834                ):835                    # An AttributeError can happen if a base class defines a836                    # class variable and we want to set an attribute with the837                    # same name by using only a type annotation.838                    with contextlib.suppress(AttributeError):839                        delattr(cls, name)840 841        # Attach our dunder methods.842        for name, value in self._cls_dict.items():843            setattr(cls, name, value)844 845        # If we've inherited an attrs __setattr__ and don't write our own,846        # reset it to object's.847        if not self._wrote_own_setattr and getattr(848            cls, "__attrs_own_setattr__", False849        ):850            cls.__attrs_own_setattr__ = False851 852            if not self._has_custom_setattr:853                cls.__setattr__ = _OBJ_SETATTR854 855        return cls856 857    def _create_slots_class(self):858        """859        Build and return a new class with a `__slots__` attribute.860        """861        cd = {862            k: v863            for k, v in self._cls_dict.items()864            if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")865        }866 867        # 3.14.0rc2+868        if hasattr(sys, "_clear_type_descriptors"):869            sys._clear_type_descriptors(self._cls)870 871        # If our class doesn't have its own implementation of __setattr__872        # (either from the user or by us), check the bases, if one of them has873        # an attrs-made __setattr__, that needs to be reset. We don't walk the874        # MRO because we only care about our immediate base classes.875        # XXX: This can be confused by subclassing a slotted attrs class with876        # XXX: a non-attrs class and subclass the resulting class with an attrs877        # XXX: class.  See `test_slotted_confused` for details.  For now that's878        # XXX: OK with us.879        if not self._wrote_own_setattr:880            cd["__attrs_own_setattr__"] = False881 882            if not self._has_custom_setattr:883                for base_cls in self._cls.__bases__:884                    if base_cls.__dict__.get("__attrs_own_setattr__", False):885                        cd["__setattr__"] = _OBJ_SETATTR886                        break887 888        # Traverse the MRO to collect existing slots889        # and check for an existing __weakref__.890        existing_slots = {}891        weakref_inherited = False892        for base_cls in self._cls.__mro__[1:-1]:893            if base_cls.__dict__.get("__weakref__", None) is not None:894                weakref_inherited = True895            existing_slots.update(896                {897                    name: getattr(base_cls, name)898                    for name in getattr(base_cls, "__slots__", [])899                }900            )901 902        base_names = set(self._base_names)903 904        names = self._attr_names905        if (906            self._weakref_slot907            and "__weakref__" not in getattr(self._cls, "__slots__", ())908            and "__weakref__" not in names909            and not weakref_inherited910        ):911            names += ("__weakref__",)912 913        cached_properties = {914            name: cached_prop.func915            for name, cached_prop in cd.items()916            if isinstance(cached_prop, cached_property)917        }918 919        # Collect methods with a `__class__` reference that are shadowed in the new class.920        # To know to update them.921        additional_closure_functions_to_update = []922        if cached_properties:923            class_annotations = _get_annotations(self._cls)924            for name, func in cached_properties.items():925                # Add cached properties to names for slotting.926                names += (name,)927                # Clear out function from class to avoid clashing.928                del cd[name]929                additional_closure_functions_to_update.append(func)930                annotation = inspect.signature(func).return_annotation931                if annotation is not inspect.Parameter.empty:932                    class_annotations[name] = annotation933 934            original_getattr = cd.get("__getattr__")935            if original_getattr is not None:936                additional_closure_functions_to_update.append(original_getattr)937 938            cd["__getattr__"] = _make_cached_property_getattr(939                cached_properties, original_getattr, self._cls940            )941 942        # We only add the names of attributes that aren't inherited.943        # Setting __slots__ to inherited attributes wastes memory.944        slot_names = [name for name in names if name not in base_names]945 946        # There are slots for attributes from current class947        # that are defined in parent classes.948        # As their descriptors may be overridden by a child class,949        # we collect them here and update the class dict950        reused_slots = {951            slot: slot_descriptor952            for slot, slot_descriptor in existing_slots.items()953            if slot in slot_names954        }955        slot_names = [name for name in slot_names if name not in reused_slots]956        cd.update(reused_slots)957        if self._cache_hash:958            slot_names.append(_HASH_CACHE_FIELD)959 960        cd["__slots__"] = tuple(slot_names)961 962        cd["__qualname__"] = self._cls.__qualname__963 964        # Create new class based on old class and our methods.965        cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd)966 967        # The following is a fix for968        # <https://github.com/python-attrs/attrs/issues/102>.969        # If a method mentions `__class__` or uses the no-arg super(), the970        # compiler will bake a reference to the class in the method itself971        # as `method.__closure__`.  Since we replace the class with a972        # clone, we rewrite these references so it keeps working.973        for item in itertools.chain(974            cls.__dict__.values(), additional_closure_functions_to_update975        ):976            if isinstance(item, (classmethod, staticmethod)):977                # Class- and staticmethods hide their functions inside.978                # These might need to be rewritten as well.979                closure_cells = getattr(item.__func__, "__closure__", None)980            elif isinstance(item, property):981                # Workaround for property `super()` shortcut (PY3-only).982                # There is no universal way for other descriptors.983                closure_cells = getattr(item.fget, "__closure__", None)984            else:985                closure_cells = getattr(item, "__closure__", None)986 987            if not closure_cells:  # Catch None or the empty list.988                continue989            for cell in closure_cells:990                try:991                    match = cell.cell_contents is self._cls992                except ValueError:  # noqa: PERF203993                    # ValueError: Cell is empty994                    pass995                else:996                    if match:997                        cell.cell_contents = cls998        return cls999 1000    def add_repr(self, ns):1001        script, globs = _make_repr_script(self._attrs, ns)1002 1003        def _attach_repr(cls_dict, globs):1004            cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"])1005 1006        self._script_snippets.append((script, globs, _attach_repr))1007        self._repr_added = True1008        return self1009 1010    def add_str(self):1011        if not self._repr_added:1012            msg = "__str__ can only be generated if a __repr__ exists."1013            raise ValueError(msg)1014 1015        def __str__(self):1016            return self.__repr__()1017 1018        self._cls_dict["__str__"] = self._add_method_dunders(__str__)1019        return self1020 1021    def _make_getstate_setstate(self):1022        """1023        Create custom __setstate__ and __getstate__ methods.1024        """1025        # __weakref__ is not writable.1026        state_attr_names = tuple(1027            an for an in self._attr_names if an != "__weakref__"1028        )1029 1030        def slots_getstate(self):1031            """1032            Automatically created by attrs.1033            """1034            return {name: getattr(self, name) for name in state_attr_names}1035 1036        hash_caching_enabled = self._cache_hash1037 1038        def slots_setstate(self, state):1039            """1040            Automatically created by attrs.1041            """1042            __bound_setattr = _OBJ_SETATTR.__get__(self)1043            if isinstance(state, tuple):1044                # Backward compatibility with attrs instances pickled with1045                # attrs versions before v22.2.0 which stored tuples.1046                for name, value in zip(state_attr_names, state):1047                    __bound_setattr(name, value)1048            else:1049                for name in state_attr_names:1050                    if name in state:1051                        __bound_setattr(name, state[name])1052 1053            # The hash code cache is not included when the object is1054            # serialized, but it still needs to be initialized to None to1055            # indicate that the first call to __hash__ should be a cache1056            # miss.1057            if hash_caching_enabled:1058                __bound_setattr(_HASH_CACHE_FIELD, None)1059 1060        return slots_getstate, slots_setstate1061 1062    def make_unhashable(self):1063        self._cls_dict["__hash__"] = None1064        return self1065 1066    def add_hash(self):1067        script, globs = _make_hash_script(1068            self._cls,1069            self._attrs,1070            frozen=self._frozen,1071            cache_hash=self._cache_hash,1072        )1073 1074        def attach_hash(cls_dict: dict, locs: dict) -> None:1075            cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"])1076 1077        self._script_snippets.append((script, globs, attach_hash))1078 1079        return self1080 1081    def add_init(self):1082        script, globs, annotations = _make_init_script(1083            self._cls,1084            self._attrs,1085            self._has_pre_init,1086            self._pre_init_has_args,1087            self._has_post_init,1088            self._frozen,1089            self._slots,1090            self._cache_hash,1091            self._base_attr_map,1092            self._is_exc,1093            self._on_setattr,1094            attrs_init=False,1095        )1096 1097        def _attach_init(cls_dict, globs):1098            init = globs["__init__"]1099            init.__annotations__ = annotations1100            cls_dict["__init__"] = self._add_method_dunders(init)1101 1102        self._script_snippets.append((script, globs, _attach_init))1103 1104        return self1105 1106    def add_replace(self):1107        self._cls_dict["__replace__"] = self._add_method_dunders(evolve)1108        return self1109 1110    def add_match_args(self):1111        self._cls_dict["__match_args__"] = tuple(1112            field.name1113            for field in self._attrs1114            if field.init and not field.kw_only1115        )1116 1117    def add_attrs_init(self):1118        script, globs, annotations = _make_init_script(1119            self._cls,1120            self._attrs,1121            self._has_pre_init,1122            self._pre_init_has_args,1123            self._has_post_init,1124            self._frozen,1125            self._slots,1126            self._cache_hash,1127            self._base_attr_map,1128            self._is_exc,1129            self._on_setattr,1130            attrs_init=True,1131        )1132 1133        def _attach_attrs_init(cls_dict, globs):1134            init = globs["__attrs_init__"]1135            init.__annotations__ = annotations1136            cls_dict["__attrs_init__"] = self._add_method_dunders(init)1137 1138        self._script_snippets.append((script, globs, _attach_attrs_init))1139 1140        return self1141 1142    def add_eq(self):1143        cd = self._cls_dict1144 1145        script, globs = _make_eq_script(self._attrs)1146 1147        def _attach_eq(cls_dict, globs):1148            cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"])1149 1150        self._script_snippets.append((script, globs, _attach_eq))1151 1152        cd["__ne__"] = __ne__1153 1154        return self1155 1156    def add_order(self):1157        cd = self._cls_dict1158 1159        cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = (1160            self._add_method_dunders(meth)1161            for meth in _make_order(self._cls, self._attrs)1162        )1163 1164        return self1165 1166    def add_setattr(self):1167        sa_attrs = {}1168        for a in self._attrs:1169            on_setattr = a.on_setattr or self._on_setattr1170            if on_setattr and on_setattr is not setters.NO_OP:1171                sa_attrs[a.name] = a, on_setattr1172 1173        if not sa_attrs:1174            return self1175 1176        if self._has_custom_setattr:1177            # We need to write a __setattr__ but there already is one!1178            msg = "Can't combine custom __setattr__ with on_setattr hooks."1179            raise ValueError(msg)1180 1181        # docstring comes from _add_method_dunders1182        def __setattr__(self, name, val):1183            try:1184                a, hook = sa_attrs[name]1185            except KeyError:1186                nval = val1187            else:1188                nval = hook(self, a, val)1189 1190            _OBJ_SETATTR(self, name, nval)1191 1192        self._cls_dict["__attrs_own_setattr__"] = True1193        self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__)1194        self._wrote_own_setattr = True1195 1196        return self1197 1198    def _add_method_dunders_unsafe(self, method: Callable) -> Callable:1199        """1200        Add __module__ and __qualname__ to a *method*.

Showing the first 1,200 of 3407 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai