Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/flask_restx/fields.py: 26%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import re
2import fnmatch
3import inspect
5from calendar import timegm
6from datetime import date, datetime
7from decimal import Decimal, ROUND_HALF_EVEN
8from email.utils import formatdate
10from urllib.parse import urlparse, urlunparse
12from flask import url_for, request
13from werkzeug.utils import cached_property
15from .inputs import (
16 date_from_iso8601,
17 datetime_from_iso8601,
18 datetime_from_rfc822,
19 boolean,
20)
21from .errors import RestError
22from .marshalling import marshal
23from .utils import camel_to_dash, not_none
25__all__ = (
26 "Raw",
27 "String",
28 "FormattedString",
29 "Url",
30 "DateTime",
31 "Date",
32 "Boolean",
33 "Integer",
34 "Float",
35 "Arbitrary",
36 "Fixed",
37 "Nested",
38 "List",
39 "ClassName",
40 "Polymorph",
41 "Wildcard",
42 "StringMixin",
43 "MinMaxMixin",
44 "NumberMixin",
45 "MarshallingError",
46)
49class MarshallingError(RestError):
50 """
51 This is an encapsulating Exception in case of marshalling error.
52 """
54 def __init__(self, underlying_exception):
55 # just put the contextual representation of the error to hint on what
56 # went wrong without exposing internals
57 super(MarshallingError, self).__init__(str(underlying_exception))
60def is_indexable_but_not_string(obj):
61 return not hasattr(obj, "strip") and hasattr(obj, "__iter__")
64def is_integer_indexable(obj):
65 return isinstance(obj, list) or isinstance(obj, tuple)
68def get_value(key, obj, default=None):
69 """Helper for pulling a keyed value off various types of objects"""
70 if isinstance(key, int):
71 return _get_value_for_key(key, obj, default)
72 elif callable(key):
73 return key(obj)
74 else:
75 return _get_value_for_keys(key.split("."), obj, default)
78def _get_value_for_keys(keys, obj, default):
79 if len(keys) == 1:
80 return _get_value_for_key(keys[0], obj, default)
81 else:
82 return _get_value_for_keys(
83 keys[1:], _get_value_for_key(keys[0], obj, default), default
84 )
87def _get_value_for_key(key, obj, default):
88 if is_indexable_but_not_string(obj):
89 try:
90 return obj[key]
91 except (IndexError, TypeError, KeyError):
92 pass
93 if is_integer_indexable(obj):
94 try:
95 return obj[int(key)]
96 except (IndexError, TypeError, ValueError):
97 pass
98 return getattr(obj, key, default)
101def to_marshallable_type(obj):
102 """
103 Helper for converting an object to a dictionary only if it is not
104 dictionary already or an indexable object nor a simple type
105 """
106 if obj is None:
107 return None # make it idempotent for None
109 if hasattr(obj, "__marshallable__"):
110 return obj.__marshallable__()
112 if hasattr(obj, "__getitem__"):
113 return obj # it is indexable it is ok
115 return dict(obj.__dict__)
118class Raw(object):
119 """
120 Raw provides a base field class from which others should extend. It
121 applies no formatting by default, and should only be used in cases where
122 data does not need to be formatted before being serialized. Fields should
123 throw a :class:`MarshallingError` in case of parsing problem.
125 :param default: The default value for the field, if no value is
126 specified.
127 :param attribute: If the public facing value differs from the internal
128 value, use this to retrieve a different attribute from the response
129 than the publicly named value.
130 :param str title: The field title (for documentation purpose)
131 :param str description: The field description (for documentation purpose)
132 :param bool required: Is the field required ?
133 :param bool readonly: Is the field read only ? (for documentation purpose)
134 :param example: An optional data example (for documentation purpose)
135 :param callable mask: An optional mask function to be applied to output
136 :param bool nullable: Whether the field accepts null values in input
137 validation. When True, the generated JSON Schema will allow null
138 values for this field during request payload validation.
139 """
141 #: The JSON/Swagger schema type
142 __schema_type__ = "object"
143 #: The JSON/Swagger schema format
144 __schema_format__ = None
145 #: An optional JSON/Swagger schema example
146 __schema_example__ = None
148 def __init__(
149 self,
150 default=None,
151 attribute=None,
152 title=None,
153 description=None,
154 required=None,
155 readonly=None,
156 example=None,
157 mask=None,
158 nullable=None,
159 **kwargs
160 ):
161 self.attribute = attribute
162 self.default = default
163 self.title = title
164 self.description = description
165 self.required = required
166 self.readonly = readonly
167 self.example = example if example is not None else self.__schema_example__
168 self.mask = mask
169 self.nullable = nullable
171 def format(self, value):
172 """
173 Formats a field's value. No-op by default - field classes that
174 modify how the value of existing object keys should be presented should
175 override this and apply the appropriate formatting.
177 :param value: The value to format
178 :raises MarshallingError: In case of formatting problem
180 Ex::
182 class TitleCase(Raw):
183 def format(self, value):
184 return unicode(value).title()
185 """
186 return value
188 def output(self, key, obj, **kwargs):
189 """
190 Pulls the value for the given key from the object, applies the
191 field's formatting and returns the result. If the key is not found
192 in the object, returns the default value. Field classes that create
193 values which do not require the existence of the key in the object
194 should override this and return the desired value.
196 :raises MarshallingError: In case of formatting problem
197 """
199 value = get_value(key if self.attribute is None else self.attribute, obj)
201 if value is None:
202 default = self._v("default")
203 return self.format(default) if default else default
205 try:
206 data = self.format(value)
207 except MarshallingError as e:
208 msg = 'Unable to marshal field "{0}" value "{1}": {2}'.format(
209 key, value, str(e)
210 )
211 raise MarshallingError(msg)
212 return self.mask.apply(data) if self.mask else data
214 def _v(self, key):
215 """Helper for getting a value from attribute allowing callable"""
216 value = getattr(self, key)
217 return value() if callable(value) else value
219 @cached_property
220 def __schema__(self):
221 return not_none(self.schema())
223 def schema(self):
224 return {
225 "type": self.__schema_type__,
226 "format": self.__schema_format__,
227 "title": self.title,
228 "description": self.description,
229 "readOnly": self.readonly,
230 "default": self._v("default"),
231 "example": self.example,
232 }
235class Nested(Raw):
236 """
237 Allows you to nest one set of fields inside another.
238 See :ref:`nested-field` for more information
240 :param dict model: The model dictionary to nest
241 :param bool allow_null: Whether to return None instead of a dictionary
242 with null keys, if a nested dictionary has all-null keys
243 :param bool skip_none: Optional key will be used to eliminate inner fields
244 which value is None or the inner field's key not
245 exist in data
246 :param kwargs: If ``default`` keyword argument is present, a nested
247 dictionary will be marshaled as its value if nested dictionary is
248 all-null keys (e.g. lets you return an empty JSON object instead of
249 null)
250 """
252 __schema_type__ = None
254 def __init__(
255 self, model, allow_null=False, skip_none=False, as_list=False, **kwargs
256 ):
257 self.model = model
258 self.as_list = as_list
259 self.allow_null = allow_null
260 self.skip_none = skip_none
261 super(Nested, self).__init__(**kwargs)
263 @property
264 def nested(self):
265 return getattr(self.model, "resolved", self.model)
267 def output(self, key, obj, ordered=False, **kwargs):
268 value = get_value(key if self.attribute is None else self.attribute, obj)
269 if value is None:
270 if self.allow_null:
271 return None
272 elif self.default is not None:
273 return self.default
275 return marshal(value, self.nested, skip_none=self.skip_none, ordered=ordered)
277 def schema(self):
278 schema = super(Nested, self).schema()
279 ref = "#/definitions/{0}".format(self.nested.name)
281 if self.as_list:
282 schema["type"] = "array"
283 schema["items"] = {"$ref": ref}
284 elif any(schema.values()):
285 # There is already some properties in the schema
286 allOf = schema.get("allOf", [])
287 allOf.append({"$ref": ref})
288 schema["allOf"] = allOf
289 else:
290 schema["$ref"] = ref
292 # If nullable is True, wrap using anyOf to permit nulls for input validation
293 if self.nullable:
294 # Remove structural keys that conflict with anyOf composition
295 for key in ("$ref", "allOf", "type", "items"):
296 schema.pop(key, None)
297 # Create anyOf with the original schema and null type
298 anyOf = [{"$ref": ref}]
299 if self.as_list:
300 anyOf = [{"type": "array", "items": {"$ref": ref}}]
301 anyOf.append({"type": "null"})
302 schema["anyOf"] = anyOf
304 return schema
306 def clone(self, mask=None):
307 kwargs = self.__dict__.copy()
308 model = kwargs.pop("model")
309 if mask:
310 model = mask.apply(model.resolved if hasattr(model, "resolved") else model)
311 return self.__class__(model, **kwargs)
314class List(Raw):
315 """
316 Field for marshalling lists of other fields.
318 See :ref:`list-field` for more information.
320 :param cls_or_instance: The field type the list will contain.
321 """
323 def __init__(self, cls_or_instance, **kwargs):
324 self.min_items = kwargs.pop("min_items", None)
325 self.max_items = kwargs.pop("max_items", None)
326 self.unique = kwargs.pop("unique", None)
327 super(List, self).__init__(**kwargs)
328 error_msg = "The type of the list elements must be a subclass of fields.Raw"
329 if isinstance(cls_or_instance, type):
330 if not issubclass(cls_or_instance, Raw):
331 raise MarshallingError(error_msg)
332 self.container = cls_or_instance()
333 else:
334 if not isinstance(cls_or_instance, Raw):
335 raise MarshallingError(error_msg)
336 self.container = cls_or_instance
338 def format(self, value):
339 # Convert all instances in typed list to container type
340 if isinstance(value, set):
341 value = list(value)
343 is_nested = isinstance(self.container, Nested) or type(self.container) is Raw
345 def is_attr(val):
346 return self.container.attribute and hasattr(val, self.container.attribute)
348 if value is None:
349 return []
350 return [
351 self.container.output(
352 idx,
353 (
354 val
355 if (isinstance(val, dict) or is_attr(val)) and not is_nested
356 else value
357 ),
358 )
359 for idx, val in enumerate(value)
360 ]
362 def output(self, key, data, ordered=False, **kwargs):
363 value = get_value(key if self.attribute is None else self.attribute, data)
364 # we cannot really test for external dict behavior
365 if is_indexable_but_not_string(value) and not isinstance(value, dict):
366 return self.format(value)
368 if value is None:
369 return self._v("default")
371 return [marshal(value, self.container.nested)]
373 def schema(self):
374 schema = super(List, self).schema()
375 schema.update(
376 minItems=self._v("min_items"),
377 maxItems=self._v("max_items"),
378 uniqueItems=self._v("unique"),
379 )
380 schema["type"] = "array"
381 schema["items"] = self.container.__schema__
382 return schema
384 def clone(self, mask=None):
385 kwargs = self.__dict__.copy()
386 model = kwargs.pop("container")
387 if mask:
388 model = mask.apply(model)
389 return self.__class__(model, **kwargs)
392class StringMixin(object):
393 __schema_type__ = "string"
395 def __init__(self, *args, **kwargs):
396 self.min_length = kwargs.pop("min_length", None)
397 self.max_length = kwargs.pop("max_length", None)
398 self.pattern = kwargs.pop("pattern", None)
399 super(StringMixin, self).__init__(*args, **kwargs)
401 def schema(self):
402 schema = super(StringMixin, self).schema()
403 schema.update(
404 minLength=self._v("min_length"),
405 maxLength=self._v("max_length"),
406 pattern=self._v("pattern"),
407 )
408 return schema
411class MinMaxMixin(object):
412 def __init__(self, *args, **kwargs):
413 self.minimum = kwargs.pop("min", None)
414 self.exclusiveMinimum = kwargs.pop("exclusiveMin", None)
415 self.maximum = kwargs.pop("max", None)
416 self.exclusiveMaximum = kwargs.pop("exclusiveMax", None)
417 super(MinMaxMixin, self).__init__(*args, **kwargs)
419 def schema(self):
420 schema = super(MinMaxMixin, self).schema()
421 schema.update(
422 minimum=self._v("minimum"),
423 exclusiveMinimum=self._v("exclusiveMinimum"),
424 maximum=self._v("maximum"),
425 exclusiveMaximum=self._v("exclusiveMaximum"),
426 )
427 return schema
430class NumberMixin(MinMaxMixin):
431 __schema_type__ = "number"
433 def __init__(self, *args, **kwargs):
434 self.multiple = kwargs.pop("multiple", None)
435 super(NumberMixin, self).__init__(*args, **kwargs)
437 def schema(self):
438 schema = super(NumberMixin, self).schema()
439 schema.update(multipleOf=self._v("multiple"))
440 return schema
443class String(StringMixin, Raw):
444 """
445 Marshal a value as a string.
446 """
448 def __init__(self, *args, **kwargs):
449 self.enum = kwargs.pop("enum", None)
450 self.discriminator = kwargs.pop("discriminator", None)
451 super(String, self).__init__(*args, **kwargs)
452 self.required = self.discriminator or self.required
454 def format(self, value):
455 try:
456 return str(value)
457 except ValueError as ve:
458 raise MarshallingError(ve)
460 def schema(self):
461 enum = self._v("enum")
462 schema = super(String, self).schema()
463 if enum:
464 schema.update(enum=enum)
465 if enum and schema["example"] is None:
466 schema["example"] = enum[0]
467 return schema
470class Integer(NumberMixin, Raw):
471 """
472 Field for outputting an integer value.
474 :param int default: The default value for the field, if no value is specified.
475 """
477 __schema_type__ = "integer"
479 def format(self, value):
480 try:
481 if value is None:
482 return self.default
483 return int(value)
484 except (ValueError, TypeError) as ve:
485 raise MarshallingError(ve)
488class Float(NumberMixin, Raw):
489 """
490 A double as IEEE-754 double precision.
492 ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf -inf
493 """
495 def format(self, value):
496 try:
497 if value is None:
498 return self.default
499 return float(value)
500 except (ValueError, TypeError) as ve:
501 raise MarshallingError(ve)
504class Arbitrary(NumberMixin, Raw):
505 """
506 A floating point number with an arbitrary precision.
508 ex: 634271127864378216478362784632784678324.23432
509 """
511 def format(self, value):
512 return str(Decimal(value))
515ZERO = Decimal()
518class Fixed(NumberMixin, Raw):
519 """
520 A decimal number with a fixed precision.
521 """
523 def __init__(self, decimals=5, **kwargs):
524 super(Fixed, self).__init__(**kwargs)
525 self.precision = Decimal("0." + "0" * (decimals - 1) + "1")
527 def format(self, value):
528 dvalue = Decimal(value)
529 if not dvalue.is_normal() and dvalue != ZERO:
530 raise MarshallingError("Invalid Fixed precision number.")
531 return str(dvalue.quantize(self.precision, rounding=ROUND_HALF_EVEN))
534class Boolean(Raw):
535 """
536 Field for outputting a boolean value.
538 Empty collections such as ``""``, ``{}``, ``[]``, etc. will be converted to ``False``.
539 """
541 __schema_type__ = "boolean"
543 def format(self, value):
544 return boolean(value)
547class DateTime(MinMaxMixin, Raw):
548 """
549 Return a formatted datetime string in UTC. Supported formats are RFC 822 and ISO 8601.
551 See :func:`email.utils.formatdate` for more info on the RFC 822 format.
553 See :meth:`datetime.datetime.isoformat` for more info on the ISO 8601 format.
555 :param str dt_format: ``rfc822`` or ``iso8601``
556 """
558 __schema_type__ = "string"
559 __schema_format__ = "date-time"
561 def __init__(self, dt_format="iso8601", **kwargs):
562 super(DateTime, self).__init__(**kwargs)
563 self.dt_format = dt_format
565 def parse(self, value):
566 if value is None:
567 return None
568 elif isinstance(value, str):
569 parser = (
570 datetime_from_iso8601
571 if self.dt_format == "iso8601"
572 else datetime_from_rfc822
573 )
574 return parser(value)
575 elif isinstance(value, datetime):
576 return value
577 elif isinstance(value, date):
578 return datetime(value.year, value.month, value.day)
579 else:
580 raise ValueError("Unsupported DateTime format")
582 def format(self, value):
583 try:
584 value = self.parse(value)
585 if self.dt_format == "iso8601":
586 return self.format_iso8601(value)
587 elif self.dt_format == "rfc822":
588 return self.format_rfc822(value)
589 else:
590 raise MarshallingError("Unsupported date format %s" % self.dt_format)
591 except (AttributeError, ValueError) as e:
592 raise MarshallingError(e)
594 def format_rfc822(self, dt):
595 """
596 Turn a datetime object into a formatted date.
598 :param datetime dt: The datetime to transform
599 :return: A RFC 822 formatted date string
600 """
601 return formatdate(timegm(dt.utctimetuple()))
603 def format_iso8601(self, dt):
604 """
605 Turn a datetime object into an ISO8601 formatted date.
607 :param datetime dt: The datetime to transform
608 :return: A ISO 8601 formatted date string
609 """
610 return dt.isoformat()
612 def _for_schema(self, name):
613 value = self.parse(self._v(name))
614 return self.format(value) if value else None
616 def schema(self):
617 schema = super(DateTime, self).schema()
618 schema["default"] = self._for_schema("default")
619 schema["minimum"] = self._for_schema("minimum")
620 schema["maximum"] = self._for_schema("maximum")
621 return schema
624class Date(DateTime):
625 """
626 Return a formatted date string in UTC in ISO 8601.
628 See :meth:`datetime.date.isoformat` for more info on the ISO 8601 format.
629 """
631 __schema_format__ = "date"
633 def __init__(self, **kwargs):
634 kwargs.pop("dt_format", None)
635 super(Date, self).__init__(dt_format="iso8601", **kwargs)
637 def parse(self, value):
638 if value is None:
639 return None
640 elif isinstance(value, str):
641 return date_from_iso8601(value)
642 elif isinstance(value, datetime):
643 return value.date()
644 elif isinstance(value, date):
645 return value
646 else:
647 raise ValueError("Unsupported Date format")
650class Url(StringMixin, Raw):
651 """
652 A string representation of a Url
654 :param str endpoint: Endpoint name. If endpoint is ``None``, ``request.endpoint`` is used instead
655 :param bool absolute: If ``True``, ensures that the generated urls will have the hostname included
656 :param str scheme: URL scheme specifier (e.g. ``http``, ``https``)
657 """
659 def __init__(self, endpoint=None, absolute=False, scheme=None, **kwargs):
660 super(Url, self).__init__(**kwargs)
661 self.endpoint = endpoint
662 self.absolute = absolute
663 self.scheme = scheme
665 def output(self, key, obj, **kwargs):
666 try:
667 data = to_marshallable_type(obj)
668 endpoint = self.endpoint if self.endpoint is not None else request.endpoint
669 o = urlparse(url_for(endpoint, _external=self.absolute, **data))
670 if self.absolute:
671 scheme = self.scheme if self.scheme is not None else o.scheme
672 return urlunparse((scheme, o.netloc, o.path, "", "", ""))
673 return urlunparse(("", "", o.path, "", "", ""))
674 except TypeError as te:
675 raise MarshallingError(te)
678class FormattedString(StringMixin, Raw):
679 """
680 FormattedString is used to interpolate other values from
681 the response into this field. The syntax for the source string is
682 the same as the string :meth:`~str.format` method from the python
683 stdlib.
685 Ex::
687 fields = {
688 'name': fields.String,
689 'greeting': fields.FormattedString("Hello {name}")
690 }
691 data = {
692 'name': 'Doug',
693 }
694 marshal(data, fields)
696 :param str src_str: the string to format with the other values from the response.
697 """
699 def __init__(self, src_str, **kwargs):
700 super(FormattedString, self).__init__(**kwargs)
701 self.src_str = str(src_str)
703 def output(self, key, obj, **kwargs):
704 try:
705 data = to_marshallable_type(obj)
706 return self.src_str.format(**data)
707 except (TypeError, IndexError) as error:
708 raise MarshallingError(error)
711class ClassName(String):
712 """
713 Return the serialized object class name as string.
715 :param bool dash: If `True`, transform CamelCase to kebab_case.
716 """
718 def __init__(self, dash=False, **kwargs):
719 super(ClassName, self).__init__(**kwargs)
720 self.dash = dash
722 def output(self, key, obj, **kwargs):
723 classname = obj.__class__.__name__
724 if classname == "dict":
725 return "object"
726 return camel_to_dash(classname) if self.dash else classname
729class Polymorph(Nested):
730 """
731 A Nested field handling inheritance.
733 Allows you to specify a mapping between Python classes and fields specifications.
735 .. code-block:: python
737 mapping = {
738 Child1: child1_fields,
739 Child2: child2_fields,
740 }
742 fields = api.model('Thing', {
743 owner: fields.Polymorph(mapping)
744 })
746 :param dict mapping: Maps classes to their model/fields representation
747 """
749 def __init__(self, mapping, required=False, **kwargs):
750 self.mapping = mapping
751 parent = self.resolve_ancestor(list(mapping.values()))
752 super(Polymorph, self).__init__(parent, allow_null=not required, **kwargs)
754 def output(self, key, obj, ordered=False, **kwargs):
755 # Copied from upstream NestedField
756 value = get_value(key if self.attribute is None else self.attribute, obj)
757 if value is None:
758 if self.allow_null:
759 return None
760 elif self.default is not None:
761 return self.default
763 # Handle mappings
764 if not hasattr(value, "__class__"):
765 raise ValueError("Polymorph field only accept class instances")
767 candidates = [
768 fields for cls, fields in self.mapping.items() if type(value) == cls
769 ]
771 if len(candidates) <= 0:
772 raise ValueError("Unknown class: " + value.__class__.__name__)
773 elif len(candidates) > 1:
774 raise ValueError(
775 "Unable to determine a candidate for: " + value.__class__.__name__
776 )
777 else:
778 return marshal(
779 value, candidates[0].resolved, mask=self.mask, ordered=ordered
780 )
782 def resolve_ancestor(self, models):
783 """
784 Resolve the common ancestor for all models.
786 Assume there is only one common ancestor.
787 """
788 ancestors = [m.ancestors for m in models]
789 candidates = set.intersection(*ancestors)
790 if len(candidates) != 1:
791 field_names = [f.name for f in models]
792 raise ValueError(
793 "Unable to determine the common ancestor for: " + ", ".join(field_names)
794 )
796 parent_name = candidates.pop()
797 return models[0].get_parent(parent_name)
799 def clone(self, mask=None):
800 data = self.__dict__.copy()
801 mapping = data.pop("mapping")
802 for field in ("allow_null", "model"):
803 data.pop(field, None)
805 data["mask"] = mask
806 return Polymorph(mapping, **data)
809class Wildcard(Raw):
810 """
811 Field for marshalling list of "unkown" fields.
813 :param cls_or_instance: The field type the list will contain.
814 """
816 exclude = set()
817 # cache the flat object
818 _flat = None
819 _obj = None
820 _cache = set()
821 _last = None
823 def __init__(self, cls_or_instance, **kwargs):
824 super(Wildcard, self).__init__(**kwargs)
825 error_msg = "The type of the wildcard elements must be a subclass of fields.Raw"
826 if isinstance(cls_or_instance, type):
827 if not issubclass(cls_or_instance, Raw):
828 raise MarshallingError(error_msg)
829 self.container = cls_or_instance()
830 else:
831 if not isinstance(cls_or_instance, Raw):
832 raise MarshallingError(error_msg)
833 self.container = cls_or_instance
835 def _flatten(self, obj):
836 if obj is None:
837 return None
838 if obj == self._obj and self._flat is not None:
839 return self._flat
840 if isinstance(obj, dict):
841 self._flat = [x for x in obj.items()]
842 else:
844 def __match_attributes(attribute):
845 attr_name, attr_obj = attribute
846 if inspect.isroutine(attr_obj) or (
847 attr_name.startswith("__") and attr_name.endswith("__")
848 ):
849 return False
850 return True
852 attributes = inspect.getmembers(obj)
853 self._flat = [x for x in attributes if __match_attributes(x)]
855 self._cache = set()
856 self._obj = obj
857 return self._flat
859 @property
860 def key(self):
861 return self._last
863 def reset(self):
864 self.exclude = set()
865 self._flat = None
866 self._obj = None
867 self._cache = set()
868 self._last = None
870 def output(self, key, obj, ordered=False):
871 value = None
872 reg = fnmatch.translate(key)
874 if self._flatten(obj):
875 while True:
876 try:
877 # we are using pop() so that we don't
878 # loop over the whole object every time dropping the
879 # complexity to O(n)
880 if ordered:
881 # Get first element if respecting order
882 objkey, val = self._flat.pop(0)
883 else:
884 # Previous default retained
885 objkey, val = self._flat.pop()
886 if (
887 objkey not in self._cache
888 and objkey not in self.exclude
889 and re.match(reg, objkey, re.IGNORECASE)
890 ):
891 value = val
892 self._cache.add(objkey)
893 self._last = objkey
894 break
895 except IndexError:
896 break
898 if value is None:
899 if self.default is not None:
900 return self.container.format(self.default)
901 return None
903 if isinstance(self.container, Nested):
904 return marshal(
905 value,
906 self.container.nested,
907 skip_none=self.container.skip_none,
908 ordered=ordered,
909 )
910 return self.container.format(value)
912 def schema(self):
913 schema = super(Wildcard, self).schema()
914 schema["type"] = "object"
915 schema["additionalProperties"] = self.container.__schema__
916 return schema
918 def clone(self):
919 kwargs = self.__dict__.copy()
920 model = kwargs.pop("container")
921 return self.__class__(model, **kwargs)