From 9a4b18f97b34b98f667b16d85d8f7bd5b7cbf5cd Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Tue, 9 Jul 2024 15:31:39 +0200 Subject: [PATCH 01/14] remove config cleanup code --- src/latexplotlib/__init__.py | 2 - src/latexplotlib/_cleanup.py | 40 ---------- src/latexplotlib/_config.py | 2 - tests/test_20_cleanup.py | 137 ----------------------------------- 4 files changed, 181 deletions(-) delete mode 100644 src/latexplotlib/_cleanup.py delete mode 100644 tests/test_20_cleanup.py diff --git a/src/latexplotlib/__init__.py b/src/latexplotlib/__init__.py index 93b7a10..2f9b595 100644 --- a/src/latexplotlib/__init__.py +++ b/src/latexplotlib/__init__.py @@ -1,6 +1,5 @@ from typing import Any -from ._cleanup import purge_old_styles from ._config import size from ._latexplotlib import ( convert_inches_to_pt, @@ -27,5 +26,4 @@ def __getattr__(name: str) -> Any: # noqa: ANN401 return getattr(plt, name) -purge_old_styles(__path__) make_styles_available(__path__) diff --git a/src/latexplotlib/_cleanup.py b/src/latexplotlib/_cleanup.py deleted file mode 100644 index 7c710b5..0000000 --- a/src/latexplotlib/_cleanup.py +++ /dev/null @@ -1,40 +0,0 @@ -import filecmp -from pathlib import Path - -from matplotlib import get_configdir - -from ._config import _PURGED_OLD, config - -STYLELIB = "stylelib" -STYLES = { - "latex9pt-minimal.mplstyle", - "latex9pt.mplstyle", - "latex10pt-minimal.mplstyle", - "latex10pt.mplstyle", - "latex11pt-minimal.mplstyle", - "latex11pt.mplstyle", - "latex12pt-minimal.mplstyle", - "latex12pt.mplstyle", -} -STYLES_FOLDER = "styles" - - -def purge_old_styles(path: list[str]) -> None: - if config[_PURGED_OLD]: - return - - old_styledir = Path(get_configdir()) / STYLELIB - - if not old_styledir.is_dir(): - config[_PURGED_OLD] = True - return - - old_styles = {s.name for s in old_styledir.glob("latex*.mplstyle")} - - for style in STYLES & old_styles: - if filecmp.cmp( - Path(path[0]) / STYLES_FOLDER / style, old_styledir / style, shallow=False - ): - (old_styledir / style).unlink() - - config[_PURGED_OLD] = True diff --git a/src/latexplotlib/_config.py b/src/latexplotlib/_config.py index 62e6978..645b3ce 100644 --- a/src/latexplotlib/_config.py +++ b/src/latexplotlib/_config.py @@ -11,8 +11,6 @@ GOLDEN_RATIO: float = (5**0.5 + 1) / 2 NAME: str = "latexplotlib" -_PURGED_OLD = "_purged_old_styles" - CONFIGFILE: str = "config.ini" CONFIGDIR: Path = Path(user_config_dir(NAME)) CONFIGPATH: Path = CONFIGDIR / CONFIGFILE diff --git a/tests/test_20_cleanup.py b/tests/test_20_cleanup.py deleted file mode 100644 index c1e9a59..0000000 --- a/tests/test_20_cleanup.py +++ /dev/null @@ -1,137 +0,0 @@ -import pytest - -import latexplotlib._cleanup as cleanup - - -def test_styles(): - old_names = { - "latex9pt-minimal.mplstyle", - "latex9pt.mplstyle", - "latex10pt-minimal.mplstyle", - "latex10pt.mplstyle", - "latex11pt-minimal.mplstyle", - "latex11pt.mplstyle", - "latex12pt-minimal.mplstyle", - "latex12pt.mplstyle", - } - assert old_names == cleanup.STYLES - - -class TestPurgeOldStyles: - @pytest.fixture - def purged_str(self, monkeypatch): - s = "banana" - monkeypatch.setattr(cleanup, "_PURGED_OLD", s) - return s - - @pytest.fixture(autouse=True) - def config(self, purged_str, monkeypatch): - default = {purged_str: False} - monkeypatch.setattr(cleanup, "config", default) - - def fun(): - assert default[purged_str] is True, "'is_purged'-flag is not set to True" - - self.assert_purged_true = fun - return default - - @pytest.fixture - def styles_folder(self, monkeypatch): - styles_folder = "new" - monkeypatch.setattr(cleanup, "STYLES_FOLDER", styles_folder) - return styles_folder - - @pytest.fixture - def new(self, tmp_path, styles_folder): - path = tmp_path / styles_folder - path.mkdir() - return path - - @pytest.fixture - def stylelib(self, monkeypatch): - stylelib = "old" - monkeypatch.setattr(cleanup, "STYLELIB", stylelib) - return stylelib - - @pytest.fixture - def old(self, tmp_path, stylelib): - path = tmp_path / stylelib - path.mkdir() - return path - - @pytest.fixture(autouse=True) - def mock_configdir(self, mocker, tmp_path): - return mocker.patch( - "latexplotlib._cleanup.get_configdir", return_value=tmp_path - ) - - def test_is_purged(self, config, purged_str, mock_configdir): - config[purged_str] = True - cleanup.purge_old_styles([]) - - assert not mock_configdir.called - self.assert_purged_true() - - def test_old_styledir_not_exists(self, mock_configdir, old): - old.rmdir() - - cleanup.purge_old_styles([]) - mock_configdir.assert_called_once() - - self.assert_purged_true() - - def test_empty_dir(self, mock_configdir, new): - cleanup.purge_old_styles([new.parent]) - mock_configdir.assert_called_once() - - self.assert_purged_true() - - @pytest.fixture( - params=[ - "latex9pt-minimal.mplstyle", - "latex9pt.mplstyle", - "latex10pt-minimal.mplstyle", - "latex10pt.mplstyle", - "latex11pt-minimal.mplstyle", - "latex11pt.mplstyle", - "latex12pt-minimal.mplstyle", - "latex12pt.mplstyle", - ] - ) - def mplstyle(self, request): - return request.param - - def test_removes_file(self, new, old, mplstyle, mocker): - cmp_spy = mocker.spy(cleanup.filecmp, "cmp") - - new_file = new / mplstyle - old_file = old / mplstyle - new_file.touch() - old_file.touch() - assert new_file.exists() - assert old_file.exists() - - cleanup.purge_old_styles([new.parent]) - - assert new_file.exists() - assert not old_file.exists() - cmp_spy.assert_called_once_with(new_file, old_file, shallow=False) - - def test_file_different(self, new, old, mplstyle, mocker): - cmp_spy = mocker.spy(cleanup.filecmp, "cmp") - - new_file = new / mplstyle - old_file = old / mplstyle - new_file.touch() - - with old_file.open("w") as fh: - fh.write("1\n") - - assert new_file.exists() - assert old_file.exists() - - cleanup.purge_old_styles([new.parent]) - - assert new_file.exists() - assert old_file.exists() - cmp_spy.assert_called_once_with(new_file, old_file, shallow=False) From acaa3d31dcfddf33bff7fa4c3750edf95ee3a2ef Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Tue, 9 Jul 2024 15:39:11 +0200 Subject: [PATCH 02/14] add tomli dependency --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0493f23..a7a83d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,10 @@ [build-system] build-backend = "setuptools.build_meta" -requires = ["matplotlib", "setuptools"] +requires = [ + "matplotlib", + "setuptools", + "tomli; python_version < '3.11'" +] [project] authors = [ From 6fdfe29b6701b41c28e46d5c29201bf6b66e3a59 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 08:54:05 +0200 Subject: [PATCH 03/14] disable ruff ANN102 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a7a83d1..b9fc0f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,7 +121,8 @@ src = ["src"] [tool.ruff.lint] fixable = ["I"] ignore = [ - "ANN101" # missing-type-self + "ANN101", # missing-type-self + "ANN102" # missing-type-cls ] select = [ "A", From 26b675e0f07c331a746ac82de88b7a9addc5ef39 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Tue, 9 Jul 2024 15:40:07 +0200 Subject: [PATCH 04/14] add support for pyproject.toml configuration and deprecated previous config --- src/latexplotlib/_config.py | 105 +++++++++++++---------- tests/test_10_config.py | 161 ++---------------------------------- 2 files changed, 68 insertions(+), 198 deletions(-) diff --git a/src/latexplotlib/_config.py b/src/latexplotlib/_config.py index 645b3ce..1512a25 100644 --- a/src/latexplotlib/_config.py +++ b/src/latexplotlib/_config.py @@ -1,11 +1,18 @@ import json -from collections.abc import Iterator, Mapping +import sys +import warnings +from collections.abc import Iterator from contextlib import contextmanager from pathlib import Path from typing import Union from appdirs import user_config_dir +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + Number = Union[int, float] ConfigData = Union[Number, bool] @@ -14,60 +21,59 @@ CONFIGFILE: str = "config.ini" CONFIGDIR: Path = Path(user_config_dir(NAME)) CONFIGPATH: Path = CONFIGDIR / CONFIGFILE -DEFAULT_CONFIG: dict[str, Number] = {"width": 630, "height": 412, _PURGED_OLD: False} - - -class Config: - def __init__(self, path: Path) -> None: - self.path = path - - if not self.path.exists(): - self.reset() +DEFAULT_WIDTH = 630 +DEFAULT_HEIGHT = 412 - self._config = self._open(path) - def _open(self, path: Path) -> dict[str, ConfigData]: - with path.open(encoding="utf-8") as fh: - config: dict[str, ConfigData] = json.load(fh) - return config +def find_pyproject_toml() -> Path: + pyproject = "pyproject.toml" + path = Path().absolute() + while path != Path("/"): + if (path / pyproject).exists(): + return path / pyproject + path = path.parent - def _write(self, cfg: Mapping[str, ConfigData]) -> None: - if not self.path.parent.exists(): - self.path.parent.mkdir(parents=True) + msg = "Could not find 'pyproject.toml'" + raise FileNotFoundError(msg) - with self.path.open("w", encoding="utf-8") as fh: - json.dump(cfg, fh, indent=4) - def reset(self) -> None: - if self.path.exists(): - self.path.unlink() +def find_config_ini() -> Path: + if CONFIGPATH.exists(): + return CONFIGPATH - self._write(DEFAULT_CONFIG) + msg = f"No such file: '{CONFIGPATH}'" + raise FileNotFoundError(msg) - def reload(self) -> None: - self._config = self._open(self.path) - def __getitem__(self, name: str) -> ConfigData: - return self._config.get(name, DEFAULT_CONFIG[name]) +class Size: + _width: Number + _height: Number - def __setitem__(self, name: str, value: ConfigData) -> None: - self._config[name] = value - self._write(self._config) + def __init__(self, width: Number, height: Number) -> None: + self._width, self._height = width, height + @classmethod + def from_pyproject_toml(cls, path: Path) -> "Size": + with path.open("rb") as fh: + cfg = tomllib.load(fh) -config = Config(CONFIGPATH) + config = cfg["tool"].get("latexplotlib", {}) + if config == {}: + return cls(DEFAULT_WIDTH, DEFAULT_HEIGHT) -class Size: - _width: Number - _height: Number + return cls( + config.get("width", DEFAULT_WIDTH), config.get("height", DEFAULT_HEIGHT) + ) - def __init__(self) -> None: - self._width, self._height = config["width"], config["height"] + @classmethod + def from_config_ini(cls, path: Path) -> "Size": + with path.open(encoding="utf-8") as fh: + config: dict[str, Number] = json.load(fh) - def reload(self) -> None: - config.reload() - self._width, self._height = config["width"], config["height"] + return cls( + config.get("width", DEFAULT_WIDTH), config.get("height", DEFAULT_HEIGHT) + ) def get(self) -> tuple[Number, Number]: """Returns the current size of the figure in pts. @@ -94,7 +100,6 @@ def set(self, width: Number, height: Number) -> None: height : int The height of the latex page in pts. """ - config["width"], config["height"] = width, height self._width, self._height = width, height @contextmanager @@ -121,4 +126,20 @@ def __str__(self) -> str: return str(f"{self._width}pt, {self._height}pt") -size = Size() +try: + path = find_pyproject_toml() + size = Size.from_pyproject_toml(path) +except FileNotFoundError: + try: + path = find_config_ini() + + msg = f""" + Configuring latexplotlib via {CONFIGPATH} is being deprecated. Please use + the [tool.latexplotlib] section of the 'pyproject.toml' file instead. + + To silence this warning, please delete the config file {CONFIGPATH} + """ + warnings.warn(msg, DeprecationWarning, stacklevel=2) + size = Size.from_config_ini(path) + except FileNotFoundError: + size = Size(DEFAULT_WIDTH, DEFAULT_HEIGHT) diff --git a/tests/test_10_config.py b/tests/test_10_config.py index 5ae8f39..ceb08b8 100644 --- a/tests/test_10_config.py +++ b/tests/test_10_config.py @@ -1,144 +1,15 @@ -import json - import pytest from latexplotlib import _config as cfg GOLDEN_RATIO = (5**0.5 + 1) / 2 - - CONFIGFILE = "config.ini" - NAME = "latexplotlib" def test_constants(): - assert cfg.CONFIGFILE - assert cfg.NAME - assert cfg.CONFIGDIR - assert cfg.CONFIGPATH - assert cfg.DEFAULT_CONFIG - - -class TestConfig: - @pytest.fixture - def default(self, monkeypatch): - default = {"apple": 10, "egg": 1, "skyscraper": "a"} - monkeypatch.setattr(cfg, "DEFAULT_CONFIG", default) - return default - - @pytest.fixture - def path(self, tmp_path, monkeypatch): - path = tmp_path / "directory" / "dir2" / "tmp.ini" - path.parent.mkdir(parents=True) - return path - - @pytest.fixture - def config(self, default, path): - with path.open("w", encoding="utf-8") as fh: - json.dump(default, fh) - - return cfg.Config(path) - - @pytest.fixture - def mock_open(self, default, mocker): - return mocker.patch("latexplotlib._config.Config._open", return_value=default) - - def test___init___path_exists(self, default, mock_open, mocker, path): - path.touch() - mocker.patch( - "latexplotlib._config.Config.reset", - side_effect=ValueError("Should not happen"), - ) - - config = cfg.Config(path) - - assert config.path == path - assert config._config == default - mock_open.assert_called_once_with(path) - - def test___init___path_not_exists(self, default, mock_open, mocker, path): - reset = mocker.patch( - "latexplotlib._config.Config.reset", side_effect=path.touch - ) - - config = cfg.Config(path) - - assert config.path == path - assert config._config == default - reset.assert_called_once() - mock_open.assert_called_once_with(path) - - def test__open(self, config, default, path): - config._config = None - assert config._open(path) == default - - def test__write(self, config, default, path): - config.path.unlink() - config._write(default) - - cfg = config._open(path) - assert cfg == default - - def test__write_no_parent(self, config, default, path): - config.path.unlink() - config.path.parent.rmdir() - - config._write(default) - - cfg = config._open(path) - assert cfg == default - - def test__write_no_parents_2(self, config, default, path): - config.path.unlink() - config.path.parent.rmdir() - config.path.parent.parent.rmdir() - - config._write(default) - - cfg = config._open(path) - assert cfg == default - - def test_reset_path_exists(self, config, default, mocker): - config._write(default) - assert config.path.exists() - - mocker.patch.object(config, "_write") - - config.reset() - - assert not config.path.exists() - config._write.assert_called_once_with(default) - - def test_reset_path_not_exists(self, config, default, mocker): - config.path.unlink() - assert not config.path.exists() - mocker.patch.object(config, "_write") - - config.reset() - - assert not config.path.exists() - config._write.assert_called_once_with(default) - - def test_reload(self, config, default, mock_open): - config._config = None - config.reload() - - assert config._config == default - mock_open.assert_called_once() - - def test___getitem__(self, config, default): - for key, item in default.items(): - assert config[key] == item - - def test___setitem__(self, config, default): - assert config["skyscraper"] != "apple" - config["skyscraper"] = "apple" - assert config["skyscraper"] == "apple" - - -def test_config_path(): - assert cfg.config.path == cfg.CONFIGPATH + assert cfg.DEFAULT_HEIGHT + assert cfg.DEFAULT_WIDTH class TestSize: @@ -150,20 +21,12 @@ def height(self): def width(self): return 10 - @pytest.fixture(autouse=True) - def _patch_config(self, height, width, monkeypatch, mocker): - d = {"width": width, "height": height} - config = mocker.MagicMock( - __getitem__=lambda _, v: d.__getitem__(v), reload=mocker.MagicMock() - ) - monkeypatch.setattr(cfg, "config", config) - @pytest.fixture - def size(self): - return cfg.Size() + def size(self, width, height): + return cfg.Size(width, height) def test___init__(self, width, height): - size = cfg.Size() + size = cfg.Size(width, height) assert size._width == width assert size._height == height @@ -171,20 +34,6 @@ def test___init__(self, width, height): def test_get(self, width, height, size): assert size.get() == (width, height) - def test_set(self, size): - size.set(43, 44) - assert size.get() == (43, 44) - - def test_reload(self, size): - cur = size.get() - - size.set(0, 0) - assert size.get() != cur - - size.reload() - cfg.config.reload.assert_called_once() - assert size.get() == cur - def test_context(self, size): assert size.get() == (10, 20) From fe53268040b09fa56fd6522d49469376faaca80b Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 09:06:17 +0200 Subject: [PATCH 05/14] clean up dependencies --- pyproject.toml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b9fc0f6..c8f3717 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,6 @@ [build-system] build-backend = "setuptools.build_meta" -requires = [ - "matplotlib", - "setuptools", - "tomli; python_version < '3.11'" -] +requires = ["setuptools"] [project] authors = [ @@ -20,7 +16,8 @@ classifiers = [ ] dependencies = [ "appdirs", - "matplotlib" + "matplotlib", + "tomli; python_version < '3.11'" ] description = "Perfect matplotlib figures for latex" dynamic = ["version"] From 09751722367b95da2aeb3d76456f21d235b2fbe7 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 09:07:30 +0200 Subject: [PATCH 06/14] exclude line form coverage report --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c8f3717..45f0faf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,10 @@ Homepage = "https://github.com/cgahr/latexplotlib" Issues = "https://github.com/cgahr/latexplotlib/issues" [tool.coverage.report] -exclude_lines = ["if TYPE_CHECKING:"] +exclude_lines = [ + "if TYPE_CHECKING:", + "import tomli as tomllib" # tested via GitHub actions +] [tool.coverage.run] source = ["src"] From aa597174cc7a932b485b4796284ed2184a898296 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 09:08:19 +0200 Subject: [PATCH 07/14] raise deprecation warning in find_config_ini --- src/latexplotlib/_config.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/latexplotlib/_config.py b/src/latexplotlib/_config.py index 1512a25..6429d54 100644 --- a/src/latexplotlib/_config.py +++ b/src/latexplotlib/_config.py @@ -38,6 +38,14 @@ def find_pyproject_toml() -> Path: def find_config_ini() -> Path: + msg = f""" + Configuring latexplotlib via {CONFIGPATH} is being deprecated. Please use + the [tool.latexplotlib] section of the 'pyproject.toml' file instead. + + To silence this warning, please delete the config file {CONFIGPATH} + """ + warnings.warn(msg, DeprecationWarning, stacklevel=2) + if CONFIGPATH.exists(): return CONFIGPATH @@ -133,13 +141,6 @@ def __str__(self) -> str: try: path = find_config_ini() - msg = f""" - Configuring latexplotlib via {CONFIGPATH} is being deprecated. Please use - the [tool.latexplotlib] section of the 'pyproject.toml' file instead. - - To silence this warning, please delete the config file {CONFIGPATH} - """ - warnings.warn(msg, DeprecationWarning, stacklevel=2) size = Size.from_config_ini(path) except FileNotFoundError: size = Size(DEFAULT_WIDTH, DEFAULT_HEIGHT) From 61d592c6592f34b6c74596d4f4b9af27a7d1f8ae Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 21:05:41 +0200 Subject: [PATCH 08/14] add pylsp to dev environment --- flake.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 336f66f..d95acde 100644 --- a/flake.nix +++ b/flake.nix @@ -33,7 +33,11 @@ attrs = project.renderers.withPackages { inherit python; extras = [ "tests" ]; - extraPackages = python-pkgs: [ python-pkgs.ipykernel ]; + extraPackages = python-pkgs: [ + python-pkgs.ipykernel + python-pkgs.python-lsp-server + python-pkgs.pylsp-mypy + ]; }; pythonEnv = python.withPackages attrs; in From a89d677cfe9449ee3dcff8e51a6f614cf9fde1d9 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 21:06:14 +0200 Subject: [PATCH 09/14] updated ignore ruff errors for tests --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 45f0faf..1e1af46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,6 +182,7 @@ suppress-dummy-args = true "ARG002", # unused-method-argument "INP", # implicit-namespace-package "PLR0913", # too-many-arguments + "PLR6301", "S101", # assert "SLF001" # private-member-access ] From 2575c058a48f1320a710b651313fb35d91cf5dac Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 21:07:00 +0200 Subject: [PATCH 10/14] pass width and height as kwarg only --- src/latexplotlib/_config.py | 15 +++++++-------- tests/test_10_config.py | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/latexplotlib/_config.py b/src/latexplotlib/_config.py index 6429d54..716708d 100644 --- a/src/latexplotlib/_config.py +++ b/src/latexplotlib/_config.py @@ -57,7 +57,7 @@ class Size: _width: Number _height: Number - def __init__(self, width: Number, height: Number) -> None: + def __init__(self, *, width: Number, height: Number) -> None: self._width, self._height = width, height @classmethod @@ -65,13 +65,11 @@ def from_pyproject_toml(cls, path: Path) -> "Size": with path.open("rb") as fh: cfg = tomllib.load(fh) - config = cfg["tool"].get("latexplotlib", {}) - - if config == {}: - return cls(DEFAULT_WIDTH, DEFAULT_HEIGHT) + config = cfg.get("tool", {}).get("latexplotlib", {}) return cls( - config.get("width", DEFAULT_WIDTH), config.get("height", DEFAULT_HEIGHT) + width=config.get("width", DEFAULT_WIDTH), + height=config.get("height", DEFAULT_HEIGHT), ) @classmethod @@ -80,7 +78,8 @@ def from_config_ini(cls, path: Path) -> "Size": config: dict[str, Number] = json.load(fh) return cls( - config.get("width", DEFAULT_WIDTH), config.get("height", DEFAULT_HEIGHT) + width=config.get("width", DEFAULT_WIDTH), + height=config.get("height", DEFAULT_HEIGHT), ) def get(self) -> tuple[Number, Number]: @@ -143,4 +142,4 @@ def __str__(self) -> str: size = Size.from_config_ini(path) except FileNotFoundError: - size = Size(DEFAULT_WIDTH, DEFAULT_HEIGHT) + size = Size(width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT) diff --git a/tests/test_10_config.py b/tests/test_10_config.py index ceb08b8..ff6c08d 100644 --- a/tests/test_10_config.py +++ b/tests/test_10_config.py @@ -23,10 +23,10 @@ def width(self): @pytest.fixture def size(self, width, height): - return cfg.Size(width, height) + return cfg.Size(width=width, height=height) def test___init__(self, width, height): - size = cfg.Size(width, height) + size = cfg.Size(width=width, height=height) assert size._width == width assert size._height == height From 451b1acafb7e414f887b11c43a98ce20847f9125 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 21:07:40 +0200 Subject: [PATCH 11/14] test find pyproject.toml/config.ini --- tests/test_10_config.py | 45 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/tests/test_10_config.py b/tests/test_10_config.py index ff6c08d..343af81 100644 --- a/tests/test_10_config.py +++ b/tests/test_10_config.py @@ -2,16 +2,53 @@ from latexplotlib import _config as cfg -GOLDEN_RATIO = (5**0.5 + 1) / 2 -CONFIGFILE = "config.ini" -NAME = "latexplotlib" - def test_constants(): assert cfg.DEFAULT_HEIGHT assert cfg.DEFAULT_WIDTH +class TestFindPyprojectToml: + @pytest.fixture + def path(self, tmp_path, mocker): + path = tmp_path / "a" / "b" / "c" + path.mkdir(parents=True) + + mocker.patch("latexplotlib._config.Path.absolute", return_value=path) + return path + + def test_pyproject_exists(self, path): + path = path.parent.parent / "pyproject.toml" + path.touch() + + cfg.find_pyproject_toml() + + def test_pyproject_not_exists(self, path): + with pytest.raises(FileNotFoundError): + cfg.find_pyproject_toml() + + +class TestFindConfigIni: + @pytest.fixture + def path(self, tmp_path, monkeypatch): + path = tmp_path / "a" + path.mkdir(parents=True) + path /= "config.123" + + monkeypatch.setattr(cfg, "CONFIGPATH", path) + return path + + def test_configini_exists(self, path): + path.touch() + + with pytest.warns(DeprecationWarning): + cfg.find_config_ini() + + def test_configini_not_exists(self, path): + with pytest.warns(DeprecationWarning), pytest.raises(FileNotFoundError): + cfg.find_config_ini() + + class TestSize: @pytest.fixture def height(self): From 8d8b39c1e9ff8c88972f380304d35e41d1191fd3 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Fri, 13 Sep 2024 21:11:04 +0200 Subject: [PATCH 12/14] test Size.from_pyproject_toml --- tests/test_10_config.py | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/test_10_config.py b/tests/test_10_config.py index 343af81..8fe3308 100644 --- a/tests/test_10_config.py +++ b/tests/test_10_config.py @@ -84,3 +84,46 @@ def test_str(self, size): def test_repr(self, size): repr(size) + + def test_from_pyproject_toml_complete(self, tmp_path): + width = 123 + height = 456 + path = tmp_path / "pyproject.toml" + with path.open("w") as fh: + fh.writelines( + [ + "[tool.latexplotlib]\n", + f"width = {width}\n", + f"height = {height}\n", + ] + ) + + size = cfg.Size.from_pyproject_toml(path) + assert size._width == width + assert size._height == height + + def test_from_pyproject_toml_empty(self, tmp_path): + path = tmp_path / "pyproject.toml" + path.touch() + + size = cfg.Size.from_pyproject_toml(path) + assert size._width == cfg.DEFAULT_WIDTH + assert size._height == cfg.DEFAULT_HEIGHT + + def test_from_pyproject_toml_tool(self, tmp_path): + path = tmp_path / "pyproject.toml" + with path.open("w") as fh: + fh.writelines(["[tool]\n"]) + + size = cfg.Size.from_pyproject_toml(path) + assert size._width == cfg.DEFAULT_WIDTH + assert size._height == cfg.DEFAULT_HEIGHT + + def test_from_pyproject_toml_tool_latexplotlib(self, tmp_path): + path = tmp_path / "pyproject.toml" + with path.open("w") as fh: + fh.writelines(["[tool.latexplotlib]\n"]) + + size = cfg.Size.from_pyproject_toml(path) + assert size._width == cfg.DEFAULT_WIDTH + assert size._height == cfg.DEFAULT_HEIGHT From 2e6401099c3bba05c3fea05be406244510702ec2 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Sat, 26 Oct 2024 10:15:30 +0200 Subject: [PATCH 13/14] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-utils': 'github:numtide/flake-utils/b1d9ab70662946ef0850d488da1c9019f3a9752a' (2024-03-11) → 'github:numtide/flake-utils/c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a' (2024-09-17) • Updated input 'nixpkgs': 'github:nixos/nixpkgs/1355a0cbfeac61d785b7183c0caaec1f97361b43' (2024-09-10) → 'github:nixos/nixpkgs/2768c7d042a37de65bb1b5b3268fc987e534c49d' (2024-10-23) • Updated input 'pyproject-nix': 'github:nix-community/pyproject.nix/6c56846759ba16382bc2bdbee42c2f56c21654be' (2024-09-11) → 'github:nix-community/pyproject.nix/b9d97172161301c82cd978123c61abc81b88537e' (2024-10-23) • Added input 'pyproject-nix/lix-unit': 'github:adisbladis/lix-unit/59c489dbc5b27a83fadc94fde2c2b69abb4c0e80' (2024-09-26) • Added input 'pyproject-nix/lix-unit/mdbook-nixdoc': follows 'pyproject-nix/mdbook-nixdoc' • Added input 'pyproject-nix/lix-unit/nix-github-actions': follows 'pyproject-nix/nix-github-actions' • Added input 'pyproject-nix/lix-unit/nixpkgs': follows 'pyproject-nix/nixpkgs' • Added input 'pyproject-nix/lix-unit/treefmt-nix': follows 'pyproject-nix/treefmt-nix' • Updated input 'pyproject-nix/treefmt-nix': 'github:numtide/treefmt-nix/3ffd842a5f50f435d3e603312eefa4790db46af5' (2024-08-28) → 'github:numtide/treefmt-nix/1bff2ba6ec22bc90e9ad3f7e94cca0d37870afa3' (2024-09-25) --- flake.lock | 58 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index ae2c6bc..84ad773 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -18,6 +18,39 @@ "type": "github" } }, + "lix-unit": { + "inputs": { + "mdbook-nixdoc": [ + "pyproject-nix", + "mdbook-nixdoc" + ], + "nix-github-actions": [ + "pyproject-nix", + "nix-github-actions" + ], + "nixpkgs": [ + "pyproject-nix", + "nixpkgs" + ], + "treefmt-nix": [ + "pyproject-nix", + "treefmt-nix" + ] + }, + "locked": { + "lastModified": 1727322567, + "narHash": "sha256-scZo6AyJTxTK9wYW0HmWDzLxVxOoFr7/XkIVJCmmOe4=", + "owner": "adisbladis", + "repo": "lix-unit", + "rev": "59c489dbc5b27a83fadc94fde2c2b69abb4c0e80", + "type": "github" + }, + "original": { + "owner": "adisbladis", + "repo": "lix-unit", + "type": "github" + } + }, "mdbook-nixdoc": { "inputs": { "nix-github-actions": [ @@ -66,11 +99,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725983898, - "narHash": "sha256-4b3A9zPpxAxLnkF9MawJNHDtOOl6ruL0r6Og1TEDGCE=", + "lastModified": 1729665710, + "narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "1355a0cbfeac61d785b7183c0caaec1f97361b43", + "rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d", "type": "github" }, "original": { @@ -82,6 +115,7 @@ }, "pyproject-nix": { "inputs": { + "lix-unit": "lix-unit", "mdbook-nixdoc": "mdbook-nixdoc", "nix-github-actions": "nix-github-actions", "nixpkgs": [ @@ -90,11 +124,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1726093189, - "narHash": "sha256-aPzd6M1k1H0C2zoMlSfAdTg4Za+WfB8Qj+OAbQAVAMs=", + "lastModified": 1729676952, + "narHash": "sha256-6Kn+s20SVpBjt2RJ6e4hQfbAcdv+lWzowWjB3M5rO8M=", "owner": "nix-community", "repo": "pyproject.nix", - "rev": "6c56846759ba16382bc2bdbee42c2f56c21654be", + "rev": "b9d97172161301c82cd978123c61abc81b88537e", "type": "github" }, "original": { @@ -133,11 +167,11 @@ ] }, "locked": { - "lastModified": 1724833132, - "narHash": "sha256-F4djBvyNRAXGusJiNYInqR6zIMI3rvlp6WiKwsRISos=", + "lastModified": 1727252110, + "narHash": "sha256-3O7RWiXpvqBcCl84Mvqa8dXudZ1Bol1ubNdSmQt7nF4=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "3ffd842a5f50f435d3e603312eefa4790db46af5", + "rev": "1bff2ba6ec22bc90e9ad3f7e94cca0d37870afa3", "type": "github" }, "original": { From a92d797ba576c989abd468e6a564d9d9fc870ed9 Mon Sep 17 00:00:00 2001 From: Constantin Gahr Date: Sat, 26 Oct 2024 10:38:54 +0200 Subject: [PATCH 14/14] WIP improve path discovery of config file improve path discovery of pyproject.toml and deprecated latexplotlib config to properly emit a deprecation warning --- src/latexplotlib/_config.py | 47 ++++++++++++++++++++----------------- tests/test_10_config.py | 11 +++++---- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/latexplotlib/_config.py b/src/latexplotlib/_config.py index 716708d..8f3e347 100644 --- a/src/latexplotlib/_config.py +++ b/src/latexplotlib/_config.py @@ -25,7 +25,7 @@ DEFAULT_HEIGHT = 412 -def find_pyproject_toml() -> Path: +def find_pyproject_toml() -> Path | None: pyproject = "pyproject.toml" path = Path().absolute() while path != Path("/"): @@ -33,24 +33,24 @@ def find_pyproject_toml() -> Path: return path / pyproject path = path.parent - msg = "Could not find 'pyproject.toml'" - raise FileNotFoundError(msg) + return None -def find_config_ini() -> Path: - msg = f""" - Configuring latexplotlib via {CONFIGPATH} is being deprecated. Please use - the [tool.latexplotlib] section of the 'pyproject.toml' file instead. +def find_config_ini() -> Path | None: + if CONFIGPATH.exists(): + msg = f""" + Configuring latexplotlib via '{CONFIGPATH}' is being deprecated. Please use + the [tool.latexplotlib] section of the 'pyproject.toml' file instead. If a + 'pyproject.toml' file is present in the current directory or a parent + directory, the configuration at '{CONFIGPATH}' is being ignored. - To silence this warning, please delete the config file {CONFIGPATH} - """ - warnings.warn(msg, DeprecationWarning, stacklevel=2) + To silence this warning, please delete the config file '{CONFIGPATH}'. + """ + warnings.warn(msg, DeprecationWarning, stacklevel=5) - if CONFIGPATH.exists(): return CONFIGPATH - msg = f"No such file: '{CONFIGPATH}'" - raise FileNotFoundError(msg) + return None class Size: @@ -133,13 +133,16 @@ def __str__(self) -> str: return str(f"{self._width}pt, {self._height}pt") -try: - path = find_pyproject_toml() - size = Size.from_pyproject_toml(path) -except FileNotFoundError: - try: - path = find_config_ini() +def get_size() -> Size: + if (path := find_pyproject_toml()) is not None: + # look for config.ini to emit deprecation warning if it exists + _ = find_config_ini() + return Size.from_pyproject_toml(path) + + if (path := find_config_ini()) is not None: + return Size.from_config_ini(path) + + return Size(width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT) + - size = Size.from_config_ini(path) - except FileNotFoundError: - size = Size(width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT) +size = get_size() diff --git a/tests/test_10_config.py b/tests/test_10_config.py index 8fe3308..be389e9 100644 --- a/tests/test_10_config.py +++ b/tests/test_10_config.py @@ -24,8 +24,7 @@ def test_pyproject_exists(self, path): cfg.find_pyproject_toml() def test_pyproject_not_exists(self, path): - with pytest.raises(FileNotFoundError): - cfg.find_pyproject_toml() + assert cfg.find_pyproject_toml() is None class TestFindConfigIni: @@ -45,8 +44,7 @@ def test_configini_exists(self, path): cfg.find_config_ini() def test_configini_not_exists(self, path): - with pytest.warns(DeprecationWarning), pytest.raises(FileNotFoundError): - cfg.find_config_ini() + assert cfg.find_config_ini() is None class TestSize: @@ -127,3 +125,8 @@ def test_from_pyproject_toml_tool_latexplotlib(self, tmp_path): size = cfg.Size.from_pyproject_toml(path) assert size._width == cfg.DEFAULT_WIDTH assert size._height == cfg.DEFAULT_HEIGHT + + +class TestGetSize: + def test_todo(self): + raise NotImplementedError