Beispiel #1
0
    def register_structure_hook(self, cl: Any, func: Callable[[Any, Type[T]],
                                                              T]):
        """Register a primitive-to-class converter function for a type.

        The converter function should take two arguments:
          * a Python object to be converted,
          * the type to convert to

        and return the instance of the class. The type may seem redundant, but
        is sometimes needed (for example, when dealing with generic classes).
        """
        if attrs_has(cl):
            resolve_types(cl)
        if is_union_type(cl):
            self._union_struct_registry[cl] = func
            self._structure_func.clear_cache()
        else:
            self._structure_func.register_cls_list([(cl, func)])
Beispiel #2
0
 def gen_structure_attrs_fromdict(self, cl: Type[T]) -> T:
     attribs = fields(cl)
     if any(isinstance(a.type, str) for a in attribs):
         # PEP 563 annotations - need to be resolved.
         resolve_types(cl)
     attrib_overrides = {
         a.name: self.type_overrides[a.type]
         for a in attribs if a.type in self.type_overrides
     }
     h = make_dict_structure_fn(
         cl,
         self,
         _cattrs_forbid_extra_keys=self.forbid_extra_keys,
         _cattrs_prefer_attrib_converters=self._prefer_attrib_converters,
         **attrib_overrides,
     )
     # only direct dispatch so that subclasses get separately generated
     return h
Beispiel #3
0
    def gen_unstructure_attrs_fromdict(self, cl: Type[T]) -> Dict[str, Any]:
        origin = get_origin(cl)
        if origin is not None:
            cl = origin
        attribs = fields(cl)
        if any(isinstance(a.type, str) for a in attribs):
            # PEP 563 annotations - need to be resolved.
            resolve_types(cl)
        attrib_overrides = {
            a.name: self.type_overrides[a.type]
            for a in attribs if a.type in self.type_overrides
        }

        h = make_dict_unstructure_fn(cl,
                                     self,
                                     omit_if_default=self.omit_if_default,
                                     **attrib_overrides)
        return h
Beispiel #4
0
 def structure_attrs_fromdict(
     self, obj: Mapping[str, Any], cl: Type[T]
 ) -> T:
     attribs = fields(cl)
     if any(isinstance(a.type, str) for a in attribs):
         # PEP 563 annotations - need to be resolved.
         resolve_types(cl)
     attrib_overrides = {
         a.name: self.type_overrides[a.type]
         for a in attribs
         if a.type in self.type_overrides
     }
     h = make_dict_structure_fn(cl, self, **attrib_overrides)
     self._structure_func.register_cls_list(
         [(cl, h)], no_singledispatch=True
     )
     # only direct dispatch so that subclasses get separately generated
     return h(obj, cl)
Beispiel #5
0
    def unstructure_attrs_asdict(self, obj: Any) -> Dict[str, Any]:
        attribs = fields(obj.__class__)
        if any(isinstance(a.type, str) for a in attribs):
            # PEP 563 annotations - need to be resolved.
            resolve_types(obj.__class__)
        attrib_overrides = {
            a.name: self.type_overrides[a.type]
            for a in attribs
            if a.type in self.type_overrides
        }

        h = make_dict_unstructure_fn(
            obj.__class__,
            self,
            omit_if_default=self.omit_if_default,
            **attrib_overrides
        )
        self.register_unstructure_hook(obj.__class__, h)
        return h(obj)
    def test_forward_reference(self, slots):
        """
        Forward references can be resolved.
        """

        @attr.s(slots=slots, auto_attribs=True)
        class A:
            a: typing.List["B"]  # noqa: will resolve below

        @attr.s(slots=slots, auto_attribs=True)
        class B:
            a: A

        assert typing.List["B"] == attr.fields(A).a.type
        assert A == attr.fields(B).a.type

        attr.resolve_types(A, globals(), locals())

        assert typing.List[B] == attr.fields(A).a.type
        assert A == attr.fields(B).a.type
    def test_basic_resolve(self):
        """
        Resolve the `Attribute.type` attr from basic type annotations.
        Unannotated types are ignored.
        """

        @attr.s
        class C:
            x: "int" = attr.ib()
            y = attr.ib(type=str)
            z = attr.ib()

        assert "int" == attr.fields(C).x.type
        assert str is attr.fields(C).y.type
        assert None is attr.fields(C).z.type

        attr.resolve_types(C)

        assert int is attr.fields(C).x.type
        assert str is attr.fields(C).y.type
        assert None is attr.fields(C).z.type
Beispiel #8
0
    def attrs_hook_factory(cls: Type[Any],
                           gen_fn: Callable[..., Callable[[Any], Any]],
                           structuring: bool) -> Callable[[Any], Any]:
        base = get_origin(cls)
        if base is None:
            base = cls
        attribs = fields(base)
        # PEP563 postponed annotations need resolving as we check Attribute.type below
        resolve_types(base)
        kwargs: dict[str, bool | AttributeOverride] = {}
        if structuring:
            kwargs["_cattrs_forbid_extra_keys"] = conv.forbid_extra_keys
            kwargs[
                "_cattrs_prefer_attrib_converters"] = conv._prefer_attrib_converters
        else:
            kwargs["_cattrs_omit_if_default"] = conv.omit_if_default
        for a in attribs:
            if a.type in conv.type_overrides:
                # cattrs' gen_(un)structure_attrs_fromdict (used by default for attrs
                # classes that don't have a custom hook registered) check for any
                # type_overrides (Dict[Type, AttributeOverride]); they allow a custom
                # converter to omit specific attributes of given type e.g.:
                # >>> conv = GenConverter(type_overrides={Image: override(omit=True)})
                attrib_override = conv.type_overrides[a.type]
            else:
                # by default, we omit all Optional attributes (i.e. with None default),
                # overriding a Converter's global 'omit_if_default' option. Specific
                # attibutes can still define their own 'omit_if_default' behavior in
                # the Attribute.metadata dict.
                attrib_override = override(
                    omit_if_default=a.metadata.get("omit_if_default",
                                                   a.default is None or None),
                    rename=a.metadata.get(
                        "rename_attr",
                        a.name[1:] if a.name[0] == "_" else None),
                    omit=not a.init,
                )
            kwargs[a.name] = attrib_override

        return gen_fn(cls, conv, **kwargs)
    def test_resolve_types_auto_attrib(self, slots):
        """
        Types can be resolved even when strings are involved.
        """

        @attr.s(slots=slots, auto_attribs=True)
        class A:
            a: typing.List[int]
            b: typing.List["int"]
            c: "typing.List[int]"

        assert typing.List[int] == attr.fields(A).a.type
        assert typing.List["int"] == attr.fields(A).b.type
        assert "typing.List[int]" == attr.fields(A).c.type

        # Note: I don't have to pass globals and locals here because
        # int is a builtin and will be available in any scope.
        attr.resolve_types(A)

        assert typing.List[int] == attr.fields(A).a.type
        assert typing.List[int] == attr.fields(A).b.type
        assert typing.List[int] == attr.fields(A).c.type
Beispiel #10
0
def make_dict_structure_fn(cl: Type, converter, **kwargs):
    """Generate a specialized dict structuring function for an attrs class."""
    cl_name = cl.__name__
    fn_name = "structure_" + cl_name
    globs = {"__c_s": converter.structure, "__cl": cl}
    lines = []
    post_lines = []

    attrs = cl.__attrs_attrs__

    if any(isinstance(a.type, str) for a in attrs):
        # PEP 563 annotations - need to be resolved.
        resolve_types(cl)

    lines.append(f"def {fn_name}(o, _):")
    lines.append("  res = {")
    for a in attrs:
        an = a.name
        override = kwargs.pop(an, _neutral)
        type = a.type
        ian = an if an[0] != "_" else an[1:]
        kn = an if override.rename is None else override.rename
        globs[f"__c_t_{an}"] = type
        if a.default is NOTHING:
            lines.append(f"    '{ian}': __c_s(o['{kn}'], __c_t_{an}),")
        else:
            post_lines.append(f"  if '{kn}' in o:")
            post_lines.append(
                f"    res['{ian}'] = __c_s(o['{kn}'], __c_t_{an})")
    lines.append("    }")

    total_lines = lines + post_lines + ["  return __cl(**res)"]

    eval(compile("\n".join(total_lines), "", "exec"), globs)

    fn = globs[fn_name]

    return fn
Beispiel #11
0
    y: int


@attr.s(slots=True, frozen=True, auto_attribs=True)
class ProtectedFields:
    public: str
    _protected: str


class MyEnum(Enum):
    VALUE1 = 1
    VALUE_WHAT = 1337
    ENUM_NAMES_YEEE = 25252


attr.resolve_types(Recursive)

# Needed for attr.s(slots=True), and __subclasses__ to work correctly.
gc.collect()

TEST_DATA: List[Tuple[TypeOrGeneric, Any, Any]] = [
    (int, 5, 5), (str, 'lel', 'lel'), (bool, True, True),
    (type(None), None, None), (List[int], [2, 3, 4], [2, 3, 4]),
    (List[List[int]], [[2], [3, 4]], [[2], [3, 4]]),
    (Tuple[int, str, int], (2, '1234', 4), [2, '1234', 4]),
    (Tuple[int, Tuple[str, int],
           int], (2, ('1234', 5), 4), [2, ['1234', 5], 4]),
    (Tuple[int, List[Tuple[str, int]],
           int], (2, [('1234', 5),
                      ('abcd', 15)], 4), [2, [['1234', 5], ['abcd', 15]], 4]),
    (Tuple[int, ...], (2, 3, 4), [2, 3, 4]),
Beispiel #12
0
def make_dict_structure_fn(cl: Type, converter, **kwargs):
    """Generate a specialized dict structuring function for an attrs class."""

    mapping = None
    if is_generic(cl):
        base = get_origin(cl)
        mapping = generate_mapping(cl, mapping)
        cl = base

    for base in getattr(cl, "__orig_bases__", ()):
        if is_generic(base) and not str(base).startswith("typing.Generic"):
            mapping = generate_mapping(base, mapping)
            break

    if isinstance(cl, TypeVar):
        cl = getattr(mapping, cl.__name__, cl)

    cl_name = cl.__name__
    fn_name = "structure_" + cl_name

    # We have generic paramters and need to generate a unique name for the function
    for p in getattr(cl, "__parameters__", ()):
        # This is nasty, I am not sure how best to handle `typing.List[str]` or `TClass[int, int]` as a parameter type here
        name_base = getattr(mapping, p.__name__)
        name = getattr(name_base, "__name__", str(name_base))
        name = re.sub(r"[\[\.\] ,]", "_", name)
        fn_name += f"_{name}"

    globs = {"__c_s": converter.structure, "__cl": cl, "__m": mapping}
    lines = []
    post_lines = []

    attrs = cl.__attrs_attrs__

    if any(isinstance(a.type, str) for a in attrs):
        # PEP 563 annotations - need to be resolved.
        resolve_types(cl)

    lines.append(f"def {fn_name}(o, *_):")
    lines.append("  res = {")
    for a in attrs:
        an = a.name
        override = kwargs.pop(an, _neutral)
        type = a.type
        if isinstance(type, TypeVar):
            type = getattr(mapping, type.__name__, type)

        ian = an if an[0] != "_" else an[1:]
        kn = an if override.rename is None else override.rename
        globs[f"__c_t_{an}"] = type
        if a.default is NOTHING:
            lines.append(f"    '{ian}': __c_s(o['{kn}'], __c_t_{an}),")
        else:
            post_lines.append(f"  if '{kn}' in o:")
            post_lines.append(
                f"    res['{ian}'] = __c_s(o['{kn}'], __c_t_{an})"
            )
    lines.append("    }")

    total_lines = lines + post_lines + ["  return __cl(**res)"]

    eval(compile("\n".join(total_lines), "", "exec"), globs)

    return globs[fn_name]
Beispiel #13
0
 def hook(cls, attribs):
     attr.resolve_types(cls, attribs=attribs)
     return [a.evolve(converter=a.type) for a in attribs]
Beispiel #14
0
 def hook(cls, attribs):
     attr.resolve_types(cls, attribs=attribs)
     return [a for a in attribs if a.type is not int]
Beispiel #15
0
 def hook(cls, attribs):
     attr.resolve_types(cls, attribs=attribs)
     results[:] = [(a.name, a.type) for a in attribs]
     return attribs
Beispiel #16
0
def make_dict_structure_fn(
    cl: Type,
    converter: "Converter",
    _cattrs_forbid_extra_keys: bool = False,
    _cattrs_use_linecache: bool = True,
    _cattrs_prefer_attrib_converters: bool = False,
    **kwargs,
):
    """Generate a specialized dict structuring function for an attrs class."""

    mapping = {}
    if is_generic(cl):
        base = get_origin(cl)
        mapping = _generate_mapping(cl, mapping)
        cl = base

    for base in getattr(cl, "__orig_bases__", ()):
        if is_generic(base) and not str(base).startswith("typing.Generic"):
            mapping = _generate_mapping(base, mapping)
            break

    if isinstance(cl, TypeVar):
        cl = mapping.get(cl.__name__, cl)

    cl_name = cl.__name__
    fn_name = "structure_" + cl_name

    # We have generic parameters and need to generate a unique name for the function
    for p in getattr(cl, "__parameters__", ()):
        # This is nasty, I am not sure how best to handle `typing.List[str]` or `TClass[int, int]` as a parameter type here
        name_base = mapping[p.__name__]
        name = getattr(name_base, "__name__", None) or str(name_base)
        name = re.sub(r"[\[\.\] ,]", "_", name)
        fn_name += f"_{name}"

    globs = {"__c_s": converter.structure, "__cl": cl}
    lines = []
    post_lines = []

    attrs = adapted_fields(cl)
    is_dc = is_dataclass(cl)

    if any(isinstance(a.type, str) for a in attrs):
        # PEP 563 annotations - need to be resolved.
        resolve_types(cl)

    lines.append(f"def {fn_name}(o, *_):")
    lines.append("  res = {")
    for a in attrs:
        an = a.name
        override = kwargs.pop(an, _neutral)
        t = a.type
        if isinstance(t, TypeVar):
            t = mapping.get(t.__name__, t)
        elif is_generic(t) and not is_bare(t) and not is_annotated(t):
            concrete_types = tuple(
                mapping.get(t.__name__, t) if isinstance(t, TypeVar) or (
                    getattr(t, "__name__", None) and is_generic(t)) else t
                for t in get_args(t))
            t = copy_with(t, concrete_types)

        # For each attribute, we try resolving the type here and now.
        # If a type is manually overwritten, this function should be
        # regenerated.
        if a.converter is not None and _cattrs_prefer_attrib_converters:
            handler = None
        elif (a.converter is not None and not _cattrs_prefer_attrib_converters
              and t is not None):
            handler = converter._structure_func.dispatch(t)
            if handler == converter._structure_error:
                handler = None
        elif t is not None:
            handler = converter._structure_func.dispatch(t)
        else:
            handler = converter.structure

        struct_handler_name = f"structure_{an}"
        globs[struct_handler_name] = handler

        ian = an if (is_dc or an[0] != "_") else an[1:]
        kn = an if override.rename is None else override.rename
        globs[f"type_{an}"] = t
        if a.default is NOTHING:
            if handler:
                lines.append(
                    f"    '{ian}': {struct_handler_name}(o['{kn}'], type_{an}),"
                )
            else:
                lines.append(f"    '{ian}': o['{kn}'],")
        else:
            post_lines.append(f"  if '{kn}' in o:")
            if handler:
                post_lines.append(
                    f"    res['{ian}'] = {struct_handler_name}(o['{kn}'], type_{an})"
                )
            else:
                post_lines.append(f"    res['{ian}'] = o['{kn}']")

    lines.append("    }")
    if _cattrs_forbid_extra_keys:
        allowed_fields = {a.name for a in attrs}
        globs["__c_a"] = allowed_fields
        post_lines += [
            "  unknown_fields = set(o.keys()) - __c_a",
            "  if unknown_fields:",
            "    raise Exception(",
            f"      'Extra fields in constructor for {cl_name}: ' + ', '.join(unknown_fields)"
            "    )",
        ]

    total_lines = lines + post_lines + ["  return __cl(**res)"]

    fname = _generate_unique_filename(cl,
                                      "structure",
                                      reserve=_cattrs_use_linecache)
    script = "\n".join(total_lines)
    eval(compile(script, fname, "exec"), globs)
    if _cattrs_use_linecache:
        linecache.cache[fname] = len(script), None, total_lines, fname

    return globs[fn_name]
Beispiel #17
0
    EVERYONE_MENTIONS = 'everyone'


@attr.frozen(kw_only=True)
class AllowedMentions:
    #: An array of allowed mention types to parse from the contentyping.
    parse: typing.List[AllowedMentionTypes]
    #: Array of role_ids to mention (Max size of 100)
    roles: typing.List[Snowflake]
    #: Array of user_ids to mention (Max size of 100)
    users: typing.List[Snowflake]
    #: For replies, whether to mention the author of the message being replied
    #: to (default false)
    replied_user: bool


@attr.frozen(kw_only=True)
class ResponseBody:
    #: the private, archived threads the current user has joined
    threads: typing.List[Channel]
    #: a thread member object for each returned thread the current user has
    #: joined
    members: typing.List[ThreadMember]
    #: whether there are potentially additional threads that could be returned
    #: on a subsequent call
    has_more: bool


# TODO: blocked on https://github.com/python-attrs/attrs/issues/842
attr.resolve_types(Channel, globals(), locals())
Beispiel #18
0
def make_dict_structure_fn(cl: Type,
                           converter,
                           _cattrs_forbid_extra_keys: bool = False,
                           **kwargs):
    """Generate a specialized dict structuring function for an attrs class."""

    mapping = None
    if is_generic(cl):
        base = get_origin(cl)
        mapping = generate_mapping(cl, mapping)
        cl = base

    for base in getattr(cl, "__orig_bases__", ()):
        if is_generic(base) and not str(base).startswith("typing.Generic"):
            mapping = generate_mapping(base, mapping)
            break

    if isinstance(cl, TypeVar):
        cl = getattr(mapping, cl.__name__, cl)

    cl_name = cl.__name__
    fn_name = "structure_" + cl_name

    # We have generic parameters and need to generate a unique name for the function
    for p in getattr(cl, "__parameters__", ()):
        # This is nasty, I am not sure how best to handle `typing.List[str]` or `TClass[int, int]` as a parameter type here
        name_base = getattr(mapping, p.__name__)
        name = getattr(name_base, "__name__", str(name_base))
        name = re.sub(r"[\[\.\] ,]", "_", name)
        fn_name += f"_{name}"

    globs = {"__c_s": converter.structure, "__cl": cl, "__m": mapping}
    lines = []
    post_lines = []

    attrs = adapted_fields(cl)
    is_dc = is_dataclass(cl)

    if any(isinstance(a.type, str) for a in attrs):
        # PEP 563 annotations - need to be resolved.
        resolve_types(cl)

    lines.append(f"def {fn_name}(o, *_):")
    lines.append("  res = {")
    for a in attrs:
        an = a.name
        override = kwargs.pop(an, _neutral)
        type = a.type
        if isinstance(type, TypeVar):
            type = getattr(mapping, type.__name__, type)

        # For each attribute, we try resolving the type here and now.
        # If a type is manually overwritten, this function should be
        # regenerated.
        if type is not None:
            handler = converter._structure_func.dispatch(type)
        else:
            handler = converter.structure

        struct_handler_name = f"__cattr_struct_handler_{an}"
        globs[struct_handler_name] = handler

        ian = an if (is_dc or an[0] != "_") else an[1:]
        kn = an if override.rename is None else override.rename
        globs[f"__c_t_{an}"] = type
        if a.default is NOTHING:
            lines.append(
                f"    '{ian}': {struct_handler_name}(o['{kn}'], __c_t_{an}),")
        else:
            post_lines.append(f"  if '{kn}' in o:")
            post_lines.append(
                f"    res['{ian}'] = {struct_handler_name}(o['{kn}'], __c_t_{an})"
            )
    lines.append("    }")
    if _cattrs_forbid_extra_keys:
        allowed_fields = {a.name for a in attrs}
        globs["__c_a"] = allowed_fields
        post_lines += [
            "  unknown_fields = set(o.keys()) - __c_a",
            "  if unknown_fields:",
            "    raise Exception(",
            f"      'Extra fields in constructor for {cl_name}: ' + ', '.join(unknown_fields)"
            "    )",
        ]

    total_lines = lines + post_lines + ["  return __cl(**res)"]

    eval(compile("\n".join(total_lines), "", "exec"), globs)

    return globs[fn_name]
Beispiel #19
0
        return cls(
            functional_hash=value_hash,
            nominal_hash=value_hash,
            exact_hash=value_hash,
            provenance=None,
        )


# This converts the field Provenance.dep_digests from a forward reference to a real
# type; if we don't do this, cattr will fail to handle it properly. (This could also be
# resolved by adding a structure hook for ForwardReference('ProvenanceDigest'), but
# that isn't available in Python 3.6. It can be accessed as _ForwardReference, but that
# seemed to cause the interpreter to abort unpredictably in our tests so I didn't want
# to use it. Similarly, we can use `from __future__ import annotations` to remove
# the need forward references, but that also isn't available in 3.6.)
attr.resolve_types(Provenance)
METADATA_CONVERTER = cattr.Converter()


# TODO It would be nice if this contained an Artifact object instead of the separate
# URL and hash, but that will require changing the cache schema. Maybe we can do this
# along with the next schema-breaking change.
@attr.s
class ArtifactMetadataRecord:
    """
    Describes a persisted artifact.  Intended to be stored as a YAML file.
    """

    artifact: Artifact = attr.ib()
    descriptor: str = attr.ib()
    provenance: Provenance = attr.ib()
Beispiel #20
0
    voice_media: Optional[VoiceMediaItem] = None
    animated_media: Optional[AnimatedMediaItem] = None
    visual_media: Optional[VisualMedia] = None
    media_share: Optional[MediaShareItem] = None
    direct_media_share: Optional[DirectMediaShareItem] = None
    reel_share: Optional[ReelShareItem] = None
    story_share: Optional[StoryShareItem] = None
    location: Optional[Location] = None
    reactions: Optional[Reactions] = None
    like: Optional[str] = None
    link: Optional[LinkItem] = None
    clip: Optional[ClipItem] = None
    felix_share: Optional[FelixShareItem] = None
    profile: Optional[ProfileItem] = None

    @classmethod
    def deserialize(cls, data: JSON, catch_errors: bool = True) -> Union["ThreadItem", Obj]:
        if not catch_errors:
            return _dict_to_attrs(cls, data)
        try:
            return _dict_to_attrs(cls, data)
        except SerializerError:
            log.debug("Failed to deserialize ThreadItem %s", data)
            return Obj(**data)


# This resolves the 'ThreadItem' string into an actual type.
# Starting Python 3.10, all type annotations will be strings and have to be resolved like this.
# TODO do this automatically for all SerializableAttrs somewhere in mautrix-python
attr.resolve_types(ThreadItem)
Beispiel #21
0
    guild_id: Snowflake
    #: all active threads in the given channels that the current user can
    #: access
    threads: typing.List[Channel]
    #: all thread member objects from the synced threads for the current user,
    #: indicating which threads the current user has been added to
    members: typing.List[ThreadMember]
    #: the parent channel ids whose threads are being synced. If omitted, then
    #: threads were synced for the entire guild. This array may contain
    #: channel_ids that have no active threads as well, so you know to clear
    #: that data.
    channel_ids: Unknownish[typing.List[Snowflake]] = UNKNOWN


# TODO: blocked on https://github.com/python-attrs/attrs/issues/842
attr.resolve_types(ThreadMember)


@attr.frozen(kw_only=True)
class ThreadMemberUpdateEvent(ThreadMember):
    guild_id: Snowflake


@attr.frozen(kw_only=True)
class ThreadMembersUpdateEvent:
    #: the id of the thread
    id: Snowflake
    #: the id of the guild
    guild_id: Snowflake
    #: the approximate number of members in the thread, capped at 50
    member_count: int