Skip to content

Commit

Permalink
refactor: change to use 'schema' as variable name for cfg schema
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Now only initialised config can be imported as cfg
  • Loading branch information
Artem Vasenin committed Jan 30, 2024
1 parent f5d7a0c commit 488109e
Show file tree
Hide file tree
Showing 60 changed files with 201 additions and 214 deletions.
46 changes: 27 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ from pycs import CL, CN
class BaseClass:
pass

cfg = CN() # Basic config node
cfg.DICT = CN() # Nested config node
cfg.DICT.FOO = "FOO" # Config leaf with actual value
cfg.DICT.INT = 1
cfg.NAME = CL(None, str, required=True) # Specification of config leaf to be defined with type
cfg.CLASSES = CN(BaseClass) # Config node with type specification of its config leafs
cfg.SUBCLASSES = CN(CL(None, BaseClass, subclass=True)) # Config node with subclass specification of its config leafs
cfg.VAL = CL(1, desc="Interesting description") # Config leaf with description
schema = CN() # Basic config node
schema.DICT = CN() # Nested config node
schema.DICT.FOO = "FOO" # Config leaf with actual value
schema.DICT.INT = 1
schema.NAME = CL(None, str, required=True) # Specification of config leaf to be defined with type
schema.CLASSES = CN(BaseClass) # Config node with type specification of its config leafs
schema.SUBCLASSES = CN(CL(None, BaseClass, subclass=True)) # Config node with subclass specification of its config leafs
schema.VAL = CL(1, desc="Interesting description") # Config leaf with description

def transform(cfg: CN) -> None:
cfg.DICT.FOO = "BAR"
Expand All @@ -50,24 +50,33 @@ def hook(cfg: CN) -> None:

# Add transform, validation & hooks function
# Transforms are run after config is loaded and can change values in config
cfg.add_transform(transform)
schema.add_transform(transform)
# Validators are run after transforms and freeze, with them you can verify additional restrictions
cfg.add_validator(validate)
schema.add_validator(validate)
# Hooks are run after validators and can perform additional actions outside of config
cfg.add_hook(hook)
schema.add_hook(hook)
# Validators and hooks should not (and mostly cannot) modify the config
```

1. Set actual values for each leaf in the config, **the import has to be absolute**:
1. If you want to use configuration with default values or make changes in the program you can use `.static_init()`:

```python
from project.config import schema

cfg = schema.static_init()
print(cfg.DICT.FOO) # FOO
```

1. If you want to store changes more permanently, please create a config file:

```python
# my_cfg.py
from pycs import CN

from project.config import cfg # Import has to be absolute
from project.config import schema

# Pass schema as an argument to the CN() to init the schema
cfg = CN(cfg)
# Pass schema as an argument to the CN() to init the config
cfg = CN(schema)

# Schema changes are not allowed here, only leafs can be altered.
cfg.NAME = "Hello World!"
Expand All @@ -80,17 +89,16 @@ You can also create another file to inherit from first and add more changes:
# my_cfg2.py
from ntc import CN

from .my_cfg import cfg # This import has to be relative and should only import cfg variable
from .my_cfg import cfg

cfg = CN(cfg)
cfg.DICT.FOO = "BAR"
```

There a few restrictions on imports in configs:

- When you are importing config schema from project that import has to be **absolute**
- When you inherit config values from another file, that import has to be **relative**
- Other than config inheritance, all other imports have to be **absolute**
- If you are inheriting changes from another config, please import variable as `cfg`
- No other import should be named `cfg`

1. Load actual config and use it in the code.

Expand Down
4 changes: 2 additions & 2 deletions pycs/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def load(cfg_path: Union[Path, str]) -> CfgNode:
module = import_module(cfg_path)
cfg: CfgNode = module.cfg
if not cfg.schema_frozen:
raise SchemaError("Changes to config must be started with `cfg = CN(cfg)`")
raise SchemaError("Found cfg with unfrozen schema, please initialise config from schema: cfg = CN(schema)")
if hasattr(cfg, "NAME"):
cfg.NAME = cfg.NAME or _cfg_path_to_name(cfg_path, cfg._root_name) # noqa: SLF001 Same class
cfg.propagate_changes()
Expand Down Expand Up @@ -442,7 +442,7 @@ def _update_module(self, key: str, value) -> None:
break

lines = [reference_comment]
valid_types = [int, float, str]
valid_types = [bool, int, float, str]
if isinstance(value, type):
module = inspect.getmodule(value)
lines.append(f"from {module.__name__} import {value.__name__}\n")
Expand Down
26 changes: 15 additions & 11 deletions pycs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from pathlib import Path
from typing import TYPE_CHECKING

import isort
import yaml

from .errors import ModuleError

if TYPE_CHECKING:
from typing import ModuleType

Expand All @@ -34,28 +33,33 @@ def merge_cfg_module(module: ModuleType, imported_modules: set[str] = None) -> l

with module_path.open() as module_file_fobj:
module_file = list(module_file_fobj)
if "cfg = CN(cfg)\n" not in module_file:
raise ModuleError("Can't find config definition, please import config schema using absolute path")
lines.append(f"# START --- {module_path} ---\n")
for line_idx, line in enumerate(module_file):
for line in module_file:
if line.startswith("from "):
_, import_path, __, *imports = line.strip().split(" ")
imported_module = importlib.import_module(import_path, package=module.__package__)
if imported_module.__spec__.name in imported_modules:
continue
if import_path.startswith("."):
if imports == ["cfg"]:
valid_cfg_names = {"cfg", "cfg,"}
for pattern in valid_cfg_names:
if pattern in imports:
lines.extend(merge_cfg_module(imported_module, imported_modules=imported_modules))
else:
raise ModuleError(f"Only cfg can be imported relatively:\n{module_path}+{line_idx}:{line}")
else:
if "as" in imports:
as_idxs = [idx for idx, elem in enumerate(imports) if elem == "as"]
for as_index in as_idxs:
if imports[as_index + 1] in pattern:
imports = imports[: as_index - 1] + imports[as_index + 2 :]
imports.remove(pattern)

line = f"from {import_path} import " + ", ".join(imports) if imports else None
if line:
lines.append(line)
else:
lines.append(line)
lines.append(f"# END --- {module_path} ---\n")

imported_modules.add(module_name)
return lines
return isort.code("".join(lines)).splitlines(keepends=True)


def add_yaml_str_representer():
Expand Down
7 changes: 0 additions & 7 deletions tests/data/bad/bad_cfg_import.py

This file was deleted.

9 changes: 0 additions & 9 deletions tests/data/bad/bad_import.py

This file was deleted.

6 changes: 3 additions & 3 deletions tests/data/bad/bad_inherit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from pycs import CN

from ..base_cfg import cfg
from ..base_cfg import schema

cfg = cfg.inherit()
schema = schema.inherit()

cfg.CLASSES.ANOTHER = CN()
schema.CLASSES.ANOTHER = CN()
4 changes: 2 additions & 2 deletions tests/data/bad/bad_inherit_changes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from pycs import CN
from tests.data.bad.bad_inherit import cfg
from tests.data.bad.bad_inherit import schema

cfg = CN(cfg)
cfg = CN(schema)
6 changes: 3 additions & 3 deletions tests/data/bad/bad_inherit_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

from pycs import CL

from ..base_cfg import cfg
from ..base_cfg import schema
from ..base_class import BaseClass


class BadClass:
pass


cfg = cfg.clone()
schema = schema.clone()

cfg.CLASSES.ANOTHER = CL(BadClass(), BaseClass)
schema.CLASSES.ANOTHER = CL(BadClass(), BaseClass)
6 changes: 3 additions & 3 deletions tests/data/bad/bad_inherit_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

from pycs import CL

from ..base_cfg import cfg
from ..base_cfg import schema
from ..base_class import BaseClass

cfg = cfg.clone()
schema = schema.clone()

cfg.CLASSES.ANOTHER = CL(None, BaseClass, subclass=True)
schema.CLASSES.ANOTHER = CL(None, BaseClass, subclass=True)
6 changes: 3 additions & 3 deletions tests/data/bad/bad_inherit_subclass_class.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

from ..base_cfg import cfg
from ..base_cfg import schema
from ..base_class import BaseClass

cfg = cfg.clone()
schema = schema.clone()

cfg.CLASSES.ANOTHER = BaseClass
schema.CLASSES.ANOTHER = BaseClass
6 changes: 3 additions & 3 deletions tests/data/bad/bad_inherit_subclass_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

from pycs import CL

from ..base_cfg import cfg
from ..base_cfg import schema
from ..base_class import BaseClass

cfg = cfg.clone()
schema = schema.clone()

cfg.SUBCLASSES.ANOTHER = CL(BaseClass(), BaseClass, subclass=True)
schema.SUBCLASSES.ANOTHER = CL(BaseClass(), BaseClass, subclass=True)
4 changes: 2 additions & 2 deletions tests/data/bad/inheritance_changes_bad.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from pycs import CN
from tests.data.good.inheritance import cfg
from tests.data.good.inheritance import schema

cfg = CN(cfg)
cfg = CN(schema)
cfg.DICT.BAR = 2
36 changes: 18 additions & 18 deletions tests/data/base_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@

from .base_class import BaseClass

cfg = CN(desc="Root config node")

cfg.NAME = CL(None, str)
cfg.DICT = CN()
cfg.DICT.INT = 1
cfg.DICT.FOO = "Default foo value"
cfg.DICT.FOO2 = "Default foo2 value"
cfg.DICT.X = "X"
cfg.LIST = [1, 2, 3, 4]
cfg.STR = "Default str value"
cfg.BOOL = False
cfg.CLASS = BaseClass()
cfg.CLASSES = CN(BaseClass)
cfg.SUBCLASS = BaseClass
cfg.SUBCLASSES = CN(CL(None, BaseClass, subclass=True))
cfg.NEW = CN(new_allowed=True)
schema = CN(desc="Root config node")

schema.NAME = CL(None, str)
schema.DICT = CN()
schema.DICT.INT = 1
schema.DICT.FOO = "Default foo value"
schema.DICT.FOO2 = "Default foo2 value"
schema.DICT.X = "X"
schema.LIST = [1, 2, 3, 4]
schema.STR = "Default str value"
schema.BOOL = False
schema.CLASS = BaseClass()
schema.CLASSES = CN(BaseClass)
schema.SUBCLASS = BaseClass
schema.SUBCLASSES = CN(CL(None, BaseClass, subclass=True))
schema.NEW = CN(new_allowed=True)


def transform(cfg: CN):
Expand All @@ -30,5 +30,5 @@ def validate(cfg: CN):
assert cfg.NAME != "Bad"


cfg.add_transform(transform)
cfg.add_validator(validate)
schema.add_transform(transform)
schema.add_validator(validate)
8 changes: 4 additions & 4 deletions tests/data/description/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from pycs import CL, CN
from tests.data.base_cfg import cfg
from tests.data.base_cfg import schema

cfg = cfg.inherit()
schema = schema.inherit()

cfg.DESCRIBED = CL("described", desc="Described leaf")
cfg.DESCRIBED_NESTING = CN(CL(str, desc="Described nesting"))
schema.DESCRIBED = CL("described", desc="Described leaf")
schema.DESCRIBED_NESTING = CN(CL(str, desc="Described nesting"))
4 changes: 2 additions & 2 deletions tests/data/description/description.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from pycs import CN
from tests.data.description.base import cfg
from tests.data.description.base import schema

cfg = CN(cfg)
cfg = CN(schema)
4 changes: 2 additions & 2 deletions tests/data/description/description_bad.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

from pycs import CN
from tests.data.description.base import cfg
from tests.data.description.base import schema

cfg = CN(cfg)
cfg = CN(schema)

cfg.DESCRIBED = 1
4 changes: 2 additions & 2 deletions tests/data/description/description_inherited.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from pycs import CL, CN
from tests.data.description.base import cfg
from tests.data.description.base import schema

cfg = CN(cfg)
cfg = CN(schema)

cfg.DESCRIBED_NESTING.FOO = "foo"
cfg.DESCRIBED_NESTING.BAR = CL("bar", desc="Overrided description")
4 changes: 2 additions & 2 deletions tests/data/description/description_value_overriding.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

from pycs import CN
from tests.data.description.base import cfg
from tests.data.description.base import schema

cfg = CN(cfg)
cfg = CN(schema)

cfg.DESCRIBED = "overrided"
6 changes: 3 additions & 3 deletions tests/data/good/full_key_node_assign.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from pycs.node import CN

from ..base_cfg import cfg
from ..base_cfg import schema

cfg = cfg.inherit()
schema = schema.inherit()

sub_cfg = CN()
sub_cfg.BAR = "bar"
sub_cfg.FOO = "foo"

cfg.FOO = sub_cfg
schema.FOO = sub_cfg
4 changes: 2 additions & 2 deletions tests/data/good/full_key_node_assign_changes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

from pycs import CN
from tests.data.good.full_key_node_assign import cfg
from tests.data.good.full_key_node_assign import schema

cfg = CN(cfg)
cfg = CN(schema)

cfg.FOO.BAR = "foo"
Loading

0 comments on commit 488109e

Please sign in to comment.