diff --git a/.github/workflows/main_coverage.yaml b/.github/workflows/main_coverage.yaml index 6e11453d..b7ef2b1d 100644 --- a/.github/workflows/main_coverage.yaml +++ b/.github/workflows/main_coverage.yaml @@ -16,14 +16,14 @@ jobs: fetch-depth: 2 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions pytest-cov + pip install --upgrade pip + pip install tox tox-uv - name: Run tests run: tox run -e py310 -- --cov matplot2tikz --cov-branch --cov-report=xml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 30776bcd..763ee90a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,30 +5,26 @@ on: jobs: test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] - include: - - python-version: '3.9' - os: ubuntu-latest - tox-env: lint + # Run on a single, standard runner + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + # We just need one modern Python version to run the 'tox' command itself. uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies + python-version: "3.11" + + - name: Install Tox and tox-uv + # Install the required tools into the workflow runner environment run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Test with tox + pip install --upgrade pip + pip install tox tox-uv + + - name: Run All Tox Environments + # The 'tox' command executes the full matrix (py310, py311, etc.) + # defined in tox.ini. tox-uv handles the fast installation. run: tox - env: - # Pass the current matrix Python version to tox-gh-actions - # so it can filter the tox environments - TOX_GH_ACTIONS_MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} - TOX_GH_ACTIONS_TOX_ENV: ${{ matrix.tox-env || '' }} diff --git a/README.md b/README.md index 376972f9..e1d75c22 100644 --- a/README.md +++ b/README.md @@ -219,35 +219,31 @@ For contributing, follow these steps: 1. Download the git repository, e.g., using `git clone git@github.com:ErwindeGelder/matplot2tikz.git`. 2. Create a virtual environment, e.g., using `python -m venv venv`. -3. Activate the virtual environment (e.g., on Windows, `venv\Scripts\activate.bat`). -4. Install the necessary libraries using `pip install -e .[dev]`. +3. Activate the virtual environment (e.g., on Windows, `venv\Scripts\activate`). +4. Install `uv` using `pip install uv` and then `tox-uv` using `uv pip install tox-uv`. 5. The main branch is protected, meaning that you cannot directly push changes to this branch. Therefore, if you want to make changes, do so in a seperate branch. For example, you can create a new branch using `git checkout -b feature/my_awesome_new_feature`. 6. Before pushing changes, ensure that the code adheres to the linting rules and that the tests are - successful. To run the linting and testing, tox first needs to know where it can find the - different Python versions that are supported. One way to do so is by making use of pyenv or - pyenv-win. Note that you only need to do this once for a single machine. -7. Run `tox`. This does a linting check and runs all test scripts. To manually perform these steps, - use the following commands (note that to ensure the same output is generated, Python 3.9 is used - in step 2 above): - 1. Run `tox -e lint`. If issues arise, fix them. You can do the linting commands manually - using: - 1. `ruff format . --check` (remove the `--check` flag to let `ruff` do the formatting) - 2. `ruff check .` - 3. `mypy .` - 2. Run `tox -e py39`. - 3. Run `tox -e py310`. - 4. Run `tox -e py311`. - 5. Run `tox -e py312`. - 6. Run `tox -e py313`. + successful. Run `tox`. This does a linting check and runs all test scripts. To manually perform + these steps, use the following commands: + 1. Run `tox -e lint`. You can do the linting commands manually using: + 1. (One time) `uv pip install -r requirements-lint.txt` + 2. `ruff format . --check` (remove the `--check` flag to let `ruff` do the formatting) + 3. `ruff check .` + 4. `mypy .` + 2. Run `tox -e py310`. + 3. Run `tox -e py311`. + 4. Run `tox -e py312`. + 5. Run `tox -e py313`. + 6. Run `tox -e py314`. 7. Run `tox -e combine-test-reports` -8. Check if the tests covered everything using the coverage report in +7. Check if the tests covered everything using the coverage report in `/reports/coverage_html/index.html`. NOTE: Currently, now all code is covered. Ideally, all code is covered, but for now, ensure that all *new* code is covered by the testing. -9. Push changes to GitHub. If everything is OK and you want to merge your changes to the `main` +8. Push changes to GitHub. If everything is OK and you want to merge your changes to the `main` branch, create a pull request. Ideally, there is at least one reviewer who reviews the pull request before the merge. diff --git a/pyproject.toml b/pyproject.toml index 25e26a7d..9ab736f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" license = "MIT" keywords = ["latex", "tikz", "matplotlib", "graphics"] dynamic = ["version"] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "matplotlib >= 1.4.0", "numpy", @@ -23,11 +23,11 @@ authors = [ ] classifiers=[ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] # Enables the usage of setuptools_scm @@ -43,6 +43,7 @@ test = [ "pytest-cov>=6.0.0", "coverage[toml]>=7.0.0", "typeguard>=4.4.0", + "pandas", ] dev = [ "tox", @@ -61,7 +62,7 @@ src = ["src"] extend-exclude = [ "conf.py", ] -target-version = "py39" +target-version = "py310" lint.select = ["ALL"] lint.ignore = [ "COM812", # Conflicts with the formatter diff --git a/requirements-310.txt b/requirements-310.txt new file mode 100644 index 00000000..dd0468aa --- /dev/null +++ b/requirements-310.txt @@ -0,0 +1,75 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra test --resolution highest --python 3.10 -o requirements-310.txt +colorama==0.4.6 + # via pytest +contourpy==1.3.2 + # via matplotlib +coverage==7.11.0 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +cycler==0.12.1 + # via matplotlib +exceptiongroup==1.3.0 + # via pytest +fonttools==4.60.1 + # via matplotlib +iniconfig==2.3.0 + # via pytest +kiwisolver==1.4.9 + # via matplotlib +matplotlib==3.10.7 + # via matplot2tikz (pyproject.toml) +numpy==2.2.6 + # via + # matplot2tikz (pyproject.toml) + # contourpy + # matplotlib + # pandas +packaging==25.0 + # via + # matplotlib + # pytest +pandas==2.3.3 + # via matplot2tikz (pyproject.toml) +pillow==12.0.0 + # via + # matplot2tikz (pyproject.toml) + # matplotlib +pluggy==1.6.0 + # via + # pytest + # pytest-cov +pygments==2.19.2 + # via pytest +pyparsing==3.2.5 + # via matplotlib +pytest==8.4.2 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +pytest-cov==7.0.0 + # via matplot2tikz (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2025.2 + # via pandas +six==1.17.0 + # via python-dateutil +tomli==2.3.0 + # via + # coverage + # pytest +typeguard==4.4.4 + # via matplot2tikz (pyproject.toml) +typing-extensions==4.15.0 + # via + # matplot2tikz (pyproject.toml) + # exceptiongroup + # typeguard +tzdata==2025.2 + # via pandas +webcolors==24.11.1 + # via matplot2tikz (pyproject.toml) diff --git a/requirements-311.txt b/requirements-311.txt new file mode 100644 index 00000000..eb35d360 --- /dev/null +++ b/requirements-311.txt @@ -0,0 +1,70 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra test --resolution highest --python 3.11 -o requirements-311.txt +colorama==0.4.6 + # via pytest +contourpy==1.3.3 + # via matplotlib +coverage==7.11.0 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +cycler==0.12.1 + # via matplotlib +fonttools==4.60.1 + # via matplotlib +iniconfig==2.3.0 + # via pytest +kiwisolver==1.4.9 + # via matplotlib +matplotlib==3.10.7 + # via matplot2tikz (pyproject.toml) +numpy==2.3.4 + # via + # matplot2tikz (pyproject.toml) + # contourpy + # matplotlib + # pandas +packaging==25.0 + # via + # matplotlib + # pytest +pandas==2.3.3 + # via matplot2tikz (pyproject.toml) +pillow==12.0.0 + # via + # matplot2tikz (pyproject.toml) + # matplotlib +pluggy==1.6.0 + # via + # pytest + # pytest-cov +pygments==2.19.2 + # via pytest +pyparsing==3.2.5 + # via matplotlib +pytest==8.4.2 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +pytest-cov==7.0.0 + # via matplot2tikz (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2025.2 + # via pandas +six==1.17.0 + # via python-dateutil +tomli==2.3.0 + # via coverage +typeguard==4.4.4 + # via matplot2tikz (pyproject.toml) +typing-extensions==4.15.0 + # via + # matplot2tikz (pyproject.toml) + # typeguard +tzdata==2025.2 + # via pandas +webcolors==24.11.1 + # via matplot2tikz (pyproject.toml) diff --git a/requirements-312.txt b/requirements-312.txt new file mode 100644 index 00000000..b9e56f36 --- /dev/null +++ b/requirements-312.txt @@ -0,0 +1,68 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra test --resolution highest --python 3.12 -o requirements-312.txt +colorama==0.4.6 + # via pytest +contourpy==1.3.3 + # via matplotlib +coverage==7.11.0 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +cycler==0.12.1 + # via matplotlib +fonttools==4.60.1 + # via matplotlib +iniconfig==2.3.0 + # via pytest +kiwisolver==1.4.9 + # via matplotlib +matplotlib==3.10.7 + # via matplot2tikz (pyproject.toml) +numpy==2.3.4 + # via + # matplot2tikz (pyproject.toml) + # contourpy + # matplotlib + # pandas +packaging==25.0 + # via + # matplotlib + # pytest +pandas==2.3.3 + # via matplot2tikz (pyproject.toml) +pillow==12.0.0 + # via + # matplot2tikz (pyproject.toml) + # matplotlib +pluggy==1.6.0 + # via + # pytest + # pytest-cov +pygments==2.19.2 + # via pytest +pyparsing==3.2.5 + # via matplotlib +pytest==8.4.2 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +pytest-cov==7.0.0 + # via matplot2tikz (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2025.2 + # via pandas +six==1.17.0 + # via python-dateutil +typeguard==4.4.4 + # via matplot2tikz (pyproject.toml) +typing-extensions==4.15.0 + # via + # matplot2tikz (pyproject.toml) + # typeguard +tzdata==2025.2 + # via pandas +webcolors==24.11.1 + # via matplot2tikz (pyproject.toml) diff --git a/requirements-313.txt b/requirements-313.txt new file mode 100644 index 00000000..6f103024 --- /dev/null +++ b/requirements-313.txt @@ -0,0 +1,68 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra test --resolution highest --python 3.13 -o requirements-313.txt +colorama==0.4.6 + # via pytest +contourpy==1.3.3 + # via matplotlib +coverage==7.11.0 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +cycler==0.12.1 + # via matplotlib +fonttools==4.60.1 + # via matplotlib +iniconfig==2.3.0 + # via pytest +kiwisolver==1.4.9 + # via matplotlib +matplotlib==3.10.7 + # via matplot2tikz (pyproject.toml) +numpy==2.3.4 + # via + # matplot2tikz (pyproject.toml) + # contourpy + # matplotlib + # pandas +packaging==25.0 + # via + # matplotlib + # pytest +pandas==2.3.3 + # via matplot2tikz (pyproject.toml) +pillow==12.0.0 + # via + # matplot2tikz (pyproject.toml) + # matplotlib +pluggy==1.6.0 + # via + # pytest + # pytest-cov +pygments==2.19.2 + # via pytest +pyparsing==3.2.5 + # via matplotlib +pytest==8.4.2 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +pytest-cov==7.0.0 + # via matplot2tikz (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2025.2 + # via pandas +six==1.17.0 + # via python-dateutil +typeguard==4.4.4 + # via matplot2tikz (pyproject.toml) +typing-extensions==4.15.0 + # via + # matplot2tikz (pyproject.toml) + # typeguard +tzdata==2025.2 + # via pandas +webcolors==24.11.1 + # via matplot2tikz (pyproject.toml) diff --git a/requirements-314.txt b/requirements-314.txt new file mode 100644 index 00000000..8bc4d7df --- /dev/null +++ b/requirements-314.txt @@ -0,0 +1,68 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra test --resolution highest --python 3.14 -o requirements-314.txt +colorama==0.4.6 + # via pytest +contourpy==1.3.3 + # via matplotlib +coverage==7.11.0 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +cycler==0.12.1 + # via matplotlib +fonttools==4.60.1 + # via matplotlib +iniconfig==2.3.0 + # via pytest +kiwisolver==1.4.9 + # via matplotlib +matplotlib==3.10.7 + # via matplot2tikz (pyproject.toml) +numpy==2.3.4 + # via + # matplot2tikz (pyproject.toml) + # contourpy + # matplotlib + # pandas +packaging==25.0 + # via + # matplotlib + # pytest +pandas==2.3.3 + # via matplot2tikz (pyproject.toml) +pillow==12.0.0 + # via + # matplot2tikz (pyproject.toml) + # matplotlib +pluggy==1.6.0 + # via + # pytest + # pytest-cov +pygments==2.19.2 + # via pytest +pyparsing==3.2.5 + # via matplotlib +pytest==8.4.2 + # via + # matplot2tikz (pyproject.toml) + # pytest-cov +pytest-cov==7.0.0 + # via matplot2tikz (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2025.2 + # via pandas +six==1.17.0 + # via python-dateutil +typeguard==4.4.4 + # via matplot2tikz (pyproject.toml) +typing-extensions==4.15.0 + # via + # matplot2tikz (pyproject.toml) + # typeguard +tzdata==2025.2 + # via pandas +webcolors==24.11.1 + # via matplot2tikz (pyproject.toml) diff --git a/requirements-lint.txt b/requirements-lint.txt new file mode 100644 index 00000000..30529a4d --- /dev/null +++ b/requirements-lint.txt @@ -0,0 +1,45 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --extra lint --python 3.10 -o requirements-lint.txt +contourpy==1.3.0 + # via matplotlib +cycler==0.12.1 + # via matplotlib +fonttools==4.60.1 + # via matplotlib +kiwisolver==1.4.7 + # via matplotlib +matplotlib==3.9.4 + # via matplot2tikz (pyproject.toml) +mypy==1.18.2 + # via matplot2tikz (pyproject.toml) +mypy-extensions==1.1.0 + # via mypy +numpy==2.0.2 + # via + # matplot2tikz (pyproject.toml) + # contourpy + # matplotlib +packaging==25.0 + # via matplotlib +pathspec==0.12.1 + # via mypy +pillow==11.3.0 + # via + # matplot2tikz (pyproject.toml) + # matplotlib +pyparsing==3.2.5 + # via matplotlib +python-dateutil==2.9.0.post0 + # via matplotlib +ruff==0.14.2 + # via matplot2tikz (pyproject.toml) +six==1.17.0 + # via python-dateutil +tomli==2.3.0 + # via mypy +typing-extensions==4.15.0 + # via + # matplot2tikz (pyproject.toml) + # mypy +webcolors==24.11.1 + # via matplot2tikz (pyproject.toml) diff --git a/src/matplot2tikz/__about__.py b/src/matplot2tikz/__about__.py index d4cebef1..977679d3 100644 --- a/src/matplot2tikz/__about__.py +++ b/src/matplot2tikz/__about__.py @@ -1,3 +1,3 @@ """Version number.""" -__version__ = "0.4.2" +__version__ = "0.5.0" diff --git a/src/matplot2tikz/_axes.py b/src/matplot2tikz/_axes.py index 0dd5b95b..68b0bc87 100644 --- a/src/matplot2tikz/_axes.py +++ b/src/matplot2tikz/_axes.py @@ -602,7 +602,7 @@ def _is_label_required(ticks: list | np.ndarray, ticklabels: list) -> bool: If one of the labels is, then all of them must appear in the TikZ plot. """ - for tick, ticklabel in zip(ticks, ticklabels): + for tick, ticklabel in zip(ticks, ticklabels, strict=False): # store the label anyway label = ticklabel.get_text() diff --git a/src/matplot2tikz/_cleanfigure.py b/src/matplot2tikz/_cleanfigure.py index 43b2551c..39533099 100644 --- a/src/matplot2tikz/_cleanfigure.py +++ b/src/matplot2tikz/_cleanfigure.py @@ -2,7 +2,7 @@ import warnings from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import matplotlib as mpl import matplotlib.pyplot as plt @@ -38,15 +38,15 @@ class CleanFigureData: target_resolution: int | list[int] | np.ndarray scale_precision: float data: np.ndarray = field(default_factory=initial_data) - visual_data: Optional[np.ndarray] = None + visual_data: np.ndarray | None = None x_lim: np.ndarray = field(default_factory=initial_axis_limits) y_lim: np.ndarray = field(default_factory=initial_axis_limits) - has_lines: Optional[bool] = None - has_markers: Optional[bool] = None + has_lines: bool | None = None + has_markers: bool | None = None def clean_figure( - fig: Optional[FigureBase] = None, + fig: FigureBase | None = None, target_resolution: int | list[int] | np.ndarray = 600, scale_precision: float = 1.0, ) -> None: diff --git a/src/matplot2tikz/_line2d.py b/src/matplot2tikz/_line2d.py index 94cc914b..a3eb21b7 100644 --- a/src/matplot2tikz/_line2d.py +++ b/src/matplot2tikz/_line2d.py @@ -4,7 +4,7 @@ import datetime from collections.abc import Iterable, Sized from dataclasses import dataclass -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from matplotlib.dates import num2date @@ -26,27 +26,30 @@ class MarkerData: marker: str | None mark_options: list - fc: Optional[ + fc: ( str | tuple[float, float, float] | tuple[float, float, float, float] | tuple[str | tuple[float, float, float], float] | tuple[tuple[float, float, float, float], float] - ] = None # facecolor - ec: Optional[ + | None + ) = None # facecolor + ec: ( str | tuple[float, float, float] | tuple[float, float, float, float] | tuple[str | tuple[float, float, float], float] | tuple[tuple[float, float, float, float], float] - ] = None # edgecolor - lc: Optional[ + | None + ) = None # edgecolor + lc: ( str | tuple[float, float, float] | tuple[float, float, float, float] | tuple[str | tuple[float, float, float], float] | tuple[tuple[float, float, float, float], float] - ] = None # linecolor + | None + ) = None # linecolor def draw_line2d(data: TikzData, obj: Line2D) -> list[str]: @@ -290,7 +293,9 @@ def _table(data: TikzData, obj: Line2D) -> list[str]: data.current_axis_options.add("unbounded coords=jump") ff = data.float_format - plot_table = [f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip(xdata, ydata)] + plot_table = [ + f"{x:{xformat}}{col_sep}{y:{ff}}{table_row_sep}" for x, y in zip(xdata, ydata, strict=True) + ] min_extern_length = 3 diff --git a/src/matplot2tikz/_patch.py b/src/matplot2tikz/_patch.py index 4532b518..bafe2609 100644 --- a/src/matplot2tikz/_patch.py +++ b/src/matplot2tikz/_patch.py @@ -114,7 +114,9 @@ def ensure_list(x: Iterable | float) -> Iterable: hatches = ensure_list(obj.get_hatch()) if obj.get_hatch() is not None else [None] paths = obj.get_paths() - for path, ec, fc, ls, lw, t, off, hatch in zip_modulo(paths, ecs, fcs, lss, lws, ts, offs, hatches): + for path, ec, fc, ls, lw, t, off, hatch in zip_modulo( + paths, ecs, fcs, lss, lws, ts, offs, hatches + ): draw_options = mypath.get_draw_options( data, mypath.LineData(obj=obj, ec=ec, fc=fc, ls=ls, lw=lw, hatch=hatch) ) @@ -151,7 +153,9 @@ def _draw_rectangle(data: TikzData, obj: Rectangle, draw_options: list) -> list[ # "_nolegend_". See . if isinstance(obj.axes, Axes): handles, labels = obj.axes.get_legend_handles_labels() - labels_found = [label for h, label in zip(handles, labels) if obj in h.get_children()] + labels_found = [ + label for h, label in zip(handles, labels, strict=True) if obj in h.get_children() + ] if len(labels_found) == 1: label = labels_found[0] diff --git a/src/matplot2tikz/_path.py b/src/matplot2tikz/_path.py index 80790483..e1da1cd0 100644 --- a/src/matplot2tikz/_path.py +++ b/src/matplot2tikz/_path.py @@ -3,7 +3,7 @@ import contextlib from collections.abc import Iterable, Sequence, Sized from dataclasses import dataclass -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from matplotlib.dates import DateConverter, num2date @@ -27,15 +27,15 @@ @dataclass class LineData: obj: Collection | Patch - ec: Optional[str | tuple] = None # edgecolor - ec_name: Optional[str] = None - ec_rgba: Optional[np.ndarray] = None - fc: Optional[str | tuple] = None # facecolor - fc_name: Optional[str] = None - fc_rgba: Optional[np.ndarray] = None - ls: Optional[str | tuple[float, Sequence[float] | None]] = None # linestyle - lw: Optional[float] = None # linewidth - hatch: Optional[str] = None + ec: str | tuple | None = None # edgecolor + ec_name: str | None = None + ec_rgba: np.ndarray | None = None + fc: str | tuple | None = None # facecolor + fc_name: str | None = None + fc_rgba: np.ndarray | None = None + ls: str | tuple[float, Sequence[float] | None] | None = None # linestyle + lw: float | None = None # linewidth + hatch: str | None = None @dataclass @@ -46,18 +46,18 @@ class PathCollectionData: labels: list table_options: list is_contour: bool - marker: Optional[str] = None + marker: str | None = None is_filled: bool = False - add_individual_color_code: Optional[bool] = False - legend_text: Optional[str] = None + add_individual_color_code: bool | None = False + legend_text: str | None = None def draw_path( data: TikzData, path: Path, - draw_options: Optional[list[str]] = None, + draw_options: list[str] | None = None, *, - simplify: Optional[bool] = None, + simplify: bool | None = None, ) -> tuple[str, bool]: """Adds code for drawing an ordinary path in PGFPlots (TikZ).""" # For some reasons, matplotlib sometimes adds void paths which consist of @@ -377,7 +377,7 @@ def _draw_pathcollection_draw_contour(path: Path, data: TikzData, pcd: PathColle dd_strings: list[list[str]] = [] if not isinstance(codes, Iterable): return # We cannot draw a path - for row, code in zip(dd, codes): + for row, code in zip(dd, codes, strict=True): if code == 1: # MOVETO # Inserts a newline to trigger "move to" in pgfplots dd_strings.append([]) @@ -534,7 +534,7 @@ def mpl_linewidth2pgfp_linewidth(data: TikzData, line_width: float) -> str | Non def mpl_linestyle2pgfplots_linestyle( data: TikzData, line_style: str | tuple[float, Sequence[float] | None], - line: Optional[Line2D] = None, + line: Line2D | None = None, ) -> str | None: """Translates a line style of matplotlib to the corresponding style in PGFPlots.""" # linestyle is a string or dash tuple. Legal string values are @@ -554,7 +554,7 @@ def mpl_linestyle2pgfplots_linestyle( pgf_line_style += " ".join( [ f"on {ls_on:{ff}}pt off {ls_off:{ff}}pt" - for ls_on, ls_off in zip(line_style[1][::2], line_style[1][1::2]) + for ls_on, ls_off in zip(line_style[1][::2], line_style[1][1::2], strict=True) ] ) return pgf_line_style @@ -574,7 +574,8 @@ def mpl_linestyle2pgfplots_linestyle( lst.append( "dash pattern=" + " ".join( - f"on {a:{ff}}pt off {b:{ff}}pt" for a, b in zip(dash_seq[0::2], dash_seq[1::2]) + f"on {a:{ff}}pt off {b:{ff}}pt" + for a, b in zip(dash_seq[0::2], dash_seq[1::2], strict=True) ) ) diff --git a/src/matplot2tikz/_util.py b/src/matplot2tikz/_util.py index ef740c16..00062692 100644 --- a/src/matplot2tikz/_util.py +++ b/src/matplot2tikz/_util.py @@ -36,7 +36,7 @@ def get_legend_text(obj: Line2D | PathCollection) -> str | None: values = [t.get_text() for t in leg.texts] label = obj.get_label() - d = dict(zip(keys, values)) + d = dict(zip(keys, values, strict=True)) if label in d: return d[label] diff --git a/tests/helpers.py b/tests/helpers.py index 21125803..a4c29b84 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,8 +1,8 @@ """Helper functions for running the tests.""" import difflib +from collections.abc import Callable from pathlib import Path -from typing import Callable import matplotlib.pyplot as plt from matplotlib.artist import Artist @@ -48,4 +48,9 @@ def assert_equality( # type: ignore[no-untyped-def] this_dir = Path(__file__).resolve().parent with (this_dir / filename).open(encoding="utf-8") as f: reference = f.read() + try: + assert reference == code, filename + "\n" + _unidiff_output(reference, code) + except AssertionError: + with (this_dir / f"{filename[:-4]}_output.tex").open("w") as f: + f.write(code) assert reference == code, filename + "\n" + _unidiff_output(reference, code) diff --git a/tests/test_barchart_logy.py b/tests/test_barchart_logy.py index 5e4acf00..6684702b 100644 --- a/tests/test_barchart_logy.py +++ b/tests/test_barchart_logy.py @@ -12,7 +12,7 @@ def plot() -> None: - fig, axes = plt.subplots() + _, axes = plt.subplots() axes.set_yscale("log") axes.set_ylim(0.002, 200) axes.bar([1, 2, 3, 4, 5], [0.01, 0.1, 1, 10, 100]) diff --git a/tests/test_cleanfigure.py b/tests/test_cleanfigure.py index 0ed22ac5..7b8e9d3e 100644 --- a/tests/test_cleanfigure.py +++ b/tests/test_cleanfigure.py @@ -222,7 +222,7 @@ def test_surface3d(self) -> None: "Cleaning Line Collections (scatter plot) is not supported yet.", ) assert len(record) == len(warnings) - for record_warning, warning in zip(record, warnings): + for record_warning, warning in zip(record, warnings, strict=True): assert str(record_warning.message) == warning plt.close("all") @@ -284,7 +284,7 @@ def cc(arg: str) -> tuple[float, float, float, float]: for _ in zs: ys = rng.random(size=len(xs)) ys[0], ys[-1] = 0, 0 - verts.append(list(zip(xs, ys))) + verts.append(list(zip(xs, ys, strict=True))) poly = PolyCollection(verts, facecolors=[cc("r"), cc("g"), cc("b"), cc("y")]) poly.set_alpha(0.7) @@ -307,7 +307,7 @@ def test_bar3d(self) -> None: ax: axes3d.Axes3D = fig.add_subplot(111, projection="3d") rng = np.random.default_rng(42) - for c, z in zip(["r", "g", "b", "y"], [30, 20, 10, 0]): + for c, z in zip(["r", "g", "b", "y"], [30, 20, 10, 0], strict=True): xs = np.arange(20) ys = rng.random(size=20) @@ -492,7 +492,7 @@ def test_subplot(self) -> None: with plt.rc_context(rc=RC_PARAMS): fig, axes = plt.subplots(2, 2, figsize=(5, 5)) plotstyles = [("-", "o"), ("-", "None"), ("None", "o"), ("--", "x")] - for ax, style in zip(axes.ravel(), plotstyles): + for ax, style in zip(axes.ravel(), plotstyles, strict=True): ax.plot(x, y, linestyle=style[0], marker=style[1]) ax.set_ylim([20, 80]) ax.set_xlim([20, 80]) diff --git a/tests/test_contourf.py b/tests/test_contourf.py index 71eb3ff2..6a60a618 100644 --- a/tests/test_contourf.py +++ b/tests/test_contourf.py @@ -34,8 +34,4 @@ def plot() -> Figure: def test() -> None: - try: - assert_equality(plot, __file__[:-3] + "_reference.tex") - except AssertionError: - # Try other output, which is the new output since Python 3.9 - assert_equality(plot, __file__[:-3] + "_reference2.tex") + assert_equality(plot, __file__[:-3] + "_reference.tex") diff --git a/tests/test_contourf_reference.tex b/tests/test_contourf_reference.tex index 49a8cb80..cb07b440 100644 --- a/tests/test_contourf_reference.tex +++ b/tests/test_contourf_reference.tex @@ -17,177 +17,137 @@ ymin=0, ymax=2, ytick style={color=black} ] -\addplot [draw=none, fill=darkslateblue7235116] -table{% -x y -0.26666667 0 -0 0 -0 0.26666667 -0.26666667 0 -}; -\addplot [draw=none, fill=darkslateblue7235116] -table{% -x y -0 2 -0.26666667 2 -0 1.7333333 -0 2 -}; -\addplot [draw=none, fill=darkslateblue7235116] -table{% -x y -2 0.26666667 -2 0 -1.7333333 0 -2 0.26666667 -}; -\addplot [draw=none, fill=darkslateblue7235116] -table{% -x y -1.7333333 2 -2 2 -2 1.7333333 -1.7333333 2 -}; -\addplot [draw=none, fill=darkslateblue5294141] -table{% -x y -0 0.5 -0 0.6 -0.033333333 0.5 -0.5 0.033333333 -0.6 0 -0.5 0 -0.26666667 0 -0 0.26666667 -0 0.5 -}; -\addplot [draw=none, fill=darkslateblue5294141] -table{% -x y -0 1.5 -0 1.7333333 -0.26666667 2 -0.5 2 -0.6 2 -0.5 1.9666667 -0.033333333 1.5 -0 1.4 -0 1.5 -}; -\addplot [draw=none, fill=darkslateblue5294141] -table{% -x y -1.5 0.033333333 -1.9666667 0.5 -2 0.6 -2 0.5 -2 0.26666667 -1.7333333 0 -1.5 0 -1.4 0 -1.5 0.033333333 -}; -\addplot [draw=none, fill=darkslateblue5294141] -table{% -x y -1.4 2 -1.5 2 -1.7333333 2 -2 1.7333333 -2 1.5 -2 1.4 -1.9666667 1.5 -1.5 1.9666667 -1.4 2 -}; -\addplot [draw=none, fill=darkcyan32144140] -table{% -x y -0.033333333 0.5 -0 0.6 -0 1 -0 1.4 -0.033333333 1.5 -0.5 1.9666667 -0.6 2 -1 2 -1.4 2 -1.5 1.9666667 -1.9666667 1.5 -2 1.4 -2 1 -2 0.6 -1.9666667 0.5 -1.5 0.033333333 -1.4 0 -1 0 -0.6 0 -0.5 0.033333333 -0.033333333 0.5 +\path [fill=darkslateblue7235116] +(axis cs:0.26666667,0) +--(axis cs:0,0) +--(axis cs:0,0.26666667) +--cycle +(axis cs:0,2) +--(axis cs:0.26666667,2) +--(axis cs:0,1.7333333) +--cycle +(axis cs:2,0.26666667) +--(axis cs:2,0) +--(axis cs:1.7333333,0) +--cycle +(axis cs:1.7333333,2) +--(axis cs:2,2) +--(axis cs:2,1.7333333) +--cycle; +\path [fill=darkslateblue5294141] +(axis cs:0,0.5) +--(axis cs:0,0.6) +--(axis cs:0.033333333,0.5) +--(axis cs:0.5,0.033333333) +--(axis cs:0.6,0) +--(axis cs:0.5,0) +--(axis cs:0.26666667,0) +--(axis cs:0,0.26666667) +--cycle +(axis cs:0,1.5) +--(axis cs:0,1.7333333) +--(axis cs:0.26666667,2) +--(axis cs:0.5,2) +--(axis cs:0.6,2) +--(axis cs:0.5,1.9666667) +--(axis cs:0.033333333,1.5) +--(axis cs:0,1.4) +--cycle +(axis cs:1.5,0.033333333) +--(axis cs:1.9666667,0.5) +--(axis cs:2,0.6) +--(axis cs:2,0.5) +--(axis cs:2,0.26666667) +--(axis cs:1.7333333,0) +--(axis cs:1.5,0) +--(axis cs:1.4,0) +--cycle +(axis cs:1.4,2) +--(axis cs:1.5,2) +--(axis cs:1.7333333,2) +--(axis cs:2,1.7333333) +--(axis cs:2,1.5) +--(axis cs:2,1.4) +--(axis cs:1.9666667,1.5) +--(axis cs:1.5,1.9666667) +--cycle; +\path [fill=darkcyan32144140] +(axis cs:0.033333333,0.5) +--(axis cs:0,0.6) +--(axis cs:0,1) +--(axis cs:0,1.4) +--(axis cs:0.033333333,1.5) +--(axis cs:0.5,1.9666667) +--(axis cs:0.6,2) +--(axis cs:1,2) +--(axis cs:1.4,2) +--(axis cs:1.5,1.9666667) +--(axis cs:1.9666667,1.5) +--(axis cs:2,1.4) +--(axis cs:2,1) +--(axis cs:2,0.6) +--(axis cs:1.9666667,0.5) +--(axis cs:1.5,0.033333333) +--(axis cs:1.4,0) +--(axis cs:1,0) +--(axis cs:0.6,0) +--(axis cs:0.5,0.033333333) +--cycle +(axis cs:0.5,0.3) +--(axis cs:1,0.13333333) +--(axis cs:1.5,0.3) +--(axis cs:1.7,0.5) +--(axis cs:1.8666667,1) +--(axis cs:1.7,1.5) +--(axis cs:1.5,1.7) +--(axis cs:1,1.8666667) +--(axis cs:0.5,1.7) +--(axis cs:0.3,1.5) +--(axis cs:0.13333333,1) +--(axis cs:0.3,0.5) +--cycle; +\path [fill=mediumseagreen68190112] +(axis cs:0.3,0.5) +--(axis cs:0.13333333,1) +--(axis cs:0.3,1.5) +--(axis cs:0.5,1.7) +--(axis cs:1,1.8666667) +--(axis cs:1.5,1.7) +--(axis cs:1.7,1.5) +--(axis cs:1.8666667,1) +--(axis cs:1.7,0.5) +--(axis cs:1.5,0.3) +--(axis cs:1,0.13333333) +--(axis cs:0.5,0.3) +--cycle +(axis cs:0.5,0.7) +--(axis cs:0.7,0.5) +--(axis cs:1,0.4) +--(axis cs:1.3,0.5) +--(axis cs:1.5,0.7) +--(axis cs:1.6,1) +--(axis cs:1.5,1.3) +--(axis cs:1.3,1.5) +--(axis cs:1,1.6) +--(axis cs:0.7,1.5) +--(axis cs:0.5,1.3) +--(axis cs:0.4,1) +--cycle; +\path [fill=greenyellow18922238] +(axis cs:0.4,1) +--(axis cs:0.5,1.3) +--(axis cs:0.7,1.5) +--(axis cs:1,1.6) +--(axis cs:1.3,1.5) +--(axis cs:1.5,1.3) +--(axis cs:1.6,1) +--(axis cs:1.5,0.7) +--(axis cs:1.3,0.5) +--(axis cs:1,0.4) +--(axis cs:0.7,0.5) +--(axis cs:0.5,0.7) +--cycle; -0.5 0.3 -1 0.13333333 -1.5 0.3 -1.7 0.5 -1.8666667 1 -1.7 1.5 -1.5 1.7 -1 1.8666667 -0.5 1.7 -0.3 1.5 -0.13333333 1 -0.3 0.5 -0.5 0.3 -}; -\addplot [draw=none, fill=mediumseagreen68190112] -table{% -x y -0.3 0.5 -0.13333333 1 -0.3 1.5 -0.5 1.7 -1 1.8666667 -1.5 1.7 -1.7 1.5 -1.8666667 1 -1.7 0.5 -1.5 0.3 -1 0.13333333 -0.5 0.3 -0.3 0.5 - -0.5 0.7 -0.7 0.5 -1 0.4 -1.3 0.5 -1.5 0.7 -1.6 1 -1.5 1.3 -1.3 1.5 -1 1.6 -0.7 1.5 -0.5 1.3 -0.4 1 -0.5 0.7 -}; -\addplot [draw=none, fill=greenyellow18922238] -table{% -x y -0.4 1 -0.5 1.3 -0.7 1.5 -1 1.6 -1.3 1.5 -1.5 1.3 -1.6 1 -1.5 0.7 -1.3 0.5 -1 0.4 -0.7 0.5 -0.5 0.7 -0.4 1 -}; \end{axis} \end{tikzpicture} diff --git a/tests/test_contourf_reference2.tex b/tests/test_contourf_reference2.tex deleted file mode 100644 index cb07b440..00000000 --- a/tests/test_contourf_reference2.tex +++ /dev/null @@ -1,153 +0,0 @@ -\begin{tikzpicture} - -\definecolor{darkcyan32144140}{RGB}{32,144,140} -\definecolor{darkgray176}{RGB}{176,176,176} -\definecolor{darkslateblue5294141}{RGB}{52,94,141} -\definecolor{darkslateblue7235116}{RGB}{72,35,116} -\definecolor{greenyellow18922238}{RGB}{189,222,38} -\definecolor{mediumseagreen68190112}{RGB}{68,190,112} - -\begin{axis}[ -tick align=outside, -tick pos=left, -x grid style={darkgray176}, -xmin=0, xmax=2, -xtick style={color=black}, -y grid style={darkgray176}, -ymin=0, ymax=2, -ytick style={color=black} -] -\path [fill=darkslateblue7235116] -(axis cs:0.26666667,0) ---(axis cs:0,0) ---(axis cs:0,0.26666667) ---cycle -(axis cs:0,2) ---(axis cs:0.26666667,2) ---(axis cs:0,1.7333333) ---cycle -(axis cs:2,0.26666667) ---(axis cs:2,0) ---(axis cs:1.7333333,0) ---cycle -(axis cs:1.7333333,2) ---(axis cs:2,2) ---(axis cs:2,1.7333333) ---cycle; -\path [fill=darkslateblue5294141] -(axis cs:0,0.5) ---(axis cs:0,0.6) ---(axis cs:0.033333333,0.5) ---(axis cs:0.5,0.033333333) ---(axis cs:0.6,0) ---(axis cs:0.5,0) ---(axis cs:0.26666667,0) ---(axis cs:0,0.26666667) ---cycle -(axis cs:0,1.5) ---(axis cs:0,1.7333333) ---(axis cs:0.26666667,2) ---(axis cs:0.5,2) ---(axis cs:0.6,2) ---(axis cs:0.5,1.9666667) ---(axis cs:0.033333333,1.5) ---(axis cs:0,1.4) ---cycle -(axis cs:1.5,0.033333333) ---(axis cs:1.9666667,0.5) ---(axis cs:2,0.6) ---(axis cs:2,0.5) ---(axis cs:2,0.26666667) ---(axis cs:1.7333333,0) ---(axis cs:1.5,0) ---(axis cs:1.4,0) ---cycle -(axis cs:1.4,2) ---(axis cs:1.5,2) ---(axis cs:1.7333333,2) ---(axis cs:2,1.7333333) ---(axis cs:2,1.5) ---(axis cs:2,1.4) ---(axis cs:1.9666667,1.5) ---(axis cs:1.5,1.9666667) ---cycle; -\path [fill=darkcyan32144140] -(axis cs:0.033333333,0.5) ---(axis cs:0,0.6) ---(axis cs:0,1) ---(axis cs:0,1.4) ---(axis cs:0.033333333,1.5) ---(axis cs:0.5,1.9666667) ---(axis cs:0.6,2) ---(axis cs:1,2) ---(axis cs:1.4,2) ---(axis cs:1.5,1.9666667) ---(axis cs:1.9666667,1.5) ---(axis cs:2,1.4) ---(axis cs:2,1) ---(axis cs:2,0.6) ---(axis cs:1.9666667,0.5) ---(axis cs:1.5,0.033333333) ---(axis cs:1.4,0) ---(axis cs:1,0) ---(axis cs:0.6,0) ---(axis cs:0.5,0.033333333) ---cycle -(axis cs:0.5,0.3) ---(axis cs:1,0.13333333) ---(axis cs:1.5,0.3) ---(axis cs:1.7,0.5) ---(axis cs:1.8666667,1) ---(axis cs:1.7,1.5) ---(axis cs:1.5,1.7) ---(axis cs:1,1.8666667) ---(axis cs:0.5,1.7) ---(axis cs:0.3,1.5) ---(axis cs:0.13333333,1) ---(axis cs:0.3,0.5) ---cycle; -\path [fill=mediumseagreen68190112] -(axis cs:0.3,0.5) ---(axis cs:0.13333333,1) ---(axis cs:0.3,1.5) ---(axis cs:0.5,1.7) ---(axis cs:1,1.8666667) ---(axis cs:1.5,1.7) ---(axis cs:1.7,1.5) ---(axis cs:1.8666667,1) ---(axis cs:1.7,0.5) ---(axis cs:1.5,0.3) ---(axis cs:1,0.13333333) ---(axis cs:0.5,0.3) ---cycle -(axis cs:0.5,0.7) ---(axis cs:0.7,0.5) ---(axis cs:1,0.4) ---(axis cs:1.3,0.5) ---(axis cs:1.5,0.7) ---(axis cs:1.6,1) ---(axis cs:1.5,1.3) ---(axis cs:1.3,1.5) ---(axis cs:1,1.6) ---(axis cs:0.7,1.5) ---(axis cs:0.5,1.3) ---(axis cs:0.4,1) ---cycle; -\path [fill=greenyellow18922238] -(axis cs:0.4,1) ---(axis cs:0.5,1.3) ---(axis cs:0.7,1.5) ---(axis cs:1,1.6) ---(axis cs:1.3,1.5) ---(axis cs:1.5,1.3) ---(axis cs:1.6,1) ---(axis cs:1.5,0.7) ---(axis cs:1.3,0.5) ---(axis cs:1,0.4) ---(axis cs:0.7,0.5) ---(axis cs:0.5,0.7) ---cycle; - -\end{axis} - -\end{tikzpicture} diff --git a/tests/test_hatch.py b/tests/test_hatch.py index d82e8c0f..2c556cdb 100644 --- a/tests/test_hatch.py +++ b/tests/test_hatch.py @@ -35,7 +35,7 @@ def plot() -> Figure: ax2.set_xticks([1.5, 2.5, 3.5, 4.5]) patterns = ("-", "+", "x", "\\", "*", "o", "O", ".") - for bar, pattern in zip(bars, patterns): + for bar, pattern in zip(bars, patterns, strict=True): bar.set_hatch(pattern) ax3.fill([1, 3, 3, 1], [1, 1, 2, 2], fill=False, hatch="\\", zorder=1, label="Square") diff --git a/tests/test_line_collection.py b/tests/test_line_collection.py index 16873bf4..dccff782 100644 --- a/tests/test_line_collection.py +++ b/tests/test_line_collection.py @@ -42,7 +42,9 @@ def plot() -> Figure: # Make a sequence of x,y pairs line_segments = LineCollection( - [list(zip(x, y)) for y in ys], linewidths=(0.5, 1, 1.5, 2), linestyles="dashdot" + [list(zip(x, y, strict=True)) for y in ys], + linewidths=(0.5, 1, 1.5, 2), + linestyles="dashdot", ) line_segments.set_array(x) ax.add_collection(line_segments) diff --git a/tests/test_patches.py b/tests/test_patches.py index 479e7514..ecfda2ca 100644 --- a/tests/test_patches.py +++ b/tests/test_patches.py @@ -24,7 +24,7 @@ def plot() -> Figure: y = rng.random(size=n_points) radii = 0.1 * rng.random(size=n_points) patches: list[Patch] = [] - for x1, y1, r in zip(x, y, radii): + for x1, y1, r in zip(x, y, radii, strict=True): circle = Circle((x1, y1), r) patches.append(circle) @@ -36,7 +36,7 @@ def plot() -> Figure: radii = 0.1 * rng.random(size=n_points) theta1 = 360.0 * rng.random(size=n_points) theta2 = 360.0 * rng.random(size=n_points) - for x1, y1, r, t1, t2 in zip(x, y, radii, theta1, theta2): + for x1, y1, r, t1, t2 in zip(x, y, radii, theta1, theta2, strict=True): wedge = Wedge((x1, y1), r, t1, t2) patches.append(wedge) diff --git a/tox.ini b/tox.ini index 313b9235..69d62774 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,14 @@ [tox] envlist = lint - py{39,310,311,312,313} + py3{10,11,12,13,14} combine-test-reports isolated_build = True [testenv] description = Run unit tests. -deps = - pandas package = wheel -extras = test setenv = PY_IGNORE_IMPORTMISMATCH=1 # https://github.com/pytest-dev/pytest/issues/2042 COVERAGE_FILE = reports{/}.coverage.{envname} @@ -19,18 +16,36 @@ commands = # Run tests from .py files pytest --typeguard-packages=matplot2tikz --junitxml=reports/pytest.xml.{envname} {posargs} - -[testenv:py39] [testenv:py310] +basepython = python3.10 +deps = + -r requirements-310.txt + [testenv:py311] +basepython = python3.11 +deps = + -r requirements-311.txt + [testenv:py312] +basepython = python3.12 +deps = + -r requirements-312.txt + [testenv:py313] +basepython = python3.13 +deps = + -r requirements-313.txt +[testenv:py314] +basepython = python3.14 +deps = + -r requirements-314.txt [testenv:lint] description = Run static checkers. -basepython = python3.9 -extras = lint +basepython = python3.10 +deps = + -r requirements-lint.txt commands = ruff format . --check ruff check . @@ -42,7 +57,7 @@ description = Combine test and coverage data from multiple test runs. skip_install = true setenv = COVERAGE_FILE = reports/.coverage -depends = py +depends = py3 deps = junitparser coverage[toml]