示例#1
0
    def get(self, *path: label.LabelInput) -> Nullable[Any]:
        '''
        Regularizes `path` to a dotted list:

        Returns value in main document under (converted) `path`.
          - Returns Null() if `path` does not exist.
        '''
        path = label.regularize(path)

        # Find out if there's anything at the end of the path.
        place = self._main()
        for key in path:
            # Check so we don't raise KeyError.
            if key not in place:
                return Null()

            # Update our place in the path and continue on to the next key.
            place = place[key]

        # We got here, so the path is either only a partial path, or we found a
        # leaf. We don't check for the difference right now, only return what
        # we ended up with.
        #
        # TODO: Figure out a way to return Null() if we're only a partial path?
        #   - Does DataDict have recursive DataDicts for sub-entries or
        #     something?
        return place
示例#2
0
    def exists(self, path: label.LabelInput) -> bool:
        '''
        If `path` is a str:
          - Expects dotted string - converts to a list using
            `label.regularize()`.
        Else, uses list provided.

        Then checks for a value in the main document at the end of the
        (converted) `path`.

        Also checks for `check` in main document under 'alias' if that exists.
        '''
        # First, check under our primary key.
        if super().exists(label.regularize(self._key_prime, path)):
            return True

        # Second, try the alias key, if it exists.
        if self.ALIAS in self:
            alias_exists = super().exists(label.regularize(self.ALIAS, path))
            return alias_exists
示例#3
0
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__)
示例#4
0
    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
示例#5
0
    def create_from_config(
        self,
        *keychain: label.LabelInput,
        context: Optional['VerediContext'] = None,
    ) -> Nullable[Any]:
        '''
        Gets value from these keychain in our config data, then tries to have
        our registry create that value.

        e.g. config.create_from_config('data', 'game', 'repository')
           -> from config file: 'veredi.repository.file-tree'
              -> from create_from_label('veredi.repository.file-tree', ...)
                 -> FileTreeRepository object

        Will use provided context, or create a ConfigContext to use via
        `make_config_context()` if none provided.

        Returns thing created using keychain or None.
        '''
        # Ensure the keychain is in good shape from whatever was passed in.
        keychain = label.regularize(*keychain)
        config_val = self.get(*keychain)
        if not isinstance(config_val, str):
            error_info = ("no config value" if not config_val else
                          "incorrect config value of type "
                          f"'{type(config_val)}' (need str)")
            log.debug(
                "Make requested for: {}. But we have {} "
                "for that. context: {}", error_info, keychain, context)
            return Null()

        if not context:
            context = self.make_config_context()

        context.add(ConfigLink.KEYCHAIN, list(keychain[:-1]))
        log.debug("Make requested for: {}. context: {}", keychain, context)

        # Assume their relevant data is one key higher up...
        # e.g. if we're making the thing under keychain (GAME, REPO, TYPE),
        # then the repository we're making will want (GAME, REPO) as its
        # root so it can get, say, DIRECTORY.
        ret_val = self.create_from_label(config_val, context=context)
        log.debug("Made: {} from {}. context: {}", ret_val, keychain, context)
        return ret_val
示例#6
0
    def ut_inject(self, value: Any, doc_type: Document,
                  *keychain: label.LabelInput) -> None:
        # Ensure the keychain is in good shape from whatever was passed in.
        keychain = label.regularize(*keychain)

        # Get document type data first.
        doc_data = self._config.get(doc_type, None)
        data = doc_data
        if data is None:
            log.debug("No doc_type {} in our config data {}.", doc_type,
                      self._config)
            return None

        # Now hunt for/create the keychain they wanted...
        for key in keychain[:-1]:
            data = data.setdefault(key, {})

        # And set the key.
        data[keychain[-1]] = value
示例#7
0
    def _query_value(self, component: Component,
                     entry: Union[str, Tuple[str, str]]) -> ValueMilieu:
        '''
        `entry` string must be canonicalized. We'll get it from
        the component.

        Returns component query result. Also returns the canonicalized
        `entry` str, in case you need to call back into here for e.g.:
          _query_value(component, 'str.mod')
            -> '(${this.score} - 10) // 2', 'strength.modifier'
          _query_value(component,
                    ('this.score', 'strength.modifier'))
            -> (20, 'strength.score')
        '''
        if isinstance(entry, tuple):
            return self._query_this(component, *entry)

        entry = self._rule_defs.canonical(entry, None)
        return self._query_split(component, *label.regularize(entry))
示例#8
0
    def get_data(self, *keychain: label.LabelInput) -> Nullable[Any]:
        '''
        Get a configuration thingy from us given some keychain use to walk into
        our config data in 'data' entry.

        Returns data found at end keychain.
        Returns None if couldn't find a key in our config data.
        '''
        # Ensure the keychain is in good shape from whatever was passed in.
        keychain = label.regularize(*keychain)

        data = self.get('data', *keychain)

        log.data_processing(self.dotted,
                            'get_data: keychain: {} -> data: {}',
                            keychain,
                            data,
                            log_minimum=log.Level.DEBUG)

        return data
示例#9
0
    def query(self, *dot_path: label.LabelInput) -> Nullable[Any]:
        '''
        Query this component's data for something on either:
          - a dotted string path.
          - (dotted) string args.
        That is either:
           query('foo.bar')
           query('foo', 'bar')

        E.g. for an ability component with data:
        {
          'ability': {
            'strength': {
              'score': 10,
              'modifier': 'some math string',
            },
            ...
          }
        }

        |------------------------+--------------------|
        | Query                  |             Result |
        |------------------------+--------------------|
        | 'strength.modifier'    | 'some math string' |
        | 'strength.score'       |                 10 |
        | 'strength'             |                 10 |
        | 'strength', 'modifier' | 'some math string' |
        | 'strength', 'score'    |                 10 |
        |------------------------+--------------------|
        '''
        # Get our input sorted out.
        dot_path = label.regularize(*dot_path)

        data = self.persistent
        for each in dot_path:
            data = data.get(each, Null())
        return data
示例#10
0
class ConfigRegistration(enum.Enum):
    '''
    Configuration settings keys for registration.
    '''

    KEY = 'registration'
    '''
    Registration is a list of entries for what to search for registration.
    '''

    NAME = label.regularize('register.name')
    '''
    Who is being registered.
    '''

    DOTTED = label.regularize('register.dotted')
    '''
    Who is being registered.
    '''

    PATH_ROOT = label.regularize('path.root')
    '''
    Path to resolve to get to the root of the file tree to be search for
    registration files.
    '''

    PATH_REGISTRARS_RUN = label.regularize('path.registrars.run')
    '''
    A list of filenames to look for when running (normal and testing).
    '''

    PATH_REGISTRARS_TEST = label.regularize('path.registrars.test')
    '''
    A list of filenames to look for when in testing mode.
    '''

    PATH_REGISTREES_RUN = label.regularize('path.registrees.run')
    '''
    A list of filenames to look for when running (normal and testing).
    '''

    PATH_REGISTREES_TEST = label.regularize('path.registrees.test')
    '''
    A list of filenames to look for when in testing mode.
    '''

    PATH_IGNORE_FILES = label.regularize('path.ignore.files')
    '''
    A list of strings and regexs of files to ignore during path searches.
    '''

    PATH_IGNORE_DIRS = label.regularize('path.ignore.directories')
    '''
    A list of strings and regexs of directories to ignore during path searches.
    '''

    FORCE_TEST = label.regularize('unit-test')
    '''
    A flag to force registration of unit-testing (or force skipping of it).
    Overrides auto-detection of unit-testing that registration does.
    '''

    # ------------------------------
    # Helpers
    # ------------------------------

    def full_key(self) -> str:
        '''
        Adds root key ('registration') to its value to form a full key
        (e.g. 'registration.path.ignores').
        '''
        return label.normalize(self.KEY.value, self.value)

    @classmethod
    def _get(klass: Type['ConfigRegistration'], path: 'ConfigRegistration',
             entry: Dict[str, Any]) -> Union[str, re.Pattern]:
        '''
        Get value at end of `path` keys in `entry`.
        '''
        if not path or not path.value:
            return Null()
        for node in path.value:
            entry = entry.get(node, Null())
        return entry

    @classmethod
    def name(klass: Type['ConfigRegistration'], entry: Dict[str, Any]) -> str:
        '''
        Returns the NAME entry of this registration entry.
        '''
        value = klass._get(klass.NAME, entry)
        return value

    @classmethod
    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)

    @classmethod
    def path_root(klass: Type['ConfigRegistration'], entry: Dict[str, Any],
                  config: 'Configuration') -> Nullable[paths.Path]:
        '''
        Returns the PATH_ROOT entry of this registration entry.

        PATH_ROOT is the resolved (absolute) path to the root of the file tree
        we should search for registration.
        '''
        field = klass._get(klass.PATH_ROOT, entry)
        path = config.path(field)
        # Clean up the path if we found it.
        if path:
            if not path.is_absolute():
                path = paths.cast(os.getcwd()) / path
            path = path.resolve()
        return path

    @classmethod
    def path_run(klass: Type['ConfigRegistration'], entry: Dict[str, Any],
                 registrars: bool) -> Nullable[str]:
        '''
        Returns either PATH_REGISTREES_RUN or PATH_REGISTRARS_RUN, depending on
        `registrars` bool.
          - True: PATH_REGISTRARS_RUN
          - False: PATH_REGISTREES_RUN

        This key's value should be a list of filenames to look for.
        '''
        if registrars:
            return klass._get(klass.PATH_REGISTRARS_RUN, entry)
        return klass._get(klass.PATH_REGISTREES_RUN, entry)

    @classmethod
    def path_test(klass: Type['ConfigRegistration'], entry: Dict[str, Any],
                  registrars: bool) -> Nullable[str]:
        '''
        Returns either PATH_REGISTREES_TEST or PATH_REGISTRARS_TEST, depending
        on `registrars` bool.
          - True: PATH_REGISTRARS_TEST
          - False: PATH_REGISTREES_TEST

        This key's value should be a list of filenames to look for.
        '''
        if registrars:
            return klass._get(klass.PATH_REGISTRARS_TEST, entry)
        return klass._get(klass.PATH_REGISTREES_TEST, entry)

    @classmethod
    def path_ignore_files(klass: Type['ConfigRegistration'],
                          entry: Dict[str, Any]) -> Nullable[str]:
        '''
        Returns the PATH_IGNORE_FILES entry of this registration entry.

        PATH_IGNORE_FILES should be a list of strings and regexes to match
        while checking file names. A matching file will be ignored.
        '''
        return klass._get(klass.PATH_IGNORE_FILES, entry)

    @classmethod
    def path_ignore_dirs(klass: Type['ConfigRegistration'],
                         entry: Dict[str, Any]) -> Nullable[str]:
        '''
        Returns the PATH_IGNORE_DIRS entry of this registration entry.

        PATH_IGNORE_DIRS should be a list of strings and regexes to match while
        checking directory names. A matching dir will be ignored.
        '''
        return klass._get(klass.PATH_IGNORE_DIRS, entry)

    @classmethod
    def force_test(klass: Type['ConfigRegistration'],
                   entry: Dict[str, Any]) -> Nullable[bool]:
        '''
        Returns the FORCE_TEST entry of this registration entry.

        FORCE_TEST, if it exists, should be true to force registration of
        testing classes or false to force skipping test class registration.

        Generally, not supplying is best choice - it will be auto-detected.
        '''
        return klass._get(klass.FORCE_TEST, entry)
示例#11
0
def register(klass: Type['Encodable'],
             dotted: Optional[label.LabelInput] = None,
             unit_test_only: Optional[bool] = False) -> None:
    '''
    Register the `klass` with the `dotted` string to our registry.

    If `unit_test_only` is Truthy, the `klass` 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 = klass.dotted
        except AttributeError:
            pass
        # No dotted string is an error.
        if not dotted:
            msg = ("Encodable sub-classes must either have a `dotted` "
                   "class attribute or be registered with a `dotted` "
                   "argument.")
            error = ValueError(msg, klass, dotted)
            log.registration(log_dotted, msg + "Got '{}' for {}.", dotted,
                             klass)
            raise log.exception(error, msg)

    if enum.needs_wrapped(klass):
        msg = ("Enum sub-classes must be wrapped in an EnumWrap for "
               "Encodable functionality. Call `register_enum()` "
               "instead of `register()`.")
        error = TypeError(msg, klass, dotted)
        log.registration(log_dotted, msg)
        raise log.exception(error,
                            msg,
                            data={
                                'klass': klass,
                                'dotted': label.normalize(dotted),
                                'unit_test_only': unit_test_only,
                            })

    # ---
    # 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(klass)
        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 '{}'...",
                     codec.klass, dotted_str, klass.klass)

    dotted_args = label.regularize(dotted)
    codec.register(klass, *dotted_args)

    log.registration(log_dotted, "{}: Registered '{}' to '{}'.", codec.klass,
                     dotted_str, klass.klass)
示例#12
0
    def get_by_doc(self, doc_type: Document,
                   *keychain: label.LabelInput) -> Nullable[Any]:
        '''
        Get value of `keychain` from `doc_type`.

        Raises a ConfigError if invalid `doc_type` supplied.
        Returns Null() if `doc_type` doesn't exist or `keychain` isn't in it.
        '''
        # Ensure the keychain is in good shape from whatever was passed in.
        keychain = label.regularize(*keychain)

        log.data_processing(self.dotted,
                            'get_by_doc: Getting doc: {}, keychain: {}...',
                            doc_type,
                            keychain,
                            log_minimum=log.Level.DEBUG)

        hierarchy = Document.hierarchy(doc_type)
        if not hierarchy.valid(*keychain):
            log.data_processing(self.dotted,
                                "get_by_doc: invalid document hierarchy for "
                                "doc: {}, keychain: {}...",
                                doc_type,
                                keychain,
                                log_minimum=log.Level.DEBUG)
            raise log.exception(
                ConfigError,
                "Invalid keychain '{}' for {} document type. See its "
                "Hierarchy class for proper layout.", keychain, doc_type)

        # Get document type data first.
        doc_data = self._config.get(doc_type, None)
        data = doc_data
        if data is None:
            log.data_processing(self.dotted,
                                "get_by_doc: No document type '{}' in "
                                "our config data: {}",
                                doc_type,
                                self._config,
                                log_minimum=log.Level.DEBUG,
                                log_success=False)
            return Null()

        # Now hunt for the keychain they wanted...
        for key in keychain:
            data = data.get(key, None)
            if data is None:
                log.data_processing(self.dotted,
                                    "get_by_doc: No data for key '{}' in "
                                    "keychain {} in our config "
                                    "document data: {}",
                                    key,
                                    keychain,
                                    doc_data,
                                    log_minimum=log.Level.DEBUG,
                                    log_success=False)
                return Null()

        log.data_processing(self.dotted, "get_by_doc: Got data for {} in "
                            "keychain {}. Data: {}",
                            doc_type,
                            keychain,
                            data,
                            log_minimum=log.Level.DEBUG,
                            log_success=True)
        return data
示例#13
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)
示例#14
0
    def _search(self, place: Dict[str, Any], dotted: Optional[label.DotStr],
                data: EncodedEither,
                data_type: Type[Encodable]) -> Optional[Encodable]:
        '''
        Provide `self._registry` as starting point of search.

        Searches the registry.

        If 'data_type' is supplied, will restrict search for registered
        Encodable to use to just that or subclasses.

        If `dotted` is provided, walks that keypath and
        returns whatever is registered to that.

        Otherwise, recursively walk our registry dict to find something that
        will claim() `data`.

        Returns registree or None.
        '''
        # Set data_type to any Encodable if not supplied.
        if data_type is None:
            data_type = Encodable

        # ---
        # Provided with a dotted key. Use that for explicit search.
        # ---
        # More like 'get' than search...
        if dotted and label.is_dotstr(dotted):
            keys = label.regularize(dotted)
            # Path shouldn't be long. Just let Null-pattern pretend to be a
            # dict if we hit a 'Does Not Exist'.
            for key in keys:
                place = place.get(key, Null())
            # Done, return either the registree or None.
            place = null_to_none(place)
            # Place must be:
            #   - An Encodable.
            #   - The same class or a subclass of data_type.
            if (not place or (type(place) != data_type
                              and not issubclass(place, data_type))):
                # Don't return some random halfway point in the registry tree.
                place = None
            return place

        # ---
        # Search for a claimer...
        # ---
        for key in place:
            node = place[key]
            # If we got a sub-tree/branch, recurse into it.
            if isinstance(node, dict):
                result = self._search(node, dotted, data, data_type)
                # Did that find it?
                if result:
                    # Yes; return decoded result.
                    return result
                else:
                    # No; done with this - go to next node.
                    continue

            # If we got a leaf node, check it.
            if not issubclass(node, Encodable):
                self._log_warning(
                    "Unexpected node in registry... expect either "
                    f"strings or Encodables, got: {node}")
                continue

            # Do they claim this?
            claiming, _, _ = node.claim(data)
            # claiming, claim, reason = node.claim(data)
            if claiming:
                # Yes; return decoded result.
                return node

            # Else, not this; continue looking.

        # ---
        # Nothing found.
        # ---
        return None
示例#15
0
 def __init__(self, dotted: label.LabelInput) -> None:
     '''
     Initialize the LabelTaxon with the provided dotted label.
     '''
     super().__init__(Rank.Domain.DEFINITIONS, *label.regularize(dotted))