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

458 statements  

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 :param bool nullable: Whether the field accepts null values in input 

138 validation. When True, the generated JSON Schema will allow null 

139 values for this field during request payload validation. 

140 """ 

141 

142 #: The JSON/Swagger schema type 

143 __schema_type__ = "object" 

144 #: The JSON/Swagger schema format 

145 __schema_format__ = None 

146 #: An optional JSON/Swagger schema example 

147 __schema_example__ = None 

148 

149 def __init__( 

150 self, 

151 default=None, 

152 attribute=None, 

153 title=None, 

154 description=None, 

155 required=None, 

156 readonly=None, 

157 example=None, 

158 mask=None, 

159 nullable=None, 

160 **kwargs 

161 ): 

162 self.attribute = attribute 

163 self.default = default 

164 self.title = title 

165 self.description = description 

166 self.required = required 

167 self.readonly = readonly 

168 self.example = example if example is not None else self.__schema_example__ 

169 self.mask = mask 

170 self.nullable = nullable 

171 

172 def format(self, value): 

173 """ 

174 Formats a field's value. No-op by default - field classes that 

175 modify how the value of existing object keys should be presented should 

176 override this and apply the appropriate formatting. 

177 

178 :param value: The value to format 

179 :raises MarshallingError: In case of formatting problem 

180 

181 Ex:: 

182 

183 class TitleCase(Raw): 

184 def format(self, value): 

185 return unicode(value).title() 

186 """ 

187 return value 

188 

189 def output(self, key, obj, **kwargs): 

190 """ 

191 Pulls the value for the given key from the object, applies the 

192 field's formatting and returns the result. If the key is not found 

193 in the object, returns the default value. Field classes that create 

194 values which do not require the existence of the key in the object 

195 should override this and return the desired value. 

196 

197 :raises MarshallingError: In case of formatting problem 

198 """ 

199 

200 value = get_value(key if self.attribute is None else self.attribute, obj) 

201 

202 if value is None: 

203 default = self._v("default") 

204 return self.format(default) if default else default 

205 

206 try: 

207 data = self.format(value) 

208 except MarshallingError as e: 

209 msg = 'Unable to marshal field "{0}" value "{1}": {2}'.format( 

210 key, value, str(e) 

211 ) 

212 raise MarshallingError(msg) 

213 return self.mask.apply(data) if self.mask else data 

214 

215 def _v(self, key): 

216 """Helper for getting a value from attribute allowing callable""" 

217 value = getattr(self, key) 

218 return value() if callable(value) else value 

219 

220 @cached_property 

221 def __schema__(self): 

222 return not_none(self.schema()) 

223 

224 def schema(self): 

225 return { 

226 "type": self.__schema_type__, 

227 "format": self.__schema_format__, 

228 "title": self.title, 

229 "description": self.description, 

230 "readOnly": self.readonly, 

231 "default": self._v("default"), 

232 "example": self.example, 

233 } 

234 

235 

236class Nested(Raw): 

237 """ 

238 Allows you to nest one set of fields inside another. 

239 See :ref:`nested-field` for more information 

240 

241 :param dict model: The model dictionary to nest 

242 :param bool allow_null: Whether to return None instead of a dictionary 

243 with null keys, if a nested dictionary has all-null keys 

244 :param bool skip_none: Optional key will be used to eliminate inner fields 

245 which value is None or the inner field's key not 

246 exist in data 

247 :param kwargs: If ``default`` keyword argument is present, a nested 

248 dictionary will be marshaled as its value if nested dictionary is 

249 all-null keys (e.g. lets you return an empty JSON object instead of 

250 null) 

251 """ 

252 

253 __schema_type__ = None 

254 

255 def __init__( 

256 self, model, allow_null=False, skip_none=False, as_list=False, **kwargs 

257 ): 

258 self.model = model 

259 self.as_list = as_list 

260 self.allow_null = allow_null 

261 self.skip_none = skip_none 

262 super(Nested, self).__init__(**kwargs) 

263 

264 @property 

265 def nested(self): 

266 return getattr(self.model, "resolved", self.model) 

267 

268 def output(self, key, obj, ordered=False, **kwargs): 

269 value = get_value(key if self.attribute is None else self.attribute, obj) 

270 if value is None: 

271 if self.allow_null: 

272 return None 

273 elif self.default is not None: 

274 return self.default 

275 

276 return marshal(value, self.nested, skip_none=self.skip_none, ordered=ordered) 

277 

278 def schema(self): 

279 schema = super(Nested, self).schema() 

280 ref = "#/definitions/{0}".format(self.nested.name) 

281 

282 if self.as_list: 

283 schema["type"] = "array" 

284 schema["items"] = {"$ref": ref} 

285 elif any(schema.values()): 

286 # There is already some properties in the schema 

287 allOf = schema.get("allOf", []) 

288 allOf.append({"$ref": ref}) 

289 schema["allOf"] = allOf 

290 else: 

291 schema["$ref"] = ref 

292 

293 # If nullable is True, wrap using anyOf to permit nulls for input validation 

294 if self.nullable: 

295 # Remove structural keys that conflict with anyOf composition 

296 for key in ("$ref", "allOf", "type", "items"): 

297 schema.pop(key, None) 

298 # Create anyOf with the original schema and null type 

299 anyOf = [{"$ref": ref}] 

300 if self.as_list: 

301 anyOf = [{"type": "array", "items": {"$ref": ref}}] 

302 anyOf.append({"type": "null"}) 

303 schema["anyOf"] = anyOf 

304 

305 return schema 

306 

307 def clone(self, mask=None): 

308 kwargs = self.__dict__.copy() 

309 model = kwargs.pop("model") 

310 if mask: 

311 model = mask.apply(model.resolved if hasattr(model, "resolved") else model) 

312 return self.__class__(model, **kwargs) 

313 

314 

315class List(Raw): 

316 """ 

317 Field for marshalling lists of other fields. 

318 

319 See :ref:`list-field` for more information. 

320 

321 :param cls_or_instance: The field type the list will contain. 

322 """ 

323 

324 def __init__(self, cls_or_instance, **kwargs): 

325 self.min_items = kwargs.pop("min_items", None) 

326 self.max_items = kwargs.pop("max_items", None) 

327 self.unique = kwargs.pop("unique", None) 

328 super(List, self).__init__(**kwargs) 

329 error_msg = "The type of the list elements must be a subclass of fields.Raw" 

330 if isinstance(cls_or_instance, type): 

331 if not issubclass(cls_or_instance, Raw): 

332 raise MarshallingError(error_msg) 

333 self.container = cls_or_instance() 

334 else: 

335 if not isinstance(cls_or_instance, Raw): 

336 raise MarshallingError(error_msg) 

337 self.container = cls_or_instance 

338 

339 def format(self, value): 

340 # Convert all instances in typed list to container type 

341 if isinstance(value, set): 

342 value = list(value) 

343 

344 is_nested = isinstance(self.container, Nested) or type(self.container) is Raw 

345 

346 def is_attr(val): 

347 return self.container.attribute and hasattr(val, self.container.attribute) 

348 

349 if value is None: 

350 return [] 

351 return [ 

352 self.container.output( 

353 idx, 

354 ( 

355 val 

356 if (isinstance(val, dict) or is_attr(val)) and not is_nested 

357 else value 

358 ), 

359 ) 

360 for idx, val in enumerate(value) 

361 ] 

362 

363 def output(self, key, data, ordered=False, **kwargs): 

364 value = get_value(key if self.attribute is None else self.attribute, data) 

365 # we cannot really test for external dict behavior 

366 if is_indexable_but_not_string(value) and not isinstance(value, dict): 

367 return self.format(value) 

368 

369 if value is None: 

370 return self._v("default") 

371 

372 return [marshal(value, self.container.nested)] 

373 

374 def schema(self): 

375 schema = super(List, self).schema() 

376 schema.update( 

377 minItems=self._v("min_items"), 

378 maxItems=self._v("max_items"), 

379 uniqueItems=self._v("unique"), 

380 ) 

381 schema["type"] = "array" 

382 schema["items"] = self.container.__schema__ 

383 return schema 

384 

385 def clone(self, mask=None): 

386 kwargs = self.__dict__.copy() 

387 model = kwargs.pop("container") 

388 if mask: 

389 model = mask.apply(model) 

390 return self.__class__(model, **kwargs) 

391 

392 

393class StringMixin(object): 

394 __schema_type__ = "string" 

395 

396 def __init__(self, *args, **kwargs): 

397 self.min_length = kwargs.pop("min_length", None) 

398 self.max_length = kwargs.pop("max_length", None) 

399 self.pattern = kwargs.pop("pattern", None) 

400 super(StringMixin, self).__init__(*args, **kwargs) 

401 

402 def schema(self): 

403 schema = super(StringMixin, self).schema() 

404 schema.update( 

405 minLength=self._v("min_length"), 

406 maxLength=self._v("max_length"), 

407 pattern=self._v("pattern"), 

408 ) 

409 return schema 

410 

411 

412class MinMaxMixin(object): 

413 def __init__(self, *args, **kwargs): 

414 self.minimum = kwargs.pop("min", None) 

415 self.exclusiveMinimum = kwargs.pop("exclusiveMin", None) 

416 self.maximum = kwargs.pop("max", None) 

417 self.exclusiveMaximum = kwargs.pop("exclusiveMax", None) 

418 super(MinMaxMixin, self).__init__(*args, **kwargs) 

419 

420 def schema(self): 

421 schema = super(MinMaxMixin, self).schema() 

422 schema.update( 

423 minimum=self._v("minimum"), 

424 exclusiveMinimum=self._v("exclusiveMinimum"), 

425 maximum=self._v("maximum"), 

426 exclusiveMaximum=self._v("exclusiveMaximum"), 

427 ) 

428 return schema 

429 

430 

431class NumberMixin(MinMaxMixin): 

432 __schema_type__ = "number" 

433 

434 def __init__(self, *args, **kwargs): 

435 self.multiple = kwargs.pop("multiple", None) 

436 super(NumberMixin, self).__init__(*args, **kwargs) 

437 

438 def schema(self): 

439 schema = super(NumberMixin, self).schema() 

440 schema.update(multipleOf=self._v("multiple")) 

441 return schema 

442 

443 

444class String(StringMixin, Raw): 

445 """ 

446 Marshal a value as a string. 

447 """ 

448 

449 def __init__(self, *args, **kwargs): 

450 self.enum = kwargs.pop("enum", None) 

451 self.discriminator = kwargs.pop("discriminator", None) 

452 super(String, self).__init__(*args, **kwargs) 

453 self.required = self.discriminator or self.required 

454 

455 def format(self, value): 

456 try: 

457 return str(value) 

458 except ValueError as ve: 

459 raise MarshallingError(ve) 

460 

461 def schema(self): 

462 enum = self._v("enum") 

463 schema = super(String, self).schema() 

464 if enum: 

465 schema.update(enum=enum) 

466 if enum and schema["example"] is None: 

467 schema["example"] = enum[0] 

468 return schema 

469 

470 

471class Integer(NumberMixin, Raw): 

472 """ 

473 Field for outputting an integer value. 

474 

475 :param int default: The default value for the field, if no value is specified. 

476 """ 

477 

478 __schema_type__ = "integer" 

479 

480 def format(self, value): 

481 try: 

482 if value is None: 

483 return self.default 

484 return int(value) 

485 except (ValueError, TypeError) as ve: 

486 raise MarshallingError(ve) 

487 

488 

489class Float(NumberMixin, Raw): 

490 """ 

491 A double as IEEE-754 double precision. 

492 

493 ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf -inf 

494 """ 

495 

496 def format(self, value): 

497 try: 

498 if value is None: 

499 return self.default 

500 return float(value) 

501 except (ValueError, TypeError) as ve: 

502 raise MarshallingError(ve) 

503 

504 

505class Arbitrary(NumberMixin, Raw): 

506 """ 

507 A floating point number with an arbitrary precision. 

508 

509 ex: 634271127864378216478362784632784678324.23432 

510 """ 

511 

512 def format(self, value): 

513 return str(Decimal(value)) 

514 

515 

516ZERO = Decimal() 

517 

518 

519class Fixed(NumberMixin, Raw): 

520 """ 

521 A decimal number with a fixed precision. 

522 """ 

523 

524 def __init__(self, decimals=5, **kwargs): 

525 super(Fixed, self).__init__(**kwargs) 

526 self.precision = Decimal("0." + "0" * (decimals - 1) + "1") 

527 

528 def format(self, value): 

529 dvalue = Decimal(value) 

530 if not dvalue.is_normal() and dvalue != ZERO: 

531 raise MarshallingError("Invalid Fixed precision number.") 

532 return str(dvalue.quantize(self.precision, rounding=ROUND_HALF_EVEN)) 

533 

534 

535class Boolean(Raw): 

536 """ 

537 Field for outputting a boolean value. 

538 

539 Empty collections such as ``""``, ``{}``, ``[]``, etc. will be converted to ``False``. 

540 """ 

541 

542 __schema_type__ = "boolean" 

543 

544 def format(self, value): 

545 return boolean(value) 

546 

547 

548class DateTime(MinMaxMixin, Raw): 

549 """ 

550 Return a formatted datetime string in UTC. Supported formats are RFC 822 and ISO 8601. 

551 

552 See :func:`email.utils.formatdate` for more info on the RFC 822 format. 

553 

554 See :meth:`datetime.datetime.isoformat` for more info on the ISO 8601 format. 

555 

556 :param str dt_format: ``rfc822`` or ``iso8601`` 

557 """ 

558 

559 __schema_type__ = "string" 

560 __schema_format__ = "date-time" 

561 

562 def __init__(self, dt_format="iso8601", **kwargs): 

563 super(DateTime, self).__init__(**kwargs) 

564 self.dt_format = dt_format 

565 

566 def parse(self, value): 

567 if value is None: 

568 return None 

569 elif isinstance(value, str): 

570 parser = ( 

571 datetime_from_iso8601 

572 if self.dt_format == "iso8601" 

573 else datetime_from_rfc822 

574 ) 

575 return parser(value) 

576 elif isinstance(value, datetime): 

577 return value 

578 elif isinstance(value, date): 

579 return datetime(value.year, value.month, value.day) 

580 else: 

581 raise ValueError("Unsupported DateTime format") 

582 

583 def format(self, value): 

584 try: 

585 value = self.parse(value) 

586 if self.dt_format == "iso8601": 

587 return self.format_iso8601(value) 

588 elif self.dt_format == "rfc822": 

589 return self.format_rfc822(value) 

590 else: 

591 raise MarshallingError("Unsupported date format %s" % self.dt_format) 

592 except (AttributeError, ValueError) as e: 

593 raise MarshallingError(e) 

594 

595 def format_rfc822(self, dt): 

596 """ 

597 Turn a datetime object into a formatted date. 

598 

599 :param datetime dt: The datetime to transform 

600 :return: A RFC 822 formatted date string 

601 """ 

602 return formatdate(timegm(dt.utctimetuple())) 

603 

604 def format_iso8601(self, dt): 

605 """ 

606 Turn a datetime object into an ISO8601 formatted date. 

607 

608 :param datetime dt: The datetime to transform 

609 :return: A ISO 8601 formatted date string 

610 """ 

611 return dt.isoformat() 

612 

613 def _for_schema(self, name): 

614 value = self.parse(self._v(name)) 

615 return self.format(value) if value else None 

616 

617 def schema(self): 

618 schema = super(DateTime, self).schema() 

619 schema["default"] = self._for_schema("default") 

620 schema["minimum"] = self._for_schema("minimum") 

621 schema["maximum"] = self._for_schema("maximum") 

622 return schema 

623 

624 

625class Date(DateTime): 

626 """ 

627 Return a formatted date string in UTC in ISO 8601. 

628 

629 See :meth:`datetime.date.isoformat` for more info on the ISO 8601 format. 

630 """ 

631 

632 __schema_format__ = "date" 

633 

634 def __init__(self, **kwargs): 

635 kwargs.pop("dt_format", None) 

636 super(Date, self).__init__(dt_format="iso8601", **kwargs) 

637 

638 def parse(self, value): 

639 if value is None: 

640 return None 

641 elif isinstance(value, str): 

642 return date_from_iso8601(value) 

643 elif isinstance(value, datetime): 

644 return value.date() 

645 elif isinstance(value, date): 

646 return value 

647 else: 

648 raise ValueError("Unsupported Date format") 

649 

650 

651class Url(StringMixin, Raw): 

652 """ 

653 A string representation of a Url 

654 

655 :param str endpoint: Endpoint name. If endpoint is ``None``, ``request.endpoint`` is used instead 

656 :param bool absolute: If ``True``, ensures that the generated urls will have the hostname included 

657 :param str scheme: URL scheme specifier (e.g. ``http``, ``https``) 

658 """ 

659 

660 def __init__(self, endpoint=None, absolute=False, scheme=None, **kwargs): 

661 super(Url, self).__init__(**kwargs) 

662 self.endpoint = endpoint 

663 self.absolute = absolute 

664 self.scheme = scheme 

665 

666 def output(self, key, obj, **kwargs): 

667 try: 

668 data = to_marshallable_type(obj) 

669 endpoint = self.endpoint if self.endpoint is not None else request.endpoint 

670 o = urlparse(url_for(endpoint, _external=self.absolute, **data)) 

671 if self.absolute: 

672 scheme = self.scheme if self.scheme is not None else o.scheme 

673 return urlunparse((scheme, o.netloc, o.path, "", "", "")) 

674 return urlunparse(("", "", o.path, "", "", "")) 

675 except TypeError as te: 

676 raise MarshallingError(te) 

677 

678 

679class FormattedString(StringMixin, Raw): 

680 """ 

681 FormattedString is used to interpolate other values from 

682 the response into this field. The syntax for the source string is 

683 the same as the string :meth:`~str.format` method from the python 

684 stdlib. 

685 

686 Ex:: 

687 

688 fields = { 

689 'name': fields.String, 

690 'greeting': fields.FormattedString("Hello {name}") 

691 } 

692 data = { 

693 'name': 'Doug', 

694 } 

695 marshal(data, fields) 

696 

697 :param str src_str: the string to format with the other values from the response. 

698 """ 

699 

700 def __init__(self, src_str, **kwargs): 

701 super(FormattedString, self).__init__(**kwargs) 

702 self.src_str = str(src_str) 

703 

704 def output(self, key, obj, **kwargs): 

705 try: 

706 data = to_marshallable_type(obj) 

707 return self.src_str.format(**data) 

708 except (TypeError, IndexError) as error: 

709 raise MarshallingError(error) 

710 

711 

712class ClassName(String): 

713 """ 

714 Return the serialized object class name as string. 

715 

716 :param bool dash: If `True`, transform CamelCase to kebab_case. 

717 """ 

718 

719 def __init__(self, dash=False, **kwargs): 

720 super(ClassName, self).__init__(**kwargs) 

721 self.dash = dash 

722 

723 def output(self, key, obj, **kwargs): 

724 classname = obj.__class__.__name__ 

725 if classname == "dict": 

726 return "object" 

727 return camel_to_dash(classname) if self.dash else classname 

728 

729 

730class Polymorph(Nested): 

731 """ 

732 A Nested field handling inheritance. 

733 

734 Allows you to specify a mapping between Python classes and fields specifications. 

735 

736 .. code-block:: python 

737 

738 mapping = { 

739 Child1: child1_fields, 

740 Child2: child2_fields, 

741 } 

742 

743 fields = api.model('Thing', { 

744 owner: fields.Polymorph(mapping) 

745 }) 

746 

747 :param dict mapping: Maps classes to their model/fields representation 

748 """ 

749 

750 def __init__(self, mapping, required=False, **kwargs): 

751 self.mapping = mapping 

752 parent = self.resolve_ancestor(list(mapping.values())) 

753 super(Polymorph, self).__init__(parent, allow_null=not required, **kwargs) 

754 

755 def output(self, key, obj, ordered=False, **kwargs): 

756 # Copied from upstream NestedField 

757 value = get_value(key if self.attribute is None else self.attribute, obj) 

758 if value is None: 

759 if self.allow_null: 

760 return None 

761 elif self.default is not None: 

762 return self.default 

763 

764 # Handle mappings 

765 if not hasattr(value, "__class__"): 

766 raise ValueError("Polymorph field only accept class instances") 

767 

768 candidates = [ 

769 fields for cls, fields in self.mapping.items() if type(value) == cls 

770 ] 

771 

772 if len(candidates) <= 0: 

773 raise ValueError("Unknown class: " + value.__class__.__name__) 

774 elif len(candidates) > 1: 

775 raise ValueError( 

776 "Unable to determine a candidate for: " + value.__class__.__name__ 

777 ) 

778 else: 

779 return marshal( 

780 value, candidates[0].resolved, mask=self.mask, ordered=ordered 

781 ) 

782 

783 def resolve_ancestor(self, models): 

784 """ 

785 Resolve the common ancestor for all models. 

786 

787 Assume there is only one common ancestor. 

788 """ 

789 ancestors = [m.ancestors for m in models] 

790 candidates = set.intersection(*ancestors) 

791 if len(candidates) != 1: 

792 field_names = [f.name for f in models] 

793 raise ValueError( 

794 "Unable to determine the common ancestor for: " + ", ".join(field_names) 

795 ) 

796 

797 parent_name = candidates.pop() 

798 return models[0].get_parent(parent_name) 

799 

800 def clone(self, mask=None): 

801 data = self.__dict__.copy() 

802 mapping = data.pop("mapping") 

803 for field in ("allow_null", "model"): 

804 data.pop(field, None) 

805 

806 data["mask"] = mask 

807 return Polymorph(mapping, **data) 

808 

809 

810class Wildcard(Raw): 

811 """ 

812 Field for marshalling list of "unkown" fields. 

813 

814 :param cls_or_instance: The field type the list will contain. 

815 """ 

816 

817 exclude = set() 

818 # cache the flat object 

819 _flat = None 

820 _obj = None 

821 _cache = set() 

822 _last = None 

823 

824 def __init__(self, cls_or_instance, **kwargs): 

825 super(Wildcard, self).__init__(**kwargs) 

826 error_msg = "The type of the wildcard elements must be a subclass of fields.Raw" 

827 if isinstance(cls_or_instance, type): 

828 if not issubclass(cls_or_instance, Raw): 

829 raise MarshallingError(error_msg) 

830 self.container = cls_or_instance() 

831 else: 

832 if not isinstance(cls_or_instance, Raw): 

833 raise MarshallingError(error_msg) 

834 self.container = cls_or_instance 

835 

836 def _flatten(self, obj): 

837 if obj is None: 

838 return None 

839 if obj == self._obj and self._flat is not None: 

840 return self._flat 

841 if isinstance(obj, dict): 

842 self._flat = [x for x in obj.items()] 

843 else: 

844 

845 def __match_attributes(attribute): 

846 attr_name, attr_obj = attribute 

847 if inspect.isroutine(attr_obj) or ( 

848 attr_name.startswith("__") and attr_name.endswith("__") 

849 ): 

850 return False 

851 return True 

852 

853 attributes = inspect.getmembers(obj) 

854 self._flat = [x for x in attributes if __match_attributes(x)] 

855 

856 self._cache = set() 

857 self._obj = obj 

858 return self._flat 

859 

860 @property 

861 def key(self): 

862 return self._last 

863 

864 def reset(self): 

865 self.exclude = set() 

866 self._flat = None 

867 self._obj = None 

868 self._cache = set() 

869 self._last = None 

870 

871 def output(self, key, obj, ordered=False): 

872 value = None 

873 reg = fnmatch.translate(key) 

874 

875 if self._flatten(obj): 

876 while True: 

877 try: 

878 # we are using pop() so that we don't 

879 # loop over the whole object every time dropping the 

880 # complexity to O(n) 

881 if ordered: 

882 # Get first element if respecting order 

883 (objkey, val) = self._flat.pop(0) 

884 else: 

885 # Previous default retained 

886 (objkey, val) = self._flat.pop() 

887 if ( 

888 objkey not in self._cache 

889 and objkey not in self.exclude 

890 and re.match(reg, objkey, re.IGNORECASE) 

891 ): 

892 value = val 

893 self._cache.add(objkey) 

894 self._last = objkey 

895 break 

896 except IndexError: 

897 break 

898 

899 if value is None: 

900 if self.default is not None: 

901 return self.container.format(self.default) 

902 return None 

903 

904 if isinstance(self.container, Nested): 

905 return marshal( 

906 value, 

907 self.container.nested, 

908 skip_none=self.container.skip_none, 

909 ordered=ordered, 

910 ) 

911 return self.container.format(value) 

912 

913 def schema(self): 

914 schema = super(Wildcard, self).schema() 

915 schema["type"] = "object" 

916 schema["additionalProperties"] = self.container.__schema__ 

917 return schema 

918 

919 def clone(self): 

920 kwargs = self.__dict__.copy() 

921 model = kwargs.pop("container") 

922 return self.__class__(model, **kwargs)