From 85176bc7270c003cff8b731456cc09f758282052 Mon Sep 17 00:00:00 2001 From: Olivier Ramonat Date: Fri, 19 Jul 2024 10:36:51 +0200 Subject: [PATCH] Remove e3.config and typeguard dependency The initial goal of e3.config was to have a structured e3.toml file that could be use to configure the behaviour of e3-core. This was using typeguard to ensure that the config file was valid. This code has only been used in e3.log, to configure the default log format. Since March 2023, the code is no longer compatible with recent versions of typeguard. Given that it is unmaintained and no longer in use it is better to remove the code. for it/e3-core#6 --- NEWS.md | 4 + pyproject.toml | 5 - src/e3/config.py | 142 ----------------------------- src/e3/log.py | 26 +----- src/e3/pytest.py | 2 - src/e3/sys.py | 10 -- tests/tests_e3/config/main_test.py | 21 ----- tests/tests_e3/main/main_test.py | 23 ----- 8 files changed, 8 insertions(+), 225 deletions(-) delete mode 100644 src/e3/config.py delete mode 100644 tests/tests_e3/config/main_test.py diff --git a/NEWS.md b/NEWS.md index a0966117..10e048d6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,10 @@ * Add DLL closure check to Anod class * Add git_shallow_fetch_since to checkout.py +* Backward incompatible change: + * Remove e3.config and typeguard dependency. This removes the possibility + to configure the default e3.log formatting using `e3.toml` + # Version 22.6.0 (2024-06-19) * Fix encoding/vex action statement for affected products diff --git a/pyproject.toml b/pyproject.toml index 9263a025..0f972fa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,11 +46,6 @@ dependencies = [ Repository = "https://github.com/AdaCore/e3-core" [project.optional-dependencies] -config = [ - "tomlkit", - # There are some backward incompatible checks in typeguard 3.x - "typeguard<3.0.0" -] test = [ "coverage", "mock", diff --git a/src/e3/config.py b/src/e3/config.py deleted file mode 100644 index 70c48b66..00000000 --- a/src/e3/config.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Read e3 config file.""" - -from __future__ import annotations -from dataclasses import fields, dataclass - -from typing import TYPE_CHECKING, get_type_hints, ClassVar - -try: - from typeguard import check_type - - CONFIG_CHECK_TYPE = True -except ImportError: # defensive code - CONFIG_CHECK_TYPE = False - - -import logging -import os - -if TYPE_CHECKING: - from typing import TypeVar - - T = TypeVar("T", bound="ConfigSection") - - -if "E3_CONFIG" in os.environ: - KNOWN_CONFIG_FILES = [os.environ["E3_CONFIG"]] -else: - KNOWN_CONFIG_FILES = [ - os.path.join( - os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), - "e3.toml", - ), - os.path.expanduser("~/e3.toml"), - ] - - -@dataclass -class ConfigSection: - title: ClassVar[str] - - @classmethod - def load(cls: type[T]) -> T: - """Load a section of the configuration file. - - To load a new section, subclass ConfigSection and document the - fields that you expect to parse, e.g.:: - - @dataclass - class MyConfig(ConfigSection): - title = "my_config_subsection" - option : str = "default value" - - my_config = MyConfig.load() - - If the package typeguard is installed type defined in the metaclass - will be verified. - """ - schema = get_type_hints(cls) - cls_fields = {f.name: schema[f.name] for f in fields(cls) if f.name != "title"} - kwargs = {} - - for k, v in Config.load_section(cls.title).items(): - if k in cls_fields: - ftype = cls_fields[k] - try: - if CONFIG_CHECK_TYPE: - check_type(f"{cls.title}.{k}", v, ftype) - except TypeError as err: - logging.error(str(err)) - else: - kwargs[k] = v - - return cls(**kwargs) - - -class Config: - """Load e3 configuration file and validate each section. - - This class expose the .load_section(
, ) method - that can be used by ConfigSection instance corresponding to the loaded - configuration section after validation. - - Note that without the tomlkit package the configuration is not read. - """ - - data: ClassVar[dict] = {} - - @classmethod - def load_section(cls, section: str) -> dict: - """Load a configuration section content. - - :param section: if contains "." nested subsection will be found. For - instance "log.fmt" will return the section: - - [log] - [log.fmt] - :return: the configuration dict - """ - if not cls.data: - cls.load() - - subsections = section.split(".") - result = cls.data - for subsection in subsections: - result = result.get(subsection, {}) - - return result - - @classmethod - def load_file(cls, filename: str) -> None: - """Load the configuration file(s). - - Note that this method is automatically loaded the first time .get() - is called. - - :param filename: configuration file to load - """ - try: - from tomlkit import parse - from tomlkit.exceptions import TOMLKitError - except ImportError: # defensive code - logging.error(f"cannot load {filename} (cannot import tomlkit)") - else: - with open(filename) as f: - try: - cls.data.update(parse(f.read())) - except TOMLKitError as e: - logging.error(str(e)) - - @classmethod - def load(cls) -> None: - """Load the configuration file(s). - - Note that this method is automatically loaded the first time .get() - is called. - - :param filename: if not None load this configuration file instead of - the default config files - """ - for config_file in KNOWN_CONFIG_FILES: - if os.path.isfile(config_file): - cls.load_file(config_file) diff --git a/src/e3/log.py b/src/e3/log.py index b654b669..8d02c5d3 100644 --- a/src/e3/log.py +++ b/src/e3/log.py @@ -1,7 +1,6 @@ """Extensions to the standard Python logging system.""" from __future__ import annotations -from dataclasses import dataclass import logging import os @@ -9,13 +8,11 @@ import sys import time import json -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING from colorama import Fore, Style from tqdm import tqdm -from e3.config import ConfigSection - if TYPE_CHECKING: from typing import ( Any, @@ -47,28 +44,13 @@ NO_DEBUG_LOGGING_MODULES = ["boto3", "botocore", "requests", "urllib3"] -@dataclass -class LogConfig(ConfigSection): - title: ClassVar[str] = "log" - - pretty: bool = True - stream_fmt: str = "%(levelname)-8s %(message)s" - file_fmt: str = "%(asctime)s: %(name)-24s: %(levelname)-8s %(message)s" - - -log_config = LogConfig.load() - - # Default output stream (sys.stdout by default, or a file descriptor if # activate() is called with a filename. default_output_stream: TextIO | IO[str] = sys.stdout # If sys.stdout is a terminal then enable "pretty" output for user # This includes progress bars and colors -if sys.stdout.isatty(): # all: no cover (not used in production!) - pretty_cli = log_config.pretty -else: - pretty_cli = False +pretty_cli = sys.stdout.isatty() console_logs: str | None = None @@ -419,8 +401,8 @@ def activate_with_args(args: Namespace, default_level: int = logging.WARNING) -> def activate( - stream_format: str = log_config.stream_fmt, - file_format: str = log_config.file_fmt, + stream_format: str = "%(levelname)-8s %(message)s", + file_format: str = "%(asctime)s: %(name)-24s: %(levelname)-8s %(message)s", datefmt: str | None = None, level: int = logging.INFO, filename: str | None = None, diff --git a/src/e3/pytest.py b/src/e3/pytest.py index cc125fd7..ff01dc10 100644 --- a/src/e3/pytest.py +++ b/src/e3/pytest.py @@ -5,7 +5,6 @@ import logging import os -from e3.config import Config from e3.env import Env from e3.fs import rm from e3.os.fs import cd, mv, which @@ -88,7 +87,6 @@ def env_protect(request: pytest.FixtureRequest) -> None: Env().store() tempd = mkdtemp() cd(tempd) - Config.data = {} os.environ["TZ"] = "UTC" os.environ["E3_ENABLE_FEATURE"] = "" diff --git a/src/e3/sys.py b/src/e3/sys.py index 4cd6c277..831a8f1f 100644 --- a/src/e3/sys.py +++ b/src/e3/sys.py @@ -7,8 +7,6 @@ import sys from typing import TYPE_CHECKING from enum import Enum -from dataclasses import asdict -from pprint import pformat import e3.log @@ -178,9 +176,6 @@ def main() -> None: m.argument_parser.add_argument( "--check", help="Run e3 sanity checking", action="store_true" ) - m.argument_parser.add_argument( - "--show-config", action="store_true", help="Show e3 config" - ) m.parse_args() if TYPE_CHECKING: @@ -200,11 +195,6 @@ def main() -> None: elif m.args.platform_info: print(getattr(Env(), m.args.platform_info)) - if m.args.show_config: - print("[log]") - for k, v in asdict(e3.log.log_config).items(): - print("{}: {}".format(k, pformat(v))) - def set_python_env(prefix: str) -> None: """Set environment for a Python distribution. diff --git a/tests/tests_e3/config/main_test.py b/tests/tests_e3/config/main_test.py deleted file mode 100644 index b72e6d7f..00000000 --- a/tests/tests_e3/config/main_test.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass -from e3.config import Config, ConfigSection - - -def test_config(): - with open("e3.toml", "w") as f: - f.write( - '[e3]\nfoo = 2\n [e3.log]\n pretty = false\n stream_fmt = "%(message)s"' - ) - - Config.load_file("e3.toml") - print(Config.data) - - @dataclass - class MyConfig(ConfigSection): - title = "e3.log" - pretty: bool = True - - config = MyConfig.load() - print(config) - assert not config.pretty diff --git a/tests/tests_e3/main/main_test.py b/tests/tests_e3/main/main_test.py index 89c2fd74..8ce75075 100644 --- a/tests/tests_e3/main/main_test.py +++ b/tests/tests_e3/main/main_test.py @@ -1,4 +1,3 @@ -import os import re import sys @@ -15,28 +14,6 @@ def test_main(): ) -def test_main_config(): - os.environ["E3_CONFIG"] = "e3.toml" - assert "pretty: True" in e3.os.process.Run(["e3", "--show-config"]).out - - with open("e3.toml", "w") as f: - f.write("[log]\npretty = false\n") - assert "pretty: False" in e3.os.process.Run(["e3", "--show-config"]).out - - # Verify that invalid config field is ignored - with open("e3.toml", "w") as f: - f.write('[log]\npretty = "false"\nstream_fmt = "%(message)s"') - out = e3.os.process.Run(["e3", "--show-config"]).out - assert "pretty: True" in out - assert "stream_fmt: '%(message)s'" in out - assert "type of log.pretty must be bool" in out - - # And finally check that invalid toml are discarded - with open("e3.toml", "w") as f: - f.write("this is an invalid toml content") - assert "pretty: True" in e3.os.process.Run(["e3", "--show-config"]).out - - def test_mainprog(): with open("mymain.py", "w") as f: f.write(