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__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) 

47 

48 

49class MarshallingError(RestError): 

50 """ 

51 This is an encapsulating Exception in case of marshalling error. 

52 """ 

53 

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)) 

58 

59 

60def is_indexable_but_not_string(obj): 

61 return not hasattr(obj, "strip") and hasattr(obj, "__iter__") 

62 

63 

64def is_integer_indexable(obj): 

65 return isinstance(obj, list) or isinstance(obj, tuple) 

66 

67 

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) 

76 

77 

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 ) 

85 

86 

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) 

99 

100 

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 

108 

109 if hasattr(obj, "__marshallable__"): 

110 return obj.__marshallable__() 

111 

112 if hasattr(obj, "__getitem__"): 

113 return obj # it is indexable it is ok 

114 

115 return dict(obj.__dict__) 

116 

117 

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. 

124 

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 """ 

140 

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 

147 

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 

170 

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. 

176 

177 :param value: The value to format 

178 :raises MarshallingError: In case of formatting problem 

179 

180 Ex:: 

181 

182 class TitleCase(Raw): 

183 def format(self, value): 

184 return unicode(value).title() 

185 """ 

186 return value 

187 

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. 

195 

196 :raises MarshallingError: In case of formatting problem 

197 """ 

198 

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

200 

201 if value is None: 

202 default = self._v("default") 

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

204 

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 

213 

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 

218 

219 @cached_property 

220 def __schema__(self): 

221 return not_none(self.schema()) 

222 

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 } 

233 

234 

235class Nested(Raw): 

236 """ 

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

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

239 

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 """ 

251 

252 __schema_type__ = None 

253 

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) 

262 

263 @property 

264 def nested(self): 

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

266 

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 

274 

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

276 

277 def schema(self): 

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

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

280 

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 

291 

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 

303 

304 return schema 

305 

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) 

312 

313 

314class List(Raw): 

315 """ 

316 Field for marshalling lists of other fields. 

317 

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

319 

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

321 """ 

322 

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 

337 

338 def format(self, value): 

339 # Convert all instances in typed list to container type 

340 if isinstance(value, set): 

341 value = list(value) 

342 

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

344 

345 def is_attr(val): 

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

347 

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 ] 

361 

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) 

367 

368 if value is None: 

369 return self._v("default") 

370 

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

372 

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 

383 

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) 

390 

391 

392class StringMixin(object): 

393 __schema_type__ = "string" 

394 

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) 

400 

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 

409 

410 

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) 

418 

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 

428 

429 

430class NumberMixin(MinMaxMixin): 

431 __schema_type__ = "number" 

432 

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

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

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

436 

437 def schema(self): 

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

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

440 return schema 

441 

442 

443class String(StringMixin, Raw): 

444 """ 

445 Marshal a value as a string. 

446 """ 

447 

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 

453 

454 def format(self, value): 

455 try: 

456 return str(value) 

457 except ValueError as ve: 

458 raise MarshallingError(ve) 

459 

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 

468 

469 

470class Integer(NumberMixin, Raw): 

471 """ 

472 Field for outputting an integer value. 

473 

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

475 """ 

476 

477 __schema_type__ = "integer" 

478 

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) 

486 

487 

488class Float(NumberMixin, Raw): 

489 """ 

490 A double as IEEE-754 double precision. 

491 

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

493 """ 

494 

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) 

502 

503 

504class Arbitrary(NumberMixin, Raw): 

505 """ 

506 A floating point number with an arbitrary precision. 

507 

508 ex: 634271127864378216478362784632784678324.23432 

509 """ 

510 

511 def format(self, value): 

512 return str(Decimal(value)) 

513 

514 

515ZERO = Decimal() 

516 

517 

518class Fixed(NumberMixin, Raw): 

519 """ 

520 A decimal number with a fixed precision. 

521 """ 

522 

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

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

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

526 

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)) 

532 

533 

534class Boolean(Raw): 

535 """ 

536 Field for outputting a boolean value. 

537 

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

539 """ 

540 

541 __schema_type__ = "boolean" 

542 

543 def format(self, value): 

544 return boolean(value) 

545 

546 

547class DateTime(MinMaxMixin, Raw): 

548 """ 

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

550 

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

552 

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

554 

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

556 """ 

557 

558 __schema_type__ = "string" 

559 __schema_format__ = "date-time" 

560 

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

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

563 self.dt_format = dt_format 

564 

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") 

581 

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) 

593 

594 def format_rfc822(self, dt): 

595 """ 

596 Turn a datetime object into a formatted date. 

597 

598 :param datetime dt: The datetime to transform 

599 :return: A RFC 822 formatted date string 

600 """ 

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

602 

603 def format_iso8601(self, dt): 

604 """ 

605 Turn a datetime object into an ISO8601 formatted date. 

606 

607 :param datetime dt: The datetime to transform 

608 :return: A ISO 8601 formatted date string 

609 """ 

610 return dt.isoformat() 

611 

612 def _for_schema(self, name): 

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

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

615 

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 

622 

623 

624class Date(DateTime): 

625 """ 

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

627 

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

629 """ 

630 

631 __schema_format__ = "date" 

632 

633 def __init__(self, **kwargs): 

634 kwargs.pop("dt_format", None) 

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

636 

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") 

648 

649 

650class Url(StringMixin, Raw): 

651 """ 

652 A string representation of a Url 

653 

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 """ 

658 

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 

664 

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) 

676 

677 

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. 

684 

685 Ex:: 

686 

687 fields = { 

688 'name': fields.String, 

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

690 } 

691 data = { 

692 'name': 'Doug', 

693 } 

694 marshal(data, fields) 

695 

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

697 """ 

698 

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

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

701 self.src_str = str(src_str) 

702 

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) 

709 

710 

711class ClassName(String): 

712 """ 

713 Return the serialized object class name as string. 

714 

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

716 """ 

717 

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

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

720 self.dash = dash 

721 

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 

727 

728 

729class Polymorph(Nested): 

730 """ 

731 A Nested field handling inheritance. 

732 

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

734 

735 .. code-block:: python 

736 

737 mapping = { 

738 Child1: child1_fields, 

739 Child2: child2_fields, 

740 } 

741 

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

743 owner: fields.Polymorph(mapping) 

744 }) 

745 

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

747 """ 

748 

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) 

753 

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 

762 

763 # Handle mappings 

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

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

766 

767 candidates = [ 

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

769 ] 

770 

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 ) 

781 

782 def resolve_ancestor(self, models): 

783 """ 

784 Resolve the common ancestor for all models. 

785 

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 ) 

795 

796 parent_name = candidates.pop() 

797 return models[0].get_parent(parent_name) 

798 

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) 

804 

805 data["mask"] = mask 

806 return Polymorph(mapping, **data) 

807 

808 

809class Wildcard(Raw): 

810 """ 

811 Field for marshalling list of "unkown" fields. 

812 

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

814 """ 

815 

816 exclude = set() 

817 # cache the flat object 

818 _flat = None 

819 _obj = None 

820 _cache = set() 

821 _last = None 

822 

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 

834 

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: 

843 

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 

851 

852 attributes = inspect.getmembers(obj) 

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

854 

855 self._cache = set() 

856 self._obj = obj 

857 return self._flat 

858 

859 @property 

860 def key(self): 

861 return self._last 

862 

863 def reset(self): 

864 self.exclude = set() 

865 self._flat = None 

866 self._obj = None 

867 self._cache = set() 

868 self._last = None 

869 

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

871 value = None 

872 reg = fnmatch.translate(key) 

873 

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 

897 

898 if value is None: 

899 if self.default is not None: 

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

901 return None 

902 

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) 

911 

912 def schema(self): 

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

914 schema["type"] = "object" 

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

916 return schema 

917 

918 def clone(self): 

919 kwargs = self.__dict__.copy() 

920 model = kwargs.pop("container") 

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