def __init__(self, schema, exc_type): if ( schema.name != exc_type.__name__ and schema.name != cases.upper_to_lower_camel(exc_type.__name__) ): LOG.warning( 'expect exc_type.__name__ == %r, not %r', schema.name, exc_type.__name__, ) args_annotation = ( exc_type.__dict__.get('__annotations__', {}).get('args') ) if args_annotation is None: element_types = [ TYPE_ASSERT.getitem(self._SIMPLE_TYPE_MAP, sf.type.which) for sf in _fields_by_code_order(schema) ] else: TYPE_ASSERT( typings.is_recursive_type(args_annotation) and args_annotation.__origin__ is tuple, 'expect typing.Tuple, not {!r}', args_annotation, ) element_types = args_annotation.__args__ self._converter = _TupleConverter(schema, element_types) self._exc_type = exc_type
def get_declared_error_types(response_type): # When there is only one error type, reqrep.make_annotations # would not generate Optional[T]. fields = dataclasses.fields(response_type.Error) if len(fields) == 1: return {ASSERT.issubclass(fields[0].type, Exception): fields[0].name} else: return { ASSERT( typings.is_recursive_type(field.type) and typings.is_union_type(field.type) and typings.match_optional_type(field.type), 'expect typing.Optional[T]: {!r}', field, ): field.name for field in fields }
def _make_optional_field_converter(sf_type, df_type): """Make a converter for a union member. * ``sf_type`` should be type of a member field of a union. * ``df_type`` should be a typing.Optional annotation. """ if typings.type_is_subclass(df_type, NoneType): # Handle typing.Optional[NoneType], which is simply NoneType. return _make_union_member_converter(sf_type, df_type) else: return _make_union_member_converter( sf_type, TYPE_ASSERT( typings.is_recursive_type(df_type) and typings.is_union_type(df_type) and typings.match_optional_type(df_type), 'expect typing.Optional, not {!r}', df_type, ), )
def _match_recursive_type(type_, value): if not typings.is_recursive_type(type_): # Base case of the recursive type. return isinstance(value, type_) elif type_.__origin__ in (list, set, frozenset): return (isinstance(value, type_.__origin__) and all( _match_recursive_type(type_.__args__[0], v) for v in value)) elif type_.__origin__ is tuple: return ( isinstance(value, tuple) and \ len(value) == len(type_.__args__) and all(_match_recursive_type(t, v) for t, v in zip(type_.__args__, value)) ) elif typings.is_union_type(type_): return any(_match_recursive_type(t, value) for t in type_.__args__) else: return False
def _encode_value(self, value_type, value): """Encode a value into a raw value. This and ``_decode_raw_value`` complement each other. """ if typings.is_recursive_type(value_type): if value_type.__origin__ in (list, set, frozenset): element_type = value_type.__args__[0] return [ self._encode_value(element_type, element) for element in value ] elif value_type.__origin__ is tuple: ASSERT.equal(len(value), len(value_type.__args__)) return tuple( self._encode_value(element_type, element) for element_type, element in zip( value_type.__args__, value, )) elif typings.is_union_type(value_type): # Make a special case for ``None``. if value is None: ASSERT.in_(NoneType, value_type.__args__) return None # Make a special case for ``Optional[T]``. type_ = typings.match_optional_type(value_type) if type_: return self._encode_value(type_, value) for type_ in value_type.__args__: if typings.is_recursive_type(type_): if _match_recursive_type(type_, value): return { str(type_): self._encode_value(type_, value) } elif isinstance(value, type_): return { type_.__name__: self._encode_value(type_, value) } return ASSERT.unreachable( 'value is not any union element type: {!r} {!r}', value_type, value, ) else: return ASSERT.unreachable('unsupported generic: {!r}', value_type) elif wiredata.is_message(value): ASSERT.predicate(value_type, wiredata.is_message_type) return { f.name: self._encode_value(f.type, getattr(value, f.name)) for f in dataclasses.fields(value) } elif isinstance(value, datetime.datetime): ASSERT.issubclass(value_type, datetime.datetime) return value.isoformat() elif isinstance(value, enum.Enum): ASSERT.issubclass(value_type, enum.Enum) return value.name # JSON does not support binary type; so it has to be encoded. elif isinstance(value, bytes): ASSERT.issubclass(value_type, bytes) return base64.standard_b64encode(value).decode('ascii') elif isinstance(value, Exception): ASSERT.issubclass(value_type, Exception) return { type(value).__name__: [ ASSERT.isinstance(arg, _DIRECTLY_SERIALIZABLE_TYPES) for arg in value.args ] } elif isinstance(value, _DIRECTLY_SERIALIZABLE_TYPES): ASSERT.issubclass(value_type, _DIRECTLY_SERIALIZABLE_TYPES) return value else: return ASSERT.unreachable('unsupported value type: {!r} {!r}', value_type, value)
def _decode_raw_value(self, value_type, raw_value): """Decode a raw value into ``value_type``-typed value. This and ``_encode_value`` complement each other. """ if typings.is_recursive_type(value_type): if value_type.__origin__ in (list, set, frozenset): element_type = value_type.__args__[0] return value_type.__origin__( self._decode_raw_value(element_type, raw_element) for raw_element in raw_value) elif value_type.__origin__ is tuple: ASSERT.equal(len(raw_value), len(value_type.__args__)) return tuple( self._decode_raw_value(element_type, raw_element) for element_type, raw_element in zip( value_type.__args__, raw_value, )) elif typings.is_union_type(value_type): # Handle ``None`` special case. if raw_value is None: ASSERT.in_(NoneType, value_type.__args__) return None # Handle ``Optional[T]`` special case. type_ = typings.match_optional_type(value_type) if type_: return self._decode_raw_value(type_, raw_value) ASSERT.equal(len(raw_value), 1) type_name, raw_element = next(iter(raw_value.items())) for type_ in value_type.__args__: if typings.is_recursive_type(type_): candidate = str(type_) else: candidate = type_.__name__ if type_name == candidate: return self._decode_raw_value(type_, raw_element) return ASSERT.unreachable( 'raw value is not any union element type: {!r} {!r}', value_type, raw_value, ) else: return ASSERT.unreachable('unsupported generic: {!r}', value_type) elif wiredata.is_message_type(value_type): return value_type( **{ f.name: self._decode_raw_value(f.type, raw_value[f.name]) for f in dataclasses.fields(value_type) if f.name in raw_value }) elif not isinstance(value_type, type): # Non-``type`` instance cannot be passed to ``issubclass``. return ASSERT.unreachable('unsupported value type: {!r}', value_type) elif issubclass(value_type, datetime.datetime): return value_type.fromisoformat(raw_value) elif issubclass(value_type, enum.Enum): return value_type[raw_value] elif issubclass(value_type, bytes): return base64.standard_b64decode(raw_value.encode('ascii')) elif issubclass(value_type, Exception): ASSERT.equal(len(raw_value), 1) return value_type( *(ASSERT.isinstance(raw_arg, _DIRECTLY_SERIALIZABLE_TYPES) for raw_arg in raw_value[value_type.__name__])) elif issubclass(value_type, _DIRECTLY_SERIALIZABLE_TYPES): if value_type in _DIRECTLY_SERIALIZABLE_TYPES: return ASSERT.isinstance(raw_value, value_type) else: # Support sub-type of int, etc. return value_type(raw_value) else: return ASSERT.unreachable('unsupported value type: {!r}', value_type)
def _make_union_member_converter(sf_type, df_type): if typings.is_recursive_type(df_type): if df_type.__origin__ is list: TYPE_ASSERT.equal(len(df_type.__args__), 1) TYPE_ASSERT.true(sf_type.is_list()) return _CollectionTypedFieldConverter.make_union_list_accessors( _ListConverter(sf_type.as_list(), df_type.__args__[0]) ) elif df_type.__origin__ is tuple: TYPE_ASSERT.true(sf_type.is_struct()) return _CollectionTypedFieldConverter.make_union_accessors( _TupleConverter(sf_type.as_struct(), df_type.__args__) ) else: return TYPE_ASSERT.unreachable( 'unsupported generic type for union: {!r}', df_type ) elif is_dataclass(df_type): TYPE_ASSERT.true(sf_type.is_struct()) return _CollectionTypedFieldConverter.make_union_accessors( _StructConverter.get(sf_type.as_struct(), df_type) ) elif issubclass(df_type, Exception): TYPE_ASSERT.true(sf_type.is_struct()) return _CollectionTypedFieldConverter.make_union_accessors( _ExceptionConverter(sf_type.as_struct(), df_type) ) elif issubclass(df_type, datetime.datetime): if sf_type.which is _DATETIME_FLOAT_TYPE: return _union_datetime_getter, _union_datetime_setter_float else: TYPE_ASSERT.in_(sf_type.which, _DATETIME_INT_TYPES) return _union_datetime_getter, _union_datetime_setter_int elif issubclass(df_type, enum.Enum): TYPE_ASSERT.true(sf_type.is_enum()) return functools.partial(_union_enum_getter, df_type), _union_setter elif issubclass(df_type, NoneType): TYPE_ASSERT.true(sf_type.is_void()) return _union_none_getter, _union_setter elif issubclass(df_type, _capnp.VoidType): TYPE_ASSERT.true(sf_type.is_void()) return operator.getitem, _union_setter elif issubclass(df_type, bool): TYPE_ASSERT.true(sf_type.is_bool()) return operator.getitem, _union_setter elif issubclass(df_type, int): # NOTE: For now we only support sub-types of int. If there are # use cases of sub-types other types, we will add support to # them as well. TYPE_ASSERT.in_(sf_type.which, _INT_TYPES) if df_type is int: getter = operator.getitem else: getter = functools.partial(_union_int_subtype_getter, df_type) return getter, _union_setter elif issubclass(df_type, float): TYPE_ASSERT.in_(sf_type.which, _FLOAT_TYPES) return operator.getitem, _union_setter elif issubclass(df_type, bytes): TYPE_ASSERT.true(sf_type.is_data()) return _bytes_getter, _union_setter elif issubclass(df_type, str): TYPE_ASSERT.true(sf_type.is_text()) return operator.getitem, _union_setter else: return TYPE_ASSERT.unreachable( 'unsupported union member type: {!r}, {!r}', sf_type, df_type )