A specialized dict that is used for JSON message payloads - Request.arguments, Response.body, and Event.body. For all members that normally throw KeyError when a requested key is missing, this dict raises InvalidMessageError instead. Thus, a message handler can skip checks for missi
| 309 | |
| 310 | |
| 311 | class MessageDict(collections.OrderedDict): |
| 312 | """A specialized dict that is used for JSON message payloads - Request.arguments, |
| 313 | Response.body, and Event.body. |
| 314 | |
| 315 | For all members that normally throw KeyError when a requested key is missing, this |
| 316 | dict raises InvalidMessageError instead. Thus, a message handler can skip checks |
| 317 | for missing properties, and just work directly with the payload on the assumption |
| 318 | that it is valid according to the protocol specification; if anything is missing, |
| 319 | it will be reported automatically in the proper manner. |
| 320 | |
| 321 | If the value for the requested key is itself a dict, it is returned as is, and not |
| 322 | automatically converted to MessageDict. Thus, to enable convenient chaining - e.g. |
| 323 | d["a"]["b"]["c"] - the dict must consistently use MessageDict instances rather than |
| 324 | vanilla dicts for all its values, recursively. This is guaranteed for the payload |
| 325 | of all freshly received messages (unless and until it is mutated), but there is no |
| 326 | such guarantee for outgoing messages. |
| 327 | """ |
| 328 | |
| 329 | def __init__(self, message, items=None): |
| 330 | assert message is None or isinstance(message, Message) |
| 331 | |
| 332 | if items is None: |
| 333 | super().__init__() |
| 334 | else: |
| 335 | super().__init__(items) |
| 336 | |
| 337 | self.message = message |
| 338 | """The Message object that owns this dict. |
| 339 | |
| 340 | For any instance exposed via a Message object corresponding to some incoming |
| 341 | message, it is guaranteed to reference that Message object. There is no similar |
| 342 | guarantee for outgoing messages. |
| 343 | """ |
| 344 | |
| 345 | def __repr__(self): |
| 346 | try: |
| 347 | return format(json.repr(self)) |
| 348 | except Exception: # pragma: no cover |
| 349 | return super().__repr__() |
| 350 | |
| 351 | def __call__(self, key, validate, optional=False): |
| 352 | """Like get(), but with validation. |
| 353 | |
| 354 | The item is first retrieved as if with self.get(key, default=()) - the default |
| 355 | value is () rather than None, so that JSON nulls are distinguishable from |
| 356 | missing properties. |
| 357 | |
| 358 | If optional=True, and the value is (), it's returned as is. Otherwise, the |
| 359 | item is validated by invoking validate(item) on it. |
| 360 | |
| 361 | If validate=False, it's treated as if it were (lambda x: x) - i.e. any value |
| 362 | is considered valid, and is returned unchanged. If validate is a type or a |
| 363 | tuple, it's treated as json.of_type(validate). Otherwise, if validate is not |
| 364 | callable(), it's treated as json.default(validate). |
| 365 | |
| 366 | If validate() returns successfully, the item is substituted with the value |
| 367 | it returns - thus, the validator can e.g. replace () with a suitable default |
| 368 | value for the property. |
no outgoing calls
no test coverage detected
searching dependent graphs…