fields.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import re
  4. import fnmatch
  5. import inspect
  6. from calendar import timegm
  7. from datetime import date, datetime
  8. from decimal import Decimal, ROUND_HALF_EVEN
  9. from email.utils import formatdate
  10. from six import iteritems, itervalues, text_type, string_types
  11. from six.moves.urllib.parse import urlparse, urlunparse
  12. from flask import url_for, request
  13. from werkzeug.utils import cached_property
  14. from .inputs import (
  15. date_from_iso8601,
  16. datetime_from_iso8601,
  17. datetime_from_rfc822,
  18. boolean,
  19. )
  20. from .errors import RestError
  21. from .marshalling import marshal
  22. from .utils import camel_to_dash, not_none
  23. __all__ = (
  24. "Raw",
  25. "String",
  26. "FormattedString",
  27. "Url",
  28. "DateTime",
  29. "Date",
  30. "Boolean",
  31. "Integer",
  32. "Float",
  33. "Arbitrary",
  34. "Fixed",
  35. "Nested",
  36. "List",
  37. "ClassName",
  38. "Polymorph",
  39. "Wildcard",
  40. "StringMixin",
  41. "MinMaxMixin",
  42. "NumberMixin",
  43. "MarshallingError",
  44. )
  45. class MarshallingError(RestError):
  46. """
  47. This is an encapsulating Exception in case of marshalling error.
  48. """
  49. def __init__(self, underlying_exception):
  50. # just put the contextual representation of the error to hint on what
  51. # went wrong without exposing internals
  52. super(MarshallingError, self).__init__(text_type(underlying_exception))
  53. def is_indexable_but_not_string(obj):
  54. return not hasattr(obj, "strip") and hasattr(obj, "__iter__")
  55. def is_integer_indexable(obj):
  56. return isinstance(obj, list) or isinstance(obj, tuple)
  57. def get_value(key, obj, default=None):
  58. """Helper for pulling a keyed value off various types of objects"""
  59. if isinstance(key, int):
  60. return _get_value_for_key(key, obj, default)
  61. elif callable(key):
  62. return key(obj)
  63. else:
  64. return _get_value_for_keys(key.split("."), obj, default)
  65. def _get_value_for_keys(keys, obj, default):
  66. if len(keys) == 1:
  67. return _get_value_for_key(keys[0], obj, default)
  68. else:
  69. return _get_value_for_keys(
  70. keys[1:], _get_value_for_key(keys[0], obj, default), default
  71. )
  72. def _get_value_for_key(key, obj, default):
  73. if is_indexable_but_not_string(obj):
  74. try:
  75. return obj[key]
  76. except (IndexError, TypeError, KeyError):
  77. pass
  78. if is_integer_indexable(obj):
  79. try:
  80. return obj[int(key)]
  81. except (IndexError, TypeError, ValueError):
  82. pass
  83. return getattr(obj, key, default)
  84. def to_marshallable_type(obj):
  85. """
  86. Helper for converting an object to a dictionary only if it is not
  87. dictionary already or an indexable object nor a simple type
  88. """
  89. if obj is None:
  90. return None # make it idempotent for None
  91. if hasattr(obj, "__marshallable__"):
  92. return obj.__marshallable__()
  93. if hasattr(obj, "__getitem__"):
  94. return obj # it is indexable it is ok
  95. return dict(obj.__dict__)
  96. class Raw(object):
  97. """
  98. Raw provides a base field class from which others should extend. It
  99. applies no formatting by default, and should only be used in cases where
  100. data does not need to be formatted before being serialized. Fields should
  101. throw a :class:`MarshallingError` in case of parsing problem.
  102. :param default: The default value for the field, if no value is
  103. specified.
  104. :param attribute: If the public facing value differs from the internal
  105. value, use this to retrieve a different attribute from the response
  106. than the publicly named value.
  107. :param str title: The field title (for documentation purpose)
  108. :param str description: The field description (for documentation purpose)
  109. :param bool required: Is the field required ?
  110. :param bool readonly: Is the field read only ? (for documentation purpose)
  111. :param example: An optional data example (for documentation purpose)
  112. :param callable mask: An optional mask function to be applied to output
  113. """
  114. #: The JSON/Swagger schema type
  115. __schema_type__ = "object"
  116. #: The JSON/Swagger schema format
  117. __schema_format__ = None
  118. #: An optional JSON/Swagger schema example
  119. __schema_example__ = None
  120. def __init__(
  121. self,
  122. default=None,
  123. attribute=None,
  124. title=None,
  125. description=None,
  126. required=None,
  127. readonly=None,
  128. example=None,
  129. mask=None,
  130. **kwargs
  131. ):
  132. self.attribute = attribute
  133. self.default = default
  134. self.title = title
  135. self.description = description
  136. self.required = required
  137. self.readonly = readonly
  138. self.example = example if example is not None else self.__schema_example__
  139. self.mask = mask
  140. def format(self, value):
  141. """
  142. Formats a field's value. No-op by default - field classes that
  143. modify how the value of existing object keys should be presented should
  144. override this and apply the appropriate formatting.
  145. :param value: The value to format
  146. :raises MarshallingError: In case of formatting problem
  147. Ex::
  148. class TitleCase(Raw):
  149. def format(self, value):
  150. return unicode(value).title()
  151. """
  152. return value
  153. def output(self, key, obj, **kwargs):
  154. """
  155. Pulls the value for the given key from the object, applies the
  156. field's formatting and returns the result. If the key is not found
  157. in the object, returns the default value. Field classes that create
  158. values which do not require the existence of the key in the object
  159. should override this and return the desired value.
  160. :raises MarshallingError: In case of formatting problem
  161. """
  162. value = get_value(key if self.attribute is None else self.attribute, obj)
  163. if value is None:
  164. default = self._v("default")
  165. return self.format(default) if default else default
  166. try:
  167. data = self.format(value)
  168. except MarshallingError as e:
  169. msg = 'Unable to marshal field "{0}" value "{1}": {2}'.format(
  170. key, value, str(e)
  171. )
  172. raise MarshallingError(msg)
  173. return self.mask.apply(data) if self.mask else data
  174. def _v(self, key):
  175. """Helper for getting a value from attribute allowing callable"""
  176. value = getattr(self, key)
  177. return value() if callable(value) else value
  178. @cached_property
  179. def __schema__(self):
  180. return not_none(self.schema())
  181. def schema(self):
  182. return {
  183. "type": self.__schema_type__,
  184. "format": self.__schema_format__,
  185. "title": self.title,
  186. "description": self.description,
  187. "readOnly": self.readonly,
  188. "default": self._v("default"),
  189. "example": self.example,
  190. }
  191. class Nested(Raw):
  192. """
  193. Allows you to nest one set of fields inside another.
  194. See :ref:`nested-field` for more information
  195. :param dict model: The model dictionary to nest
  196. :param bool allow_null: Whether to return None instead of a dictionary
  197. with null keys, if a nested dictionary has all-null keys
  198. :param bool skip_none: Optional key will be used to eliminate inner fields
  199. which value is None or the inner field's key not
  200. exist in data
  201. :param kwargs: If ``default`` keyword argument is present, a nested
  202. dictionary will be marshaled as its value if nested dictionary is
  203. all-null keys (e.g. lets you return an empty JSON object instead of
  204. null)
  205. """
  206. __schema_type__ = None
  207. def __init__(
  208. self, model, allow_null=False, skip_none=False, as_list=False, **kwargs
  209. ):
  210. self.model = model
  211. self.as_list = as_list
  212. self.allow_null = allow_null
  213. self.skip_none = skip_none
  214. super(Nested, self).__init__(**kwargs)
  215. @property
  216. def nested(self):
  217. return getattr(self.model, "resolved", self.model)
  218. def output(self, key, obj, ordered=False, **kwargs):
  219. value = get_value(key if self.attribute is None else self.attribute, obj)
  220. if value is None:
  221. if self.allow_null:
  222. return None
  223. elif self.default is not None:
  224. return self.default
  225. return marshal(value, self.nested, skip_none=self.skip_none, ordered=ordered)
  226. def schema(self):
  227. schema = super(Nested, self).schema()
  228. ref = "#/definitions/{0}".format(self.nested.name)
  229. if self.as_list:
  230. schema["type"] = "array"
  231. schema["items"] = {"$ref": ref}
  232. elif any(schema.values()):
  233. # There is already some properties in the schema
  234. allOf = schema.get("allOf", [])
  235. allOf.append({"$ref": ref})
  236. schema["allOf"] = allOf
  237. else:
  238. schema["$ref"] = ref
  239. return schema
  240. def clone(self, mask=None):
  241. kwargs = self.__dict__.copy()
  242. model = kwargs.pop("model")
  243. if mask:
  244. model = mask.apply(model.resolved if hasattr(model, "resolved") else model)
  245. return self.__class__(model, **kwargs)
  246. class List(Raw):
  247. """
  248. Field for marshalling lists of other fields.
  249. See :ref:`list-field` for more information.
  250. :param cls_or_instance: The field type the list will contain.
  251. """
  252. def __init__(self, cls_or_instance, **kwargs):
  253. self.min_items = kwargs.pop("min_items", None)
  254. self.max_items = kwargs.pop("max_items", None)
  255. self.unique = kwargs.pop("unique", None)
  256. super(List, self).__init__(**kwargs)
  257. error_msg = "The type of the list elements must be a subclass of fields.Raw"
  258. if isinstance(cls_or_instance, type):
  259. if not issubclass(cls_or_instance, Raw):
  260. raise MarshallingError(error_msg)
  261. self.container = cls_or_instance()
  262. else:
  263. if not isinstance(cls_or_instance, Raw):
  264. raise MarshallingError(error_msg)
  265. self.container = cls_or_instance
  266. def format(self, value):
  267. # Convert all instances in typed list to container type
  268. if isinstance(value, set):
  269. value = list(value)
  270. is_nested = isinstance(self.container, Nested) or type(self.container) is Raw
  271. def is_attr(val):
  272. return self.container.attribute and hasattr(val, self.container.attribute)
  273. if value is None:
  274. return []
  275. return [
  276. self.container.output(
  277. idx,
  278. val
  279. if (isinstance(val, dict) or is_attr(val)) and not is_nested
  280. else value,
  281. )
  282. for idx, val in enumerate(value)
  283. ]
  284. def output(self, key, data, ordered=False, **kwargs):
  285. value = get_value(key if self.attribute is None else self.attribute, data)
  286. # we cannot really test for external dict behavior
  287. if is_indexable_but_not_string(value) and not isinstance(value, dict):
  288. return self.format(value)
  289. if value is None:
  290. return self._v("default")
  291. return [marshal(value, self.container.nested)]
  292. def schema(self):
  293. schema = super(List, self).schema()
  294. schema.update(
  295. minItems=self._v("min_items"),
  296. maxItems=self._v("max_items"),
  297. uniqueItems=self._v("unique"),
  298. )
  299. schema["type"] = "array"
  300. schema["items"] = self.container.__schema__
  301. return schema
  302. def clone(self, mask=None):
  303. kwargs = self.__dict__.copy()
  304. model = kwargs.pop("container")
  305. if mask:
  306. model = mask.apply(model)
  307. return self.__class__(model, **kwargs)
  308. class StringMixin(object):
  309. __schema_type__ = "string"
  310. def __init__(self, *args, **kwargs):
  311. self.min_length = kwargs.pop("min_length", None)
  312. self.max_length = kwargs.pop("max_length", None)
  313. self.pattern = kwargs.pop("pattern", None)
  314. super(StringMixin, self).__init__(*args, **kwargs)
  315. def schema(self):
  316. schema = super(StringMixin, self).schema()
  317. schema.update(
  318. minLength=self._v("min_length"),
  319. maxLength=self._v("max_length"),
  320. pattern=self._v("pattern"),
  321. )
  322. return schema
  323. class MinMaxMixin(object):
  324. def __init__(self, *args, **kwargs):
  325. self.minimum = kwargs.pop("min", None)
  326. self.exclusiveMinimum = kwargs.pop("exclusiveMin", None)
  327. self.maximum = kwargs.pop("max", None)
  328. self.exclusiveMaximum = kwargs.pop("exclusiveMax", None)
  329. super(MinMaxMixin, self).__init__(*args, **kwargs)
  330. def schema(self):
  331. schema = super(MinMaxMixin, self).schema()
  332. schema.update(
  333. minimum=self._v("minimum"),
  334. exclusiveMinimum=self._v("exclusiveMinimum"),
  335. maximum=self._v("maximum"),
  336. exclusiveMaximum=self._v("exclusiveMaximum"),
  337. )
  338. return schema
  339. class NumberMixin(MinMaxMixin):
  340. __schema_type__ = "number"
  341. def __init__(self, *args, **kwargs):
  342. self.multiple = kwargs.pop("multiple", None)
  343. super(NumberMixin, self).__init__(*args, **kwargs)
  344. def schema(self):
  345. schema = super(NumberMixin, self).schema()
  346. schema.update(multipleOf=self._v("multiple"))
  347. return schema
  348. class String(StringMixin, Raw):
  349. """
  350. Marshal a value as a string. Uses ``six.text_type`` so values will
  351. be converted to :class:`unicode` in python2 and :class:`str` in
  352. python3.
  353. """
  354. def __init__(self, *args, **kwargs):
  355. self.enum = kwargs.pop("enum", None)
  356. self.discriminator = kwargs.pop("discriminator", None)
  357. super(String, self).__init__(*args, **kwargs)
  358. self.required = self.discriminator or self.required
  359. def format(self, value):
  360. try:
  361. return text_type(value)
  362. except ValueError as ve:
  363. raise MarshallingError(ve)
  364. def schema(self):
  365. enum = self._v("enum")
  366. schema = super(String, self).schema()
  367. if enum:
  368. schema.update(enum=enum)
  369. if enum and schema["example"] is None:
  370. schema["example"] = enum[0]
  371. return schema
  372. class Integer(NumberMixin, Raw):
  373. """
  374. Field for outputting an integer value.
  375. :param int default: The default value for the field, if no value is specified.
  376. """
  377. __schema_type__ = "integer"
  378. def format(self, value):
  379. try:
  380. if value is None:
  381. return self.default
  382. return int(value)
  383. except (ValueError, TypeError) as ve:
  384. raise MarshallingError(ve)
  385. class Float(NumberMixin, Raw):
  386. """
  387. A double as IEEE-754 double precision.
  388. ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf -inf
  389. """
  390. def format(self, value):
  391. try:
  392. if value is None:
  393. return self.default
  394. return float(value)
  395. except (ValueError, TypeError) as ve:
  396. raise MarshallingError(ve)
  397. class Arbitrary(NumberMixin, Raw):
  398. """
  399. A floating point number with an arbitrary precision.
  400. ex: 634271127864378216478362784632784678324.23432
  401. """
  402. def format(self, value):
  403. return text_type(Decimal(value))
  404. ZERO = Decimal()
  405. class Fixed(NumberMixin, Raw):
  406. """
  407. A decimal number with a fixed precision.
  408. """
  409. def __init__(self, decimals=5, **kwargs):
  410. super(Fixed, self).__init__(**kwargs)
  411. self.precision = Decimal("0." + "0" * (decimals - 1) + "1")
  412. def format(self, value):
  413. dvalue = Decimal(value)
  414. if not dvalue.is_normal() and dvalue != ZERO:
  415. raise MarshallingError("Invalid Fixed precision number.")
  416. return text_type(dvalue.quantize(self.precision, rounding=ROUND_HALF_EVEN))
  417. class Boolean(Raw):
  418. """
  419. Field for outputting a boolean value.
  420. Empty collections such as ``""``, ``{}``, ``[]``, etc. will be converted to ``False``.
  421. """
  422. __schema_type__ = "boolean"
  423. def format(self, value):
  424. return boolean(value)
  425. class DateTime(MinMaxMixin, Raw):
  426. """
  427. Return a formatted datetime string in UTC. Supported formats are RFC 822 and ISO 8601.
  428. See :func:`email.utils.formatdate` for more info on the RFC 822 format.
  429. See :meth:`datetime.datetime.isoformat` for more info on the ISO 8601 format.
  430. :param str dt_format: ``rfc822`` or ``iso8601``
  431. """
  432. __schema_type__ = "string"
  433. __schema_format__ = "date-time"
  434. def __init__(self, dt_format="iso8601", **kwargs):
  435. super(DateTime, self).__init__(**kwargs)
  436. self.dt_format = dt_format
  437. def parse(self, value):
  438. if value is None:
  439. return None
  440. elif isinstance(value, string_types):
  441. parser = (
  442. datetime_from_iso8601
  443. if self.dt_format == "iso8601"
  444. else datetime_from_rfc822
  445. )
  446. return parser(value)
  447. elif isinstance(value, datetime):
  448. return value
  449. elif isinstance(value, date):
  450. return datetime(value.year, value.month, value.day)
  451. else:
  452. raise ValueError("Unsupported DateTime format")
  453. def format(self, value):
  454. try:
  455. value = self.parse(value)
  456. if self.dt_format == "iso8601":
  457. return self.format_iso8601(value)
  458. elif self.dt_format == "rfc822":
  459. return self.format_rfc822(value)
  460. else:
  461. raise MarshallingError("Unsupported date format %s" % self.dt_format)
  462. except (AttributeError, ValueError) as e:
  463. raise MarshallingError(e)
  464. def format_rfc822(self, dt):
  465. """
  466. Turn a datetime object into a formatted date.
  467. :param datetime dt: The datetime to transform
  468. :return: A RFC 822 formatted date string
  469. """
  470. return formatdate(timegm(dt.utctimetuple()))
  471. def format_iso8601(self, dt):
  472. """
  473. Turn a datetime object into an ISO8601 formatted date.
  474. :param datetime dt: The datetime to transform
  475. :return: A ISO 8601 formatted date string
  476. """
  477. return dt.isoformat()
  478. def _for_schema(self, name):
  479. value = self.parse(self._v(name))
  480. return self.format(value) if value else None
  481. def schema(self):
  482. schema = super(DateTime, self).schema()
  483. schema["default"] = self._for_schema("default")
  484. schema["minimum"] = self._for_schema("minimum")
  485. schema["maximum"] = self._for_schema("maximum")
  486. return schema
  487. class Date(DateTime):
  488. """
  489. Return a formatted date string in UTC in ISO 8601.
  490. See :meth:`datetime.date.isoformat` for more info on the ISO 8601 format.
  491. """
  492. __schema_format__ = "date"
  493. def __init__(self, **kwargs):
  494. kwargs.pop("dt_format", None)
  495. super(Date, self).__init__(dt_format="iso8601", **kwargs)
  496. def parse(self, value):
  497. if value is None:
  498. return None
  499. elif isinstance(value, string_types):
  500. return date_from_iso8601(value)
  501. elif isinstance(value, datetime):
  502. return value.date()
  503. elif isinstance(value, date):
  504. return value
  505. else:
  506. raise ValueError("Unsupported Date format")
  507. class Url(StringMixin, Raw):
  508. """
  509. A string representation of a Url
  510. :param str endpoint: Endpoint name. If endpoint is ``None``, ``request.endpoint`` is used instead
  511. :param bool absolute: If ``True``, ensures that the generated urls will have the hostname included
  512. :param str scheme: URL scheme specifier (e.g. ``http``, ``https``)
  513. """
  514. def __init__(self, endpoint=None, absolute=False, scheme=None, **kwargs):
  515. super(Url, self).__init__(**kwargs)
  516. self.endpoint = endpoint
  517. self.absolute = absolute
  518. self.scheme = scheme
  519. def output(self, key, obj, **kwargs):
  520. try:
  521. data = to_marshallable_type(obj)
  522. endpoint = self.endpoint if self.endpoint is not None else request.endpoint
  523. o = urlparse(url_for(endpoint, _external=self.absolute, **data))
  524. if self.absolute:
  525. scheme = self.scheme if self.scheme is not None else o.scheme
  526. return urlunparse((scheme, o.netloc, o.path, "", "", ""))
  527. return urlunparse(("", "", o.path, "", "", ""))
  528. except TypeError as te:
  529. raise MarshallingError(te)
  530. class FormattedString(StringMixin, Raw):
  531. """
  532. FormattedString is used to interpolate other values from
  533. the response into this field. The syntax for the source string is
  534. the same as the string :meth:`~str.format` method from the python
  535. stdlib.
  536. Ex::
  537. fields = {
  538. 'name': fields.String,
  539. 'greeting': fields.FormattedString("Hello {name}")
  540. }
  541. data = {
  542. 'name': 'Doug',
  543. }
  544. marshal(data, fields)
  545. :param str src_str: the string to format with the other values from the response.
  546. """
  547. def __init__(self, src_str, **kwargs):
  548. super(FormattedString, self).__init__(**kwargs)
  549. self.src_str = text_type(src_str)
  550. def output(self, key, obj, **kwargs):
  551. try:
  552. data = to_marshallable_type(obj)
  553. return self.src_str.format(**data)
  554. except (TypeError, IndexError) as error:
  555. raise MarshallingError(error)
  556. class ClassName(String):
  557. """
  558. Return the serialized object class name as string.
  559. :param bool dash: If `True`, transform CamelCase to kebab_case.
  560. """
  561. def __init__(self, dash=False, **kwargs):
  562. super(ClassName, self).__init__(**kwargs)
  563. self.dash = dash
  564. def output(self, key, obj, **kwargs):
  565. classname = obj.__class__.__name__
  566. if classname == "dict":
  567. return "object"
  568. return camel_to_dash(classname) if self.dash else classname
  569. class Polymorph(Nested):
  570. """
  571. A Nested field handling inheritance.
  572. Allows you to specify a mapping between Python classes and fields specifications.
  573. .. code-block:: python
  574. mapping = {
  575. Child1: child1_fields,
  576. Child2: child2_fields,
  577. }
  578. fields = api.model('Thing', {
  579. owner: fields.Polymorph(mapping)
  580. })
  581. :param dict mapping: Maps classes to their model/fields representation
  582. """
  583. def __init__(self, mapping, required=False, **kwargs):
  584. self.mapping = mapping
  585. parent = self.resolve_ancestor(list(itervalues(mapping)))
  586. super(Polymorph, self).__init__(parent, allow_null=not required, **kwargs)
  587. def output(self, key, obj, ordered=False, **kwargs):
  588. # Copied from upstream NestedField
  589. value = get_value(key if self.attribute is None else self.attribute, obj)
  590. if value is None:
  591. if self.allow_null:
  592. return None
  593. elif self.default is not None:
  594. return self.default
  595. # Handle mappings
  596. if not hasattr(value, "__class__"):
  597. raise ValueError("Polymorph field only accept class instances")
  598. candidates = [
  599. fields for cls, fields in iteritems(self.mapping) if type(value) == cls
  600. ]
  601. if len(candidates) <= 0:
  602. raise ValueError("Unknown class: " + value.__class__.__name__)
  603. elif len(candidates) > 1:
  604. raise ValueError(
  605. "Unable to determine a candidate for: " + value.__class__.__name__
  606. )
  607. else:
  608. return marshal(
  609. value, candidates[0].resolved, mask=self.mask, ordered=ordered
  610. )
  611. def resolve_ancestor(self, models):
  612. """
  613. Resolve the common ancestor for all models.
  614. Assume there is only one common ancestor.
  615. """
  616. ancestors = [m.ancestors for m in models]
  617. candidates = set.intersection(*ancestors)
  618. if len(candidates) != 1:
  619. field_names = [f.name for f in models]
  620. raise ValueError(
  621. "Unable to determine the common ancestor for: " + ", ".join(field_names)
  622. )
  623. parent_name = candidates.pop()
  624. return models[0].get_parent(parent_name)
  625. def clone(self, mask=None):
  626. data = self.__dict__.copy()
  627. mapping = data.pop("mapping")
  628. for field in ("allow_null", "model"):
  629. data.pop(field, None)
  630. data["mask"] = mask
  631. return Polymorph(mapping, **data)
  632. class Wildcard(Raw):
  633. """
  634. Field for marshalling list of "unkown" fields.
  635. :param cls_or_instance: The field type the list will contain.
  636. """
  637. exclude = set()
  638. # cache the flat object
  639. _flat = None
  640. _obj = None
  641. _cache = set()
  642. _last = None
  643. def __init__(self, cls_or_instance, **kwargs):
  644. super(Wildcard, self).__init__(**kwargs)
  645. error_msg = "The type of the wildcard elements must be a subclass of fields.Raw"
  646. if isinstance(cls_or_instance, type):
  647. if not issubclass(cls_or_instance, Raw):
  648. raise MarshallingError(error_msg)
  649. self.container = cls_or_instance()
  650. else:
  651. if not isinstance(cls_or_instance, Raw):
  652. raise MarshallingError(error_msg)
  653. self.container = cls_or_instance
  654. def _flatten(self, obj):
  655. if obj is None:
  656. return None
  657. if obj == self._obj and self._flat is not None:
  658. return self._flat
  659. if isinstance(obj, dict):
  660. self._flat = [x for x in iteritems(obj)]
  661. else:
  662. def __match_attributes(attribute):
  663. attr_name, attr_obj = attribute
  664. if inspect.isroutine(attr_obj) or (
  665. attr_name.startswith("__") and attr_name.endswith("__")
  666. ):
  667. return False
  668. return True
  669. attributes = inspect.getmembers(obj)
  670. self._flat = [x for x in attributes if __match_attributes(x)]
  671. self._cache = set()
  672. self._obj = obj
  673. return self._flat
  674. @property
  675. def key(self):
  676. return self._last
  677. def reset(self):
  678. self.exclude = set()
  679. self._flat = None
  680. self._obj = None
  681. self._cache = set()
  682. self._last = None
  683. def output(self, key, obj, ordered=False):
  684. value = None
  685. reg = fnmatch.translate(key)
  686. if self._flatten(obj):
  687. while True:
  688. try:
  689. # we are using pop() so that we don't
  690. # loop over the whole object every time dropping the
  691. # complexity to O(n)
  692. if ordered:
  693. # Get first element if respecting order
  694. (objkey, val) = self._flat.pop(0)
  695. else:
  696. # Previous default retained
  697. (objkey, val) = self._flat.pop()
  698. if (
  699. objkey not in self._cache
  700. and objkey not in self.exclude
  701. and re.match(reg, objkey, re.IGNORECASE)
  702. ):
  703. value = val
  704. self._cache.add(objkey)
  705. self._last = objkey
  706. break
  707. except IndexError:
  708. break
  709. if value is None:
  710. if self.default is not None:
  711. return self.container.format(self.default)
  712. return None
  713. if isinstance(self.container, Nested):
  714. return marshal(
  715. value,
  716. self.container.nested,
  717. skip_none=self.container.skip_none,
  718. ordered=ordered,
  719. )
  720. return self.container.format(value)
  721. def schema(self):
  722. schema = super(Wildcard, self).schema()
  723. schema["type"] = "object"
  724. schema["additionalProperties"] = self.container.__schema__
  725. return schema
  726. def clone(self):
  727. kwargs = self.__dict__.copy()
  728. model = kwargs.pop("container")
  729. return self.__class__(model, **kwargs)