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