diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f8b5a..68352f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [1.5.2] + +### Added + +- `isolated` added +- `local_modules` and `shared_modules` added + ## [1.5.1] ### Fixed diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index e00e287..a213b5a 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -326,6 +326,17 @@ Example: Top-Level Configuration Keys ---------------------------- +isolated +~~~~~~~~ + +The ``isolated`` key controls how this configuration module is registered when imported. + +- ``true`` *(default)* → The module is added as a **local module** for the importing parser only. + Other parsers that import this module will have it in their **local_modules**. + +- ``false`` → The module is added to **shared_modules** if not already present. + Shared modules are globally accessible and can be reused by multiple parsers. + local ~~~~~ diff --git a/docs/source/parser.rst b/docs/source/parser.rst index 34a8bdc..9639292 100644 --- a/docs/source/parser.rst +++ b/docs/source/parser.rst @@ -28,7 +28,9 @@ Initialization ConfigParser( config_path: str | Path, - kwargs: dict | None = None + kwargs: dict | None = None, + *, + isolated: bool = True, ) Parameters: @@ -40,6 +42,17 @@ Parameters: Optional runtime keyword arguments. These values take precedence during resolution. +- ``isolated`` *(default: True)* + Controls how this parser is registered in Kaizo's module system. + + - If ``true`` *(default)* + The module is added as a **local module** for any parser that imports it. + This means it is only accessible to the parser that imported it. + + - If ``false`` + The module is added to **shared_modules** if it isn't already present. + Shared modules are globally accessible to all parsers, allowing cross-file references and preventing duplication. + .. note:: Runtime ``kwargs`` override values found in configuration files diff --git a/kaizo/parser.py b/kaizo/parser.py index 3377f04..ee88fbf 100644 --- a/kaizo/parser.py +++ b/kaizo/parser.py @@ -1,7 +1,7 @@ import os from pathlib import Path from types import ModuleType -from typing import Any +from typing import Any, Self import yaml @@ -24,10 +24,18 @@ class ConfigParser: local: ModuleType | None storage: dict[str, Storage] kwargs: DictEntry[str] - modules: dict[str, "ConfigParser"] | None + local_modules: dict[str, Self] | None + shared_modules: dict[str, Self] = {} plugins: dict[str, FnWithKwargs[Plugin]] | None + isolated: bool - def __init__(self, config_path: str | Path, kwargs: dict[str] | None = None) -> None: + def __init__( + self, + config_path: str | Path, + kwargs: dict[str] | None = None, + *, + isolated: bool = True, + ) -> None: root, _ = os.path.split(config_path) root = Path(root) @@ -38,6 +46,8 @@ def __init__(self, config_path: str | Path, kwargs: dict[str] | None = None) -> with Path.open(config_path) as file: self.config = yaml.safe_load(file) + self.isolated = self.config.pop("isolated", isolated) + if "local" in self.config: local_path = Path(self.config.pop("local")) @@ -55,9 +65,23 @@ def __init__(self, config_path: str | Path, kwargs: dict[str] | None = None) -> msg = f"import module should be a dict, got {type(modules)}" raise TypeError(msg) - self.modules = self._import_modules(root, modules, kwargs) + imported_modules = self._import_modules( + root, + modules, + kwargs, + isolated=isolated, + ) + + self.local_modules = {} + + for key, value in imported_modules.items(): + if value.isolated: + self.local_modules[key] = value + elif key not in ConfigParser.shared_modules: + ConfigParser.shared_modules[key] = value + else: - self.modules = None + self.local_modules = None if "plugins" in self.config: plugins = self.config.pop("plugins") @@ -75,7 +99,9 @@ def _import_modules( root: Path, modules: dict[str, str], kwargs: dict[str] | None = None, - ) -> dict[str, "ConfigParser"]: + *, + isolated: bool = True, + ) -> dict[str, Self]: module_dict = {} for module_name, module_path_str in modules.items(): @@ -84,7 +110,7 @@ def _import_modules( if not module_path.is_absolute(): module_path = root / module_path - parser = ConfigParser(module_path, kwargs) + parser = ConfigParser(module_path, kwargs, isolated=isolated) parser.parse() module_dict[module_name] = parser @@ -154,9 +180,24 @@ def _load_symbol_from_module(self, module_path: str, symbol_name: str) -> Any: return ModuleLoader.load_object(module_path, symbol_name) + def _resolve_parser(self, key: str) -> Self: + if self.local_modules is None: + msg = "import module is not given" + raise ValueError(msg) + + module = self.local_modules.get(key) + + if module is None: + module = ConfigParser.shared_modules.get(key) + + if module is None: + msg = f"keyword not found, got {key}" + raise ValueError(msg) + + return module + def _resolve_from_storage( self, - storage: dict[str, Storage], *, key: str, entry_key: str | None, @@ -172,7 +213,7 @@ def _resolve_from_storage( if not storage_key: storage_key = key - storage_i = storage.get(storage_key) + storage_i = self.storage.get(storage_key) if storage_i is None: return None @@ -190,25 +231,15 @@ def _resolve_string(self, key: str, entry: str) -> Entry: return self.kwargs[entry_sub_key] parsed_entry = self._resolve_from_storage( - self.storage, key=key, entry_key=entry_key, entry_sub_key=entry_sub_key, ) else: - if self.modules is None: - msg = "import module is not given" - raise ValueError(msg) + parser = self._resolve_parser(entry_module) - module = self.modules.get(entry_module) - - if module is None: - msg = f"keyword not found, got {entry_module}" - raise ValueError(msg) - - parsed_entry = self._resolve_from_storage( - module.storage, + parsed_entry = parser._resolve_from_storage( key=key, entry_key=entry_key, entry_sub_key=entry_sub_key, diff --git a/pyproject.toml b/pyproject.toml index eb6d1ff..2105df5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kaizo" -version = "1.5.1" +version = "1.5.2" description = "declarative YAML-based configuration parser" authors = [{ name = "Mohammad Ghazanfari", email = "mgh.5225@gmail.com" }] readme = "README.md"