def _get_configuration(self, origin: Type, flags: SerdeFlags) -> SerdeConfig: if hasattr(origin, SERDE_FLAGS_ATTR): flags = getattr(origin, SERDE_FLAGS_ATTR) # Get all the annotated fields params = util.safe_get_params(origin) # This is probably a builtin and has no signature fields: Dict[str, Annotation] = {} hints = util.cached_type_hints(origin) for name, t in hints.items(): fields[name] = self.annotation( t, flags=dataclasses.replace(flags, fields={}), default=getattr(origin, name, EMPTY), namespace=origin, ) # Filter out any annotations which aren't part of the object's signature. if flags.signature_only: fields = {x: fields[x] for x in fields.keys() & params.keys()} # Create a field-to-field mapping fields_out = {x: x for x in fields} # Make sure to include any fields explicitly listed include = flags.fields if include: if isinstance(include, Mapping): fields_out.update(include) else: fields_out.update({x: x for x in include}) # Transform the output fields to the correct case. if flags.case: case = Case(flags.case) fields_out = {x: case.transformer(y) for x, y in fields_out.items()}
def get_fields( self, type: Type, as_source: bool = False ) -> Optional[Mapping[str, inspect.Parameter]]: """Get the fields for the given type. Notes ----- We want this to be the type's signature, we really do. But if for some reason we can't make that happen, we fallback to a few known, semi-reliable methods for making this happen. """ # Try first with the signature of the target if this is the target type params = safe_get_params(type) undefined = self.sig_is_undef(params) if not as_source and not undefined: return params # Now we start building a fake signature k: ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD # **kwargs if self.kw_only(params): k = inspect.Parameter.KEYWORD_ONLY # *args elif self.pos_only(params): k = inspect.Parameter.POSITIONAL_ONLY # Fetch any type hints and try to use those. hints = cached_type_hints(type) if hints: return self._fields_from_hints(k, hints) # Fallback to the target object's defined attributes # This will basically work for ORM models, Pydantic models... # Anything that defines the instance using the class body. attrs = cached_simple_attributes(type) if attrs: return self._fields_from_attrs(k, attrs) # Can't be done. return None if undefined else params
def _resolve_from_env( cls: Type[ObjectT], prefix: str, case_sensitive: bool, aliases: Mapping[str, str], ) -> Type[ObjectT]: fields = cached_type_hints(cls) vars = { (f"{prefix}{x}".lower() if not case_sensitive else f"{prefix}{x}"): (x, y) for x, y in fields.items() } attr_to_aliases = collections.defaultdict(set) for alias, attr in aliases.items(): attr_to_aliases[attr].add(alias) sentinel = object() for name in vars: attr, typ = vars[name] names = attr_to_aliases[name] field = getattr(cls, attr, sentinel) if field is sentinel: field = dataclasses.field() elif not isinstance(field, dataclasses.Field): field = dataclasses.field(default=field) if field.default_factory != dataclasses.MISSING: continue kwargs = dict(var=name, ci=not case_sensitive) if field.default != dataclasses.MISSING: kwargs["default"] = field.default field.default = dataclasses.MISSING factory = environ.register(typ, *names, name=name) field.default_factory = functools.partial(factory, **kwargs) setattr(cls, attr, field) return cls
def protocols(self, obj, *, strict: bool = False) -> SerdeProtocolsT: """Get a mapping of param/attr name -> :py:class:`SerdeProtocol` Parameters ---------- obj The class or callable object you wish to extract resolved annotations from. strict Whether to validate instead of coerce. Examples -------- >>> import typic >>> >>> @typic.klass ... class Foo: ... bar: str ... >>> protocols = typic.protocols(Foo) See Also -------- :py:class:`SerdeProtocol` """ if not any((inspect.ismethod(obj), inspect.isfunction(obj), inspect.isclass(obj))): obj = obj.__class__ hints = util.cached_type_hints(obj) params = util.safe_get_params(obj) fields: Mapping[str, dataclasses.Field] = {} if dataclasses.is_dataclass(obj): fields = {f.name: f for f in dataclasses.fields(obj)} ann = {} for name in params.keys() | hints.keys(): param = params.get(name) hint = hints.get(name) field = fields.get(name) annotation = hint or param.annotation # type: ignore annotation = util.resolve_supertype(annotation) param = param or inspect.Parameter( name, inspect.Parameter.POSITIONAL_OR_KEYWORD, default=EMPTY, annotation=hint or annotation, ) if repr(param.default) == "<factory>": param = param.replace(default=EMPTY) if checks.isclassvartype(annotation): val = getattr(obj, name) if annotation is ClassVar: annotation = annotation[type(val)] default = val param = param.replace(default=default) if (field and field.default is not dataclasses.MISSING and param.default is EMPTY): if field.init is False and util.origin( annotation) is not ReadOnly: annotation = ReadOnly[annotation] # type: ignore param = param.replace(default=field.default) if not checks.ishashable(param.default): param = param.replace(default=...) resolved = self.resolve( annotation, parameter=param, name=name, is_strict=strict, namespace=obj, ) ann[name] = resolved try: setattr(obj, TYPIC_ANNOS_NAME, ann) # We wrapped a bound method, or # are wrapping a static-/classmethod # after they were wrapped with @static/class except (AttributeError, TypeError): pass return ann
def _from_class( t: Type[VT], *, nullable: bool = False, name: str = None, cls: Type = None ) -> ConstraintsProtocolT[VT]: if not istypeddict(t) and not isnamedtuple(t) and isbuiltinsubtype(t): return cast( ConstraintsProtocolT, _from_strict_type(t, nullable=nullable, name=name) ) try: params: Dict[str, inspect.Parameter] = {**cached_signature(t).parameters} hints = cached_type_hints(t) for x in hints.keys() & params.keys(): p = params[x] params[x] = inspect.Parameter( p.name, p.kind, default=p.default, annotation=hints[x] ) for x in hints.keys() - params.keys(): hint = hints[x] if not isclassvartype(hint): continue # Hack in the classvars as "parameters" to allow for validation. default = getattr(t, x, empty) args = get_args(hint) if not args: hint = ClassVar[default.__class__] # type: ignore params[x] = inspect.Parameter( x, inspect.Parameter.KEYWORD_ONLY, default=default, annotation=hint ) except (ValueError, TypeError): return cast( ConstraintsProtocolT, _from_strict_type(t, nullable=nullable, name=name) ) name = name or get_name(t) items: Optional[frozendict.FrozenDict[Hashable, ConstraintsT]] = ( frozendict.FrozenDict(_resolve_params(t, **params)) or None ) required = frozenset( ( pname for pname, p in params.items() if ( p.kind not in {p.VAR_POSITIONAL, p.VAR_KEYWORD} and p.default is p.empty ) ) ) has_varargs = any( p.kind in {p.VAR_KEYWORD, p.VAR_POSITIONAL} for p in params.values() ) kwargs = { "type": t, "nullable": nullable, "name": name, "required_keys": required, "items": items, "total": not has_varargs, } cls = ObjectConstraints if istypeddict(t): cls = TypedDictConstraints kwargs.update(type=dict, ttype=t, total=getattr(t, "__total__", bool(required))) c = cls(**kwargs) # type: ignore return cast(ConstraintsProtocolT, c)