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(