Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/flask_restx/fields.py: 27%

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

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

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)