Repository: kedro-org/kedro
File: kedro/io/data_catalog.py (Python)

Code before:
    """Replace non-word characters in data set names since Kedro 0.16.2.

    Args:
        data_set_name: The data set name registered in the data catalog.

    Returns:
        The name used in `DataCatalog.datasets`.
    """
    return re.sub(WORDS_REGEX_PATTERN, "__", data_set_name)


def _specificity(pattern: str) -> int:
    """Helper function to check the length of exactly matched characters not inside brackets
    Example -
    specificity("{namespace}.companies") = 10
    specificity("{namespace}.{dataset}") = 1
    specificity("france.companies") = 16
    Args:
        pattern: The factory pattern
    """
    # Remove all the placeholders from the pattern
    result = re.sub(r"\{.*?\}", "", pattern)
    return len(result)


def _sort_dataset_factory_patterns(dataset_patterns: list) -> list:
    """Helper function to ort all the patterns according to the parsing rules -
    1. Decreasing specificity (no of characters outside the brackets)
    2. Decreasing number of placeholders (no of curly brackets)
    3. Alphabetical
    """
    return sorted(
        dataset_patterns,
        key=lambda pattern: (
            -(_specificity(pattern)),
            -pattern.count("{"),
            pattern,
        ),
    )


class _FrozenDatasets:
    """Helper class to access underlying loaded datasets."""

    def __init__(
        self,
        *datasets_collections: _FrozenDatasets | dict[str, AbstractDataSet],
    ):
        """Return a _FrozenDatasets instance from some datasets collections.
        Each collection could either be another _FrozenDatasets or a dictionary.
        """

Review comment (refactor):
Having helper functions outside of the class makes inheritance a bit harder. Can you add this one and the previous one as a class method to the `DataCatalog`?

Code after:
    """Replace non-word characters in data set names since Kedro 0.16.2.

    Args:
        data_set_name: The data set name registered in the data catalog.

    Returns:
        The name used in `DataCatalog.datasets`.
    """
    return re.sub(WORDS_REGEX_PATTERN, "__", data_set_name)


class _FrozenDatasets:
    """Helper class to access underlying loaded datasets."""

    def __init__(
        self,
        *datasets_collections: _FrozenDatasets | dict[str, AbstractDataSet],
    ):
        """Return a _FrozenDatasets instance from some datasets collections.
        Each collection could either be another _FrozenDatasets or a dictionary.
        """
        for collection in datasets_collections:
            if isinstance(collection, _FrozenDatasets):
                self.__dict__.update(collection.__dict__)
            else:
                # Non-word characters in dataset names are replaced with `__`
                # for easy access to transcoded/prefixed datasets.
                self.__dict__.update(
                    {
                        _sub_nonword_chars(dataset_name): dataset
                        for dataset_name, dataset in collection.items()
                    }
                )

    # Don't allow users to add/change attributes on the fly
    def __setattr__(self, key, value):
        msg = "Operation not allowed! "
        if key in self.__dict__:
            msg += "Please change datasets through configuration."
        else:
            msg += "Please use DataCatalog.add() instead."
        raise AttributeError(msg)


class DataCatalog:
    """``DataCatalog`` stores instances of ``AbstractDataSet`` implementations
    to provide ``load`` and ``save`` capabilities from anywhere in the
    program. To use a ``DataCatalog``, you need to instantiate it with
    a dictionary of data sets. Then it will act as a single point of reference
    for your calls, relaying load and save functions
    to the underlying data sets.