Example #1
0
    def _attach(self, id_or_comp: Union[ComponentId, Component]):
        '''
        DO NOT CALL THIS UNLESS YOUR NAME IS EntityManager!

        Attaches the component to this entity.
        '''
        component = id_or_comp
        if isinstance(id_or_comp, ComponentId):
            component = self._component_manager.get(id_or_comp)
        if not component:
            log.error(
                "Ignoring 'attach' requested for a non-existing "
                "component. ID or instance: {} -> {}", id_or_comp, component)
            return

        existing = self._components.get(type(component), None)
        if not existing:
            self._components[type(component)] = component
        else:
            # Entity has component already and it cannot
            # be replaced.
            log.warning(
                "Ignoring 'attach' requested for component type already "
                "existing on entity. entity_id: {}, existing: {}, "
                "requested: {}", self.id, existing, component)
Example #2
0
 async def _htx_ack(self, msg: Message, ctx: Optional[MediatorContext],
                    conn: UserConnToken) -> Optional[Message]:
     '''
     Handle sending a message with an ACK_ID payload.
     '''
     log.warning("_htx_ack: ...Why is the TX handler for ACK involved?")
     return await self._htx_generic(msg, ctx, conn, log_type='ack')
Example #3
0
    def _is_type_field(klass: Type['Encodable'],
                       data: Optional[EncodedEither]) -> bool:
        '''
        Returns False if `klass._get_type_field()` or `klass.type_field()`
        return None.

        Returns True if `data` has type field (via `klass._get_type_field()`)
        and it matches the expected type field (via `klass.type_field()`).

        Returns False otherwise.
        '''
        data_type_field = klass._get_type_field(data)
        if data_type_field is None:
            # This may be common for simply encoded stuff. Not sure. If so
            # change to debug level.
            log.warning("No type field in data. {}", data)
            return False

        class_type_field = klass.type_field()
        if class_type_field is None:
            msg = (f"Class {klass} returned 'None' from type_field(). "
                   "type_field() is a required function. Cannot determine "
                   f"type of data: {data}")
            error = EncodableError(msg, None, None, data)
            log.error(error, None, msg)
            return False

        return class_type_field == data_type_field
Example #4
0
    def parse(
        self, input_safe: str, context: VerediContext
    ) -> Tuple[Iterable, Dict[str, Any], CommandStatus]:
        '''
        Parse verified safe/sanitized `input_safe` string into the args &
        kwargs this command wants.

        Returns parsed tuple of: (args, kwargs, CommandStatus of parsing)
          - args is a list in correct order
          - kwargs is a dict
        '''
        if self.language == InputLanguage.NONE:
            # I think just a warning? Could error out, but don't see exactly
            # why I should - would be annoying to be the user in that case?
            if input_safe:
                log.warning(
                    "Command '{}' has no input args but received input.",
                    self.name,
                    context=context)
            return ([], {}, CommandStatus.successful(context))

        elif self.language == InputLanguage.MATH:
            return self._parse_math(input_safe, context)

        elif self.language == InputLanguage.TEXT:
            return self._parse_text(input_safe, context)

        # Er, oops?
        raise NotImplementedError(
            f"TODO: parse() for {self.language} is not implemented yet.", self)
Example #5
0
 def _set_up_registries(self) -> None:
     '''
     Nukes all entries in various registries.
     '''
     if self.config is None:
         log.warning("No configuration created?! Test should have created "
                     f"it during `{self.__class__.__name__}"
                     "._set_up_config`. Creating one for now, but "
                     "this should be fixed.")
         self.config = zmake.config(self.type)
     zinit.set_up_registries(self.config)
Example #6
0
def wait(processes: Mapping[str, multiprocessing.Process]) -> None:
    '''
    Waits forever. Kills server on Ctrl-C/SIGINT.

    Returns 0 if all exitcodes are 0.
    Returns None or some int if all exitcodes are not 0.
    '''
    lumberjack = log.get_logger(ProcessType.MAIN.value)
    log.info("Waiting for game to finish...", veredi_logger=lumberjack)

    try:
        game_running = _game_running(processes)
        while game_running:
            # Do nothing and take namps forever until SIGINT received or game
            # finished.
            game_running = _game_running(processes)

    except KeyboardInterrupt:
        # First, ask for a gentle, graceful shutdown...
        log.warning("Received SIGINT.", veredi_logger=lumberjack)

    # Finally, end the game.
    _game_over(processes)
    _logs_over(processes)

    # Give up and ask for the terminator... If necessary.
    for each in processes.proc:
        if processes.proc[each].exitcode is None:
            # Still not exited; terminate them.
            processes.proc[each].terminate()

    # Figure out our exitcode return value.
    time.sleep(0.1)  # Short nap for our kids to clean up...
    retval = 0
    for each in processes.proc:
        exited = processes.proc[each].exitcode
        if exited is None:
            # Might have to print instead of log this?
            log.warning(
                "Process '{}' is still running slightly after termination...",
                each.value,
                veredi_logger=lumberjack)

            retval = None
        elif exited == 0:
            # Do nothing; only get a retval exit code of 0 if all of them
            # hit this case and do nothing and leave it at its original 0.
            pass
        elif retval is not None:
            # Don't override 'None'... that indicates someone's still alive and
            # kicking...
            retval = exited

    return retval
Example #7
0
def register(event: CommandRegistrationBroadcast) -> None:
    '''
    Our event handler for the registration broadcast.
    '''
    if not background.manager.event:
        log.warning(f"'{DOTTED_NAME}' cannot register its commands as there "
                    "is no EventManager in the background meeting.")
        return

    # Get our debug commands' registration events sent out.
    background.manager.event.notify(debug.register(event))
    background.manager.event.notify(dbg_bg.register(event))
Example #8
0
    async def _hook_consume(
            self, msg: Optional[Message],
            websocket: websockets.WebSocketCommonProtocol
    ) -> Optional[Message]:
        '''
        Check all received messages from server?
        '''
        self.debug("_hook_consume saw msg on socket {}: {}", websocket, msg)
        if not msg:
            self.debug(
                "_hook_consume ignoring null message "
                "on socket {}: {}", websocket, msg)
            return msg

        # ------------------------------
        # Check User Fields
        # ------------------------------
        valid = True
        # ---
        # User-Id
        # ---
        if msg.user_id != self._id:
            valid = False
            log.warning(
                "_hook_consume: "
                "Client received user-id that doesn't match expected. "
                "expected: {}, got: {}. "
                "msg: {}", self._id, msg.user_id, msg)
        else:
            self.debug(
                "_hook_consume: "
                "Client received msg for our user-id: ", msg)
        # ---
        # User-Key
        # ---
        if msg.user_key != self._key:
            valid = False
            log.warning(
                "_hook_consume: "
                "Client received user-key that doesn't match expected. "
                "expected: {}, got: {}. "
                "msg: {}", self._key, msg.user_key, msg)
        else:
            self.debug(
                "_hook_consume: "
                "Client received msg for our user-key: ", msg)

        # TODO: Not sure if we need to actually do anything about
        # `valid == False` here...

        self.debug("_hook_consume: Done.")
        return msg
Example #9
0
    def acted(self, entity_id) -> Decimal:
        '''
        Step to next entity's turn. Maybe it's the same round; maybe not.

        Returns current seconds.
        '''
        # Can't do anything?
        if not self._turn_order:
            log.warning("No turn order - cannot update turns/rounds "
                        "based on action.")
            return self._seconds

        next_turn = (self._turn_index + 1) % len(self._turn_order)
        if next_turn == 0:
            self._step_round()

        self._turn_index = next_turn
        return self._seconds
Example #10
0
def validate(
    string_unsafe: Optional[str],
    string_source0: Optional[str],
    string_source1: Optional[str],
    context_log: Optional[VerediContext] = None,
) -> Tuple[Optional[str], InputValid]:
    '''
    Checks `string_unsafe` for invalid conditions.

    `source0` and `source1` are for logging only.
    For PCs:
      - `source0`: Should be username.
      - `source1`: Should be player name.
    For other entities:
      - `source0`: Should be name.
      - `source1`: Don't care.

    Returns a Tuple of:
      - string_safe or None (if failed validation).
      - InputValid flags describing success/failure reasons.
    '''
    valid = InputValid.UNKNOWN

    strlen = len(string_unsafe)
    if strlen > STRING_MAX_LENGTH:
        valid = InputValid.set_flag(InputValid.STR_TOO_LONG)
        log.warning(
            "Source '{}' ('{}') provided input string which was "
            "very long. str-len: {}", string_source0, string_source1, strlen)

    if not string_unsafe.isprintable():
        valid = InputValid.set_flag(InputValid.STR_UNPRINTABLE)
        # What in heck are they sending us then?
        log.warning(
            "Source '{}' ('{}') provided input string with "
            "unprintable characters in it. str-len: {}", string_source0,
            string_source1, strlen)

    # If I still don't know, I guess it passes the tests...
    if valid == InputValid.UNKNOWN:
        return string_unsafe, InputValid.VALID

    return None, valid
Example #11
0
    def __bool__(self) -> bool:
        '''
        Returns True /if and only if/:
          - We have any data.
          - That data has any data under the main key.

        In other words: Returns true if this record has its main data.
        '''
        anything_exists = bool(self._documents)
        main_exists = False
        if anything_exists:
            main_exists = bool(self._main())

            if not main_exists:
                log.warning(
                    "{} has data of some sort, but no "
                    "main document data (nothing under "
                    "key '{}').", str(self), str(self._primary_doc))

        return anything_exists and main_exists
Example #12
0
    async def _hrx_root(self, match: re.Match, path: str, msg: Message,
                        context: Optional[MediatorContext]) -> None:
        '''
        Handle receiving a request to root path ("/").
        '''
        self.debug("_hrx_root: "
                   "Received: path: {}, match: {}, msg: {}", path, match, msg)

        # Do I have someone else to give this to?
        _, receiver = self._hp_paths_type.get(msg.type, None)
        if receiver:
            self.debug("_hrx_root: Forward to: {}", receiver)
            return await receiver(match, path, msg, context)

        # else:
        # Ok, give up.
        log.warning(
            "_hrx_root: "
            "No handlers for msg; ignoring: "
            "path: {}, match: {}, msg: {}", path, match, msg)
        return None
Example #13
0
def is_duration(check: Any) -> bool:
    '''
    Returns True if `check` is a type usable for durations:
      - py_dt.timedelta
      - numbers.NumberTypesTuple
      - Something `duration()` can parse.
    '''
    if isinstance(check, py_dt.timedelta):
        return True
    elif isinstance(check, numbers.NumberTypesTuple):
        return True
    elif isinstance(check, str):
        try:
            duration(check)
            return True
        except:
            return False

    # What even is it?
    log.warning(f"time.parse.is_duration(): Unknown type {type} for: {check}")
    return False
Example #14
0
    def update(self, tick: SystemTick) -> VerediHealth:
        '''
        Pre-update. For any systems that need to squeeze in something just
        before actual tick.
        '''
        # ------------------------------
        # Short-cuts
        # ------------------------------

        # Ignored Tick?
        if not self._ticks or not self._ticks.has(tick):
            # Don't even care about my health since we don't even want
            # this tick.
            return VerediHealth.IGNORE

        # Doctor checkup.
        if not self._health_ok_tick(SystemTick.STANDARD):
            return self.health

        # ------------------------------
        # Full Tick Rate: Start
        # ------------------------------
        health = tick_health_init(tick)

        # Nothing, at the moment.

        # ------------------------------
        # Full Tick Rate: End
        # - - - - - - - - - - -
        if not self._meeting.time.is_reduced_tick(
                tick,
                self._reduced_tick_rate):
            self.health = health
            return self.health
        # - - - - - - - - - - -
        # !! REDUCED Tick Rate: START !!
        # ------------------------------

        # Ok - our reduced tick is happening so make sure our dictionaries are
        # up to date with the entities.

        # ---
        # Update EntityIds, UserIds, UserKeys.
        # ---
        all_with_id = set()
        for entity in self._entity.each_with(self._component_type):
            id_comp = self.component(entity.id)
            if not id_comp:
                continue

            # Have an entity to update/add.
            self._user_ident_update(entity.id,
                                    user_id=id_comp.user_id,
                                    user_key=id_comp.user_key)
            all_with_id.add(entity.id)

        # ---
        # Remove EntityIds, UserIds, UserKeys.
        # ---
        for entity_id in self._uids:
            if entity_id in all_with_id:
                continue

            # EntityId wasn't present in updated (so they don't exist now or
            # their IdentityComponent doesn't), but is in our dict. Remove from
            # our dict to sync up.
            self._user_ident_update(entity_id,
                                    delete=True)

        # This shouldn't find anyone new... Assuming the dicts are in sync and
        # all entities with UserKeys also have UserIds.
        for entity_id in self._ukeys:
            if entity_id in all_with_id:
                continue

            # EntityId wasn't present in updated (so they don't exist now or
            # their IdentityComponent doesn't), but is in our dict. Remove from
            # our dict to sync up.
            ukey = self.user_key(entity_id)
            self._user_ident_update(entity_id,
                                    delete=True)
            log.warning("Entity/User sync: UserId and UserKey dicts were out "
                        "of sync. Entity {} had a UserKey {} but no UserId.",
                        entity_id, ukey)
            health = health.update(VerediHealth.UNHEALTHY)

        # ------------------------------
        # !! REDUCED Tick Rate: END !!
        # ------------------------------
        self.health = health
        return health
Example #15
0
    def _deflict(self, resolution: Conflict, d_to: Dict[str,
                                                        Any], key_sender: str,
                 value_sender: Any, verb: str, preposition: str) -> None:

        # Special handling for 'dotted'.
        if key_sender == self._KEY_DOTTED:
            self._deflict_dotted(resolution, d_to, key_sender, value_sender,
                                 verb, preposition)
            return

        quiet = (resolution & Conflict.QUIET) == Conflict.QUIET
        resolution = resolution & ~Conflict.QUIET

        # Overwrite options are easy enough.
        if resolution == Conflict.SENDER_WINS:
            if not quiet:
                log.warning(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "De-conflicting keys by overwriting receiver's value. "
                    "Values before overwrite: currently: {}, overwrite-to: {}",
                    verb,
                    resolution,
                    key_sender,
                    d_to[key_sender],
                    value_sender,
                    context=self)
            d_to[key_sender] = value_sender
            return

        elif resolution == Conflict.RECEIVER_WINS:
            if not quiet:
                log.warning(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "De-conflicting keys by ignoring sender's value. "
                    "Values: currently: {}, ignoring: {}",
                    verb,
                    resolution,
                    key_sender,
                    d_to[key_sender],
                    value_sender,
                    context=self)
            return

        # Munging options still to do - can only do those to strings.
        if not isinstance(key_sender, str):
            msg = "Cannot munge-to-deconflict keys that are not strings."
            error = ContextError(msg,
                                 context=self,
                                 data={
                                     'd_to': d_to,
                                     'resolution': resolution,
                                     'key_sender': key_sender,
                                     'value_sender': value_sender,
                                     'd_to[key_sender]': d_to[key_sender],
                                 })
            raise log.exception(error, msg, context=self)

        if resolution == Conflict.SENDER_MUNGED:
            key_munged = key_sender + '-' + uuid.uuid4().hex[:6]
            # Add sender's value to munged key.
            d_to[key_munged] = value_sender

            if not quiet:
                log.warning(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "Sender's key will get random string appended to "
                    "de-conflict, but this could cause issues further along. "
                    "sender[{}] = {}, vs receiver[{}] = {}. "
                    "De-conflicted key: {}",
                    verb,
                    resolution,
                    key_sender,
                    key_sender,
                    value_sender,
                    key_sender,
                    d_to[key_munged],
                    key_munged,
                    context=self)

        elif resolution == Conflict.SENDER_MUNGED:
            key_munged = key_sender + '-' + uuid.uuid4().hex[:6]
            # Move receiver's value to munged key.
            old_value = d_to.pop(key_sender)
            d_to[key_munged] = old_value
            # Add sender to original key.
            d_to[key_sender] = value_sender

            if not quiet:
                log.warning(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "Receiver's key will get random string appended to "
                    "de-conflict, but this could cause issues further along. "
                    "sender[{}] = {}, vs receiver[{}] = {}. "
                    "De-conflicted key: {}",
                    verb,
                    resolution,
                    key_sender,
                    key_sender,
                    value_sender,
                    key_sender,
                    old_value,
                    key_munged,
                    context=self)
Example #16
0
    def _deflict_dotted(self, resolution: Conflict, d_to: Dict[str, Any],
                        key_sender: str, value_sender: Any, verb: str,
                        preposition: str) -> None:
        '''
        Special conflict-resolution for a 'dotted' key.

        All dotteds go into a list and only one remains at 'dotted' key.
        No munging of keys.
        '''
        quiet = (resolution & Conflict.QUIET) == Conflict.QUIET
        resolution = resolution & ~Conflict.QUIET

        # Put all dotteds into a list, and then keep only one.

        # ---
        # List of Dotteds
        # ---
        all_dotted = d_to.setdefault(self._KEYS_DOTTED_CONFLICT_LIST, [])
        value_receiver = d_to[key_sender]

        # ---
        # Deconflict Dotteds
        # ---
        if resolution in (Conflict.SENDER_WINS, Conflict.RECEIVER_MUNGED):
            # Sender wins or receiver 'loses' - so sender is more important.
            if value_receiver not in all_dotted:
                all_dotted.append(value_receiver)
            # Delete from original spot so we can put at the tail in the
            # 'winning' position.
            try:
                all_dotted.remove(value_sender)
            except ValueError:
                pass
            all_dotted.append(value_sender)

            # And now deconflict the 'dotted' key itself.
            d_to[key_sender] = value_sender

            if not quiet:
                log.info(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "De-conflicting keys by using sender's value as key. "
                    "sender[{}] = {}, vs receiver[{}] = {}. "
                    "List of Dotteds: {}",
                    verb,
                    resolution,
                    key_sender,
                    key_sender,
                    value_sender,
                    key_sender,
                    value_receiver,
                    all_dotted,
                    context=self)

        elif resolution in (Conflict.RECEIVER_WINS, Conflict.SENDER_MUNGED):
            # Receiver wins or sender 'loses' - so receiver is more important.
            if value_sender not in all_dotted:
                all_dotted.append(value_sender)
            # Delete from original spot so we can put at the tail in the
            # 'winning' position.
            try:
                all_dotted.remove(value_receiver)
            except ValueError:
                pass
            all_dotted.append(value_receiver)

            # And now deconflict the 'dotted' key itself.
            d_to[key_sender] = value_receiver

            if not quiet:
                log.info(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "De-conflicting keys by using receiver's value as key. "
                    "sender[{}] = {}, vs receiver[{}] = {}. "
                    "List of Dotteds: {}",
                    verb,
                    resolution,
                    key_sender,
                    key_sender,
                    value_sender,
                    key_sender,
                    value_receiver,
                    all_dotted,
                    context=self)
        else:
            if not quiet:
                log.warning(
                    "{}({}): Sender and Receiver have same key: {}. "
                    "Don't know how to de-conflict though?! "
                    "resolution '{}' is unhandled! Leaving context as-is.",
                    verb,
                    resolution,
                    resolution,
                    context=self)
Example #17
0
    def add(self, cls_or_func: 'RegisterType',
            *dotted_label: label.LabelInput) -> None:
        '''
        This function does the actual registration.
        '''
        # Ignored?
        if self.ignored(cls_or_func):
            msg = (f"{cls_or_func} is in our set of ignored "
                   "classes/functions that should not be registered.")
            error = RegistryError(msg,
                                  data={
                                      'registree': cls_or_func,
                                      'dotted': label.normalize(dotted_label),
                                      'ignored': self._ignore,
                                  })
            raise log.exception(error, msg)

        # Do any initial steps.
        dotted_list = label.regularize(*dotted_label)
        if not self._init_register(cls_or_func, dotted_list):
            # Totally ignore if not successful. _init_register() should do
            # all the erroring itself.
            return

        # Pull final key off of list so we don't make too many
        # dictionaries.
        name = str(cls_or_func)
        try:
            # Final key where the registration will actually be stored.
            leaf_key = dotted_list[-1]
        except IndexError as error:
            kwargs = log.incr_stack_level(None)
            raise log.exception(
                RegistryError, "Need to know what to register this ({}) as. "
                "E.g. @register('jeff', 'geoff'). Got no dotted_list: {}",
                name, dotted_list, **kwargs) from error

        # Our register - full info saved here.
        registry_our = self._registry

        # Background register - just names saved here.
        registry_bg = background.registry.registry(self.dotted)

        # ------------------------------
        # Get reg dicts to the leaf.
        # ------------------------------

        length = len(dotted_list)
        # -1 as we've got our config name already from that final
        # dotted_list entry.
        for i in range(length - 1):
            # Walk down into both dicts, making new empty sub-entries as
            # necessary.
            registry_our = registry_our.setdefault(dotted_list[i], {})
            registry_bg = registry_bg.setdefault(dotted_list[i], {})

        # ------------------------------
        # Register (warn if occupied).
        # ------------------------------

        # Helpful messages - but registering either way.
        try:
            if leaf_key in registry_our:
                if background.testing.get_unit_testing():
                    msg = ("Something was already registered under this "
                           f"registry_our key... keys: {dotted_list}, "
                           f"replacing {str(registry_our[leaf_key])}' with "
                           f"this '{name}'.")
                    error = KeyError(leaf_key, msg, cls_or_func)
                    log.exception(error, None, msg, stacklevel=3)
                else:
                    log.warning(
                        "Something was already registered under this "
                        "registry_our key... keys: {}, replacing "
                        "'{}' with this '{}'",
                        dotted_list,
                        str(registry_our[leaf_key]),
                        name,
                        stacklevel=3)
            else:
                log.debug("Registered: keys: {}, value '{}'",
                          dotted_list,
                          name,
                          stacklevel=3)
        except TypeError as error:
            msg = (f"{self.klass}.add(): Our "
                   "'registry_our' dict is the incorrect type? Expected "
                   "something that can deal with 'in' operator. Have: "
                   f"{type(registry_our)} -> {registry_our}. Trying to "
                   f"register {cls_or_func} at "
                   f"'{label.normalize(dotted_list)}'. "
                   "Registry: \n{}")
            from veredi.base.strings import pretty
            log.exception(error, msg, pretty.indented(self._registry))
            # Reraise it. Just want more info.
            raise

        # Register cls/func to our registry, save some info to our
        # background registry.
        self._register(cls_or_func, dotted_list, leaf_key, registry_our,
                       registry_bg)

        # ------------------------------
        # Finalize (if desired).
        # ------------------------------
        self._finalize_register(cls_or_func, dotted_list, registry_our,
                                registry_bg)