def _parse_text( self, input_safe: str, context: VerediContext ) -> Tuple[Iterable, Dict[str, Any], CommandStatus]: ''' Parse verified safe/sanitized `input_safe` string into the args & kwargs this text command wants. Returns parsed tuple of: (args, kwargs, CommandStatus) - args is a list in correct order - kwargs is a dict ''' args = [] kwargs = {} # We're a text thing... so parse it ourself? remainder = input_safe for cmd_arg in self._args: parsed, remainder = cmd_arg.parse(remainder) if not null_or_none(parsed): args.append(parsed) if remainder: for cmd_kwarg in self._kwargs: parsed, remainder = cmd_kwarg.parse(remainder) if not null_or_none(parsed): kwargs[cmd_kwarg.kwarg] = parsed return args, kwargs, CommandStatus.successful(context)
def _configure(self, context: Optional['VerediContext']) -> None: ''' Get rounds-per-tick and current-seconds from repository. ''' # ------------------------------ # UNIT-TEST HACKS # ------------------------------ if isinstance(context, UnitTestContext): # If constructed specifically with a UnitTestContext, don't do # _configure() as we have no DataManager. ctx = context.sub_get(self.dotted) self._seconds_per_round = time.to_decimal(ctx['seconds-per-round']) self._current_round = numbers.to_decimal(ctx['current-round']) return # ------------------------------ # Grab our config from DataManager's Game Rules. # ------------------------------ # Game Rules has both the game definition and the game saved records. rules = background.manager.data.game # ------------------------------ # Definitions # ------------------------------ # Round Time will be stored in game rules definition data. # Get round time duration. key_round_time = ('time', 'round') # definition.game -> time.round round_time = rules.definition.get(*key_round_time) if null_or_none(round_time): msg = ("Could not get Round Time from RulesGame's Definition " f"data: {label.normalize(*key_round_time)} " f"{round_time}") raise background.config.exception(None, msg) self._seconds_per_round = time.to_decimal(round_time) # ------------------------------ # Saved Data # ------------------------------ # Current Time will be stored in game rules saved data. # Get current round number. key_round_number = ('time', 'round') # saved.game -> time.round round_num = rules.saved.get(*key_round_number) if null_or_none(round_num) or not numbers.is_number(round_num): msg = ("Could not get Current Round (or it is not a number) " "from RulesGame's Saved data: " f"{label.normalize(key_round_number)} " f"{round_num}") raise background.config.exception(None, msg) self._current_round = numbers.to_decimal(round_num)
def encode_map(self, encode_from: Mapping, encode_to: Optional[Mapping] = None, ) -> Mapping[str, Union[str, numbers.NumberTypes, None]]: ''' If `encode_to` is supplied, use that. Else create an empty `encode_to` dictionary. Get values in `encode_from` dict, encode them, and put them in `encode_to` under an encoded key. Returns `encode_to` instance (either the new one we created or the existing updated one). ''' if null_or_none(encode_from): # Null/None encode to None. return None if encode_to is None: encode_to = {} # log.debug(f"\n\nlogging.encode_map: {encode_from}\n\n") for key, value in encode_from.items(): field = self._encode_key(key) node = self._encode_value(value) encode_to[field] = node # log.debug(f"\n\n done.\nencode_map: {encode_to}\n\n") return encode_to
def path(klass: Type['ConfigContext'], context: VerediContext) -> Nullable[pathlib.Path]: ''' Checks for a PATH link in config's spot in this context. If none, returns PATH from background.manager.data. ''' path = context.sub_get(ConfigLink.PATH) if null_or_none(path): log.debug( "No path in context; using background's. " "context.path: {}, ", "bg.path: {}", path, background.manager.data.path, context=context) path = background.manager.data.path return path
def decode_map(self, mapping: NullNoneOr[Mapping], expected: Iterable[Type[Encodable]] = None ) -> Mapping[str, Any]: ''' Decode a mapping. ''' if null_or_none(mapping): self._log_data_processing( self.dotted, "decode_map: Cannot decode nothing.\n" " expecting: {}\n" " mapping: {}", expected, log.format_pretty(mapping, prefix=' ')) return None self._log_data_processing( self.dotted, "decode_map: Decoding...\n" " expecting: {}\n" "{}", expected, log.format_pretty(mapping, prefix=' ')) # --- # Decode the Base Level # --- decoded = {} for key, value in mapping.items(): field = self._decode_key(key, expected) node = self._decode_value(value, expected) decoded[field] = node self._log_data_processing( self.dotted, "decode_map: Decoded to:\n" " decoded: {}", decoded) return decoded
def _call_if_exists(self, manager: EcsManager, health: VerediHealth, function_name: str, *args: Any, **kwargs: Any) -> VerediHealth: ''' If `manager` is Null or None, do nothing and return `health`. Else, try to get `function_name` from manager, try to call it with `*args` and `**kwargs`. Update `health` with the function call result and return updated health. ''' if null_or_none(manager): return health # Have a manager, at least. Use 'Null()' as fallback so we can just # fail for a bit and recover at the end. function = getattr(manager, function_name, Null()) result = function(*args, **kwargs) # Sanity check. if isinstance(result, VerediHealth): return health.update(result) # Recover from Null here; works also for functions that returned an # incorrect type. We just fail if we got not-a-health result. else: msg = (f"Attempt to call `{function_name}` gave unexpected result " "(expected a VerediHealth value). Function does not exist " "or returned incorrect result.") error = HealthError(VerediHealth.FATAL, health, msg, data={ 'manager': manager, 'prev_health': health, 'function_name': function_name, 'function': function, 'result': result, }) raise self._log_exception(error, msg)
def data_request( self, entity_id: EntityId, *taxonomy: Any, event_type: Optional[EventTypeInput] = None, data_type: DataType = DataType.SAVED, data_action: DataAction = DataAction.LOAD) -> DataRequestEvent: ''' Create a DataLoadRequest or DataSavedRequest from the given taxonomy. If an `event_type` is not supplied, `self.event_type_dont_care()` will be used. ''' taxon = self.manager.data.taxon(data_type, *taxonomy) if null_or_none(event_type): event_type = self.event_type_dont_care() request = self.manager.data.request(self.dotted, entity_id, event_type, data_action, taxon) return request
def from_logging(lvl: LogLvlConversion) -> 'Level': if null_or_none(lvl): return Level.NOTSET return Level(lvl)
def to_logging(lvl: LogLvlConversion) -> int: if null_or_none(lvl): lvl = Level.NOTSET return int(lvl)
def valid(klass: Type['SuccessType'], success: NullNoneOr['SuccessType']) -> bool: ''' Returns true if `success` is a SuccessType value other than IGNORE. ''' return (not null_or_none(success) and success is not klass.IGNORE)
def decode(self, target: Optional[EncodableTypes], data: EncodedEither, error_squelch: bool = False, reg_find_dotted: Optional[str] = None, reg_find_types: Optional[Type[Encodable]] = None, map_expected: Iterable[Type[EncodableTypes]] = None, fallback: Optional[Type[Any]] = None ) -> Optional[Any]: ''' Decode simple or complex `data` input, using it to build an instance of the `target` class. If `target` is known, it is used to decode and return a new `target` instance or None. If `target` is unknown (and therefore None), `data` must exist and have keys: - Encodable.ENCODABLE_REG_FIELD - Encodable.ENCODABLE_PAYLOAD_FIELD Raises KeyError if not present. Takes EncodedComplex `data` input, and uses `Encodable.ENCODABLE_REG_FIELD` key to find registered Encodable to decode `data[Encodable.ENCODABLE_PAYLOAD_FIELD]`. These keyword args are used for getting Encodables from the EncodableRegistry: - reg_find_dotted: Encodable's dotted registry string to use for searching for the encodable that can decode the data. - reg_find_types: Search for Encodables of this class or its subclasses that can decode the data. - fallback: Thing to return if no valid Encodable found for decoding. If data is a map with several expected Encodables in it, supply those in `map_expected` or just use `decode_map()`. `error_squelch` will try to only raise the exception, instead of raising it through log.exception(). ''' # --- # Decode at all? # --- if null_or_none(data): # Can't decode nothing; return nothing. self._log_data_processing( self.dotted, "decode: Cannot decode nothing:\n" " data: {}", data) return self._decode_finalize(None) # --- # Decode target already known? # --- if target: self._log_data_processing( self.dotted, "decode: Attempting to decode via Encodable:\n" " data: {}", data) decoded = self._decode_encodable(target, data) self._log_data_processing( self.dotted, "decode: Decode via Encodable returned:\n" " decoded: {}", decoded) return self._decode_finalize(decoded) self._log_data_processing( self.dotted, "decode: No target...\n" " type: {}\n" " data: {}", type(data), data) # --- # Is it an Encoding.SIMPLE? # --- if isinstance(data, EncodedSimpleTuple): self._log_data_processing( self.dotted, "decode: Attempting to decode simply encoded data...\n" " data: {}", data) decoded = self._decode_simple(data, None) if decoded: self._log_data_processing( self.dotted, "decode: Decoded simply encoded data to:\n" " decoded: {}", decoded) return self._decode_finalize(decoded) # Else: not this... Keep looking. self._log_data_processing( self.dotted, "decode: Data is not Encoding.SIMPLE. Continuing...") # --- # Does the EncodableRegistry know about it? # --- try: self._log_data_processing( self.dotted, "decode: Attempting to decode with registry...\n" " reg_find_dotted: {}\n" " reg_find_types: {}\n" " error_squelch: {}\n\n" " data:\n{}\n" " fallback:\n{}\n", reg_find_dotted, reg_find_types, error_squelch, log.format_pretty(data, prefix=' '), log.format_pretty(fallback, prefix=' ')) decoded = self._decode_with_registry(data, dotted=reg_find_dotted, data_types=reg_find_types, error_squelch=error_squelch, fallback=fallback) if self._log_will_output(log.Group.DATA_PROCESSING): self._log_data_processing( self.dotted, "decode: Decode with registry returned:\n" " type: {}\n" "{}", type(decoded), log.format_pretty(decoded, prefix=' ')) return self._decode_finalize(decoded) except (KeyError, ValueError, TypeError): # Expected exceptions from `_decode_with_registry`... # Try more things? pass # --- # Mapping? # --- if isinstance(data, dict): self._log_data_processing( self.dotted, "decode: Attempting to decode mapping...\n" " map_expected: {}\n" " data: {}", map_expected, data) # Decode via our map helper. decoded = self.decode_map(data, expected=map_expected) self._log_data_processing( self.dotted, "decode: Decoded mapping returned:\n" " decoded: {}", decoded) return self._decode_finalize(decoded) # --- # Something Basic? # --- try: self._log_data_processing( self.dotted, "decode: Attempting to decode basic data type...\n" " data: {}", data) decoded = self._decode_basic_types(data) self._log_data_processing( self.dotted, "decode: Decoded basic data to:\n" " decoded: {}", decoded) return self._decode_finalize(decoded) except EncodableError: # Not this either... pass # --- # Ran out of options... Return fallback or error out. # --- if fallback: self._log_data_processing( self.dotted, "decode: No decoding known for data; returning fallback:\n" " fallback: {}", fallback) return self._decode_finalize(fallback) msg = (f"{self.klass}.decode: unknown " f"type of data {type(data)}. Cannot decode.") error = EncodableError(msg, data={ 'target': target, 'data': data, 'error_squelch': error_squelch, 'reg_find_dotted': reg_find_dotted, 'reg_find_types': reg_find_types, 'fallback': fallback, }) raise self._log_exception(error, msg)
def _encode_encodable(self, target: Optional[Encodable], in_progress: Optional[EncodedComplex] = None, with_reg_field: bool = True) -> EncodedEither: ''' Encode `target` as a simple or complex encoding, depending on `target`.encoding(). If `target`.encoding() is SIMPLE, encodes to a string/number. Otherwise: - If `encode_in_progress` is provided, encodes this to a sub-field under `target.type_field()`. - Else encodes this to a dict and provides `target.type_field()` as the value of `target.TYPE_FIELD_NAME`. If `with_reg_field` is True, returns: An output dict with key/values: - ENCODABLE_REG_FIELD: `target.dotted` - ENCODABLE_PAYLOAD_FIELD: `target` encoded data Else returns: `target` encoded data ''' # TODO v://future/2021-03-14T12:27:54 self._log_data_processing(self.dotted, '_encode_encodable(): target: {}, ' 'in_progress: {}, with_reg_field: {}', target, in_progress, with_reg_field) encoded = None if null_or_none(target): # Null/None encode to None. return encoded encoding, encoded = target.encode(self) # TODO v://future/2021-03-14T12:27:54 self._log_data_processing(self.dotted, '_encode_encodable(): Encoded.\n' ' encoding: {}\n' ' data: {}', encoding, encoded) # --- # Encoding.SIMPLE # --- # If we encoded it simply, we're basically done. if encoding.has(Encoding.SIMPLE): # If there's an in_progress that's been pass in, and we just # encoded ourtarget to a string... That's a bit awkward. But I # guess do this. Will make weird-ish looking stuff like: 'v.mid': # 'v.mid:1' if in_progress is not None: in_progress[target.type_field()] = encoded # TODO v://future/2021-03-14T12:27:54 self._log_data_processing( self.dotted, '_encode_encodable(): Simple encoding was inserted into ' '`in_progress` data and is complete.\n' ' field: {}\n' ' in_progress: {}', target.type_field(), in_progress) return in_progress # TODO v://future/2021-03-14T12:27:54 self._log_data_processing( self.dotted, '_encode_encodable(): Simple encoding is complete.\n' ' encoded: {}', encoded) return encoded # --- # Encoding.COMPLEX # --- # Put the type somewhere and return encoded data. if in_progress is not None: # Encode as a sub-field in the provided data. in_progress[target.type_field()] = encoded # TODO v://future/2021-03-14T12:27:54 self._log_data_processing( self.dotted, '_encode_encodable(): Complex encoding inserted into ' '`in_progress` data.\n' ' field: {}\n' ' in_progress: {}', target.type_field(), in_progress) return in_progress encoded[target.TYPE_FIELD_NAME] = target.type_field() # TODO v://future/2021-03-14T12:27:54 self._log_data_processing( self.dotted, '_encode_encodable(): Complex encoding had type-field ' 'added to its data.\n' ' field: {}\n' ' data: {}', target.type_field(), encoded) # Encode with reg/payload fields if requested. if with_reg_field: enc_with_reg = { Encodable.ENCODABLE_REG_FIELD: target.dotted, Encodable.ENCODABLE_PAYLOAD_FIELD: encoded, } # TODO v://future/2021-03-14T12:27:54 self._log_data_processing( self.dotted, '_encode_encodable(): Complex encoding had reg-field ' 'added to its data and is complete.\n' ' reg: {}\n' ' value: {}\n' ' payload: {}\n' ' value: {}\n' ' encoded: {}', Encodable.ENCODABLE_REG_FIELD, target.dotted, Encodable.ENCODABLE_PAYLOAD_FIELD, encoded, enc_with_reg) return enc_with_reg # Or just return the encoded data. # TODO v://future/2021-03-14T12:27:54 self._log_data_processing( self.dotted, '_encode_encodable(): Complex encoding is complete.\n' ' encoded: {}', encoded) return encoded
def is_timed_out(self, timer: TimerInput, timeout: TimeoutInput = None) -> bool: ''' Calls `get_timer(timer)` on str/timer/None provided. See `get_timer` for details. TL;DR: - None -> Get/use default timer. - str -> Get/use timer by name. - timer -> Use that timer. If `timeout` is None, uses _DEFAULT_TIMEOUT_SEC. If `timeout` is a number, uses that. If `timeout` is a string, checks config for a setting associated with that key under the TimeManager's settings. Returns true if timeout timer is: - Not timing. - Past timeout value. - (Past _DEFAULT_TIMEOUT_SEC if timeout value is None.) ''' # ------------------------------ # Get a Timer based on input. # ------------------------------ check_timer = self.get_timer(timer) if not check_timer: msg = ("is_timed_out() requires a timer or timer name. " f"Got '{timer}' which didn't resolve to a timer: " f"{check_timer}") raise self._log_exception(ValueError(msg, timer, timeout), msg + f", timeout: {timeout}") # Verified it, so we can assign it `timer` since we don't need to know # what the input value was anymore. timer = check_timer # Not timing - not sure? Returning timed out is a good bet for figuring # out who forgot to start their timer, so use that. if not timer.timing: return True # ------------------------------ # Figure out the timeout. # ------------------------------ if timeout and isinstance(timeout, str): config = background.config.config(self.klass, self.dotted, None, raises_error=False) if not config: self._log_info( "TimeManager cannot get config for checking " "timeout value of '{}'", timeout) timeout = self._DEFAULT_TIMEOUT_SEC else: try: timeout = config.get('engine', 'time', 'timeouts', timeout) if null_or_none(timeout): timeout_dotted = label.normalize( 'engine', 'time', 'timeouts', timeout) self._log_warning("TimeManager didn't find timeout " f"for '{timeout_dotted}' in the " "config.") timeout = self._DEFAULT_TIMEOUT_SEC # If it's not a duration, set to the default. elif not time.is_duration(timeout): timeout = self._DEFAULT_TIMEOUT_SEC # If it's not a float, (try to) convert it to one. if not isinstance(timeout, float): timeout = time.to_float(timeout) except ConfigError: self._log_info( "TimeManager cannot get config " "for checking timeout value of '{}'", timeout) timeout = self._DEFAULT_TIMEOUT_SEC # Timeout should be a float now. # ------------------------------ # Use default? # ------------------------------ if not timeout or timeout <= 0: timeout = self._DEFAULT_TIMEOUT_SEC # ------------------------------ # Now we can finally check if timed out. # ------------------------------ timed_out = timer.timed_out(timeout) return timed_out
def registration(configuration: Configuration) -> None: ''' Searches for all of Veredi's required registries, registrars, registrees, and invisible elephants. Eagerly loads them so they are available at run-time when needed. ''' log_dotted = label.normalize(_DOTTED, 'registration') log.group_multi( _LOG_INIT, log_dotted, "Importing and loading registries, " "registrars & registrees...") # --- # Sanity. # --- if not configuration: msg = "Configuration must be provided." log.group_multi(_LOG_INIT, log_dotted, msg) error = ConfigError(msg, data={ 'configuration': str(configuration), }) raise log.exception(error, msg) # --- # Find all registry modules. # --- log.group_multi(_LOG_INIT, log_dotted, "Finding registry modules...") successes = [] failures = [] registrations = configuration.get(ConfigRegistration.KEY.value) if null_or_none(registrations): cfg_str, cfg_data = configuration.info_for_error() msg = ("No registration settings in configuration. " "Registration settings required if running registration.") log.group_multi(_LOG_INIT, log_dotted, msg + "\n {}", data={ 'configuration': cfg_str, 'settings': cfg_data, }, log_minimum=log.Level.ERROR, log_success=False) error = ConfigError(msg, data={ 'configuration': str(configuration), 'registrations': registrations, }) raise log.exception(error, msg) log.group_multi(_LOG_INIT, log_dotted, f"{len(registrations)} registration entries to run.") for entry in registrations: # If a config exception is raised, ok. Otherwise track success/failure. registered, dotted = _register_entry(configuration, entry, log_dotted) if registered: successes.append(dotted) else: failures.append(dotted) log.group_multi( _LOG_INIT, log_dotted, "Registration completed.\n" f" Attempted: {len(registrations)}\n" f" Succeeded: {len(successes)}\n" f" Failed: {len(failures)}\n", "{data}", # TODO v://future/2021-03-14T12:27:54 # And get rid of that '\n' data={ 'success': successes, 'failure': failures, }) # --- # Done. # --- # Did we completely succeed? success = log.SuccessType.success_or_failure(successes, failures) log.group_multi(_LOG_INIT, log_dotted, "Done with registration importing & loading.", log_success=success)
def encode(self, target: EncodableAny, in_progress: Optional[EncodedComplex] = None, with_reg_field: bool = True) -> EncodedEither: ''' Encode `target`, depending on target's type and encoding settings. See typing of `target` for all encodable types. If target is Null or None: - returns None If target is an Encodable or enum with registered Encodable wrapper: - Encodes using Encodable functionality. See `_encode_encodable()` for details. If target is a Mapping: - Encodes as a dictionary. If target is a non-Encodable py_enum.Enum: - Encodes target.value. If target is an EncodeAsIs type: - Returns as-is; already 'encoded'. Else raises an EncodableError. ''' # log.debug(f"{self.klass}.encode: {target}") encoded = None if null_or_none(target): # Null/None encode to None. return encoded # Translate enum to its wrapper if needed/supported. if enum.is_encodable(target): input_enum = target target = enum.wrap(target) self._log_data_processing(self.dotted, "codec.encode: enum found. " "enum: {} -wrap-> {}", input_enum, target) if isinstance(target, Encodable): # Encode via its function. encoded = self._encode_encodable(target, in_progress, with_reg_field) elif isinstance(target, collections.abc.Mapping): # Encode via our map helper. encoded = self.encode_map(target) elif isinstance(target, py_enum.Enum): # Assume, if it's a py_enum.Enum (that isn't an Encodable), that # just value is fine. If that isn't fine, the enum can make itself # an Encodable. encoded = target.value elif (isinstance(target, time.DateTypesTuple) or isinstance(target, SimpleTypesTuple)): encoded = self._encode_simple_types(target) else: msg = (f"Do not know how to encode type '{type(target)}'.") error = EncodableError(msg, data={ 'target': target, 'in_progress': in_progress, 'with_reg_field': with_reg_field, }) raise self._log_exception(error, msg) # log.debug(f"{self.klass}.encode: Done. {encoded}") return encoded
def _configure(self, context: Optional[ConfigContext], require_config: bool = True) -> None: ''' Allows repos to grab anything from the config data that they need to set up themselves. ''' self._log_group_multi(self._LOG_INIT, self.dotted, "FileRepository(Base) configure...") # ------------------------------ # Get Config. # ------------------------------ config = background.config.config(self.klass, self.dotted, context, raises_error=require_config) if null_or_none(config): if not require_config: self._log_group_multi(self._LOG_INIT, self.dotted, "Config not required and is Null/None.", log_minimum=log.Level.DEBUG) else: self._log_group_multi(self._LOG_INIT, self.dotted, "Config required and is Null/None!", log_minimum=log.Level.ERROR, log_success=False) msg = (f"{self.klass}: " "Configuration required, but found Null/None!") raise background.config.exception(context, msg) # ------------------------------ # Game ID # ------------------------------ # Grab our primary id from the context too. self._primary_id = ConfigContext.id(context) self._log_group_multi(self._LOG_INIT, self.dotted, "Set primary-id to: {}", self._primary_id, log_minimum=log.Level.DEBUG) # ------------------------------ # Paths # ------------------------------ self._log_group_multi(self._LOG_INIT, self.dotted, "Setting up paths...", self._primary_id, log_minimum=log.Level.DEBUG) # --- # Path Safing # --- self._init_path_safing(context, require_config) # --- # Repo Paths # --- # Start at ConfigContext's path... self._root = ConfigContext.path(context) # ...and then it depends. if not require_config: # No config required. So the root is.... done. self._log_group_multi(self._LOG_INIT, self.dotted, "No config required; set root to " "context path: {}", self._root, log_minimum=log.Level.DEBUG) else: # We have a config. So add config's repo path on top of it (in case # it's a relative path (pathlib is smart enough to correctly handle # when it's not)). self._root = self._root / paths.cast( config.get_data(*self._PATH_KEYCHAIN)) # Resolve it to turn into absolute path and remove ".."s and stuff. self._root = self._root.resolve() self._log_group_multi(self._LOG_INIT, self.dotted, "Set root based on context and config: {}", self._root, log_minimum=log.Level.DEBUG) # Now we can set the temp root based on root. self._root_temp = self._path_temp() self._log_group_multi(self._LOG_INIT, self.dotted, "Set root-temp to: {}", self._root_temp, log_minimum=log.Level.DEBUG) # ------------------------------ # Background # ------------------------------ # Add our data to the background context. self._make_background() self._log_group_multi(self._LOG_INIT, self.dotted, "Made background data.", log_minimum=log.Level.DEBUG) # ------------------------------ # Done. # ------------------------------ self._log_group_multi(self._LOG_INIT, self.dotted, "FileRepository._configure() completed.", log_minimum=log.Level.DEBUG)
def _serialize_prep(self, data: SerializeTypes, codec: Codec, context: 'VerediContext') -> Mapping[str, Any]: ''' Tries to turn the various possibilities for data (list, dict, etc) into something ready for json to serialize. ''' self._log_data_processing(self.dotted, "Serialize preparation...", context=context) serialized = None if null_or_none(data): self._log_data_processing(self.dotted, "No data to prep.", context=context) return serialized # Is it just an Encodable object? if isinstance(data, Encodable): self._log_data_processing(self.dotted, "Encoding `Encodable` data " "for serialization.", context=context) serialized = codec.encode(data) return serialized # Is it a simple type? if text.serialize_claim(data) or time.serialize_claim(data): # Let json handle it. serialized = data return serialized if paths.serialize_claim(data): serialized = paths.serialize(data) return serialized if numbers.serialize_claim(data): serialized = numbers.serialize(data) return serialized # Mapping? with contextlib.suppress(AttributeError, TypeError): # Do the thing that spawns the exception before # we log about doing the thing... keys = data.keys() self._log_data_processing(self.dotted, "Prepping `Mapping` of data " "for serialization.", context=context) serialized = {} for each in keys: # TODO [2020-07-29]: Change to non-recursive? serialized[str(each)] = self._serialize_prep(data[each], codec, context) return serialized # Iterable with contextlib.suppress(AttributeError, TypeError): # Do the thing that spawns the exception before # we log about doing the thing... iterable = iter(data) self._log_data_processing(self.dotted, "Prepping `Iterable` of data " "for serialization.", context=context) serialized = [] for each in iterable: # TODO [2020-07-29]: Change to non-recursive? serialized.append(self._serialize_prep(each, codec, context)) return serialized # Falling through to here is bad; raise Exception. msg = f"Don't know how to process '{type(data)}' data." self._log_data_processing(self.dotted, msg, context=context, success=False) error = exceptions.WriteError(msg, context=context, data={ 'data': data, }) raise log.exception(error, msg, context=context)