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)])
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
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
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)
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
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
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
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]),
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]
def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) return [a.evolve(converter=a.type) for a in attribs]
def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) return [a for a in attribs if a.type is not int]
def hook(cls, attribs): attr.resolve_types(cls, attribs=attribs) results[:] = [(a.name, a.type) for a in attribs] return attribs
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]
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())
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]
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()
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)
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