Files
TeraHz/backend/venv/lib/python3.6/site-packages/serial/properties.py
2019-01-21 17:36:00 +01:00

942 lines
29 KiB
Python

# Tell the linters what's up:
# pylint:disable=wrong-import-position,consider-using-enumerate,useless-object-inheritance
# mccabe:options:max-complexity=999
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
print_function, unicode_literals
from .utilities.compatibility import backport
backport() # noqa
from future.utils import native_str
import numbers # noqa
from base64 import b64decode, b64encode # noqa
from copy import deepcopy # noqa
from datetime import date, datetime # noqa
try:
from typing import Union, Optional, Sequence, Mapping, Set, Sequence, Callable, Dict, Any, Hashable, Collection,\
Tuple
except ImportError:
Union = Optional = Sequence = Mapping = Set = Sequence = Callable = Dict = Any = Hashable = Collection = Tuple =\
Iterable = None
import iso8601 # noqa
from .utilities import collections, collections_abc, qualified_name, properties_values, parameters_defaults,\
calling_function_qualified_name
from serial import abc, errors, meta
import serial
NoneType = type(None)
NULL = None
class Null(object): # noqa - inheriting from object is intentional, as this is needed for python 2x compatibility
"""
Instances of this class represent an *explicit* null value, rather than the absence of a
property/attribute/element, as would be inferred from a value of `None`.
"""
def __init__(self):
if NULL is not None:
raise errors.DefinitionExistsError(
'%s may only be defined once.' % repr(self)
)
def __bool__(self):
# type: (...) -> bool
return False
def __eq__(self, other):
# type: (Any) -> bool
return id(other) == id(self)
def __hash__(self):
# type: (...) -> int
return 0
def __str__(self):
# type: (...) -> str
return 'null'
def _marshal(self):
# type: (...) -> None
return None
def __repr__(self):
# type: (...) -> str
return (
'NULL'
if self.__module__ in ('__main__', 'builtins', '__builtin__') else
'%s.NULL' % self.__module__
)
def __copy__(self):
# type: (...) -> Null
return self
def __deepcopy__(self, memo):
# type: (Dict[Hashable, Any]) -> Null
return self
NULL = Null()
def _validate_type_or_property(type_or_property):
# type: (Union[type, Property]) -> (Union[type, Property])
if not isinstance(type_or_property, (type, Property)):
raise TypeError(type_or_property)
if not (
(type_or_property is Null) or
(
isinstance(type_or_property, type) and
issubclass(
type_or_property,
(
abc.model.Model,
str,
native_str,
bytes,
numbers.Number,
date,
datetime,
Null,
collections_abc.Iterable,
dict,
collections.OrderedDict,
bool
)
)
) or
isinstance(type_or_property, Property)
):
raise TypeError(type_or_property)
return type_or_property
class Types(list):
"""
Instances of this class are lists which will only take values which are valid types for describing a property
definition.
"""
def __init__(
self,
property_, # type: Property
items=None # type: Optional[Union[Sequence[Union[type, Property], Types], type, Property]]
):
# (...) -> None
if not isinstance(property_, Property):
raise TypeError(
'The parameter `property` must be a `type`, or an instance of `%s`.' % qualified_name(Property)
)
self.property_ = property_
if isinstance(items, (type, Property)):
items = (items,)
if items is None:
super().__init__()
else:
super().__init__(items)
def __setitem__(self, index, value):
# type: (int, Union[type, Property]) -> None
super().__setitem__(index, _validate_type_or_property(value))
if value is str and (native_str is not str) and (native_str not in self):
super().append(native_str)
def append(self, value):
# type: (Union[type, Property]) -> None
super().append(_validate_type_or_property(value))
if value is str and (native_str is not str) and (native_str not in self):
super().append(native_str)
def __delitem__(self, index):
# type: (int) -> None
value = self[index]
super().__delitem__(index)
if (value is str) and (native_str in self):
self.remove(native_str)
def pop(self, index=-1):
# type: (int) -> Union[type, Property]
value = super().pop(index)
if (value is str) and (native_str in self):
self.remove(native_str)
return value
def __copy__(self):
# type: () -> Types
return self.__class__(self.property_, self)
def __deepcopy__(self, memo=None):
# type: (dict) -> Types
return self.__class__(
self.property_,
tuple(
deepcopy(v, memo=memo)
for v in self
)
)
def __repr__(self):
representation = [
qualified_name(type(self)) + '('
]
if self:
representation[0] += '['
for v in self:
rv = (
qualified_name(v) if isinstance(v, type) else
repr(v)
)
rvls = rv.split('\n')
if len(rvls) > 1:
rvs = [rvls[0]]
for rvl in rvls[1:]:
rvs.append(' ' + rvl)
rv = '\n'.join(rvs)
representation += [
' %s' % rv,
]
else:
representation.append(
' %s,' % rv
)
representation[-1] = representation[-1][:-1]
representation.append(
']'
)
representation[-1] += ')'
return '\n'.join(representation) if len(representation) > 2 else ''.join(representation)
class Property(object):
"""
This is the base class for defining a property.
Properties
- value_types ([type|Property]): One or more expected value_types or `Property` instances. Values are checked,
sequentially, against each type or `Property` instance, and the first appropriate match is used.
- required (bool|collections.Callable): If `True`--dumping the json_object will throw an error if this value
is `None`.
- versions ([str]|{str:Property}): The property should be one of the following:
- A set/tuple/list of version numbers to which this property applies.
- A mapping of version numbers to an instance of `Property` applicable to that version.
Version numbers prefixed by "<" indicate any version less than the one specified, so "<3.0" indicates that
this property is available in versions prior to 3.0. The inverse is true for version numbers prefixed by ">".
">=" and "<=" have similar meanings, but are inclusive.
Versioning can be applied to an json_object by calling `serial.meta.set_version` in the `__init__` method of
an `serial.model.Object` sub-class. For an example, see `oapi.model.OpenAPI.__init__`.
- name (str): The name of the property when loaded from or dumped into a JSON/YAML/XML json_object. Specifying a
`name` facilitates mapping of PEP8 compliant property to JSON or YAML attribute names, or XML element names,
which are either camelCased, are python keywords, or otherwise not appropriate for usage in python code.
"""
def __init__(
self,
types=None, # type: Sequence[Union[type, Property]]
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Sequence[Union[str, meta.Version]]]
):
self._types = None # type: Optional[Sequence[Union[type, Property]]]
self.types = types
self.name = name
self.required = required
self._versions = None # type: Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
self.versions = versions # type: Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
@property
def types(self):
return self._types
@types.setter
def types(self, types_or_properties):
# type: (Optional[Sequence[Union[type, Property, abc.model.Model]]]) -> None
if types_or_properties is not None:
if callable(types_or_properties):
if native_str is not str:
_types_or_properties = types_or_properties
def types_or_properties(d):
# type: (Sequence[Union[type, Property, abc.model.Model]]) -> Types
return Types(self, _types_or_properties(d))
else:
types_or_properties = Types(self, types_or_properties)
self._types = types_or_properties
@property
def versions(self):
# type: () -> Optional[Sequence[meta.Version]]
return self._versions
@versions.setter
def versions(
self,
versions # type: Optional[Sequence[Union[str, collections_abc.Iterable, meta.Version]]]
):
# type: (...) -> Optional[Union[Mapping[str, Optional[Property]], Set[Union[str, Number]]]]
if versions is not None:
if isinstance(versions, (str, Number, meta.Version)):
versions = (versions,)
if isinstance(versions, collections_abc.Iterable):
versions = tuple(
(v if isinstance(v, meta.Version) else meta.Version(v))
for v in versions
)
else:
repr_versions = repr(versions)
raise TypeError(
(
'`%s` requires a sequence of version strings or ' %
calling_function_qualified_name()
) + (
'`%s` instances, not' % qualified_name(meta.Version)
) + (
':\n' + repr_versions
if '\n' in repr_versions else
' `%s`.' % repr_versions
)
)
self._versions = versions
def unmarshal(self, data):
# type: (Any) -> Any
# return data if self.types is None else unmarshal(data, types=self.types)
if isinstance(
data,
collections_abc.Iterable
) and not isinstance(
data,
(str, bytes, bytearray, native_str)
) and not isinstance(
data,
abc.model.Model
):
if isinstance(data, (dict, collections.OrderedDict)):
for k, v in data.items():
if v is None:
data[k] = NULL
else:
data = tuple((NULL if i is None else i) for i in data)
return serial.marshal.unmarshal(data, types=self.types)
def marshal(self, data):
# type: (Any) -> Any
return serial.marshal.marshal(data, types=self.types) #, types=self.types, value_types=self.value_types)
def __repr__(self):
representation = [qualified_name(type(self)) + '(']
pd = parameters_defaults(self.__init__)
for p, v in properties_values(self):
if (p not in pd) or pd[p] == v:
continue
if (v is not None) and (v is not NULL):
if isinstance(v, collections_abc.Sequence) and not isinstance(v, (str, bytes)):
rvs = ['(']
for i in v:
ri = (
qualified_name(i)
if isinstance(i, type) else
"'%s'" % str(i)
if isinstance(i, meta.Version) else
repr(i)
)
rils = ri.split('\n')
if len(rils) > 1:
ris = [rils[0]]
for ril in rils[1:]:
ris.append(' ' + ril)
ri = '\n'.join(ris)
rvs.append(' %s,' % ri)
if len(v) > 1:
rvs[-1] = rvs[-1][:-1]
rvs.append(' )')
rv = '\n'.join(rvs)
else:
rv = (
qualified_name(v)
if isinstance(v, type) else
"'%s'" % str(v)
if isinstance(v, meta.Version) else
repr(v)
)
rvls = rv.split('\n')
if len(rvls) > 2:
rvs = [rvls[0]]
for rvl in rvls[1:]:
rvs.append(' ' + rvl)
rv = '\n'.join(rvs)
representation.append(
' %s=%s,' % (p, rv)
)
representation.append(')')
if len(representation) > 2:
return '\n'.join(representation)
else:
return ''.join(representation)
def __copy__(self):
new_instance = self.__class__()
for a in dir(self):
if a[0] != '_' and a != 'data':
v = getattr(self, a)
if not callable(v):
setattr(new_instance, a, v)
return new_instance
def __deepcopy__(self, memo):
# type: (dict) -> Property
new_instance = self.__class__()
for a, v in properties_values(self):
setattr(new_instance, a, deepcopy(v, memo))
return new_instance
abc.properties.Property.register(Property)
class String(Property):
"""
See `serial.properties.Property`
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
super().__init__(
types=(str,),
name=name,
required=required,
versions=versions,
)
class Date(Property):
"""
See `serial.properties.Property`
Additional Properties:
- marshal (collections.Callable): A function, taking one argument (a python `date` json_object), and returning
a date string in the desired format. The default is `date.isoformat`--returning an iso8601 compliant date
string.
- unmarshal (collections.Callable): A function, taking one argument (a date string), and returning a python
`date` json_object. By default, this is `iso8601.parse_date`.
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
date2str=date.isoformat, # type: Optional[Callable]
str2date=iso8601.parse_date # type: Callable
):
super().__init__(
types=(date,),
name=name,
required=required,
versions=versions,
)
self.date2str = date2str
self.str2date = str2date
def unmarshal(self, data):
# type: (Optional[str]) -> Union[date, NoneType]
if data is None:
return data
else:
if isinstance(data, date):
date_ = data
elif isinstance(data, str):
date_ = self.str2date(data)
else:
raise TypeError(
'%s is not a `str`.' % repr(data)
)
if isinstance(date_, date):
return date_
else:
raise TypeError(
'"%s" is not a properly formatted date string.' % data
)
def marshal(self, data):
# type: (Optional[date]) -> Optional[str]
if data is None:
return data
else:
ds = self.date2str(data)
if not isinstance(ds, str):
if isinstance(ds, native_str):
ds = str(ds)
else:
raise TypeError(
'The date2str function should return a `str`, not a `%s`: %s' % (
type(ds).__name__,
repr(ds)
)
)
return ds
class DateTime(Property):
"""
See `serial.properties.Property`
Additional Properties:
- marshal (collections.Callable): A function, taking one argument (a python `datetime` json_object), and
returning a date-time string in the desired format. The default is `datetime.isoformat`--returning an
iso8601 compliant date-time string.
- unmarshal (collections.Callable): A function, taking one argument (a datetime string), and returning a python
`datetime` json_object. By default, this is `iso8601.parse_date`.
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
datetime2str=datetime.isoformat, # type: Optional[Callable]
str2datetime=iso8601.parse_date # type: Callable
):
self.datetime2str = datetime2str
self.str2datetime = str2datetime
super().__init__(
types=(datetime,),
name=name,
required=required,
versions=versions,
)
def unmarshal(self, data):
# type: (Optional[str]) -> Union[datetime, NoneType]
if data is None:
return data
else:
if isinstance(data, datetime):
datetime_ = data
elif isinstance(data, str):
datetime_ = self.str2datetime(data)
else:
raise TypeError(
'%s is not a `str`.' % repr(data)
)
if isinstance(datetime_, datetime):
return datetime_
else:
raise TypeError(
'"%s" is not a properly formatted date-time string.' % data
)
def marshal(self, data):
# type: (Optional[datetime]) -> Optional[str]
if data is None:
return data
else:
datetime_string = self.datetime2str(data)
if not isinstance(datetime_string, str):
if isinstance(datetime_string, native_str):
datetime_string = str(datetime_string)
else:
repr_datetime_string = repr(datetime_string).strip()
raise TypeError(
'The datetime2str function should return a `str`, not:' + (
'\n'
if '\n' in repr_datetime_string else
' '
) + repr_datetime_string
)
return datetime_string
class Bytes(Property):
"""
See `serial.properties.Property`
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: bool
versions=None, # type: Optional[Collection]
):
super().__init__(
types=(bytes, bytearray),
name=name,
required=required,
versions=versions,
)
def unmarshal(self, data):
# type: (str) -> Optional[bytes]
"""
Un-marshal a base-64 encoded string into bytes
"""
if data is None:
return data
elif isinstance(data, str):
return b64decode(data)
elif isinstance(data, bytes):
return data
else:
raise TypeError(
'`data` must be a base64 encoded `str` or `bytes`--not `%s`' % qualified_name(type(data))
)
def marshal(self, data):
# type: (bytes) -> str
"""
Marshal bytes into a base-64 encoded string
"""
if (data is None) or isinstance(data, str):
return data
elif isinstance(data, bytes):
return str(b64encode(data), 'ascii')
else:
raise TypeError(
'`data` must be a base64 encoded `str` or `bytes`--not `%s`' % qualified_name(type(data))
)
class Enumerated(Property):
"""
See `serial.properties.Property`...
+ Properties:
- values ([Any]): A list of possible values. If the parameter `types` is specified--typing is
applied prior to validation.
"""
def __init__(
self,
types=None, # type: Optional[Sequence[Union[type, Property]]]
values=None, # type: Optional[Union[Sequence, Set]]
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
self._values = None
super().__init__(
types=types,
name=name,
required=required,
versions=versions
)
self.values = values # type: Optional[Sequence]
@property
def values(self):
# type: () -> Optional[Union[Tuple, Callable]]
return self._values
@values.setter
def values(self, values):
# type: (Iterable) -> None
if not ((values is None) or callable(values)):
if (values is not None) and (not isinstance(values, (collections_abc.Sequence, collections_abc.Set))):
raise TypeError(
'`values` must be a finite set or sequence, not `%s`.' % qualified_name(type(values))
)
if values is not None:
values = [
serial.marshal.unmarshal(v, types=self.types)
for v in values
]
self._values = values
def unmarshal(self, data):
# type: (Any) -> Any
if self.types is not None:
data = serial.marshal.unmarshal(data, types=self.types)
if (
(data is not None) and
(self.values is not None) and
(data not in self.values)
):
raise ValueError(
'The value provided is not a valid option:\n%s\n\n' % repr(data) +
'Valid options include:\n%s' % (
', '.join(repr(t) for t in self.values)
)
)
return data
class Number(Property):
"""
See `serial.properties.Property`
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
# type: (...) -> None
super().__init__(
types=(numbers.Number,),
name=name,
required=required,
versions=versions,
)
class Integer(Property):
"""
See `serial.properties.Property`
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
super().__init__(
types=(int,),
name=name,
required=required,
versions=versions,
)
# def unmarshal(self, data):
# # type: (Any) -> Any
# if data is None:
# return data
# else:
# return int(data)
#
# def marshal(self, data):
# # type: (Any) -> Any
# if data is None:
# return data
# else:
# return int(data)
class Boolean(Property):
"""
See `serial.properties.Property`
"""
def __init__(
self,
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
# type: (...) -> None
super().__init__(
types=(bool,),
name=name,
required=required,
versions=versions,
)
class Array(Property):
"""
See `serial.properties.Property`...
+ Properties:
- item_types (type|Property|[type|Property]): The type(s) of values/objects contained in the array. Similar to
`serial.properties.Property().value_types`, but applied to items in the array, not the array itself.
"""
def __init__(
self,
item_types=None, # type: Optional[Union[type, Sequence[Union[type, Property]]]]
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
self._item_types = None
self.item_types = item_types
super().__init__(
types=(serial.model.Array,),
name=name,
required=required,
versions=versions,
)
def unmarshal(self, data):
# type: (Any) -> Any
return serial.marshal.unmarshal(data, types=self.types, item_types=self.item_types)
def marshal(self, data):
# type: (Any) -> Any
return serial.marshal.marshal(data, types=self.types, item_types=self.item_types)
@property
def item_types(self):
return self._item_types
@item_types.setter
def item_types(self, item_types):
# type: (Optional[Sequence[Union[type, Property, abc.model.Object]]]) -> None
if item_types is not None:
if callable(item_types):
if native_str is not str:
_item_types = item_types
def item_types(d):
# type: (Sequence[Union[type, Property, abc.model.Object]]) -> Types
return Types(self, _item_types(d))
else:
item_types = Types(self, item_types)
self._item_types = item_types
class Dictionary(Property):
"""
See `serial.properties.Property`...
+ Properties:
- value_types (type|Property|[type|Property]): The type(s) of values/objects comprising the mapped
values. Similar to `serial.properties.Property.types`, but applies to *values* in the dictionary
object, not the dictionary itself.
"""
def __init__(
self,
value_types=None, # type: Optional[Union[type, Sequence[Union[type, Property]]]]
name=None, # type: Optional[str]
required=False, # type: Union[bool, collections.Callable]
versions=None, # type: Optional[Collection]
):
self._value_types = None
self.value_types = value_types
super().__init__(
types=(serial.model.Dictionary,),
name=name,
required=required,
versions=versions,
)
def unmarshal(self, data):
# type: (Any) -> Any
return serial.marshal.unmarshal(data, types=self.types, value_types=self.value_types)
@property
def value_types(self):
return self._value_types
@value_types.setter
def value_types(self, value_types_):
# type: (Optional[Sequence[Union[type, Property, abc.model.Object]]]) -> None
"""
The `types` can be either:
- A sequence of types and/or `serial.properties.Property` instances.
- A function which accepts exactly one argument (a dictionary), and which returns a sequence of types and/or
`serial.properties.Property` instances.
If more than one type or property definition is provided, un-marshalling is attempted using each `value_type`,
in sequential order. If a value could be cast into more than one of the `types` without throwing a
`ValueError`, `TypeError`, or `serial.errors.ValidationError`, the value type occuring *first* in the sequence
will be used.
"""
if value_types_ is not None:
if callable(value_types_):
if native_str is not str:
original_value_types_ = value_types_
def value_types_(data):
# type: (Sequence[Union[type, Property, abc.model.Object]]) -> Types
return Types(self, original_value_types_(data))
else:
value_types_ = Types(self, value_types_)
self._value_types = value_types_