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

448 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:03 +0000

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 val 

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

338 else value, 

339 ) 

340 for idx, val in enumerate(value) 

341 ] 

342 

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

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

345 # we cannot really test for external dict behavior 

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

347 return self.format(value) 

348 

349 if value is None: 

350 return self._v("default") 

351 

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

353 

354 def schema(self): 

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

356 schema.update( 

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

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

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

360 ) 

361 schema["type"] = "array" 

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

363 return schema 

364 

365 def clone(self, mask=None): 

366 kwargs = self.__dict__.copy() 

367 model = kwargs.pop("container") 

368 if mask: 

369 model = mask.apply(model) 

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

371 

372 

373class StringMixin(object): 

374 __schema_type__ = "string" 

375 

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

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

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

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

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

381 

382 def schema(self): 

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

384 schema.update( 

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

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

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

388 ) 

389 return schema 

390 

391 

392class MinMaxMixin(object): 

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

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

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

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

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

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

399 

400 def schema(self): 

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

402 schema.update( 

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

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

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

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

407 ) 

408 return schema 

409 

410 

411class NumberMixin(MinMaxMixin): 

412 __schema_type__ = "number" 

413 

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

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

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

417 

418 def schema(self): 

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

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

421 return schema 

422 

423 

424class String(StringMixin, Raw): 

425 """ 

426 Marshal a value as a string. 

427 """ 

428 

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

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

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

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

433 self.required = self.discriminator or self.required 

434 

435 def format(self, value): 

436 try: 

437 return str(value) 

438 except ValueError as ve: 

439 raise MarshallingError(ve) 

440 

441 def schema(self): 

442 enum = self._v("enum") 

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

444 if enum: 

445 schema.update(enum=enum) 

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

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

448 return schema 

449 

450 

451class Integer(NumberMixin, Raw): 

452 """ 

453 Field for outputting an integer value. 

454 

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

456 """ 

457 

458 __schema_type__ = "integer" 

459 

460 def format(self, value): 

461 try: 

462 if value is None: 

463 return self.default 

464 return int(value) 

465 except (ValueError, TypeError) as ve: 

466 raise MarshallingError(ve) 

467 

468 

469class Float(NumberMixin, Raw): 

470 """ 

471 A double as IEEE-754 double precision. 

472 

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

474 """ 

475 

476 def format(self, value): 

477 try: 

478 if value is None: 

479 return self.default 

480 return float(value) 

481 except (ValueError, TypeError) as ve: 

482 raise MarshallingError(ve) 

483 

484 

485class Arbitrary(NumberMixin, Raw): 

486 """ 

487 A floating point number with an arbitrary precision. 

488 

489 ex: 634271127864378216478362784632784678324.23432 

490 """ 

491 

492 def format(self, value): 

493 return str(Decimal(value)) 

494 

495 

496ZERO = Decimal() 

497 

498 

499class Fixed(NumberMixin, Raw): 

500 """ 

501 A decimal number with a fixed precision. 

502 """ 

503 

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

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

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

507 

508 def format(self, value): 

509 dvalue = Decimal(value) 

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

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

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

513 

514 

515class Boolean(Raw): 

516 """ 

517 Field for outputting a boolean value. 

518 

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

520 """ 

521 

522 __schema_type__ = "boolean" 

523 

524 def format(self, value): 

525 return boolean(value) 

526 

527 

528class DateTime(MinMaxMixin, Raw): 

529 """ 

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

531 

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

533 

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

535 

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

537 """ 

538 

539 __schema_type__ = "string" 

540 __schema_format__ = "date-time" 

541 

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

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

544 self.dt_format = dt_format 

545 

546 def parse(self, value): 

547 if value is None: 

548 return None 

549 elif isinstance(value, str): 

550 parser = ( 

551 datetime_from_iso8601 

552 if self.dt_format == "iso8601" 

553 else datetime_from_rfc822 

554 ) 

555 return parser(value) 

556 elif isinstance(value, datetime): 

557 return value 

558 elif isinstance(value, date): 

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

560 else: 

561 raise ValueError("Unsupported DateTime format") 

562 

563 def format(self, value): 

564 try: 

565 value = self.parse(value) 

566 if self.dt_format == "iso8601": 

567 return self.format_iso8601(value) 

568 elif self.dt_format == "rfc822": 

569 return self.format_rfc822(value) 

570 else: 

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

572 except (AttributeError, ValueError) as e: 

573 raise MarshallingError(e) 

574 

575 def format_rfc822(self, dt): 

576 """ 

577 Turn a datetime object into a formatted date. 

578 

579 :param datetime dt: The datetime to transform 

580 :return: A RFC 822 formatted date string 

581 """ 

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

583 

584 def format_iso8601(self, dt): 

585 """ 

586 Turn a datetime object into an ISO8601 formatted date. 

587 

588 :param datetime dt: The datetime to transform 

589 :return: A ISO 8601 formatted date string 

590 """ 

591 return dt.isoformat() 

592 

593 def _for_schema(self, name): 

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

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

596 

597 def schema(self): 

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

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

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

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

602 return schema 

603 

604 

605class Date(DateTime): 

606 """ 

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

608 

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

610 """ 

611 

612 __schema_format__ = "date" 

613 

614 def __init__(self, **kwargs): 

615 kwargs.pop("dt_format", None) 

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

617 

618 def parse(self, value): 

619 if value is None: 

620 return None 

621 elif isinstance(value, str): 

622 return date_from_iso8601(value) 

623 elif isinstance(value, datetime): 

624 return value.date() 

625 elif isinstance(value, date): 

626 return value 

627 else: 

628 raise ValueError("Unsupported Date format") 

629 

630 

631class Url(StringMixin, Raw): 

632 """ 

633 A string representation of a Url 

634 

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

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

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

638 """ 

639 

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

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

642 self.endpoint = endpoint 

643 self.absolute = absolute 

644 self.scheme = scheme 

645 

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

647 try: 

648 data = to_marshallable_type(obj) 

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

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

651 if self.absolute: 

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

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

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

655 except TypeError as te: 

656 raise MarshallingError(te) 

657 

658 

659class FormattedString(StringMixin, Raw): 

660 """ 

661 FormattedString is used to interpolate other values from 

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

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

664 stdlib. 

665 

666 Ex:: 

667 

668 fields = { 

669 'name': fields.String, 

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

671 } 

672 data = { 

673 'name': 'Doug', 

674 } 

675 marshal(data, fields) 

676 

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

678 """ 

679 

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

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

682 self.src_str = str(src_str) 

683 

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

685 try: 

686 data = to_marshallable_type(obj) 

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

688 except (TypeError, IndexError) as error: 

689 raise MarshallingError(error) 

690 

691 

692class ClassName(String): 

693 """ 

694 Return the serialized object class name as string. 

695 

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

697 """ 

698 

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

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

701 self.dash = dash 

702 

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

704 classname = obj.__class__.__name__ 

705 if classname == "dict": 

706 return "object" 

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

708 

709 

710class Polymorph(Nested): 

711 """ 

712 A Nested field handling inheritance. 

713 

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

715 

716 .. code-block:: python 

717 

718 mapping = { 

719 Child1: child1_fields, 

720 Child2: child2_fields, 

721 } 

722 

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

724 owner: fields.Polymorph(mapping) 

725 }) 

726 

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

728 """ 

729 

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

731 self.mapping = mapping 

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

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

734 

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

736 # Copied from upstream NestedField 

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

738 if value is None: 

739 if self.allow_null: 

740 return None 

741 elif self.default is not None: 

742 return self.default 

743 

744 # Handle mappings 

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

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

747 

748 candidates = [ 

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

750 ] 

751 

752 if len(candidates) <= 0: 

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

754 elif len(candidates) > 1: 

755 raise ValueError( 

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

757 ) 

758 else: 

759 return marshal( 

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

761 ) 

762 

763 def resolve_ancestor(self, models): 

764 """ 

765 Resolve the common ancestor for all models. 

766 

767 Assume there is only one common ancestor. 

768 """ 

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

770 candidates = set.intersection(*ancestors) 

771 if len(candidates) != 1: 

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

773 raise ValueError( 

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

775 ) 

776 

777 parent_name = candidates.pop() 

778 return models[0].get_parent(parent_name) 

779 

780 def clone(self, mask=None): 

781 data = self.__dict__.copy() 

782 mapping = data.pop("mapping") 

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

784 data.pop(field, None) 

785 

786 data["mask"] = mask 

787 return Polymorph(mapping, **data) 

788 

789 

790class Wildcard(Raw): 

791 """ 

792 Field for marshalling list of "unkown" fields. 

793 

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

795 """ 

796 

797 exclude = set() 

798 # cache the flat object 

799 _flat = None 

800 _obj = None 

801 _cache = set() 

802 _last = None 

803 

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

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

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

807 if isinstance(cls_or_instance, type): 

808 if not issubclass(cls_or_instance, Raw): 

809 raise MarshallingError(error_msg) 

810 self.container = cls_or_instance() 

811 else: 

812 if not isinstance(cls_or_instance, Raw): 

813 raise MarshallingError(error_msg) 

814 self.container = cls_or_instance 

815 

816 def _flatten(self, obj): 

817 if obj is None: 

818 return None 

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

820 return self._flat 

821 if isinstance(obj, dict): 

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

823 else: 

824 

825 def __match_attributes(attribute): 

826 attr_name, attr_obj = attribute 

827 if inspect.isroutine(attr_obj) or ( 

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

829 ): 

830 return False 

831 return True 

832 

833 attributes = inspect.getmembers(obj) 

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

835 

836 self._cache = set() 

837 self._obj = obj 

838 return self._flat 

839 

840 @property 

841 def key(self): 

842 return self._last 

843 

844 def reset(self): 

845 self.exclude = set() 

846 self._flat = None 

847 self._obj = None 

848 self._cache = set() 

849 self._last = None 

850 

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

852 value = None 

853 reg = fnmatch.translate(key) 

854 

855 if self._flatten(obj): 

856 while True: 

857 try: 

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

859 # loop over the whole object every time dropping the 

860 # complexity to O(n) 

861 if ordered: 

862 # Get first element if respecting order 

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

864 else: 

865 # Previous default retained 

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

867 if ( 

868 objkey not in self._cache 

869 and objkey not in self.exclude 

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

871 ): 

872 value = val 

873 self._cache.add(objkey) 

874 self._last = objkey 

875 break 

876 except IndexError: 

877 break 

878 

879 if value is None: 

880 if self.default is not None: 

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

882 return None 

883 

884 if isinstance(self.container, Nested): 

885 return marshal( 

886 value, 

887 self.container.nested, 

888 skip_none=self.container.skip_none, 

889 ordered=ordered, 

890 ) 

891 return self.container.format(value) 

892 

893 def schema(self): 

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

895 schema["type"] = "object" 

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

897 return schema 

898 

899 def clone(self): 

900 kwargs = self.__dict__.copy() 

901 model = kwargs.pop("container") 

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