def wrap( cls_or_instance: Union[EnumEncode, Type[EnumEncode]] ) -> Union['EnumWrap', Type['EnumWrap']]: ''' Returns the EnumWrap class for this enum `klass`. Returns None if not found. ''' # Get Class: klass, is_class = _to_class(cls_or_instance) # Do we know of this guy? wrap_type = _WRAPPED_ENUMS.get(klass, None) # Return what we were given: instance or class type if is_class: log.data_processing( label.normalize(_DOTTED, 'wrap'), "wrap:\n" " --in--> {}\n" " --type- {}\n" " <-type- {}", cls_or_instance, wrap_type, wrap_type) return wrap_type wrap_instance = wrap_type(cls_or_instance) log.data_processing( label.normalize(_DOTTED, 'wrap'), "wrap:\n" " -in--> {}\n" " -type- {}\n" " <-out- {}", cls_or_instance, wrap_type, wrap_instance) return wrap_instance
def create(log_groups: List[log.Group], context: 'ConfigContext') -> EncodableRegistry: ''' Create the EncodableRegistry instance. ''' log.registration(label.normalize(_DOTTED, 'create'), 'Creating EncodableRegistry...') global codec codec = base_registrar(EncodableRegistry, log_groups, context, codec) log.registration(label.normalize(_DOTTED, 'create'), 'Created EncodableRegistry.')
def register(cls_or_func: RegisterType, dotted: Optional[label.LabelInput] = None, unit_test_only: Optional[bool] = False) -> None: ''' Register the `cls_or_func` with the `dotted` string to our registry. If `unit_test_only` is Truthy, the `cls_or_func` will be registered if we are running a unit test, or handed off to `ignore()` if we are not. ''' log_dotted = label.normalize(_DOTTED, 'register') # --- # Sanity # --- if not dotted: # Check for class's dotted. try: dotted = cls_or_func.dotted except AttributeError: pass # No dotted string is an error. if not dotted: msg = ("Config sub-classes must either have a `dotted` " "class attribute or be registered with a `dotted` " "argument.") error = ValueError(msg, cls_or_func, dotted) log.registration(log_dotted, msg + "Got '{}' for {}.", dotted, cls_or_func) raise log.exception(error, msg) # --- # Unit Testing? # --- # Unit-test registrees should continue on if in unit-testing mode, # or be diverted to ignore if not. if unit_test_only and not background.testing.get_unit_testing(): ignore(cls_or_func) return # --- # Register # --- # Registry should check if it is ignored already by someone previous, # if it cares. dotted_str = label.normalize(dotted) log.registration(log_dotted, "{}: Registering '{}' to '{}'...", config.klass, dotted_str, cls_or_func.__name__) dotted_args = label.regularize(dotted) config.register(cls_or_func, *dotted_args) log.registration(log_dotted, "{}: Registered '{}' to '{}'.", config.klass, dotted_str, cls_or_func.__name__)
def register_enum( klass: Type['Encodable'], dotted: Optional[label.LabelInput] = None, name_encode: Optional[str] = None, name_klass: Optional[str] = None, enum_encode_type: Optional['enum.EnumWrap'] = None, unit_test_only: Optional[bool] = False, ) -> None: ''' Create a WrapEnum Encodable for this enum class. Required: - klass - dotted - name_encode - enum_encode_type Optional: - name_klass - unit_test_only ''' if not enum.needs_wrapped(klass): log_dotted = label.normalize(_DOTTED, 'register_enum') msg = ("Only Enum sub-classes should be wrapped in an EnumWrap for " "Encodable functionality. Call `register()` " "instead of `register_wrap()` for this class.") error = TypeError(msg, klass, dotted) log.registration(log_dotted, msg + f" {klass}") raise log.exception(error, msg, data={ 'klass': klass, 'dotted': label.normalize(dotted), 'name_encode': name_encode, 'name_klass': name_klass, 'enum_encode_type': enum_encode_type, 'unit_test_only': unit_test_only, }) # ------------------------------ # Create wrapper and register it. # ------------------------------ # This is an enum and we need to make a wrapper for it to be able to be # an Encodable. wrapped = enum.encodable(klass, name_dotted=dotted, name_string=name_encode, name_klass=name_klass, enum_encode_type=enum_encode_type) register(wrapped, dotted=dotted, unit_test_only=unit_test_only)
def configuration(rules: label.LabelInput, game_id: Any, path: pathlib.Path = None) -> Configuration: ''' Find config file and parse into the Configuration object for this game. `rules` is the Label for the rules type, e.g. 'veredi.rules.d20.pf2' `game_id` is the repository identity key for the specific game. `path` can be either a directory or the config file path. If `path` is None, this looks in the current directory for the first file that matches the _CONFIG_FILE_NAME_GLOBS patterns. ''' log_dotted = label.normalize(_DOTTED, 'configuration') log.start_up(log_dotted, "Creating Veredi Configuration...") # ------------------------------ # Sanity Checks? # ------------------------------ log.start_up(log_dotted, "Checking Inputs...", path, _CONFIG_FILE_NAME_GLOBS) # Normalize rules to a dotted string. rules = label.normalize(rules) # game_id... dunno much about it until we get a repository for it. # ------------------------------ # Figure out actual config file path from input path. # ------------------------------ log.start_up(log_dotted, "Finding Config File...", path, _CONFIG_FILE_NAME_GLOBS) path = _config_path(log_dotted, path) # ------------------------------ # Create Configuration. # ------------------------------ log.start_up(log_dotted, "Creating Configuration...", path, _CONFIG_FILE_NAME_GLOBS) config = Configuration(rules, game_id, config_path=path) # ------------------------------ # Done. # ------------------------------ log.start_up(log_dotted, "Done creating Veredi Configuration.", log_success=log.SuccessType.SUCCESS) return config
def unwrap(instance: Union['EnumWrap', 'Encodable']) -> EnumEncode: ''' If `instance` is an EnumWrap, returns the enum instance/value that the EnumWrap `instance` contains. Else returns `instance`. ''' if not isinstance(instance, EnumWrap): log.data_processing(label.normalize(_DOTTED, 'unwrap'), "unwrap:\n" " --ignore-> {}\n" " <-ignore-- {}", instance, instance) return instance log.data_processing(label.normalize(_DOTTED, 'unwrap'), "unwrap:\n" " -in--> {}\n" " <-out- {}", instance, instance.enum) return instance.enum
def __set__(self, instance: Optional[Any], dotted: Optional[label.LabelInput]) -> None: ''' Setter for context's dotted label. ''' if not instance: return # Walk into context dict using keys to find dotted. # Don't go to final key. We'll use that to set it. data, keys = self._get_from_ctx(instance) for key in keys[:-1]: # What to do with an empty key in data?.. Not exactly expected but # I guess set to an empty dict. data = data.setdefault(key, {}) if not data: return # Found the dict sub-entry that will hold our dotted label. if not dotted: # It might have a dotted already. Try to clear it out. data.pop(keys[-1], None) else: # Normalize and set the dotted string now. data[keys[-1]] = label.normalize(dotted)
def __init__(self, test_case: 'TestCase', test_name: Optional[str] = None, data: MutableMapping[str, Any] = {}, starting_context: MutableMapping[str, Any] = None) -> None: ''' Initialize Context with test class/name. e.g.: context = UnitTestContext(self, 'test_something', data={...}) ''' # Build dotted based on inputs. dotted = (test_case.dotted if not test_name else label.normalize( test_case.dotted, test_name)) super().__init__(dotted, 'unit-testing') # Set starting context. if starting_context: self.data = starting_context else: self.data = {} self.dotted = test_case.dotted # Ensure our sub-context and ingest the provided data. sub = self._ensure() # Munge any starting_context/defaults with what was specifically # supplied as the subcontext data. self._merge_dicts(data, sub, Conflict.RECEIVER_MUNGED, 'UnitTestContext.init.pull', 'from')
def _make(self, *name: str) -> str: ''' Make **ANY** LogName from `*name` strings. Should use `rooted()` unless you're special. ''' return label.normalize(*name)
def __str__(self) -> str: ''' Python 'to string' function. ''' # Build a string based on whatever taxonomic ranks we have... dotted = label.normalize(*[str(rank) for rank in self._taxon]) return f"{self.__class__.__name__}['{dotted}']"
def _tear_down_end(proc: ProcToSubComm, logger: Optional[log.PyLogType]) -> ExitCodeTuple: ''' Checks that process finished shutdown. If not, we terminate it immediately. In any case, we return its exit code. ''' _log_dotted = label.normalize(_DOTTED_FUNCS, '_tear_down.end') # Make sure it shut down and gave a good exit code. if (proc.process and proc.process.is_alive() and proc.process.exitcode is None): # Still not exited; terminate it. log.group_multi(_LOG_KILL, _log_dotted, "_tear_down_end({}): " "'{}' has still not exited; terminate it... " "Immediately.", proc.name, proc.name, veredi_logger=logger) proc.process.terminate() exitcode = (proc.process.exitcode if (proc and proc.process) else None) log.group_multi(_LOG_KILL, _log_dotted, "_tear_down_end({}): " "'{}' has exited with exit code: {}", proc.name, proc.name, str(exitcode), veredi_logger=logger) return ExitCodeTuple(proc.name, exitcode)
def dotted(klass: Type['ConfigRegistration'], entry: Dict[str, Any]) -> label.DotStr: ''' Returns the DOTTED entry of this registration entry. ''' value = klass._get(klass.DOTTED, entry) return label.normalize(value)
def _tear_down_check( proc: ProcToSubComm, logger: Optional[log.PyLogType]) -> Optional[ExitCodeTuple]: ''' Checks that process exists, then if process has good exit code. ''' _log_dotted = label.normalize(_DOTTED_FUNCS, '_tear_down.check') if not proc or not proc.process: if proc: log.group_multi(_LOG_KILL, _log_dotted, "_tear_down_check({}): " "No {} to stop.", proc.name, proc.name, veredi_logger=logger) else: log.group_multi(_LOG_KILL, _log_dotted, "_tear_down_check(): " "Cannot stop None/Null sub-process: {}", proc, veredi_logger=logger) # Pretend it exited with good exit code? return ExitCodeTuple(proc.name, 0) if proc.process.exitcode == 0: log.group_multi(_LOG_KILL, _log_dotted, "_tear_down_check({}): " "Process '{}' is already stopped.", proc.name, proc.name, veredi_logger=logger) return ExitCodeTuple(proc.name, proc.process.exitcode) return None
def __init__(self, context: Optional['VerediContext'], cid: ComponentId) -> None: '''DO NOT CALL THIS UNLESS YOUR NAME IS ComponentManager!''' super().__init__(context, cid) # Set up our actual mock dotted label. self.dotted = label.normalize(self.dotted, self.__class__.__name__.lower())
def __init__(self, context: Optional['VerediContext'], sid: SystemId, managers: 'Meeting') -> None: super().__init__(context, sid, managers) # Set up our actual mock dotted label. self.dotted = label.normalize(self.dotted, self.klass.lower())
def create(log_groups: List[log.Group], context: 'ConfigContext') -> ConfigRegistry: ''' Create the ConfigRegistry instance. ''' log_dotted = label.normalize(_DOTTED, 'create') log.registration(log_dotted, 'Creating EncodableRegistry...') global config config = base_registrar(ConfigRegistry, log_groups, context, config) log.registration(log_dotted, 'Created EncodableRegistry.')
def rooted(self, *name: str) -> str: ''' Make a LogName rooted from LogName enum called from. Examples: LogName.ROOT.rooted('jeff') -> 'veredi.jeff' LogName.MULTIPROC.rooted('server', 'jeff') -> 'veredi.multiproc.server.jeff' ''' return label.normalize(str(self), *name)
def init(config: 'Configuration') -> None: ''' Do some importing and set-up. ''' log_dotted = label.normalize(_DOTTED, 'init') log.start_up(log_dotted, "Initializing Veredi...") registration(config) log.start_up(log_dotted, "Initialization done.")
def nonblocking_tear_down_start( proc: ProcToSubComm) -> Optional[ExitCodeTuple]: ''' Kicks off tear-down. Caller will have to loop calling `nonblocking_tear_down_wait` for however long they want to wait for a clean shutdown, then call `nonblocking_tear_down_end` to finish. ''' _log_dotted = label.normalize(_DOTTED_FUNCS, 'tear_down.nonblocking.start') logger = log.get_logger(proc.name) log.group_multi(_LOG_KILL, _log_dotted, "nonblocking_tear_down_start({}): Begin.", proc.name, veredi_logger=logger) # ------------------------------ # Sanity Check, Early Out. # ------------------------------ result = _tear_down_check(proc, logger) if result: log.group_multi(_LOG_KILL, _log_dotted, "nonblocking_tear_down_start({}): ", "Check returned exit code: {}", proc.name, result, veredi_logger=logger) return result # ------------------------------ # Kick off tear-down. # ------------------------------ log.group_multi(_LOG_KILL, _log_dotted, "nonblocking_tear_down_start({}): ", "Starting tear-down...", proc.name, veredi_logger=logger) _tear_down_start(proc, logger) # No return value for `_tear_down_start()`; can't check anything. # if result: # log.group_multi(_LOG_KILL, # _log_dotted, # "nonblocking_tear_down_start({}): ", # "_tear_down_start returned exit code: {}", # proc.name, result, # veredi_logger=logger) # return result log.group_multi(_LOG_KILL, _log_dotted, "nonblocking_tear_down_start({}): Done.", proc.name, veredi_logger=logger)
def _background(self): ''' Get background data for init_background()/background.mediator.set(). ''' self._bg = { 'dotted': self.dotted, 'type': label.normalize('websocket', self.name), 'serdes': self._serdes.dotted, 'codec': self._codec.dotted, } return self._bg, background.Ownership.SHARE
def _query_split(self, component: Component, *entry: str) -> ValueMilieu: ''' `entry` args must have been canonicalized. Gets `entry` from the component. Returns value and dotted entry string. E.g.: _query_split(component, 'strength', 'score') -> (20, 'strength.score') ''' return ValueMilieu(component.query(*entry), label.normalize(*entry))
def rules(self, context: 'VerediContext') -> Nullable[RulesGame]: ''' Creates and returns the proper RulesGame object for this specific game with its game definition and saved data. Raises a ConfigError if no rules label or game id. ''' log_groups = [log.Group.START_UP, log.Group.DATA_PROCESSING] log.group_multi( log_groups, self.dotted, "rules: Creating game rules object " "from rules: {}, id: {}", self._rules, self._id) # --- # Sanity # --- if not self._rules or not self._id: log.group_multi(log_groups, self.dotted, "rules: Failed to create game rules... missing " "our rules or id: rules {}, id: {}", self._rules, self._id, log_success=False) raise log.exception( ConfigError, "No rules label or id for game; cannot create the " "RulesGame object. rules: {}, id: {}", self._rules, self._id) # --- # Context # --- # Allow something else if the caller wants, but... if not context: # ...this default w/ rules/id should be good in most cases. context = ConfigContext(self._path, self.dotted, key=self._rules, id=self._id) # --- # Create the rules. # --- rules = self.create_from_label( # '<rules-dotted>.game' is our full dotted string. label.normalize(self._rules, 'game'), context=context) log.group_multi(log_groups, self.dotted, "rules: Created game rules.", log_success=True) return rules
class LogName(enum.Enum): ROOT = label.normalize('veredi') ''' The default/root veredi logger. ''' MULTIPROC = label.normalize(ROOT, 'multiproc') ''' multiproc's logger for setting up/tearing down sub-processes. ''' # TODO [2020-09-12]: More logger names. Some formats? def _make(self, *name: str) -> str: ''' Make **ANY** LogName from `*name` strings. Should use `rooted()` unless you're special. ''' return label.normalize(*name) def rooted(self, *name: str) -> str: ''' Make a LogName rooted from LogName enum called from. Examples: LogName.ROOT.rooted('jeff') -> 'veredi.jeff' LogName.MULTIPROC.rooted('server', 'jeff') -> 'veredi.multiproc.server.jeff' ''' return label.normalize(str(self), *name) def __str__(self) -> str: ''' Returns value string of enum. ''' return self.value
def ignore(ignoree: RegisterType) -> None: ''' For flagging an Config class as one that is a base class or should not be registered for some other reason. ''' log_dotted = label.normalize(ConfigRegistry.dotted, 'ignore') log.registration(log_dotted, "{}: '{}' marking as ignored for registration...", config.klass, ignoree) config.ignore(ignoree) log.registration(log_dotted, "{}: '{}' marked as ignored.", config.klass, ignoree)
def add(system: System) -> SystemId: ''' Helper to take an already created system, and add it into the SystemManager's systems. Returns the SystemId assigned by SystemManager. ''' log_dotted = label.normalize(_DOTTED, 'add') log.start_up(log_dotted, f"Adding system '{str(system.klass)}'...") sid = background.manager.system.add(system) log.start_up(log_dotted, f"Added system '{str(system.klass)}' with " f"SystemId: {str(sid)}", log_success=log.SuccessType.SUCCESS) return sid
def ignore(ignoree: Type['Encodable']) -> None: ''' For flagging an Encodable class as one that is a base class or should not be registered for some other reason. ''' log_dotted = label.normalize(EncodableRegistry.dotted, 'ignore') log.registration(log_dotted, "{}: '{}' marking as ignored for registration...", codec.klass, ignoree) codec.ignore(ignoree) log.registration(log_dotted, "{}: '{}' marked as ignored.", codec.klass, ignoree)
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
def _tear_down_start( proc: ProcToSubComm, logger: Optional[log.PyLogType], ) -> None: ''' Set shutdown flag. Proc should notice soon (not immediately) and start its shutdown. ''' _log_dotted = label.normalize(_DOTTED_FUNCS, '_tear_down.start') log.group_multi(_LOG_KILL, _log_dotted, "_tear_down_start({}): " "Asking '{}' to end gracefully...", proc.name, proc.name, veredi_logger=logger) proc.shutdown.set()
def create(config: Optional[Configuration], context: VerediContext, system_type: Type[System], debug_flags: Optional[DebugFlag] = None) -> SystemId: ''' Helper to create a system. Returns the created system's SystemId. ''' log_dotted = label.normalize(_DOTTED, 'create') log.start_up(log_dotted, f"Creating system '{str(system_type.klass)}'...") sid = background.manager.system.create(system_type, context) log.start_up(log_dotted, f"Created system '{str(system_type.klass)}' with " f"SystemId: {str(sid)}", log_success=log.SuccessType.SUCCESS) return sid
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