def decode( self, json: JsValue, ancestors: List[JsValue] ) -> TOrError[Optional[T]]: if json is None: return Boxed(None) result = self.inner_decoder.decode(json, ancestors) if isinstance(result, Boxed): return Boxed(result.t) else: return result
def extract_if_dataclass_type(t: type) -> Optional[Dict[str, FieldType]]: """ :param t: Type that needs to be checked for being a dataclass. :return: If `t` is a dataclass, returns a mapping from `t`'s field names to their types and default values. Otherwise, returns None. """ dataclass_fields: Dict[str, Field] if is_dataclass(t): dataclass_fields = cast(Dict[str, Field], t.__dataclass_fields__) # type: ignore elif hasattr(t, "__origin__") and hasattr( t.__origin__, "__args__") and is_dataclass(t.__origin__): dataclass_fields = cast( Dict[str, Field], t.__origin__.__dataclass_fields__) # type: ignore else: return None fields: Dict[str, FieldType] = {} for (field_name, field_definition) in dataclass_fields.items(): field_default: Union[None, Boxed[Any], Callable[[], Any]] = None if field_definition.default is not MISSING: field_default = Boxed(field_definition.default) elif field_definition.default_factory is not MISSING: # type: ignore field_default = field_definition.default_factory # type: ignore fields[field_name] = FieldType(field_definition.type, field_default) return fields
def decode( self, json: JsValue, ancestors: List[JsValue] ) -> TOrError[List[T]]: if not isinstance(json, list): return [ JsDecodeErrorFinal( "Expected a JSON array but received something else." ) ] decoded_elements: List[Any] = [] decoding_errors: List[JsDecodeError] = [] for index, element_json in enumerate(json): element_json = cast(JsValue, element_json) parent: JsValue = json decoded_element = self.element_decoder.decode( element_json, [parent] + ancestors ) if isinstance(decoded_element, Boxed): decoded_elements.append(decoded_element.t) else: for err in decoded_element: decoding_errors.append(JsDecodeErrorInArray(index, err)) if len(decoding_errors) > 0: return decoding_errors else: return Boxed(decoded_elements)
def decode( self, json: JsValue, ancestors: List[JsValue] ) -> TOrError[Dict[str, T]]: if not isinstance(json, dict): return [ JsDecodeErrorFinal( "Expected a JSON object but received something else." ) ] decoded_elements: Dict[str, Any] = {} decoding_errors: List[JsDecodeError] = [] for key, value_json in json.items(): if type(key) is not str: decoding_errors.append( JsDecodeErrorFinal("Found non-string key %s in a json object (type: %s)." % (str(key), type(key))) ) else: value_json = cast(JsValue, value_json) parent: JsValue = json decoded_value = self.element_decoder.decode( value_json, [parent] + ancestors ) if isinstance(decoded_value, Boxed): if key not in decoded_elements: decoded_elements[key] = decoded_value.t else: decoding_errors.append(JsDecodeErrorFinal("Found key %s more than once." % key)) else: for err in decoded_value: decoding_errors.append(JsDecodeErrorInField(key, err)) if len(decoding_errors) > 0: return decoding_errors else: return Boxed(decoded_elements)
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[Tuple[Any, ...]]: if not isinstance(json, list): return [ JsDecodeErrorFinal( "Expected a JSON array but received something else." ) ] if len(json) != len(self.field_decoders): return [ JsDecodeErrorFinal( "Expected a JSON array of size %d but received one of size %d." % (len(self.field_decoders), len(json)) ) ] decoded_fields: List[Any] = [] decoding_errors: List[JsDecodeError] = [] for index, field_decoder in enumerate(self.field_decoders): # The field_name is a key and its associated value is not None. So, it should be decoded. field_json = cast(JsValue, json[index]) decoded_field = field_decoder.decode(field_json, [cast(JsValue, json)] + ancestors) if isinstance(decoded_field, Boxed): decoded_fields.append(decoded_field.t) else: for err in decoded_field: decoding_errors.append( JsDecodeErrorInArray(index, err) ) if len(decoding_errors) > 0: return decoding_errors else: return Boxed(tuple(decoded_fields))
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[T]: decoded_field = self.field_decoder.decode(json, ancestors) if isinstance(decoded_field, Boxed): return Boxed( self.get_constructor()({self.field_name: decoded_field.t}) ) else: return decoded_field
def _extract_unnamed_product_type( self, product_type: type) -> Optional[Boxed[T]]: maybe_fields = Extractor.extract_if_tuple_type(product_type) if maybe_fields is None: return None fields: List[T] = [self._make(t) for t in maybe_fields] return Boxed(self.unnamed_product_extractor(product_type, fields))
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[str]: if not isinstance(json, str): return [ JsDecodeErrorFinal( "Expected a JSON string but received something else." ) ] return Boxed(json)
def _extract_named_sum_type(self, sum_type: type) -> Optional[Boxed[T]]: maybe_named_sum_type = Extractor.extract_if_has_sub_classes(sum_type) if maybe_named_sum_type is None: return None extracted_branches: Dict[str, Tuple[type, T]] = { s: (t, self._make(t)) for (s, t) in maybe_named_sum_type.branches.items() } return Boxed(self.named_sum_extractor(sum_type, extracted_branches))
def _extract_enum_type(self, enum_type: type) -> Optional[Boxed[T]]: try: if not issubclass(enum_type, Enum): return None except Exception: return None value_dict = cast(Dict[str, Any], enum_type._value2member_map_) value_list = cast(List[Tuple[str, Any]], list(value_dict.items())) return Boxed(self.enum_extractor(str(enum_type), value_list))
def _extract_list_type(self, list_type: type) -> Optional[Boxed[T]]: maybe_list_var_name = Extractor.extract_if_list_type(list_type) if maybe_list_var_name is None: return None list_inner_type: type if isinstance(maybe_list_var_name, str): list_inner_type = self._var_to_type(maybe_list_var_name) else: list_inner_type = maybe_list_var_name.t return Boxed(self.list_extractor(self._make(list_inner_type)))
def decode( self, json: JsValue, ancestors: List[JsValue] ) -> TOrError[Decimal]: if isinstance(json, Decimal): return Boxed(json) elif ( isinstance(json, float) or isinstance(json, int) or isinstance(json, str) ): try: return Boxed(Decimal(json)) except DecimalException: return [JsDecodeErrorFinal("Value not convertible to decimal: '%s'." % str(json))] return [ JsDecodeErrorFinal( "Expected a JSON number or a JSON string encoding a number but received something of type %s." % type(json) ) ]
class AutoMetricExporter(Extractor[MetricsExporter[Any]]): _enum_exporter = EnumExporter() _basics: Dict[type, Boxed[MetricsExporter[Any]]] = { bool: Boxed(BooleanExporter()), int: Boxed(ValueExporter()), float: Boxed(ValueExporter()), Decimal: Boxed(ValueExporter()), str: Boxed(TagExporter()), date: Boxed(DateExporter()), datetime: Boxed(DateTimeExporter()) } @property def basics(self) -> Dict[type, Boxed[MetricsExporter[Any]]]: return self._basics def named_product_extractor( self, t: type, fields: Dict[str, WithDefault[MetricsExporter[Any]]] ) -> MetricsExporter[Any]: return NamedProductExporter(field_exporters={f_name: f_exporter.t for f_name, f_exporter in fields.items()}) def unnamed_product_extractor(self, t: type, fields: List[MetricsExporter[Any]]) -> MetricsExporter[Any]: return TupleExporter(fields) def named_sum_extractor( self, t: type, branches: Dict[str, Tuple[type, MetricsExporter[Any]]] ) -> MetricsExporter[Any]: return TaggedExporter(branches=branches, tag_tag=t.__name__) def unnamed_sum_extractor(self, t: type, branches: List[Tuple[type, MetricsExporter[Any]]]) -> MetricsExporter[Any]: return PriorityExporter(branches=branches) def optional_extractor(self, t: MetricsExporter[Any]) -> MetricsExporter[Any]: return OptionalExporter(t) def list_extractor(self, t: MetricsExporter[Any]) -> MetricsExporter[Any]: return ListExporter(t) def dictionary_extractor( self, key: type, value: type, key_ext: MetricsExporter[Any], val_ext: MetricsExporter[Any] ) -> MetricsExporter[Any]: if key is str: return StringDictionaryExporter(val_ext) raise NotImplementedError() def enum_extractor(self, enum_name: str, enum_values: List[Tuple[str, Any]]) -> MetricsExporter[Any]: return self._enum_exporter
def extract_if_list_type(t: type) -> Union[None, str, Boxed[type]]: """ :param t: type that needs to be checked for being a list. :return: Returns `Boxed("X")` if `t` is the type `List[X]` with X being the type variable name used by `List`. """ if not hasattr(t, "__origin__"): return None origin = cast(type, t.__origin__) # type: ignore if origin is List: return origin.__parameters__[0].__name__ # type: ignore if origin is list: return Boxed(t.__args__[0]) # type: ignore return None
def to_enum_dict(d: Dict[str, Any]) -> TOrError[Dict[Any, Any]]: result: Dict[Any, Any] = {} errors: List[JsDecodeError] = [] for k, v in d.items(): try: e = cast(Enum, key_ext.read(k)) result[e] = v except JsDecodeException as exception: for err in exception.errors: errors.append(err) if len(errors) > 0: return errors return Boxed(result)
def _extract_named_product_type(self, product_type: type) -> Optional[Boxed[T]]: maybe_fields = Extractor.extract_if_named_tuple_type(product_type) maybe_fields = Extractor.or_else( maybe_fields, lambda: Extractor.extract_if_dataclass_type(product_type)) if maybe_fields is None: return None fields: Dict[str, WithDefault[T]] = { n: v.map(self._make) for (n, v) in maybe_fields.items() } return Boxed(self.named_product_extractor(product_type, fields))
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[int]: decimal_or_error = json_number_decoder.decode(json, ancestors) if isinstance(decimal_or_error, Boxed): d = decimal_or_error.t if int(d) == d: return Boxed(int(d)) else: return [ JsDecodeErrorFinal( "Expected an integral number but received non-intgeral number." ) ] else: return decimal_or_error
def extract_if_dictionary_type( t: type) -> Union[None, Tuple[str, str], Boxed[Tuple[type, type]]]: """ :param t: type that needs to be checked for being a dictionary. :return: Returns `Boxed(("X", "Y"))` if `t` is the type `Dict[X, Y]` and X and Y are type variable names for key and value types respectively. """ if not hasattr(t, "__origin__"): return None origin = cast(type, t.__origin__) # type: ignore if origin is Dict: return origin.__parameters__[0].__name__, origin.__parameters__[ 1].__name__ # type: ignore if origin is dict: return Boxed((t.__args__[0], t.__args__[1])) # type: ignore return None
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[T]: string_or_error = json_string_decoder.decode(json, ancestors) if isinstance(string_or_error, Boxed): s = string_or_error.t enum_value = self.enum_values.get(s) if enum_value is None: return [ JsDecodeErrorFinal( "Unexpected value %s while deserializing enum %s." % (s, self.enum_name) ) ] else: return Boxed(enum_value) else: return string_or_error
def _extract_unnamed_sum_type(self, sum_type: type) -> Optional[Boxed[T]]: maybe_unnamed_sum_type = Extractor.extract_if_union_type(sum_type) if maybe_unnamed_sum_type is None: return None extracted_branches: List[Tuple[type, T]] = [ (t, self._make(t)) for t in maybe_unnamed_sum_type.branches ] extracted_union: T if len(extracted_branches) == 1: extracted_union = extracted_branches[0][1] else: extracted_union = self.unnamed_sum_extractor( sum_type, extracted_branches) if maybe_unnamed_sum_type.is_optional: extracted_union = self.optional_extractor(extracted_union) return Boxed(extracted_union)
def decode( self, json: JsValue, ancestors: List[JsValue] ) -> TOrError[datetime]: string_or_error = json_string_decoder.decode(json, ancestors) if isinstance(string_or_error, Boxed): s = string_or_error.t try: return Boxed(parser.isoparse(s)) except ValueError: return [ JsDecodeErrorFinal( "Expected a string representing a datetime but received '%s'." % s ) ] else: return string_or_error
def _make(self, t: type) -> T: if isinstance(t, TypeVar): # type: ignore t = self._var_to_type(t.__name__) t_origin, t_assignments, new_context = self.assignments(t) key = (t_origin, t_assignments) if key in self.memoized: result = self.memoized[key].t if isinstance(result, RecursiveTypeApplication): result.ref_count = result.ref_count + 1 return result recursion_placeholder = RecursiveTypeApplication( 0, t_origin, t_assignments) self.memoized[key] = Boxed(recursion_placeholder) old_context = self._context self._context = new_context result: Optional[Boxed[T]] = None result = Extractor.or_else(result, lambda: self._extract_basic_type(t_origin)) result = Extractor.or_else( result, lambda: self._extract_unnamed_sum_type(t_origin)) result = Extractor.or_else( result, lambda: self._extract_named_sum_type(t_origin)) result = Extractor.or_else(result, lambda: self._extract_list_type(t_origin)) result = Extractor.or_else( result, lambda: self._extract_dictionary_type(t_origin)) result = Extractor.or_else( result, lambda: self._extract_unnamed_product_type(t_origin)) result = Extractor.or_else( result, lambda: self._extract_named_product_type(t_origin)) result = Extractor.or_else(result, lambda: self._extract_enum_type(t_origin)) if result is None: raise UnknownExtractorException(t_origin) if recursion_placeholder.ref_count > 0: replace_all_refs(recursion_placeholder, result.t) self._context = old_context self.memoized[key] = result return result.t
def extract_if_named_tuple_type(t: type) -> Optional[Dict[str, FieldType]]: """ :param t: Type that needs to be checked for being a named tuple. :return: If `t` is a NamedTuple, returns a mapping from `t`'s field names to their types and default values. Otherwise, returns None. """ if not hasattr(t, "_field_types"): return None t = cast(Type[NamedTuple], t) fields: Dict[str, FieldType] = {} for (f_name, f_type) in t._field_types.items(): f_default: Optional[Boxed[Any]] = None if f_name in t._field_defaults: f_default = Boxed(t._field_defaults[f_name]) fields[f_name] = FieldType(f_type, f_default) return fields
def _extract_dictionary_type(self, dict_type: type) -> Optional[Boxed[T]]: maybe_dict_vars = Extractor.extract_if_dictionary_type(dict_type) if maybe_dict_vars is None: return None key_type: type value_type: type if isinstance(maybe_dict_vars, Boxed): key_type, value_type = maybe_dict_vars.t else: (key_var_name, value_var_name) = maybe_dict_vars key_type = self._var_to_type(key_var_name) value_type = self._var_to_type(value_var_name) key_extractor = self._make(key_type) value_extractor = self._make(value_type) return Boxed( self.dictionary_extractor(key_type, value_type, key_extractor, value_extractor))
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[T]: if not isinstance(json, dict): return [ JsDecodeErrorFinal( "Expected a JSON object but received something else." ) ] decoded_fields: Dict[str, Any] = {} decoding_errors: List[JsDecodeError] = [] for field_name, field_decoder in self.field_decoders.items(): if (field_name not in json) or (json[field_name] is None): if isinstance(field_decoder, JsonOptionalDecoder): decoded_fields[field_name] = None elif field_name in self.field_defaults: def_or_fac = self.field_defaults[field_name] decoded_fields[field_name] = def_or_fac.t if isinstance(def_or_fac, Boxed) else def_or_fac() else: decoding_errors.append( JsDecodeErrorInField( field_name, JsDecodeErrorFinal( "Non-optional field was not found" ), ) ) else: # The field_name is a key and its associated value is not None. So, it should be decoded. field_json = cast(JsValue, json[field_name]) decoded_field = field_decoder.decode( field_json, [cast(JsValue, json)] + ancestors ) if isinstance(decoded_field, Boxed): decoded_fields[field_name] = decoded_field.t else: for err in decoded_field: decoding_errors.append( JsDecodeErrorInField(field_name, err) ) if len(decoding_errors) > 0: return decoding_errors else: return Boxed(self.get_constructor()(decoded_fields))
class AutoJsonDecoder(Extractor[JsonDecoder[Any]]): json_boolean_decoder = JsonBooleanDecoder() json_integer_decoder = JsonIntegerDecoder() json_date_decoder = JsonDateDecoder() json_datetime_decoder = JsonDatetimeDecoder() basic_json_decoders: Dict[type, Boxed[JsonDecoder[Any]]] = { bool: Boxed(json_boolean_decoder), str: Boxed(json_string_decoder), int: Boxed(json_integer_decoder), Decimal: Boxed(json_number_decoder), datetime: Boxed(json_datetime_decoder), date: Boxed(json_date_decoder), type(None): Boxed(cast(JsonDecoder[Any], JsonNoneDecoder)) } def __init__(self) -> None: super().__init__() @property def basics(self) -> Dict[type, Boxed[JsonDecoder[Any]]]: return self.basic_json_decoders def named_product_extractor(self, t: type, fields: Dict[str, WithDefault[JsonDecoder[Any]]]) -> JsonDecoder[Any]: field_decoders: Dict[str, JsonDecoder[Any]] = { n: v.t for (n, v) in fields.items() } field_defaults: Dict[str, Union[Boxed[Any], Callable[[], Any]]] = { n: v.default for (n, v) in fields.items() if v.default is not None } return JsonObjectDecoder( field_decoders=field_decoders, field_defaults=field_defaults, constructor=lambda args: t(**args), ) def unnamed_product_extractor(self, t: type, fields: List[JsonDecoder[Any]]) -> JsonDecoder[Tuple[Any, ...]]: return JsonTupleDecoder(fields) def named_sum_extractor(self, t: type, branches: Dict[str, Tuple[type, JsonDecoder[Any]]]) -> JsonDecoder[Any]: return JsonTaggedDecoder( branch_decoders={s: t for (s, (_, t)) in branches.items()}, tag_field_name=t.__name__, value_field_name=None ) def unnamed_sum_extractor(self, t: type, branches: List[Tuple[type, JsonDecoder[Any]]]) -> JsonDecoder[Any]: return JsonPriorityDecoder([d for (_, d) in branches]) def optional_extractor(self, t: JsonDecoder[T]) -> JsonDecoder[Optional[T]]: return JsonOptionalDecoder(t) def list_extractor(self, t: JsonDecoder[T]) -> JsonDecoder[List[T]]: return JsonListDecoder(t) def dictionary_extractor( self, key: type, value: type, key_ext: JsonDecoder[Any], val_ext: JsonDecoder[Any] ) -> JsonDecoder[Dict[Any, Any]]: def to_enum_dict(d: Dict[str, Any]) -> TOrError[Dict[Any, Any]]: result: Dict[Any, Any] = {} errors: List[JsDecodeError] = [] for k, v in d.items(): try: e = cast(Enum, key_ext.read(k)) result[e] = v except JsDecodeException as exception: for err in exception.errors: errors.append(err) if len(errors) > 0: return errors return Boxed(result) if key is str: return JsonStringDictionaryDecoder(val_ext) if issubclass(key, Enum): return JsonFlatMappedDecoder(JsonStringDictionaryDecoder(val_ext), to_enum_dict) raise NotImplementedError() def enum_extractor(self, enum_name: str, enum_values: List[Tuple[str, Any]]) -> JsonDecoder[Any]: return JsonEnumDecoder(enum_name, {n: v for (n, v) in enum_values})
def decode( self, json: JsValue, ancestors: List[JsValue] ) -> TOrError[None]: return Boxed(None)
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[T]: result = self.u_decoder.decode(json, ancestors) if isinstance(result, Boxed): transformer = cast(Callable[[U], T], self.u_to_t) return Boxed(transformer(result.t)) return result
class AutoJsonEncoder(Extractor[JsonEncoder[Any]]): json_basic_encoder: JsonBasicEncoder = JsonBasicEncoder() json_decimal_encoder: JsonDecimalEncoder = JsonDecimalEncoder() json_date_encoder: JsonDateEncoder = JsonDateEncoder() json_enum_encoder: JsonEnumEncoder = JsonEnumEncoder() basic_encoders: Dict[type, Boxed[JsonEncoder[Any]]] = { bool: Boxed(json_basic_encoder), str: Boxed(json_basic_encoder), int: Boxed(json_basic_encoder), float: Boxed(json_basic_encoder), Decimal: Boxed(json_decimal_encoder), datetime: Boxed(json_date_encoder), date: Boxed(json_date_encoder), type(None): Boxed(JsonNoneEncoder()) } def __init__(self) -> None: super().__init__() @property def basics(self) -> Dict[type, Boxed[JsonEncoder[Any]]]: return self.basic_encoders def named_product_extractor( self, t: type, fields: Dict[str, WithDefault[JsonEncoder[Any]]]) -> JsonEncoder[Any]: field_encoders: Dict[str, JsonEncoder[Any]] = { n: v.t for (n, v) in fields.items() } return JsonObjectEncoder(field_encoders=field_encoders) def unnamed_product_extractor( self, t: type, fields: List[JsonEncoder[Any]]) -> JsonEncoder[Tuple[Any, ...]]: return JsonTupleEncoder(fields) def named_sum_extractor( self, t: type, branches: Dict[str, Tuple[type, JsonEncoder[Any]]]) -> JsonEncoder[Any]: return JsonTaggedEncoder(branches=branches, tag_field_name=t.__name__, value_field_name=None) def unnamed_sum_extractor( self, t: type, branches: List[Tuple[type, JsonEncoder[Any]]]) -> JsonEncoder[Any]: return JsonPriorityEncoder(branches) def optional_extractor(self, t: JsonEncoder[T]) -> JsonEncoder[Optional[T]]: return JsonOptionalEncoder(t) def list_extractor(self, t: JsonEncoder[T]) -> JsonEncoder[List[T]]: return JsonListEncoder(t) def dictionary_extractor( self, key: type, value: type, key_ext: JsonEncoder[Any], val_ext: JsonEncoder[Any]) -> JsonEncoder[Dict[Any, Any]]: def to_str_dict(d: Dict[Enum, Any]) -> Dict[str, Any]: return {cast(str, k.value): v for k, v in d.items()} if key is str: return JsonStringDictionaryEncoder(val_ext) if issubclass(key, Enum): return JsonMappedEncoder(JsonStringDictionaryEncoder(val_ext), to_str_dict) raise NotImplementedError() def enum_extractor(self, enum_name: str, enum_values: List[Tuple[str, Any]]) -> JsonEncoder[Any]: return self.json_enum_encoder
def decode(self, json: JsValue, ancestors: List[JsValue]) -> TOrError[T]: result = self.inner_decoder.decode(json, ancestors) if isinstance(result, Boxed): return result return Boxed(self.default)