diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index f75fb1b..d4689d3 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -38,7 +38,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest"] - version: ["3.9", "3.10", "3.11"] + version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout 🔖 uses: actions/checkout@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 233c012..7da5af2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-ast @@ -12,29 +12,19 @@ repos: - id: check-json - id: check-toml - id: check-yaml - exclude: mkdocs.yml - - repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black - exclude: test/data/schema/wrong_syntax.py - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.0.0 + rev: v1.15.0 hooks: - id: mypy exclude: test/data/schema/wrong_syntax.py - repo: https://github.com/dosisod/refurb - rev: v1.11.1 + rev: v2.0.0 hooks: - id: refurb exclude: test/data/schema/wrong_syntax.py - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.247' + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.4 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - - repo: https://github.com/tcort/markdown-link-check - rev: 'v3.11.2' - hooks: - - id: markdown-link-check - args: [-q] + exclude: test/data/schema/wrong_syntax.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eba1de..fefffa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,14 +12,18 @@ While in this phase, we will denote breaking changes with a minor increase. ## 0.4.3 -### Fixed +### Added + +* Added support for python `3.12` + +### Changed * Bump all dependencies to the latest version and introduce necessary adaptation in the source code (affecting only `dac info`): - `build~=0.9` -> `build==1.2.2` - `toml~=0.10` -> `toml==0.10.2` - `typer[all]~=0.7` -> `typer[all]==0.15.2` - `wheel~=0.38` -> `wheel==0.45.1` -* Prevent installation with python > `3.11` +* Prevent installation with python > `3.12` ## 0.4.2 diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index e835ac1..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,35 +0,0 @@ -site_name: Data as Code - -repo_url: https://github.com/data-as-code/dac -edit_uri: tree/main/doc - -nav: - - Home: index.md - - Examples: examples.md - -theme: - name: material - features: - - navigation.instant - - navigation.tracking - - navigation.sections - - navigation.expand - - content.code.annotate - -plugins: - - search - -markdown_extensions: - - tables - - attr_list - - admonition - - pymdownx.details - - pymdownx.superfences - - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format diff --git a/pyproject.toml b/pyproject.toml index ae131dc..b96028a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "dac" dynamic = ["version"] description = "Tool to distribute data as code" readme = "README.md" -requires-python = ">=3.9,<3.12" +requires-python = ">=3.9,<3.13" license = { text = "MIT" } authors = [ { name = "Francesco Calcavecchia", email = "francesco.calcavecchia@gmail.com" }, diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index 24c9772..0000000 --- a/requirements-docs.txt +++ /dev/null @@ -1 +0,0 @@ -mkdocs-material~=8.5 diff --git a/src/dac/_input/pyproject.py b/src/dac/_input/pyproject.py index e6cb476..9c9a609 100644 --- a/src/dac/_input/pyproject.py +++ b/src/dac/_input/pyproject.py @@ -31,4 +31,4 @@ def generate_pyproject_toml(self) -> str: def _get_list_of_project_dependencies(self) -> List[str]: splitted_by_newline = self.project_dependencies.splitlines() splitted_by_newline_or_comma = [s for ss in splitted_by_newline for s in ss.split(";")] - return sorted(map(lambda x: x.strip(), splitted_by_newline_or_comma)) + return sorted(map(str.strip, splitted_by_newline_or_comma)) diff --git a/src/dac/_version_management.py b/src/dac/_version_management.py index 22ac57c..5cb6992 100644 --- a/src/dac/_version_management.py +++ b/src/dac/_version_management.py @@ -1,30 +1,60 @@ +import tempfile import re import subprocess from typing import Optional +from pathlib import Path def find_latest_version(pkg_name: str, major: Optional[int] = None) -> str: - output = subprocess.check_output( - [ - "pip", - "install", - "--no-deps", - "--ignore-installed", - "--no-cache-dir", - "--dry-run", - f"{pkg_name}{f'=={major}.*' if major is not None else ''}", - ], - stderr=subprocess.DEVNULL, + pip_log = _pretend_pip_install(pkg_name=pkg_name, major=major) + would_install_line = _extract_would_install_line(pip_log=pip_log) + package_with_version = _extract_package_with_version( + pip_log_would_install_line=would_install_line, pkg_name=pkg_name, major=major ) - output_lines = output.decode("utf-8").splitlines() - would_install_line = [line for line in output_lines if "Would install" in line][0] - regex_rule = f"{pkg_name.replace('_', '-')}-{major if major is not None else ''}.[^ ]+" - match = re.search(regex_rule, would_install_line.replace("_", "-")) - assert match is not None - return match[0][len(f"{pkg_name}-") :] + return package_with_version[len(f"{pkg_name}-") :] def increase_minor(version: str) -> str: major, minor, patch = version.split(".") assert major.isdigit() and minor.isdigit() return f"{major}.{int(minor) + 1}.0" + + +def _pretend_pip_install(pkg_name: str, major: Optional[int]) -> str: + with tempfile.NamedTemporaryFile() as log_file: + result = subprocess.run( + [ + "pip", + "install", + "--no-deps", + "--ignore-installed", + "--dry-run", + f"--log={log_file.name}", + f"{pkg_name}{f'=={major}.*' if major is not None else ''}", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + if result.returncode != 0: + raise subprocess.SubprocessError( + f"Something went wrong while using pip to find the version of {pkg_name}{f'(major version {major})' if major else ''}.\n\nSTDOUT: {result.stdout}\n\nSTDERR: {result.stderr}" + ) + return Path(log_file.name).read_text() + + +def _extract_would_install_line(pip_log: str) -> str: + would_install_lines = [line for line in pip_log.splitlines() if "Would install" in line] + assert ( + len(would_install_lines) == 1 + ), f"Expected exactly one line containing 'Would install' in the pip log generated by pip installing the requested package, but found {len(would_install_lines)} lines." + return would_install_lines[0] + + +def _extract_package_with_version(pip_log_would_install_line: str, pkg_name: str, major: Optional[int]) -> str: + regex_rule = f"{pkg_name.replace('_', '-')}-{major if major is not None else ''}.[^ ]+" + match = re.search(regex_rule, pip_log_would_install_line.replace("_", "-")) + assert ( + match is not None and match[0] != "" + ), f"It was not possible to determine the version, because we could not find a match to the regex {regex_rule} in {pip_log_would_install_line.replace('_', '-')}" + return match[0] diff --git a/test/integration_test/version_management_test.py b/test/integration_test/version_management_test.py index 09e63ba..7175302 100644 --- a/test/integration_test/version_management_test.py +++ b/test/integration_test/version_management_test.py @@ -9,7 +9,7 @@ def test_if_find_latest_version_is_called_then_return_latest_version(): def test_if_find_latest_version_is_called_with_major_constraint_then_return_latest_major_version(): - assert "0.25.3" == find_latest_version(pkg_name="pandas", major=0) + assert "1.5.3" == find_latest_version(pkg_name="pandas", major=1) def test_if_pkg_does_not_exist_then_find_package_raises_exception(): @@ -23,5 +23,5 @@ def test_if_next_version_without_major_spec_then_return_latest_version_with_mino def test_if_next_version_with_major_spec_then_return_minor_upgrade_for_that_major(): - result = invoke_dac_next_version(pkg_name="pandas", major=0) - assert result.stdout == "0.26.0\n" + result = invoke_dac_next_version(pkg_name="pandas", major=1) + assert result.stdout == "1.6.0\n" diff --git a/test/unit_test/_packing/data_as_code_project_test.py b/test/unit_test/_packing/data_as_code_project_test.py index 52ebf46..16600d9 100644 --- a/test/unit_test/_packing/data_as_code_project_test.py +++ b/test/unit_test/_packing/data_as_code_project_test.py @@ -32,7 +32,7 @@ def test_if_project_init_is_inspected_then_load_and_schema_is_found(): def test_if_project_load_is_inspected_then_has_same_content_as_original(): with input_with_self_contained_data() as config: with data_as_code_project(config=config) as proj_dir: - project_load_path = proj_dir / "src" / config.pyproject.project_name / f"{DaCProjectFactory.load_file_name}" + project_load_path = proj_dir / "src" / config.pyproject.project_name / str(DaCProjectFactory.load_file_name) assert project_load_path.exists() assert filecmp.cmp(config.load_path, project_load_path) @@ -41,7 +41,7 @@ def test_if_project_schema_is_inspected_then_has_same_content_as_original(): with input_with_self_contained_data() as config: with data_as_code_project(config=config) as proj_dir: project_schema_path = ( - proj_dir / "src" / config.pyproject.project_name / f"{DaCProjectFactory.schema_file_name}" + proj_dir / "src" / config.pyproject.project_name / str(DaCProjectFactory.schema_file_name) ) assert project_schema_path.exists() assert filecmp.cmp(config.schema_path, project_schema_path) diff --git a/test/unit_test/_version_management/find_latest_test.py b/test/unit_test/_version_management/find_latest_test.py index 25a6dca..cfe7675 100644 --- a/test/unit_test/_version_management/find_latest_test.py +++ b/test/unit_test/_version_management/find_latest_test.py @@ -1,54 +1,40 @@ -import subprocess from test.data import get_pip_log_with_dash, get_pip_log_with_underscore -from unittest.mock import MagicMock +from unittest.mock import patch from dac._version_management import find_latest_version -from pytest import MonkeyPatch, fixture -def test_if_pkg_name_uses_dash_separator_and_pip_log_dash_then_correct_latest_version(mock_pip_output_with_dash: None): - latest_version = find_latest_version(pkg_name="investing-algorithm-framework") - assert latest_version == "2.3.2" +def test_if_pkg_name_uses_dash_separator_and_pip_log_dash_then_correct_latest_version(): + with patch("dac._version_management._pretend_pip_install") as mock_pretend_pip_install: + mock_pretend_pip_install.return_value = get_pip_log_with_dash() + latest_version = find_latest_version(pkg_name="investing-algorithm-framework") -def test_if_pkg_name_uses_dash_separator_and_pip_log_underscore_then_correct_latest_version( - mock_pip_output_with_underscore: None, -): - latest_version = find_latest_version(pkg_name="investing-algorithm-framework") - assert latest_version == "2.3.2" + assert latest_version == "2.3.2" -def test_if_pkg_name_uses_underscore_separator_and_pip_log_dash_then_correct_latest_version( - mock_pip_output_with_dash: None, -): - latest_version = find_latest_version(pkg_name="investing_algorithm_framework") - assert latest_version == "2.3.2" +def test_if_pkg_name_uses_dash_separator_and_pip_log_underscore_then_correct_latest_version(): + with patch("dac._version_management._pretend_pip_install") as mock_pretend_pip_install: + mock_pretend_pip_install.return_value = get_pip_log_with_underscore() + latest_version = find_latest_version(pkg_name="investing-algorithm-framework") -def test_if_pkg_name_uses_underscore_separator_and_pip_log_underscore_then_correct_latest_version( - mock_pip_output_with_underscore: None, -): - latest_version = find_latest_version(pkg_name="investing_algorithm_framework") - assert latest_version == "2.3.2" + assert latest_version == "2.3.2" -@fixture -def mock_pip_output_with_dash(monkeypatch: MonkeyPatch): - output = MagicMock() - output.decode.return_value = get_pip_log_with_dash() +def test_if_pkg_name_uses_underscore_separator_and_pip_log_dash_then_correct_latest_version(): + with patch("dac._version_management._pretend_pip_install") as mock_pretend_pip_install: + mock_pretend_pip_install.return_value = get_pip_log_with_dash() - def return_foo(*args, **kwargs) -> str: - return output + latest_version = find_latest_version(pkg_name="investing_algorithm_framework") - monkeypatch.setattr(subprocess, "check_output", return_foo) + assert latest_version == "2.3.2" -@fixture -def mock_pip_output_with_underscore(monkeypatch: MonkeyPatch): - output = MagicMock() - output.decode.return_value = get_pip_log_with_underscore() +def test_if_pkg_name_uses_underscore_separator_and_pip_log_underscore_then_correct_latest_version(): + with patch("dac._version_management._pretend_pip_install") as mock_pretend_pip_install: + mock_pretend_pip_install.return_value = get_pip_log_with_underscore() - def return_foo(*args, **kwargs) -> str: - return output + latest_version = find_latest_version(pkg_name="investing_algorithm_framework") - monkeypatch.setattr(subprocess, "check_output", return_foo) + assert latest_version == "2.3.2"