예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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)
예제 #7
0
    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
예제 #8
0
 def from_logging(lvl: LogLvlConversion) -> 'Level':
     if null_or_none(lvl):
         return Level.NOTSET
     return Level(lvl)
예제 #9
0
 def to_logging(lvl: LogLvlConversion) -> int:
     if null_or_none(lvl):
         lvl = Level.NOTSET
     return int(lvl)
예제 #10
0
 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)
예제 #11
0
    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)
예제 #12
0
    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
예제 #13
0
    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
예제 #14
0
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)
예제 #15
0
    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
예제 #16
0
    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)
예제 #17
0
    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)