コード例 #1
0
ファイル: enum.py プロジェクト: cole-brown/veredi-code
    def decode_simple(klass: Type['FlagEncodeValue'], data: str,
                      codec: 'Codec') -> 'FlagEncodeValue':
        '''
        Decode ourself from a string, return a new instance of `klass` as
        the result of the decoding.
        '''
        rx = klass._get_decode_rx()
        if not rx:
            msg = (f"{klass.klass}: No decode regex - "
                   f"- cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, but does it work on data?
        match = rx.match(data)
        if not match or not match.group('value'):
            msg = (f"{klass.klass}: Decode regex failed to match "
                   f"data - cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, have match. Init enum (not wrapping) from it.
        decoded = klass.type(int(match.group('value')))
        # print(f"FlagEncodeValue.decode_simple: {data} -> {decoded}")
        return decoded
コード例 #2
0
def run_mediator(conn: multiprocessing.connection.Connection = None,
                 config_path: Union[pathlib.Path, str] = None,
                 log_level: Union[log.Level, int] = None,
                 shutdown_flag: multiprocessing.Event = None) -> None:
    '''
    Init and run client/engine IO mediator.
    '''
    _sigint_ignore()
    log_client.init("outdated mediator", log_level)

    if not conn:
        lumberjack = log.get_logger(ProcessType.MEDIATOR.value)
        raise log.exception(
            ConfigError,
            "Mediator requires a pipe connection; received None.",
            veredi_logger=lumberjack)
    if not config_path:
        lumberjack = log.get_logger(ProcessType.MEDIATOR.value)
        raise log.exception(
            ConfigError,
            "Mediator requires a config file; received no path to one.",
            veredi_logger=lumberjack)
    if not log_level:
        lumberjack = log.get_logger(ProcessType.MEDIATOR.value)
        raise log.exception(
            ConfigError,
            "Mediator requires a default log level (int); received None.",
            veredi_logger=lumberjack)

    log.get_logger(
        ProcessType.MEDIATOR.value).critical("todo... server/mediator")
コード例 #3
0
ファイル: enum.py プロジェクト: cole-brown/veredi-code
    def decode_simple(klass: Type['FlagEncodeName'], data: str,
                      codec: 'Codec') -> 'FlagEncodeName':
        '''
        Decode ourself from a string, return a new instance of `klass` as
        the result of the decoding.
        '''
        rx = klass._get_decode_rx()
        if not rx:
            msg = (f"{klass.klass}: No decode regex - "
                   f"- cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, but does it work on data?
        match = rx.match(data)
        if not match or not match.group('names'):
            msg = (f"{klass.klass}: Decode regex failed to match "
                   f"data - cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, have match.
        # Chop up match by separator to build flags up.
        names = match.group('names').split('|')
        total = None
        # Ignore the blank ones. Use the rest to OR together flag enums.
        for name in filter(None, names):
            flag = klass.type[name]
            if total is None:
                total = flag
            else:
                total = total | flag

        # Return final OR'd enum instance.
        return total
コード例 #4
0
ファイル: enum.py プロジェクト: cole-brown/veredi-code
    def decode_simple(klass: Type['EnumEncodeName'], data: str,
                      codec: 'Codec') -> 'EnumEncodeName':
        '''
        Decode ourself from a string, return a new instance of `klass` as
        the result of the decoding.
        '''
        rx = klass._get_decode_rx()
        if not rx:
            msg = (f"{klass.klass}: No decode regex - "
                   f"- cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, but does it work on data?
        match = rx.match(data)
        if not match or not match.group('name'):
            msg = (f"{klass.klass}: Decode regex failed to match "
                   f"data - cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, have match.
        # Turn into an enum value.
        name = match.group('name')
        flag = klass.type[name]
        return flag
コード例 #5
0
ファイル: identity.py プロジェクト: cole-brown/veredi-code
    def decode_simple(klass: Type['SerializableId'],
                      data:  str,
                      codec: 'Codec') -> 'SerializableId':
        '''
        Decode ourself from a string, return a new instance of `klass` as
        the result of the decoding.

        Raises:
          - ValueError if value isn't a hyphen-separated, base 16 int string.
        '''
        rx = klass._get_decode_rx()
        if not rx:
            msg = (f"{klass.klass}: No decode regex - "
                   f"- cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, but does it work on data?
        match = rx.match(data)
        if not match or not match.group('value'):
            msg = (f"{klass.klass}: Decode regex failed to match "
                   f"data - cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Get actual value from match, remove nice separators so we have just a
        # hex number...
        hex_str = match.group('value')
        hex_str = hex_str.replace('-', '')
        # Convert that into an actual number.
        # Could throw a value error if something's wrong...
        hex_value = int(hex_str, 16)

        # And now we should be able to decode.
        return klass._decode_simple_init(hex_value, codec)
コード例 #6
0
ファイル: identity.py プロジェクト: cole-brown/veredi-code
    def decode_simple(klass: Type['MonotonicId'],
                      data:  str,
                      codec: 'Codec') -> 'MonotonicId':
        '''
        Decode ourself from a string, return a new instance of `klass` as
        the result of the decoding.
        '''
        rx = klass._get_decode_rx()
        if not rx:
            msg = (f"{klass.klass}: No decode regex - "
                   f"- cannot decode: {data}")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        # Have regex, but does it work on data?
        match = rx.match(data)
        if not match or not match.group('value'):
            msg = (f"{klass.klass}: Decode regex failed to match "
                   f"data - cannot decode: {data} "
                   f"(regex: {klass._get_decode_str_rx()})")
            error = ValueError(msg, data)
            raise log.exception(error, msg)

        value = int(match.group('value'))
        # And now we should be able to decode.
        return klass._decode_simple_init(value, codec)
コード例 #7
0
    def hierarchy(doc_type: 'Document') -> 'Hierarchy':
        '''
        Gets the Hierarchy sub-class for the `doc_type`.
        '''
        if doc_type is Document.INVALID:
            error = exceptions.ConfigError(
                "Document.INVALID has no hierarchy class.",
                data={
                    'doc_type': doc_type,
                })
            raise log.exception(
                error,
                "Should never be looking for INVALID document type.")

        elif doc_type is Document.METADATA:
            return MetadataHierarchy

        elif doc_type is Document.CONFIG:
            return ConfigHierarchy

        else:
            error = exceptions.ConfigError(
                "Unknown document type - cannot get hierarchy class.",
                data={
                    'doc_type': doc_type,
                })
            raise log.exception(
                error,
                "No case check for Document type: {}", doc_type)
コード例 #8
0
ファイル: record.py プロジェクト: cole-brown/veredi-code
    def verify_and_get(klass: ['DocType'], doc_type: Union[str,
                                                           enum.Enum]) -> str:
        '''
        Normalizes string, verifies that it is one of our DocTypes, and returns
        the validated, normalized string.

        Raises a ValueError if verification fails.
        '''
        # Sanity check / convert input to string.
        string = doc_type
        if isinstance(string, enum.Enum):
            string = doc_type.type
        if not isinstance(string, str):
            msg = (f"'{doc_type}' is not valid. Must be a string or "
                   "string-backed Enum. "
                   f"Got: {type(doc_type)}, value: {string}")
            raise log.exception(ValueError(msg, doc_type), msg)

        # Expect whatever - normalize to lowercase.
        string = text.normalize(string)

        # Chain together all our enums so as to do a dumb search:
        for each_type in itertools.chain(klass.types()):
            for each in each_type:
                if string == each.type:
                    return each.type

        # Didn't find it anywhere. Error out.
        msg = f"'{string}' is not a DocType value."
        raise log.exception(ValueError(msg, string), msg)
コード例 #9
0
    def start(self) -> None:
        '''
        Start our socket listening.
        '''
        self.debug("start: Starting clientt...")

        # Kick it off into asyncio's hands.
        try:
            asyncio.run(
                self._a_main(self._shutdown_watcher(), self._queue_watcher(),
                             self._med_queue_watcher(), self._server_watcher(),
                             self._test_watcher()))

        except websockets.exceptions.ConnectionClosedOK:
            pass

        except Exception as error:
            # TODO [2020-07-28]: Should we shut it all down, or keep going?
            self.shutdown = True
            import traceback
            trace = traceback.format_exc()
            log.exception(
                error,
                "Caught exception running MediatorClient coroutines:\n{}",
                trace)

        self.debug("start: Done.")
コード例 #10
0
    def _connect_manager(self) -> 'WebSocketClient':
        '''
        Manages attempts at connecting to the server.
        Manages: self._connect_request flag, self._connect_attempts
        '''
        # ------------------------------
        # Checks and Prep for Attempt.
        # ------------------------------
        self.debug("_connect_manager: Starting connection to server...")

        # We're trying to connect now, so we don't need our connect request
        # flag set anymore.
        self._clear_connect()

        # Error check: time to give up?
        if self._connect_attempts > self._MAX_CONNECT_ATTEMPT_FAILS:
            # Raise error to get out of context manager.
            msg = (f"{self.klass}: Failed to connect to "
                   f"the server! Failed {str(self._connect_attempts + 1)} "
                   "attempts.")
            error = ConnectionError(msg, None)
            raise log.exception(error, msg)

        # Increment our attempts counter, as we are now attempting.
        self._connect_attempts += 1

        # ------------------------------
        # Do an attempt.
        # ------------------------------
        try:
            self.debug(
                "_connect_manager: "
                "Yielding for Client->Server Connection Attempt {}/{} ",
                self._connect_attempts, self._MAX_CONNECT_ATTEMPT_FAILS)

            # "Do the code now."
            yield self

        # Always reraise all exceptions - we're in a `with` context.
        except Exception as error:
            log.exception(
                error, "Client->Server Connection Attempt {}/{} "
                "failed with error: {}", self._connect_attempts,
                self._MAX_CONNECT_ATTEMPT_FAILS, error)
            raise

        # ------------------------------
        # Done with management. Clean up.
        # ------------------------------
        # Don't clear attempts counter yet... We're done asking to connect.
        # Server needs to reply for us to actually connect successfully.
        # return self._connection_attempt_success()
        self.debug(
            "_connect_manager: "
            "Done managing Client->Server Connection Attempts. "
            "final: {}/{}", self._connect_attempts,
            self._MAX_CONNECT_ATTEMPT_FAILS)
コード例 #11
0
ファイル: codec.py プロジェクト: cole-brown/veredi-code
    def _encode_key(self, key: Any) -> str:
        '''
        Encode a dict key.
        '''
        # log.debug(f"\n\nlogging._encode_key: {key}\n\n")
        field = None

        if enum.is_encodable(key):
            input_enum = key
            key = enum.wrap(key)
            self._log_data_processing(self.dotted,
                                      "codec._encode_key: enum found. "
                                      "enum: {} -wrap-> {}",
                                      input_enum, key)

        # If key is an encodable, can it encode into a key?
        if isinstance(key, Encodable):
            if key.encoding().has(Encoding.SIMPLE):
                field = self._encode_encodable(key)
            else:
                msg = (f"{self.klass}._encode_key: Encodable "
                       f"'{key}' cannot be encoded into a key value for "
                       "a dict - only Encoding.SIMPLE can be used here.")
                error = EncodableError(msg,
                                       data={
                                           'key': key,
                                       })
                raise log.exception(error, msg)

        # Is key something simple?
        elif isinstance(key, SimpleTypesTuple):
            field = self._encode_simple_types(key)

        # If key is an enum that is not an Encodable, use it's value, I guess?
        elif isinstance(key, py_enum.Enum):
            field = self._encode_simple_types(key.value)

        # If key is a just a number, just use it.
        elif isinstance(key, numbers.NumberTypesTuple):
            field = numbers.serialize(key)

        # No idea... error on it.
        else:
            # # Final guess: stringify it.
            # field = str(key)
            msg = (f"{self.klass}._encode_key: Key of type "
                   f"'{type(key)}' is not currently supported for encoding "
                   " into a field for an encoded dictionary.")
            error = EncodableError(msg,
                                   data={
                                       'key': key,
                                   })
            raise log.exception(error, msg)

        # log.debug(f"\n\n   done._encode_key: {field}\n\n")
        return field
コード例 #12
0
    def _load(self) -> VerediHealth:
        '''
        Load our configuration data from its file.

        Raises LoadError
        '''
        log_groups = [log.Group.START_UP, log.Group.DATA_PROCESSING]
        log.group_multi(log_groups, self.dotted, "Configuration load...")

        # Spawn a context from what we know, and ask the config repo to load
        # something based on that.
        ctx = DataBareContext(self.dotted, ConfigContext.KEY, self._path,
                              DataAction.LOAD, self._meta())
        log.group_multi(log_groups, self.dotted,
                        "Configuration loading from repo...")
        with self._repo.load(ctx) as stream:
            # Decode w/ serdes.
            # Can raise an error - we'll let it.
            try:
                log.group_multi(log_groups, self.dotted,
                                "Configuration deserializing with serdes...")
                log.debug(
                    "Config Load Context: {}, "
                    "Confgig Repo: {}, "
                    "Confgig Serdes: {}", ctx, self._repo, self._serdes)
                for each in self._serdes.deserialize_all(
                        stream, self._codec, ctx):
                    log.debug("Config Loading Doc: {}", each)
                    self._load_doc(each)

            except LoadError as error:
                log.group_multi(log_groups,
                                self.dotted,
                                "Configuration load/deserialization failed "
                                "with a LoadError. Erroring out.",
                                log_success=False)
                # Log exception and let bubble up as-is.
                raise log.exception(
                    error, "Configuration init load/deserialization failed "
                    "with a LoadError:  type: {}, str: {}", type(error),
                    str(error))

            except Exception as error:
                log.group_multi(log_groups,
                                self.dotted,
                                "Configuration load/deserialization failed "
                                "with an error of type {}. Erroring out.",
                                type(error),
                                log_success=False)
                # Complain that we found an exception we don't handle.
                # ...then let it bubble up as-is.
                raise log.exception(LoadError,
                                    "Unhandled exception! type: {}, str: {}",
                                    type(error), str(error)) from error

        return VerediHealth.HEALTHY
コード例 #13
0
def _internal_register(tag: str, klass: Type) -> None:
    '''
    Add YAML `tag` / `klass` type to both tag->class and class->tag registries.

    Try to prevent overwriting one tag/class pair with a different pair that
    shares a value. That is, try to prevent a tag from being stolen from a
    class and vice versa.

    DO NOT try to prevent re-registrations. Currently files register stuff when
    imported, and we don't want to have to police some weird 'only import once'
    thing.
    '''
    # Just make sure they have a good tag name...
    valid, reason = tags.valid(tag)
    if not valid:
        msg = (f"Invalid tag '{tag}'! {reason}")
        error = RegistryError(msg, data={
            'tag': tag,
            'class': klass,
        })
        raise log.exception(error, msg)

    # Prevent overwrites.
    if tag in _TAG_TO_CLASS and _TAG_TO_CLASS[tag] != klass:
        ex_klass = _TAG_TO_CLASS[tag]
        msg = "Tag already exists in registry. "
        error = RegistryError(msg,
                              data={
                                  'tag': tag,
                                  'class': klass,
                                  'existing_class': ex_klass,
                              })
        msg += (f"Existing: {tag} -> {ex_klass}. "
                f"Requested: {tag} -> {klass}.")
        raise log.exception(
            None, RegistryError, "Tag already exists in registry. "
            f"Existing: {tag} -> {ex_klass}. "
            f"Requested: {tag} -> {klass}.")

    if klass in _CLASS_TO_TAG and _CLASS_TO_TAG[klass] != tag:
        ex_tag = _CLASS_TO_TAG[klass]
        msg = "Class already exists in registry. "
        error = RegistryError(msg,
                              data={
                                  'tag': tag,
                                  'class': klass,
                                  'existing_tag': ex_tag,
                              })
        msg += (f"Existing: {klass} -> {ex_tag}. "
                f"Requested: {klass} -> {tag}.")
        raise log.exception(error, msg)

    # Add to both registries.
    _TAG_TO_CLASS[tag] = klass
    _CLASS_TO_TAG[klass] = tag
コード例 #14
0
ファイル: mediator.py プロジェクト: cole-brown/veredi-code
    async def _test_watcher(self) -> None:
        '''
        Looks for a unit testing message to take from unit testing pipe and
        handle. It is always for this mediator.
        '''
        while True:
            # Die if requested.
            if self.any_shutdown():
                break

            # Check for something in connection; don't block.
            if not self._test_has_data():
                await self._continuing()
                continue

            # Get that something, do something with it.
            try:
                msg, ctx = self._test_pipe_get()
                self._log_data_processing(self.dotted,
                                          "_test_watcher: "
                                          "{} got from test pipe:\n"
                                          "  data: {}\n"
                                          "   ctx: {}",
                                          self.name, msg, ctx)

            except EOFError as error:
                log.exception(error,
                              "_test_watcher: "
                              "Failed getting from test pipe; "
                              "ignoring and continuing.")
                # EOFError gets raised if nothing left to receive or other end
                # closed. Wait til we know what that means to our test/mediator
                # pair before deciding to take (drastic?) action here...

            if msg == _UT_ENABLE:
                self._testing = True
                self.debug("_test_watcher: Enabled unit testing flag.")

            elif msg == _UT_DISABLE:
                self._testing = True
                self.debug("_test_watcher: Disabled unit testing flag.")

            else:
                error_msg = ("_test_watcher: "
                             "{self.name} doesn't know what to do "
                             "with received testing message: {}")
                raise log.exception(
                    ValueError(error_msg.format(self.name, msg)),
                    error_msg,
                    self.name,
                    msg)

            # Done processing; sleep a bit then continue.
            await self._continuing()
コード例 #15
0
    def _merge(self, m_from: Optional['VerediContext'],
               m_to: Optional['VerediContext'], resolution: Conflict,
               verb: str, preposition: str) -> None:
        '''
        Merge 'from' context into 'to' context.

        Assignment/shallow copy (not deep copy, currently).
        '''
        if m_from is None or m_to is None:
            msg = f"Cannot {verb} a 'None' context. from: {m_from}, to: {m_to}"
            error = ContextError(msg,
                                 context=self,
                                 data={
                                     'm_from': m_from,
                                     'm_to': m_to,
                                     'resolution': resolution,
                                 })
            raise log.exception(error, msg, context=self)

        elif m_to is m_from:
            msg = (f"Cannot {verb} something with itself. "
                   f"from: {m_from}, to: {m_to}")
            error = ContextError(msg,
                                 context=self,
                                 data={
                                     'm_from': m_from,
                                     'm_to': m_to,
                                     'resolution': resolution,
                                 })
            raise log.exception(error, msg, context=self)

        elif isinstance(m_to, PersistentContext):
            msg = (f"Cannot {verb} {preposition} a PersistentContext. "
                   f"from: {m_from}, to: {m_to}")
            error = ContextError(msg,
                                 context=self,
                                 data={
                                     'm_from': m_from,
                                     'm_to': m_to,
                                     'resolution': resolution,
                                 })
            raise log.exception(error, msg, context=self)

        elif isinstance(m_from, dict) or isinstance(m_to, dict):
            # This was for catching any "a context is a dict" places that still
            # existed back when VerediContext was created. It can probably be
            # deleted someday.
            raise TypeError('Context needs to merge with Context, not dict. ',
                            m_from, m_to)

        d_from = m_from._get()
        d_to = m_to._get()
        self._merge_dicts(d_from, d_to, resolution, verb, preposition)
コード例 #16
0
ファイル: mediator.py プロジェクト: cole-brown/veredi-code
    async def _handle_produce_get_msg(
        self, conn: UserConnToken
    ) -> Tuple[Union[Message, None, Literal[False]], Union[
            MessageContext, None, Literal[False]]]:
        '''
        Looks for a message to take from produce buffer(s) and return for
        sending.

        Returns:
          - (False, False) if it found nothing to produce/send.
          - (Message, MessageContext) if it found something to produce/send.
            - Could be Nones or MsgType.IGNORE.
        '''
        # Give our lil' queue priority over game...
        if self._med_tx_has_data():
            try:
                msg, ctx = self._med_tx_get()
            except asyncio.QueueEmpty:
                # get_nowait() got nothing. That's fine; go on to check other
                # things.
                pass
            else:
                # Someone else check that this isn't None, plz.
                if msg.type == MsgType.IGNORE:
                    self.debug(
                        "_handle_produce_get_msg: "
                        "send: ignoring IGNORE msg: {}", msg)
                else:
                    self.debug(
                        "_handle_produce_get_msg: "
                        "send: mediator message: {}", msg)
                    return msg, ctx

        # Check for something in game connection to send; don't block.
        if self._game_has_data():
            try:
                msg, ctx = self._game_pipe_get()
                # Just return; let caller deal with if these are none, ignore,
                # etc.
                return msg, ctx

            except EOFError as error:
                log.exception(
                    error, "Failed getting from game pipe; "
                    "ignoring and continuing.")
                # EOFError gets raised if nothing left to receive or other end
                # closed. Wait til we know what that means to our game/mediator
                # pair before deciding to take (drastic?) action here...

        # Nothing in queues. Sleep a bit then return.
        await self._sleep()
        return False, False
コード例 #17
0
ファイル: registrar.py プロジェクト: cole-brown/veredi-code
    def get_by_dotted(self, dotted: label.LabelInput,
                      context: Optional[VerediContext]) -> 'RegisterType':
        '''
        Get by dotted name.

        Returns a registered class/func from the dot-separated keys (e.g.
        "repository.player.file-tree").

        Context just used for errors/exceptions.

        Raises:
          KeyError - dotted string not found in our registry.
        '''
        registration = self._registry
        split_keys = label.regularize(dotted)

        # ---
        # Walk into our registry using the keys for our path.
        # ---
        i = 0
        for key in split_keys:
            if registration is None:
                break
            # This can throw the KeyError...
            try:
                registration = registration[key]
            except KeyError as error:
                raise log.exception(
                    RegistryError,
                    "Registry has nothing at: {} (full path: {})",
                    split_keys[:i + 1],
                    split_keys,
                    context=context) from error
            i += 1

        # ---
        # Sanity Check - ended at leaf node?
        # ---
        if isinstance(registration, dict):
            raise log.exception(RegistryError,
                                "Registry for '{}' is not at a leaf - "
                                "still has entries to go: {}",
                                label.normalize(dotted),
                                registration,
                                context=context)

        # Good; return the leaf value (a RegisterType).
        return registration
コード例 #18
0
    def create_from_label(
            self,
            dotted_str: label.DotStr,
            # Leave (k)args for people who are not me...
            *args: Any,
            context: Optional['VerediContext'] = None,
            **kwargs: Any) -> Any:
        '''
        Mediator between any game systems that don't care about any deep
        knowledge of veredi basics. Pass in a 'dotted' registration string,
        like: "veredi.rules.d11.health", and we will ask our registry to create
        it.

        Catches all exceptions and rewraps outside errors in a VerediError.
        '''
        try:
            retval = registrar.config.invoke(dotted_str, context, *args,
                                             **kwargs)

        except VerediError:
            # Ignore these and subclasses - bubble up.
            raise

        except Exception as error:
            raise log.exception(ConfigError,
                                "Configuration could not create '{}'. "
                                "args: {}, kwargs: {}",
                                dotted_str,
                                args,
                                kwargs,
                                context=context) from error

        return retval
コード例 #19
0
    def is_solo(flag: 'FlagCheckMixin') -> bool:
        '''
        Returns True if this instance is exactly one flag bit.
        Returns False if this instance has:
          - More than one bit set.
          - Zero bits set.
          - Invalid `flag` value (e.g. None).
        '''
        # ---
        # Simple Cases
        # ---

        if not isinstance(flag, enum.Flag):
            msg = (f"FlagCheckMixin.is_solo: '{flag}' is not an "
                   "enum.Flag derived type.")
            error = ValueError(msg, flag)
            raise log.exception(error, msg)

        # A nice clean-looking way of doing, but could miss some cases. For
        # example, if a flag mask exists as a value in the enum and the flag
        # value passed in happens to be equal to that mask.
        if flag not in flag.__class__:
            return False

        # ---
        # Not As Simple Case
        # ---

        # Count the bits set to get a sure answer. Apparently this ridiculous
        # "decimal number -> binary string -> count characters" is fast for
        # small, non-parallel/vectorizable things.
        #   https://stackoverflow.com/a/9831671/425816
        bits_set = bin(flag.value).count("1")
        return bits_set == 1
コード例 #20
0
ファイル: group.py プロジェクト: cole-brown/veredi-code
    def split_key(self,
                  full_key: str,
                  allow_only_member_key: bool = False) -> Tuple[str, str]:
        '''
        Splits a full-key into a tuple of (group-key, member-key).
        '''
        if isinstance(full_key, (UserDefinedMarker,
                                 KeyGroupMarker,
                                 KeyGroup)):
            full_key = full_key.name

        retval = None
        result = self._re_member_key.match(full_key)
        if result:
            group = self.normalize(result.group('group'))
            member = self.normalize(result.group('member'))
            retval = (group, member)
        elif allow_only_member_key:
            # Couldn't match our full-name regex to get a member-key out...
            # assume that we were given a member-key instead?
            retval = (None, self.normalize(full_key))
        else:
            msg = f"Could not split '{full_key}' into (group, member) keys."
            raise log.exception(
                ValueError(msg, full_key, allow_only_member_key),
                msg)

        return retval
コード例 #21
0
ファイル: background.py プロジェクト: cole-brown/veredi-code
    def exception(klass:      Type['config'],
                  context:    Optional['VerediContext'],
                  msg:        Optional[str],
                  *args:      Any,
                  error_data: Optional[Mapping[Any, Any]] = None,
                  **kwargs:   Any) -> None:
        '''
        Calls log.exception() to raise a ConfigError with message built from
        msg, args, kwargs and with supplied context.

        NOTE: If no context is supplied, WILL NOT LOG ANY! Provide
        config.make_config_context() if you are using it as your default!

        Sets stack level one more than usual so that caller of this should be
        the stacktrace of the exception.

        If optional `error_data` is not None, it will be supplied to the
        created ConfigError as the `data` parameter in the constructor.
        '''
        kwargs = log.incr_stack_level(kwargs)
        # If we raised instead of returned, we could add an extra stacklevel to
        # get the log back to whoever called us...
        #                             amount=2)

        # Let a generic ConfigError be made.
        return log.exception(
            ConfigError,
            msg, *args, **kwargs,
            error_data=error_data,
            context=context)
コード例 #22
0
ファイル: event.py プロジェクト: cole-brown/veredi-code
    def add_alias(self,
                  cmd_name: str,
                  equivalent: Optional[str] = None) -> None:
        '''
        Add an alias command to this command. For example:
            If the command is 'ability', there could also be a 'strength'
            command, but:
                'strength <whatever>'
            is probably basically
                'ability strength <whatever>'
            So, make 'strength' just an alias of 'ability', and your system
            will only have to deal with the one command. The InputSystem will
            translate an alias into its underlying command.
        '''
        cmd_name = text.normalize(cmd_name)
        equivalent = text.normalize(equivalent)

        if not equivalent.startswith(self.name):
            raise log.exception(
                CommandRegisterError,
                "An alias' `equivalent` command must start with the actual"
                "command. '{}' must start with '{}' for '{}' to be an alias of"
                "this command.",
                equivalent,
                self.name,
                cmd_name)

        self.aliases[cmd_name] = equivalent
コード例 #23
0
ファイル: enum.py プロジェクト: cole-brown/veredi-code
    def encode_simple(self, codec: 'Codec') -> str:
        '''
        Encode ourself as a string, return that value.
        '''
        # These will be all our separated flags' names.
        names = []

        # Haven't found a better way without using power-of-two assumption for
        # names that I don't want to because we're just a ...
        #
        # Iterate over /all/ flags defined.
        for each in self.enum.__class__:
            if each in self.enum:
                names.append(each.name)

        if not names:
            msg = (f"{self.klass}: No enum values found?! "
                   f"'{str(self)}' didn't resolve to any of its class's "
                   "enums values.")
            error = ValueError(msg, self, names)
            raise log.exception(error, msg)

        # Turn list of names into one string for final return string.
        return self._ENCODE_SIMPLE_FMT.format(type_field=self.type_field(),
                                              names='|'.join(names))
コード例 #24
0
    def message(self, msg_id: MsgIdTypes, security_subject: 'abac.Subject',
                user: BaseUser) -> Optional[Message]:
        '''
        Creates a message for the `user` at `security_subject`.

        Returns None if no message for that recipient.
        '''
        # ---
        # Sanity Checks
        # ---
        if not security_subject.is_solo:
            err_msg = ("security_subject must be a single value."
                       f"Got: '{security_subject}' for user {user}.")
            error = ValueError(err_msg, security_subject, user)
            raise log.exception(error, err_msg)

        # -------------------------------
        # Payload == OutputEvent's Output
        # -------------------------------
        payload = self._event.output

        # -------------------------------
        # Build Message
        # -------------------------------
        message = Message(
            msg_id,
            MsgType.ENCODED,
            payload=payload,
            # entity_id=user.entity_prime,
            user_id=user.id,
            user_key=user.key,
            subject=security_subject)
        return message
コード例 #25
0
    def _set_sub(self,
                 key: str,
                 sub_ctx: NullNoneOr[Dict[Any, Any]] = None,
                 overwrite: bool = False) -> None:
        '''
        Sets the /specified/ sub-context dictionary.

        If `sub_ctx` is Null, None, or otherwise Falsy: pop the sub out of
        existance.

        If `overwrite` is False and `key` already exists, raises a KeyError.
        '''
        full_ctx = self._get(False)

        # Remove sub-context?
        if not sub_ctx:
            full_ctx.pop(key, None)
            # Done
            return

        # Overwrite check.
        if not overwrite and key in full_ctx:
            msg = (f"Cannot set sub-context. Key '{key}' already exists "
                   "in context and `overwrite` is not set.")
            error = KeyError(key, msg, full_ctx, sub_ctx)
            raise log.exception(error, msg)

        # Set the sub-context.
        full_ctx[key] = sub_ctx
コード例 #26
0
    async def ping(self, msg: Message, context: MediatorContext) -> float:
        '''
        Send out a ping, wait for pong (response) back. Returns the time it
        took in fractional seconds.
        '''
        if not self._socket:
            log.error(f"Cannot ping; no socket connection: {self._socket}")
            return

        if msg.type != MsgType.PING:
            error = ValueError("Requested ping of non-ping message.", msg)
            raise log.exception(error,
                                f"Requested ping of non-ping message: {msg}")

        timer = MonotonicTimer()  # Timer starts timing on creation.

        # Run our actual ping.
        self.debug('ping pinging...')
        pong = await self._socket.ping()
        self.debug('ping ponging...')
        await pong
        self.debug('ping ponged.')

        # Return the ping time.
        self.debug('ping: {}', timer.elapsed_str)
        return timer.elapsed
コード例 #27
0
    def _read(self,
              stream:  Union[TextIO, str],
              codec:   Codec,
              context: 'VerediContext') -> _DeserializeMidTypes:
        '''
        Read data from a single data stream.

        Returns:
          Output of yaml.safe_load().
          Mix of:
            - yaml objects
            - our subclasses of yaml objects
            - and python objects

        Raises:
          - exceptions.ReadError
          Maybes:
            - Other yaml/stream errors?
        '''
        self._log_data_processing(self.dotted,
                                  "Reading from '{}'...",
                                  type(stream),
                                  context=context)
        if isinstance(stream, TextIOBase):
            self._log_data_processing(self.dotted,
                                      "Seek to beginning first.",
                                      context=context)
            # Assume we are supposed to read the entire stream.
            stream.seek(0)

        data = None
        try:
            data = yaml.safe_load(stream)
            # TODO [2020-07-04]: may need to evaluate this in some way to get
            # it past its lazy loading... I want to catch any yaml exceptions
            # here and not let them infect unrelated code.
        except yaml.YAMLError as yaml_error:
            data = None
            error_info = {
                'data': stream,
            }
            error_info = self._stream_data(stream, error_info)
            msg = 'YAML failed while reading the data.'
            self._log_data_processing(self.dotted,
                                      msg,
                                      context=context,
                                      success=False)
            error = exceptions.ReadError(
                msg,
                context=context,
                data=error_info)
            raise log.exception(error, msg, context=context) from yaml_error

        self._log_data_processing(self.dotted,
                                  "Read YAML from '{}'!",
                                  type(stream),
                                  context=context,
                                  success=True)
        return data
コード例 #28
0
ファイル: definition.py プロジェクト: cole-brown/veredi-code
    def _canon_this(self, canon: List[str], these: List[str],
                    next: str) -> List[str]:
        '''
        If a 'this' was found, it was replaced with the milieu. So we'll have
        one of these kinds of lists to deal with:

        ['speed']  <- No 'this'.
        ['agility', 'ranks']  <- No 'this'.
        [['strength', 'modifier'], 'score']  <- Had a 'this'.

        But _canon_make() has been going through, and we only get what's been
        canonicalized, and a 'this' that got replaced with 'these'. We need to
        figure out what is canon now. So we get `next` to peek at the next
        thing in order to figure out a resolution.

        ['speed']  <- No 'this'.
        ['agility', 'ranks']  <- No 'this'.
        [['strength', 'modifier'], 'score']  <- Had a 'this'.

        ['speed']  <- No 'this'.
        ['agility', 'ranks']  <- No 'this'.
        [['strength', 'modifier']]  <- Had a 'this'; no next.

        Returns list of canonical names. Using our examples:
          ['speed']
            - No 'this'; we're not responsible for adding in default.
          ['agility', 'ranks']
            - No 'this'; was complete.
          ['strength', 'score']
            - Had a 'this'; figured out correct replacement value.
          [['strength', 'modifier']]
            - Had a 'this'; no next; fail on purpose.
        '''
        if not next:
            msg = ("Trying to 'un-this' in order to canonicalize a name, but "
                   "there is nothing up next to warrent a 'this' reference? "
                   f"canon-so-far: {canon}, 'this': {these}, next: {next}")
            error = ValueError(msg, canon, these, next)
            raise log.exception(error, msg)

        # Push canon input into canon output; walk down definitions tree while
        # we're at it.
        canon_this = []
        bookmark = self[self._key_prime]
        for name in canon:
            bookmark = bookmark[name]
            canon_this.append(name)

        # Check each piece of the 'this' pie to see if it fits or not with
        # what's up next.
        return_these = []  # We only want to return pieces of `these`.
        for name in these:
            if (name in bookmark and next in bookmark[name]):
                canon_this.append(name)
                return_these.append(name)

        # Don't add next - that's the caller's job. We just needed to
        # do our job.
        return return_these
コード例 #29
0
def _start_server(comms: multiproc.SubToProcComm,
                  context: VerediContext) -> None:
    '''
    Entry function for our mediator server.

    Basically create mediator from config and call its `start()`.
    '''

    # ------------------------------
    # Set-Up
    # ------------------------------
    log_level = ConfigContext.log_level(context)
    lumberjack = log.get_logger(comms.name,
                                min_log_level=log_level)
    lumberjack.setLevel(log_level)
    log.debug(f"_start_server: {comms.name} {log_level}",
              veredi_logger=lumberjack)

    # log.set_group_level(log.Group.DATA_PROCESSING, log.Level.DEBUG)
    # log.set_group_level(log.Group.PARALLEL, log.Level.DEBUG)

    # ---
    # Config
    # ---
    comms = ConfigContext.subproc(context)
    if not comms:
        raise log.exception(
            TypeError,
            "MediatorServer requires a SubToProcComm; received None.")

    config = background.config.config(
        '_start_server',
        'veredi.interface.mediator._start_server',
        context)

    # ---
    # Ignore Ctrl-C. Have parent process deal with it and us.
    # ---
    multiproc._sigint_ignore()

    # ---
    # Logging
    # ---
    # Do not set up log_client here - multiproc does that.

    # ------------------------------
    # Create & Start
    # ------------------------------

    log.debug(f"MediatorSystem's _start_server for {comms.name} "
              "starting MediatorServer...",
              veredi_logger=lumberjack)
    mediator = config.create_from_config('server',
                                         'mediator',
                                         'type',
                                         context=context)
    mediator.start()
    log.debug(f"MediatorSystem's _start_server for {comms.name} done.",
              veredi_logger=lumberjack)
コード例 #30
0
ファイル: engine.py プロジェクト: cole-brown/veredi-code
def engine(
    configuration: Configuration,
    meeting: Meeting,
    # Optional Debug Stuff:
    debug_flags: Optional[DebugFlag] = None,
) -> Engine:
    '''
    Create and configure a game engine using the other supplied parameters.
    '''
    log_dotted = label.normalize(_DOTTED, 'engine')
    log.start_up(log_dotted, "Building the Engine...")

    # ---
    # Sanity.
    # ---
    if not configuration:
        msg = "Configuration must be provided."
        error = ConfigError(msg,
                            data={
                                'configuration': str(configuration),
                                'meeting': str(meeting),
                            })
        raise log.exception(error, msg)

    if not meeting:
        # This shouldn't happen - should raise an error before here, right?
        msg = "Managers' Meeting must be provided."
        error = ConfigError(msg,
                            data={
                                'configuration': str(configuration),
                                'meeting': str(meeting),
                            })
        raise log.exception(error, msg)

    # ---
    # Create engine.
    # ---
    log.start_up(log_dotted, "Initializing the Engine...")
    owner = None
    campaign_id = None
    engine = Engine(owner, campaign_id, configuration, meeting, debug_flags)

    log.start_up(log_dotted,
                 "Done building the Engine.",
                 log_success=log.SuccessType.SUCCESS)
    return engine