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)
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')
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
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)
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)
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
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))
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
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
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
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
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
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
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
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)
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)
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)