def _compile_defined_serializer( self, annotation: "Annotation", ser: SerializerT, ) -> SerializerT: func_name = self._get_name(annotation) ser_name = "ser" ns = {ser_name: ser} with gen.Block(ns) as main: with main.f( func_name, main.param("o"), main.param("lazy", default=False), main.param("name", default=None), ) as func: self._check_add_null_check(func, annotation) self._add_type_check(func, annotation) line = f"{ser_name}(o)" if annotation.origin in (type(o) for o in self.resolver.OPTIONALS): line = "None" func.l(f"{gen.Keyword.RET} {line}") serializer: SerializerT = main.compile(name=func_name, ns=ns) return serializer
def validator(self) -> ValidatorT: ns = dict(__t=self.type, VT=VT, __values=(*self.type, )) func_name = self._get_validator_name() with gen.Block(ns) as main: with self.define(main, func_name) as f: if self.nullable: with f.b(f"if value in {self.NULLABLES}:") as b: b.l(f"{gen.Keyword.RET} True, value") # This is O(N), but so is casting to the enum # And handling a ValueError is an order of magnitude heavier f.l(f"{gen.Keyword.RET} value in __values, value") validator: ValidatorT = main.compile(name=func_name, ns=ns) return validator
def validator(self) -> ValidatorT: ns = dict(__t=self.type, VT=VT, __values=frozenset(self.values)) func_name = self._get_validator_name() with gen.Block(ns) as main: with self.define(main, func_name) as f: if self.nullable: with f.b(f"if value in {self.NULLABLES}:") as b: b.l(f"{gen.Keyword.RET} True, value") with f.b("try:") as b: b.l(f"{gen.Keyword.RET} value in __values, value") with f.b("except TypeError:") as b: b.l(f"{gen.Keyword.RET} False, value") validator: ValidatorT = main.compile(name=func_name, ns=ns) return validator
def validator(self) -> ValidatorT: """Accessor for the generated multi-validator. Validators are keyed by the origin-type of :py:class:`BaseConstraints` inheritors. If a value does not match any origin-type, as reported by :py:func:`typic.origin`, then we will report the value as invalid. """ func_name = self._get_validator_name() vmap = util.TypeMap({c.type: c for c in self.constraints}) ns = {"tag": self.tag and self.tag.tag, "empty": util.empty} with gen.Block(ns) as main: with self.define(main, func_name) as f: if not vmap: f.l(f"return True, {self.VALUE}") else: f.l(f"{self.VALTNAME} = {self.VALUE}.__class__") if self.nullable: with f.b(f"if {self.VALUE} is None:") as b: b.l(f"return True, {self.VALUE}") if self.tag: validators = { value: vmap[t] for value, t in self.tag.types_by_values } f.namespace.update(vmap=validators) with f.b( f"if issubclass({self.VALTNAME}, Mapping):", Mapping=collections.abc.Mapping, ) as b: b.l(f"tag_value = {self.VALUE}.get(tag, empty)") with f.b("else:") as b: b.l(f"tag_value = getattr({self.VALUE}, tag, empty)" ) f.l(f"valid, {self.VALUE} = " f"(True, vmap[tag_value].validate({self.VALUE}, field=field)) " f"if tag_value in vmap else (False, {self.VALUE})") else: vmap = util.TypeMap( {util.origin(t): v for t, v in vmap.items()}) f.namespace.update(vmap=vmap) f.l(f"v = vmap.get_by_parent({self.VALTNAME}, None)") f.l(f"valid, {self.VALUE} = (True, v.validate(value, field=field)) " f"if v else (False, value)") f.l(f"return valid, {self.VALUE}") validator = main.compile(name=func_name) return validator # type: ignore
def validator(self) -> ValidatorT: ns = dict(__t=self.type, VT=VT) func_name = self._get_validator_name() with gen.Block(ns) as main: with self.define(main, func_name) as f: # Standard instancecheck is default. check = "isinstance(value, __t)" retval = "value" # Have to allow nulls if its nullable. if self.nullable: check = "(value is None or isinstance(value, __t))" elif self.type is Any: check = "True" f.l(f"{gen.Keyword.RET} {check}, {retval}") validator: ValidatorT = main.compile(name=func_name, ns=ns) return validator
def _compile_defined_serializer( self, annotation: Annotation[Type[_T]], ser: SerializerT[_T], ) -> SerializerT[_T]: func_name = self._get_name(annotation) ser_name = "ser" ns = {ser_name: ser} with gen.Block(ns) as main: with self._define(main, func_name) as func: self._check_add_null_check(func, annotation) self._add_type_check(func, annotation) line = f"{ser_name}(o)" if annotation.origin in (type(o) for o in self.resolver.OPTIONALS): line = "None" func.l(f"{gen.Keyword.RET} {line}") serializer: SerializerT = main.compile(name=func_name, ns=ns) return serializer
def _build_des( # noqa: C901 self, annotation: Annotation[Type[ObjectT]], func_name: str, namespace: Type = None, ) -> DeserializerT[ObjectT]: args = annotation.args # Get the "origin" of the annotation. # For natives and their typing.* equivs, this will be a builtin type. # For SpecialForms (Union, mainly) this will be the un-subscripted type. # For custom types or classes, this will be the same as the annotation. origin = annotation.resolved_origin anno_name = get_unique_name(origin) ns = { anno_name: origin, "parent": getattr(origin, "__parent__", origin), "issubclass": cached_issubclass, **annotation.serde.asdict(), } if checks.isliteral(origin): return self._build_literal_des(annotation, func_name, namespace) with gen.Block(ns) as main: with main.f(func_name, main.param(f"{self.VNAME}")) as func: needs_return = None context = BuildContext(annotation, ns, anno_name, func, namespace) if origin not in self.UNRESOLVABLE: # Set our top-level sanity checks. self._set_checks(func, anno_name, annotation) # Move through our queue. for check, handler in self._HANDLERS.items(): # If this is a valid type for this handler, # write the deserializer. if check(origin, args): needs_return = handler(self, context) break # If the deserializer doesn't contain a return statement, add one. if needs_return is not False: func.l(f"{gen.Keyword.RET} {self.VNAME}") deserializer = main.compile(ns=ns, name=func_name) return deserializer
def _compile_validator(self) -> ValidatorT: func_name = self._get_validator_name() origin = util.origin(self.type) type_name = self.type_name self._check_syntax() assertions = self._get_assertions() context: ContextT = {type_name: self.type} with gen.Block() as main: with self.define(main, func_name) as f: # This is a signal that -*-anything can happen...-*- if origin in {Any, Signature.empty}: f.l(f"return True, {self.VALUE}") return main.compile(name=func_name) f.l(f"{self.VALTNAME} = {type_name!r}") f.l(f"{self.FNAME} = {self.VALTNAME} if field is None else field" ) # Short-circuit validation if the value isn't the correct type. if self.instancecheck == InstanceCheck.IS: line = f"if isinstance({self.VALUE}, {type_name}):" if self.nullable: line = (f"if {self.VALUE} in {self.NULLABLES} " f"or isinstance({self.VALUE}, {type_name}):") with f.b(line, **context) as b: # type: ignore b.l(f"return True, {self.VALUE}") else: if self.nullable: with f.b(f"if {self.VALUE} in {self.NULLABLES}:") as b: b.l(f"return True, {self.VALUE}") line = f"if not isinstance({self.VALUE}, {type_name}):" with f.b(line, **context) as b: # type: ignore b.l(f"return False, {self.VALUE}") context = self._build_validator(f, context=context, assertions=assertions) f.namespace.update(context) f.localize_context(*context) f.l(f"return True, {self.VALUE}") return main.compile(name=func_name)
def _build_key_serializer(self, name: str, kser: SerializerT, annotation: "Annotation") -> SerializerT: kser_name = util.get_name(kser) # Build the namespace ns: Dict[str, Any] = { kser_name: kser, } kvar = "k_" with gen.Block(ns) as main: with main.f(name, main.param(kvar), main.param("lazy", default=False)) as kf: k = f"{kser_name}({kvar})" # If there are args & field mapping, get the correct field name # AND serialize the key. if annotation.serde.fields_out: ns["fields_out"] = annotation.serde.fields_out k = f"{kser_name}(fields_out.get({kvar}, {kvar}))" # If there are only serializers, get the serialized value if annotation.serde.flags.case: ns.update(case=annotation.serde.flags.case.transformer) k = f"case({k})" kf.l(f"{gen.Keyword.RET} {k}") return main.compile(name=name, ns=ns)
def _compile_serializer( self, annotation: Annotation[Type[_T]]) -> SerializerT[_T]: # Check for an optional and extract the type if possible. func_name = self._get_name(annotation) # We've been here before... if func_name in self._serializer_cache: return self._serializer_cache[func_name] serializer: SerializerT origin = annotation.resolved_origin # Lazy shortcut for messy paths (Union, Any, ...) if (origin in self._DYNAMIC or not annotation.static or checks.isuniontype(origin)): serializer = cast(SerializerT, self.resolver.primitive) # Routines (functions or methods) can't be serialized... elif issubclass( origin, abc.Callable) or inspect.isroutine(origin): # type: ignore name = util.get_qualname(origin) with gen.Block() as main: with self._define(main, func_name) as func: func.l( f'raise TypeError("Routines are not serializable. ({name!r}).")' ) serializer = main.compile(name=func_name) self._serializer_cache[func_name] = serializer # Enums are special elif checks.isenumtype(annotation.resolved): serializer = self._compile_enum_serializer(annotation) # Primitives don't require further processing. # Just check for nullable and the correct type. elif origin in self._PRIMITIVES: ns: dict = {} with gen.Block(ns) as main: with self._define(main, func_name) as func: self._check_add_null_check(func, annotation) self._add_type_check(func, annotation) line = "o" if annotation.origin in (type(o) for o in self.resolver.OPTIONALS): line = "None" func.l(f"{gen.Keyword.RET} {line}") serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer # Defined cases are pre-compiled, but we have to check for optionals. elif origin in self._DEFINED: serializer = self._compile_defined_serializer( annotation, self._DEFINED[origin]) elif issubclass(origin, (*self._DEFINED, )): serializer = self._compile_defined_subclass_serializer( origin, annotation) elif issubclass(origin, self._PRIMITIVES): serializer = self._compile_primitive_subclass_serializer( origin, annotation) else: # Build the function namespace anno_name = f"{func_name}_anno" ns = {anno_name: origin, **annotation.serde.asdict()} with gen.Block(ns) as main: with self._define(main, func_name) as func: # Mapping types need special nested processing as well istypeddict = checks.istypeddict(origin) istypedtuple = checks.istypedtuple(origin) istypicklass = checks.istypicklass(origin) if not istypeddict and issubclass(origin, self._DICTITER): self._build_dict_serializer(func, annotation) # Array types need nested processing. elif (not istypedtuple and not istypeddict and not istypicklass and issubclass(origin, self._LISTITER)): self._build_list_serializer(func, annotation) # Build a serializer for a structured class. else: self._build_class_serializer(func, annotation) serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer return serializer
def _build_des( # noqa: C901 self, annotation: "Annotation", func_name: str, namespace: Type = None) -> Callable: args = annotation.args # Get the "origin" of the annotation. # For natives and their typing.* equivs, this will be a builtin type. # For SpecialForms (Union, mainly) this will be the un-subscripted type. # For custom types or classes, this will be the same as the annotation. origin = annotation.resolved_origin anno_name = get_unique_name(origin) ns = { anno_name: origin, "parent": getattr(origin, "__parent__", origin), "issubclass": cached_issubclass, **annotation.serde.asdict(), } if checks.isliteral(origin): return self._build_literal_des(annotation, func_name, namespace) with gen.Block(ns) as main: with main.f(func_name, main.param(f"{self.VNAME}")) as func: if origin not in self.UNRESOLVABLE: self._set_checks(func, anno_name, annotation) if origin is Union: self._build_union_des(func, annotation, namespace) elif checks.isdatetype(origin): self._build_date_des(func, anno_name, annotation) elif checks.istimetype(origin): self._build_time_des(func, anno_name, annotation) elif checks.istimedeltatype(origin): self._build_timedelta_des(func, anno_name, annotation) elif checks.isuuidtype(origin): self._build_uuid_des(func, anno_name, annotation) elif origin in {Pattern, re.Pattern}: # type: ignore self._build_pattern_des(func, anno_name) elif issubclass(origin, pathlib.Path): self._build_path_des(func, anno_name) elif not args and checks.isbuiltintype(origin): self._build_builtin_des(func, anno_name, annotation) elif checks.isfromdictclass(origin): self._build_fromdict_des(func, anno_name) elif checks.isenumtype(origin): self._build_builtin_des(func, anno_name, annotation) elif checks.istypeddict(origin): self._build_typeddict_des( func, anno_name, annotation, total=origin.__total__, # type: ignore namespace=namespace, ) elif checks.istypedtuple(origin) or checks.isnamedtuple( origin): self._build_typedtuple_des(func, anno_name, annotation, namespace=namespace) elif not args and checks.isbuiltinsubtype(origin): self._build_builtin_des(func, anno_name, annotation) elif checks.ismappingtype(origin): self._build_mapping_des(func, anno_name, annotation, namespace=namespace) elif checks.istupletype(origin): self._build_tuple_des(func, anno_name, annotation, namespace=namespace) elif checks.iscollectiontype(origin): self._build_collection_des(func, anno_name, annotation, namespace=namespace) else: self._build_generic_des(func, anno_name, annotation, namespace=namespace) func.l(f"{gen.Keyword.RET} {self.VNAME}") deserializer = main.compile(ns=ns, name=func_name) return deserializer
def _compile_serializer(self, annotation: "Annotation") -> SerializerT: # Check for an optional and extract the type if possible. func_name = self._get_name(annotation) # We've been here before... if func_name in self._serializer_cache: return self._serializer_cache[func_name] serializer: SerializerT origin = annotation.resolved_origin # Lazy shortcut for messy paths (Union, Any, ...) if origin in self._DYNAMIC or not annotation.static: serializer = self.resolver.primitive # Enums are special elif checks.isenumtype(annotation.resolved): serializer = self._compile_enum_serializer(annotation) # Primitives don't require further processing. # Just check for nullable and the correct type. elif origin in self._PRIMITIVES: ns: dict = {} with gen.Block(ns) as main: with main.f( func_name, main.param("o"), main.param("lazy", default=False), main.param("name", default=None), ) as func: self._check_add_null_check(func, annotation) self._add_type_check(func, annotation) line = "o" if annotation.origin in (type(o) for o in self.resolver.OPTIONALS): line = "None" func.l(f"{gen.Keyword.RET} {line}") serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer # Defined cases are pre-compiled, but we have to check for optionals. elif origin in self._DEFINED: serializer = self._compile_defined_serializer( annotation, self._DEFINED[origin]) elif issubclass(origin, (*self._DEFINED, )): serializer = self._compile_defined_subclass_serializer( origin, annotation) elif issubclass(origin, self._PRIMITIVES): serializer = self._compile_primitive_subclass_serializer( origin, annotation) else: # Build the function namespace anno_name = f"{func_name}_anno" ns = {anno_name: origin, **annotation.serde.asdict()} with gen.Block(ns) as main: with main.f( func_name, main.param("o"), main.param("lazy", default=False), main.param("name", default=None), ) as func: # Mapping types need special nested processing as well if not checks.istypeddict(origin) and issubclass( origin, self._DICTITER): self._build_dict_serializer(func, annotation) # Array types need nested processing. elif not checks.istypedtuple(origin) and issubclass( origin, self._LISTITER): self._build_list_serializer(func, annotation) # Build a serializer for a structured class. else: self._build_class_serializer(func, annotation) serializer = main.compile(name=func_name, ns=ns) self._serializer_cache[func_name] = serializer return serializer