rhiza 0.8.4__tar.gz → 0.8.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_book.yml +4 -1
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_ci.yml +9 -8
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_deptry.yml +5 -8
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_release.yml +2 -2
- rhiza-0.8.5/.rhiza/.cfg.toml +31 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/history +7 -4
- rhiza-0.8.5/.rhiza/requirements/README.md +27 -0
- rhiza-0.8.5/.rhiza/requirements/docs.txt +2 -0
- rhiza-0.8.5/.rhiza/requirements/marimo.txt +2 -0
- rhiza-0.8.5/.rhiza/requirements/tests.txt +4 -0
- rhiza-0.8.5/.rhiza/requirements/tools.txt +3 -0
- rhiza-0.8.5/.rhiza/scripts/install-dev-deps.sh +25 -0
- rhiza-0.8.5/.rhiza/utils/version_matrix.py +117 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/Makefile +37 -15
- {rhiza-0.8.4 → rhiza-0.8.5}/PKG-INFO +1 -11
- {rhiza-0.8.4 → rhiza-0.8.5}/pyproject.toml +8 -15
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/Makefile.tests +2 -2
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_makefile.py +5 -44
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_release_script.py +2 -3
- rhiza-0.8.5/tests/test_rhiza/test_requirements_folder.py +64 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/uv.lock +459 -310
- rhiza-0.8.4/.rhiza/utils/version_matrix.py +0 -63
- rhiza-0.8.4/.rhiza/utils/version_max.py +0 -61
- {rhiza-0.8.4 → rhiza-0.8.5}/.editorconfig +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/ISSUE_TEMPLATE/assign_ui_implementation.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_codeql.yml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_marimo.yml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_pre-commit.yml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_sync.yml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.github/workflows/rhiza_validate.yml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.gitignore +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.pre-commit-config.yaml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.python-version +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/docs/CONFIG.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/docs/TOKEN_SETUP.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/book.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/bump.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/customisations/build-extras.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/customisations/post-release.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/generate-coverage-badge.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/marimushka.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/release.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/scripts/update-readme-help.sh +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza/template.yml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/.rhiza.env +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/CLI.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/CODE_OF_CONDUCT.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/CONTRIBUTING.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/GETTING_STARTED.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/LICENSE +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/README.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/USAGE.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/Makefile.book +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/README.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/marimo/.gitkeep +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/marimo/README.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/marimo/rhiza.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/minibook-templates/custom.html.jinja2 +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/book/pdoc-templates/module.html.jinja2 +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/presentation/Makefile.presentation +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/presentation/README.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/pytest.ini +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/renovate.json +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/ruff.toml +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/__init__.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/__main__.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/_templates/basic/__init__.py.jinja2 +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/_templates/basic/main.py.jinja2 +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/_templates/basic/pyproject.toml.jinja2 +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/cli.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/__init__.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/init.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/materialize.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/migrate.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/uninstall.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/validate.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/commands/welcome.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/models.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/src/rhiza/subprocess_utils.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_cli_commands.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_commands/test_init.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_commands/test_materialize.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_commands/test_migrate.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_commands/test_uninstall.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_commands/test_validate.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_models.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_package.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/README.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/benchmarks/.gitignore +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/benchmarks/README.md +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/benchmarks/analyze_benchmarks.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/conftest.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_bump_script.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_coverage_badge.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_docstrings.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_git_repo_fixture.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_marimushka_script.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_notebooks.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_readme.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_structure.py +0 -0
- {rhiza-0.8.4 → rhiza-0.8.5}/tests/test_rhiza/test_updatereadme_script.py +0 -0
|
@@ -50,7 +50,10 @@ jobs:
|
|
|
50
50
|
run: |
|
|
51
51
|
export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}"
|
|
52
52
|
# will just use .python-version?
|
|
53
|
-
uv sync --all-extras --frozen
|
|
53
|
+
uv sync --all-extras --all-groups --frozen
|
|
54
|
+
|
|
55
|
+
- name: Install dev dependencies
|
|
56
|
+
run: bash .rhiza/scripts/install-dev-deps.sh
|
|
54
57
|
|
|
55
58
|
- name: "Make the book"
|
|
56
59
|
run: |
|
|
@@ -26,10 +26,15 @@ jobs:
|
|
|
26
26
|
steps:
|
|
27
27
|
- uses: actions/checkout@v6
|
|
28
28
|
|
|
29
|
+
- name: Install uv
|
|
30
|
+
uses: astral-sh/setup-uv@v7
|
|
31
|
+
with:
|
|
32
|
+
version: "0.9.21"
|
|
33
|
+
|
|
29
34
|
- id: versions
|
|
30
35
|
run: |
|
|
31
36
|
# Generate Python versions JSON from the script
|
|
32
|
-
JSON=$(
|
|
37
|
+
JSON=$(make -s version-matrix)
|
|
33
38
|
echo "list=$JSON" >> "$GITHUB_OUTPUT"
|
|
34
39
|
|
|
35
40
|
- name: Debug matrix
|
|
@@ -60,14 +65,10 @@ jobs:
|
|
|
60
65
|
run: |
|
|
61
66
|
export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}"
|
|
62
67
|
uv venv --python ${{ matrix.python-version }}
|
|
63
|
-
uv sync --all-extras --frozen
|
|
68
|
+
uv sync --all-extras --all-groups --frozen
|
|
64
69
|
|
|
65
|
-
- name: Install dependencies
|
|
66
|
-
run:
|
|
67
|
-
if [ -f "tests/requirements.txt" ]; then
|
|
68
|
-
uv pip install -r tests/requirements.txt
|
|
69
|
-
fi
|
|
70
|
-
uv pip install pytest
|
|
70
|
+
- name: Install dev dependencies
|
|
71
|
+
run: bash .rhiza/scripts/install-dev-deps.sh
|
|
71
72
|
|
|
72
73
|
- name: Run tests
|
|
73
74
|
run: uv run pytest tests
|
|
@@ -33,11 +33,8 @@ jobs:
|
|
|
33
33
|
- uses: actions/checkout@v6
|
|
34
34
|
|
|
35
35
|
- name: Run deptry
|
|
36
|
-
run:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
SOURCE_FOLDER="."
|
|
42
|
-
fi
|
|
43
|
-
uvx deptry "$SOURCE_FOLDER"
|
|
36
|
+
run: make deptry
|
|
37
|
+
# NOTE: make deptry is good style because it encapsulates the folders to check
|
|
38
|
+
# (e.g. src and book/marimo) and keeps CI in sync with local development.
|
|
39
|
+
# Since we use a 'uv' container, the Makefile is optimized to use the
|
|
40
|
+
# pre-installed 'uv' and 'uvx' from the system PATH.
|
|
@@ -118,7 +118,7 @@ jobs:
|
|
|
118
118
|
run: |
|
|
119
119
|
export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}"
|
|
120
120
|
# will just use .python-version?
|
|
121
|
-
uv sync --all-extras --frozen
|
|
121
|
+
uv sync --all-extras --all-groups --frozen
|
|
122
122
|
|
|
123
123
|
- name: Verify version matches tag
|
|
124
124
|
if: hashFiles('pyproject.toml') != ''
|
|
@@ -329,7 +329,7 @@ jobs:
|
|
|
329
329
|
run: |
|
|
330
330
|
export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}"
|
|
331
331
|
# will just use .python-version?
|
|
332
|
-
uv sync --all-extras --frozen
|
|
332
|
+
uv sync --all-extras --all-groups --frozen
|
|
333
333
|
|
|
334
334
|
- name: Set up Python
|
|
335
335
|
uses: actions/setup-python@v6
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[tool.bumpversion]
|
|
2
|
+
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(?:-(?P<release>[a-z]+)\\.(?P<pre_n>\\d+))?(?:\\+build\\.(?P<build_n>\\d+))?"
|
|
3
|
+
serialize = ["{major}.{minor}.{patch}-{release}.{pre_n}+build.{build_n}", "{major}.{minor}.{patch}+build.{build_n}", "{major}.{minor}.{patch}-{release}.{pre_n}", "{major}.{minor}.{patch}"]
|
|
4
|
+
search = "{current_version}"
|
|
5
|
+
replace = "{new_version}"
|
|
6
|
+
regex = false
|
|
7
|
+
ignore_missing_version = false
|
|
8
|
+
ignore_missing_files = false
|
|
9
|
+
tag = false
|
|
10
|
+
sign_tags = false
|
|
11
|
+
tag_name = "v{new_version}"
|
|
12
|
+
tag_message = "Bump version: {current_version} → {new_version}"
|
|
13
|
+
allow_dirty = false
|
|
14
|
+
commit = false
|
|
15
|
+
message = "Chore: bump version {current_version} → {new_version}"
|
|
16
|
+
commit_args = ""
|
|
17
|
+
|
|
18
|
+
[tool.bumpversion.parts.release]
|
|
19
|
+
optional_value = "prod"
|
|
20
|
+
values = [
|
|
21
|
+
"dev",
|
|
22
|
+
"alpha",
|
|
23
|
+
"beta",
|
|
24
|
+
"rc",
|
|
25
|
+
"prod"
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[tool.bumpversion.files]]
|
|
29
|
+
filename = "pyproject.toml"
|
|
30
|
+
search = 'version = "{current_version}"'
|
|
31
|
+
replace = 'version = "{new_version}"'
|
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Files under template control:
|
|
7
7
|
.editorconfig
|
|
8
|
-
.github/dependabot.yml
|
|
9
8
|
.github/workflows/rhiza_book.yml
|
|
10
9
|
.github/workflows/rhiza_ci.yml
|
|
11
10
|
.github/workflows/rhiza_codeql.yml
|
|
12
11
|
.github/workflows/rhiza_deptry.yml
|
|
13
|
-
.github/workflows/rhiza_devcontainer.yml
|
|
14
|
-
.github/workflows/rhiza_docker.yml
|
|
15
12
|
.github/workflows/rhiza_marimo.yml
|
|
16
13
|
.github/workflows/rhiza_pre-commit.yml
|
|
17
14
|
.github/workflows/rhiza_release.yml
|
|
@@ -21,16 +18,21 @@
|
|
|
21
18
|
.pre-commit-config.yaml
|
|
22
19
|
.rhiza/docs/CONFIG.md
|
|
23
20
|
.rhiza/docs/TOKEN_SETUP.md
|
|
21
|
+
.rhiza/requirements/README.md
|
|
22
|
+
.rhiza/requirements/docs.txt
|
|
23
|
+
.rhiza/requirements/marimo.txt
|
|
24
|
+
.rhiza/requirements/tests.txt
|
|
25
|
+
.rhiza/requirements/tools.txt
|
|
24
26
|
.rhiza/scripts/book.sh
|
|
25
27
|
.rhiza/scripts/bump.sh
|
|
26
28
|
.rhiza/scripts/customisations/build-extras.sh
|
|
27
29
|
.rhiza/scripts/customisations/post-release.sh
|
|
28
30
|
.rhiza/scripts/generate-coverage-badge.sh
|
|
31
|
+
.rhiza/scripts/install-dev-deps.sh
|
|
29
32
|
.rhiza/scripts/marimushka.sh
|
|
30
33
|
.rhiza/scripts/release.sh
|
|
31
34
|
.rhiza/scripts/update-readme-help.sh
|
|
32
35
|
.rhiza/utils/version_matrix.py
|
|
33
|
-
.rhiza/utils/version_max.py
|
|
34
36
|
CODE_OF_CONDUCT.md
|
|
35
37
|
CONTRIBUTING.md
|
|
36
38
|
Makefile
|
|
@@ -60,5 +62,6 @@ tests/test_rhiza/test_marimushka_script.py
|
|
|
60
62
|
tests/test_rhiza/test_notebooks.py
|
|
61
63
|
tests/test_rhiza/test_readme.py
|
|
62
64
|
tests/test_rhiza/test_release_script.py
|
|
65
|
+
tests/test_rhiza/test_requirements_folder.py
|
|
63
66
|
tests/test_rhiza/test_structure.py
|
|
64
67
|
tests/test_rhiza/test_updatereadme_script.py
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Requirements Folder
|
|
2
|
+
|
|
3
|
+
This folder contains the development dependencies for the Rhiza project, organized by purpose.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- **tests.txt** - Testing dependencies (pytest, pytest-cov, pytest-html)
|
|
8
|
+
- **marimo.txt** - Marimo notebook dependencies
|
|
9
|
+
- **docs.txt** - Documentation generation dependencies (pdoc)
|
|
10
|
+
- **tools.txt** - Development tools (pre-commit, python-dotenv)
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
These requirements files are automatically installed by the `make install` command.
|
|
15
|
+
|
|
16
|
+
To install specific requirement files manually:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv pip install -r .rhiza/requirements/tests.txt
|
|
20
|
+
uv pip install -r .rhiza/requirements/marimo.txt
|
|
21
|
+
uv pip install -r .rhiza/requirements/docs.txt
|
|
22
|
+
uv pip install -r .rhiza/requirements/tools.txt
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## CI/CD
|
|
26
|
+
|
|
27
|
+
GitHub Actions workflows automatically install these requirements as needed.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install dev dependencies from .rhiza/requirements/*.txt files
|
|
3
|
+
# This script is used by GitHub Actions workflows to install development dependencies
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
REQUIREMENTS_DIR=".rhiza/requirements"
|
|
8
|
+
|
|
9
|
+
if [ ! -d "$REQUIREMENTS_DIR" ]; then
|
|
10
|
+
echo "Warning: Requirements directory $REQUIREMENTS_DIR not found, skipping dev dependencies install" >&2
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
echo "Installing dev dependencies from $REQUIREMENTS_DIR"
|
|
15
|
+
|
|
16
|
+
shopt -s nullglob
|
|
17
|
+
for req_file in "$REQUIREMENTS_DIR"/*.txt; do
|
|
18
|
+
if [ -f "$req_file" ]; then
|
|
19
|
+
echo "Installing requirements from $req_file"
|
|
20
|
+
uv pip install -r "$req_file"
|
|
21
|
+
fi
|
|
22
|
+
done
|
|
23
|
+
shopt -u nullglob
|
|
24
|
+
|
|
25
|
+
echo "Dev dependencies installed successfully"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Emit the list of supported Python versions from pyproject.toml.
|
|
3
|
+
|
|
4
|
+
This helper is used in GitHub Actions to compute the test matrix.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import tomllib
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
PYPROJECT = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
13
|
+
CANDIDATES = ["3.11", "3.12", "3.13", "3.14"] # extend as needed
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_version(v: str) -> tuple[int, ...]:
|
|
17
|
+
"""Parse a version string into a tuple of integers.
|
|
18
|
+
|
|
19
|
+
This is intentionally simple and only supports numeric components.
|
|
20
|
+
If a component contains non-numeric suffixes (e.g. '3.11.0rc1'),
|
|
21
|
+
the leading numeric portion will be used (e.g. '0rc1' -> 0). If a
|
|
22
|
+
component has no leading digits at all, a ValueError is raised.
|
|
23
|
+
"""
|
|
24
|
+
parts: list[int] = []
|
|
25
|
+
for part in v.split("."):
|
|
26
|
+
match = re.match(r"\d+", part)
|
|
27
|
+
if not match:
|
|
28
|
+
msg = f"Invalid version component {part!r} in version {v!r}; expected a numeric prefix."
|
|
29
|
+
raise ValueError(msg)
|
|
30
|
+
parts.append(int(match.group(0)))
|
|
31
|
+
return tuple(parts)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _check_operator(version_tuple: tuple[int, ...], op: str, spec_v_tuple: tuple[int, ...]) -> bool:
|
|
35
|
+
"""Check if a version tuple satisfies an operator constraint."""
|
|
36
|
+
operators = {
|
|
37
|
+
">=": lambda v, s: v >= s,
|
|
38
|
+
"<=": lambda v, s: v <= s,
|
|
39
|
+
">": lambda v, s: v > s,
|
|
40
|
+
"<": lambda v, s: v < s,
|
|
41
|
+
"==": lambda v, s: v == s,
|
|
42
|
+
"!=": lambda v, s: v != s,
|
|
43
|
+
}
|
|
44
|
+
return operators[op](version_tuple, spec_v_tuple)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def satisfies(version: str, specifier: str) -> bool:
|
|
48
|
+
"""Check if a version satisfies a comma-separated list of specifiers.
|
|
49
|
+
|
|
50
|
+
This is a simplified version of packaging.specifiers.SpecifierSet.
|
|
51
|
+
Supported operators: >=, <=, >, <, ==, !=
|
|
52
|
+
"""
|
|
53
|
+
version_tuple = parse_version(version)
|
|
54
|
+
|
|
55
|
+
# Split by comma for multiple constraints
|
|
56
|
+
for spec in specifier.split(","):
|
|
57
|
+
spec = spec.strip()
|
|
58
|
+
# Match operator and version part
|
|
59
|
+
match = re.match(r"(>=|<=|>|<|==|!=)\s*([\d.]+)", spec)
|
|
60
|
+
if not match:
|
|
61
|
+
# If no operator, assume ==
|
|
62
|
+
if re.match(r"[\d.]+", spec):
|
|
63
|
+
if version_tuple != parse_version(spec):
|
|
64
|
+
return False
|
|
65
|
+
continue
|
|
66
|
+
raise ValueError(f"Invalid specifier: {spec}")
|
|
67
|
+
|
|
68
|
+
op, spec_v = match.groups()
|
|
69
|
+
spec_v_tuple = parse_version(spec_v)
|
|
70
|
+
|
|
71
|
+
if not _check_operator(version_tuple, op, spec_v_tuple):
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def supported_versions() -> list[str]:
|
|
78
|
+
"""Return all supported Python versions declared in pyproject.toml.
|
|
79
|
+
|
|
80
|
+
Reads project.requires-python, evaluates candidate versions against the
|
|
81
|
+
specifier, and returns the subset that satisfy the constraint, in ascending order.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
list[str]: The supported versions (e.g., ["3.11", "3.12"]).
|
|
85
|
+
"""
|
|
86
|
+
# Load pyproject.toml using the tomllib standard library (Python 3.11+)
|
|
87
|
+
with PYPROJECT.open("rb") as f:
|
|
88
|
+
data = tomllib.load(f)
|
|
89
|
+
|
|
90
|
+
# Extract the requires-python field from project metadata
|
|
91
|
+
# This specifies the Python version constraint (e.g., ">=3.11")
|
|
92
|
+
spec_str = data.get("project", {}).get("requires-python")
|
|
93
|
+
if not spec_str:
|
|
94
|
+
msg = "pyproject.toml: missing 'project.requires-python'"
|
|
95
|
+
raise KeyError(msg)
|
|
96
|
+
|
|
97
|
+
# Filter candidate versions to find which ones satisfy the constraint
|
|
98
|
+
versions: list[str] = []
|
|
99
|
+
for v in CANDIDATES:
|
|
100
|
+
if satisfies(v, spec_str):
|
|
101
|
+
versions.append(v)
|
|
102
|
+
|
|
103
|
+
if not versions:
|
|
104
|
+
msg = f"pyproject.toml: no supported Python versions match '{spec_str}'"
|
|
105
|
+
raise ValueError(msg)
|
|
106
|
+
|
|
107
|
+
return versions
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
# Check if pyproject.toml exists in the expected location
|
|
112
|
+
# If it exists, use it to determine supported versions
|
|
113
|
+
# Otherwise, fall back to returning all candidates (for edge cases)
|
|
114
|
+
if PYPROJECT.exists():
|
|
115
|
+
print(json.dumps(supported_versions()))
|
|
116
|
+
else:
|
|
117
|
+
print(json.dumps(CANDIDATES))
|
|
@@ -34,8 +34,8 @@ RESET := \033[0m
|
|
|
34
34
|
update-readme
|
|
35
35
|
|
|
36
36
|
UV_INSTALL_DIR ?= ./bin
|
|
37
|
-
UV_BIN
|
|
38
|
-
UVX_BIN
|
|
37
|
+
UV_BIN ?= $(shell command -v uv 2>/dev/null || echo ${UV_INSTALL_DIR}/uv)
|
|
38
|
+
UVX_BIN ?= $(shell command -v uvx 2>/dev/null || echo ${UV_INSTALL_DIR}/uvx)
|
|
39
39
|
VENV ?= .venv
|
|
40
40
|
|
|
41
41
|
export UV_NO_MODIFY_PATH := 1
|
|
@@ -54,11 +54,13 @@ install-uv: ## ensure uv/uvx is installed
|
|
|
54
54
|
# Ensure the ${UV_INSTALL_DIR} folder exists
|
|
55
55
|
@mkdir -p ${UV_INSTALL_DIR}
|
|
56
56
|
|
|
57
|
-
# Install uv/uvx only if they are not already present
|
|
58
|
-
@if
|
|
57
|
+
# Install uv/uvx only if they are not already present in PATH or in the install dir
|
|
58
|
+
@if command -v uv >/dev/null 2>&1 && command -v uvx >/dev/null 2>&1; then \
|
|
59
|
+
:; \
|
|
60
|
+
elif [ -x "${UV_INSTALL_DIR}/uv" ] && [ -x "${UV_INSTALL_DIR}/uvx" ]; then \
|
|
59
61
|
printf "${BLUE}[INFO] uv and uvx already installed in ${UV_INSTALL_DIR}, skipping.${RESET}\n"; \
|
|
60
62
|
else \
|
|
61
|
-
printf "${BLUE}[INFO] Installing uv and uvx...${RESET}\n"; \
|
|
63
|
+
printf "${BLUE}[INFO] Installing uv and uvx into ${UV_INSTALL_DIR}...${RESET}\n"; \
|
|
62
64
|
if ! curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR="${UV_INSTALL_DIR}" sh >/dev/null 2>&1; then \
|
|
63
65
|
printf "${RED}[ERROR] Failed to install uv${RESET}\n"; \
|
|
64
66
|
exit 1; \
|
|
@@ -84,16 +86,11 @@ install: install-uv install-extras ## install
|
|
|
84
86
|
printf "${BLUE}[INFO] Using existing virtual environment at ${VENV}, skipping creation${RESET}\n"; \
|
|
85
87
|
fi
|
|
86
88
|
|
|
87
|
-
# Check if there is requirements.txt file in the tests folder
|
|
88
|
-
@if [ -f "tests/requirements.txt" ]; then \
|
|
89
|
-
${UV_BIN} pip install -r tests/requirements.txt || { printf "${RED}[ERROR] Failed to install test requirements${RESET}\n"; exit 1; }; \
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
89
|
# Install the dependencies from pyproject.toml (if it exists)
|
|
93
90
|
@if [ -f "pyproject.toml" ]; then \
|
|
94
91
|
if [ -f "uv.lock" ]; then \
|
|
95
92
|
printf "${BLUE}[INFO] Installing dependencies from lock file${RESET}\n"; \
|
|
96
|
-
${UV_BIN} sync --all-extras --frozen || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }; \
|
|
93
|
+
${UV_BIN} sync --all-extras --all-groups --frozen || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }; \
|
|
97
94
|
else \
|
|
98
95
|
printf "${YELLOW}[WARN] uv.lock not found. Generating lock file and installing dependencies...${RESET}\n"; \
|
|
99
96
|
${UV_BIN} sync --all-extras || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }; \
|
|
@@ -102,6 +99,22 @@ install: install-uv install-extras ## install
|
|
|
102
99
|
printf "${YELLOW}[WARN] No pyproject.toml found, skipping install${RESET}\n"; \
|
|
103
100
|
fi
|
|
104
101
|
|
|
102
|
+
# Install dev dependencies from .rhiza/requirements/*.txt files
|
|
103
|
+
@if [ -d ".rhiza/requirements" ] && ls .rhiza/requirements/*.txt >/dev/null 2>&1; then \
|
|
104
|
+
for req_file in .rhiza/requirements/*.txt; do \
|
|
105
|
+
if [ -f "$$req_file" ]; then \
|
|
106
|
+
printf "${BLUE}[INFO] Installing requirements from $$req_file${RESET}\n"; \
|
|
107
|
+
${UV_BIN} pip install -r "$$req_file" || { printf "${RED}[ERROR] Failed to install requirements from $$req_file${RESET}\n"; exit 1; }; \
|
|
108
|
+
fi; \
|
|
109
|
+
done; \
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Check if there is requirements.txt file in the tests folder (legacy support)
|
|
113
|
+
@if [ -f "tests/requirements.txt" ]; then \
|
|
114
|
+
printf "${BLUE}[INFO] Installing requirements from tests/requirements.txt${RESET}\n"; \
|
|
115
|
+
${UV_BIN} pip install -r tests/requirements.txt || { printf "${RED}[ERROR] Failed to install test requirements${RESET}\n"; exit 1; }; \
|
|
116
|
+
fi
|
|
117
|
+
|
|
105
118
|
sync: ## sync with template repository as defined in .github/template.yml
|
|
106
119
|
@if git remote get-url origin 2>/dev/null | grep -iq 'jebel-quant/rhiza'; then \
|
|
107
120
|
printf "${BLUE}[INFO] Skipping sync in rhiza repository (no template.yml by design)${RESET}\n"; \
|
|
@@ -150,10 +163,16 @@ marimo: install ## fire up Marimo server
|
|
|
150
163
|
|
|
151
164
|
##@ Quality and Formatting
|
|
152
165
|
deptry: install-uv ## Run deptry
|
|
153
|
-
@if [ -d
|
|
154
|
-
$(UVX_BIN) deptry
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
@if [ -d ${SOURCE_FOLDER} ]; then \
|
|
167
|
+
$(UVX_BIN) deptry ${SOURCE_FOLDER}; \
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
@if [ -d ${MARIMO_FOLDER} ]; then \
|
|
171
|
+
if [ -d ${SOURCE_FOLDER} ]; then \
|
|
172
|
+
$(UVX_BIN) deptry ${MARIMO_FOLDER} ${SOURCE_FOLDER} --ignore DEP004; \
|
|
173
|
+
else \
|
|
174
|
+
$(UVX_BIN) deptry ${MARIMO_FOLDER} --ignore DEP004; \
|
|
175
|
+
fi \
|
|
157
176
|
fi
|
|
158
177
|
|
|
159
178
|
fmt: install-uv ## check the pre-commit hooks and the linting
|
|
@@ -197,6 +216,9 @@ customisations: ## list available customisation scripts
|
|
|
197
216
|
update-readme: ## update README.md with current Makefile help output
|
|
198
217
|
@/bin/sh "${SCRIPTS_FOLDER}/update-readme-help.sh"
|
|
199
218
|
|
|
219
|
+
version-matrix: install-uv ## Emit the list of supported Python versions from pyproject.toml
|
|
220
|
+
@${UV_BIN} run .rhiza/utils/version_matrix.py
|
|
221
|
+
|
|
200
222
|
# debugger tools
|
|
201
223
|
custom-%: ## run a custom script (usage: make custom-scriptname)
|
|
202
224
|
@SCRIPT="${CUSTOM_SCRIPTS_FOLDER}/$*.sh"; \
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rhiza
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.5
|
|
4
4
|
Summary: Reusable configuration templates for modern Python projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/jebel-quant/rhiza-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/jebel-quant/rhiza-cli
|
|
@@ -23,16 +23,6 @@ Requires-Dist: jinja2>=3.1.0
|
|
|
23
23
|
Requires-Dist: loguru>=0.7.3
|
|
24
24
|
Requires-Dist: pyyaml==6.0.3
|
|
25
25
|
Requires-Dist: typer>=0.20.0
|
|
26
|
-
Provides-Extra: dev
|
|
27
|
-
Requires-Dist: marimo==0.18.4; extra == 'dev'
|
|
28
|
-
Requires-Dist: pdoc>=16.0.0; extra == 'dev'
|
|
29
|
-
Requires-Dist: pre-commit==4.5.1; extra == 'dev'
|
|
30
|
-
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
31
|
-
Requires-Dist: pytest-html>=4.1.1; extra == 'dev'
|
|
32
|
-
Requires-Dist: pytest==9.0.2; extra == 'dev'
|
|
33
|
-
Requires-Dist: python-dotenv==1.2.1; extra == 'dev'
|
|
34
|
-
Provides-Extra: tools
|
|
35
|
-
Requires-Dist: rhiza-tools; extra == 'tools'
|
|
36
26
|
Description-Content-Type: text/markdown
|
|
37
27
|
|
|
38
28
|
<div align="center">
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rhiza"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.5"
|
|
8
8
|
description = "Reusable configuration templates for modern Python projects"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -38,27 +38,20 @@ Repository = "https://github.com/jebel-quant/rhiza-cli"
|
|
|
38
38
|
Issues = "https://github.com/jebel-quant/rhiza/issues-cli"
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
[
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
tools = ["rhiza-tools>=0.1.2"]
|
|
43
|
+
|
|
42
44
|
dev = [
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"marimo==0.18.4",
|
|
48
|
-
"pdoc>=16.0.0",
|
|
49
|
-
"python-dotenv==1.2.1"
|
|
45
|
+
"numpy>=2.4",
|
|
46
|
+
"marimo>=0.18.0",
|
|
47
|
+
"pandas>=2.3",
|
|
48
|
+
"plotly>=6.5",
|
|
50
49
|
]
|
|
51
|
-
tools = ["rhiza-tools"]
|
|
52
|
-
|
|
53
50
|
|
|
54
51
|
[tool.hatch.build.targets.wheel]
|
|
55
52
|
# Use src-layout: package code is under src/config
|
|
56
53
|
packages = ["src/rhiza"]
|
|
57
54
|
|
|
58
|
-
[tool.deptry]
|
|
59
|
-
# see https://deptry.com/usage/#pep-621-dev-dependency-groups
|
|
60
|
-
pep621_dev_dependency_groups = ["dev"]
|
|
61
|
-
|
|
62
55
|
[tool.deptry.package_module_name_map]
|
|
63
56
|
PyYAML = "yaml"
|
|
64
57
|
|
|
@@ -9,11 +9,11 @@ TESTS_FOLDER := tests
|
|
|
9
9
|
|
|
10
10
|
##@ Development and Testing
|
|
11
11
|
test: install ## run all tests
|
|
12
|
-
@if [ -d ${
|
|
12
|
+
@if [ -d ${TESTS_FOLDER} ]; then \
|
|
13
13
|
mkdir -p _tests/html-coverage _tests/html-report; \
|
|
14
14
|
${VENV}/bin/python -m pytest ${TESTS_FOLDER} --ignore=${TESTS_FOLDER}/benchmarks --cov=${SOURCE_FOLDER} --cov-report=term --cov-report=html:_tests/html-coverage --cov-report=json:_tests/coverage.json --html=_tests/html-report/report.html; \
|
|
15
15
|
else \
|
|
16
|
-
printf "${YELLOW}[WARN]
|
|
16
|
+
printf "${YELLOW}[WARN] Test folder ${TESTS_FOLDER} not found, skipping tests${RESET}\n"; \
|
|
17
17
|
fi
|
|
18
18
|
|
|
19
19
|
benchmark: install ## run performance benchmarks
|
|
@@ -34,12 +34,6 @@ def strip_ansi(text: str) -> str:
|
|
|
34
34
|
return ansi_escape.sub("", text)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
@pytest.fixture
|
|
38
|
-
def expected_uv_install_dir() -> str:
|
|
39
|
-
"""Get the expected UV_INSTALL_DIR from environment or default to ./bin."""
|
|
40
|
-
return os.environ.get("UV_INSTALL_DIR", "./bin")
|
|
41
|
-
|
|
42
|
-
|
|
43
37
|
@pytest.fixture(autouse=True)
|
|
44
38
|
def setup_tmp_makefile(logger, root, tmp_path: Path):
|
|
45
39
|
"""Copy the Makefile and split Makefiles into a temp directory and chdir there.
|
|
@@ -136,23 +130,14 @@ class TestMakefile:
|
|
|
136
130
|
assert "Targets:" in out
|
|
137
131
|
assert "Bootstrap" in out or "Meta" in out # section headers
|
|
138
132
|
|
|
139
|
-
def test_fmt_target_dry_run(self, logger
|
|
133
|
+
def test_fmt_target_dry_run(self, logger):
|
|
140
134
|
"""Fmt target should invoke pre-commit via uvx in dry-run output."""
|
|
141
135
|
proc = run_make(logger, ["fmt"])
|
|
142
136
|
out = proc.stdout
|
|
143
137
|
# Check for uvx command with the configured path
|
|
144
|
-
|
|
145
|
-
assert f"{expected_uvx} pre-commit run --all-files" in out
|
|
146
|
-
|
|
147
|
-
def test_deptry_target_dry_run(self, logger, expected_uv_install_dir):
|
|
148
|
-
"""Deptry target should invoke deptry via uvx in dry-run output."""
|
|
149
|
-
proc = run_make(logger, ["deptry"])
|
|
150
|
-
out = proc.stdout
|
|
151
|
-
# Check for uvx command with the configured path
|
|
152
|
-
expected_uvx = f"{expected_uv_install_dir}/uvx"
|
|
153
|
-
assert f"{expected_uvx} deptry ." in out
|
|
138
|
+
assert "uvx pre-commit run --all-files" in out
|
|
154
139
|
|
|
155
|
-
def test_test_target_dry_run(self, logger
|
|
140
|
+
def test_test_target_dry_run(self, logger):
|
|
156
141
|
"""Test target should invoke pytest via uv with coverage and HTML outputs in dry-run output."""
|
|
157
142
|
proc = run_make(logger, ["test"])
|
|
158
143
|
out = proc.stdout
|
|
@@ -162,14 +147,13 @@ class TestMakefile:
|
|
|
162
147
|
# expected_uv = f"{expected_uv_install_dir}/uv"
|
|
163
148
|
# assert f"{expected_uv} run pytest" in out
|
|
164
149
|
|
|
165
|
-
def test_book_target_dry_run(self, logger
|
|
150
|
+
def test_book_target_dry_run(self, logger):
|
|
166
151
|
"""Book target should run inline commands to assemble the book without go-task."""
|
|
167
152
|
proc = run_make(logger, ["book"])
|
|
168
153
|
out = proc.stdout
|
|
169
154
|
# Expect marimushka export to install marimo and minibook to be invoked
|
|
170
155
|
# Check for uvx command with the configured path
|
|
171
|
-
|
|
172
|
-
assert f"{expected_uvx} minibook" in out
|
|
156
|
+
assert "uvx minibook" in out
|
|
173
157
|
|
|
174
158
|
@pytest.mark.parametrize("target", ["book", "docs", "marimushka"])
|
|
175
159
|
def test_book_related_targets_fallback_without_book_folder(self, logger, tmp_path, target):
|
|
@@ -193,29 +177,6 @@ class TestMakefile:
|
|
|
193
177
|
out = strip_ansi(proc.stdout)
|
|
194
178
|
assert "Value of UV_NO_MODIFY_PATH:\n1" in out
|
|
195
179
|
|
|
196
|
-
def test_uv_install_dir_is_bin(self, logger, expected_uv_install_dir):
|
|
197
|
-
"""`UV_INSTALL_DIR` can be configured via environment variable or defaults to ./bin."""
|
|
198
|
-
proc = run_make(logger, ["print-UV_INSTALL_DIR"], dry_run=False)
|
|
199
|
-
out = strip_ansi(proc.stdout)
|
|
200
|
-
# Check if UV_INSTALL_DIR is set in environment, otherwise expect default ./bin
|
|
201
|
-
assert f"Value of UV_INSTALL_DIR:\n{expected_uv_install_dir}" in out
|
|
202
|
-
|
|
203
|
-
def test_uv_bin_is_bin_uv(self, logger, expected_uv_install_dir):
|
|
204
|
-
"""`UV_BIN` is derived from UV_INSTALL_DIR environment variable or defaults to ./bin/uv."""
|
|
205
|
-
proc = run_make(logger, ["print-UV_BIN"], dry_run=False)
|
|
206
|
-
out = strip_ansi(proc.stdout)
|
|
207
|
-
# Check if UV_INSTALL_DIR is set in environment, otherwise expect default ./bin
|
|
208
|
-
expected_bin = f"{expected_uv_install_dir}/uv"
|
|
209
|
-
assert f"Value of UV_BIN:\n{expected_bin}" in out
|
|
210
|
-
|
|
211
|
-
def test_uvx_bin_is_bin_uvx(self, logger, expected_uv_install_dir):
|
|
212
|
-
"""`UVX_BIN` is derived from UV_INSTALL_DIR environment variable or defaults to ./bin/uvx."""
|
|
213
|
-
proc = run_make(logger, ["print-UVX_BIN"], dry_run=False)
|
|
214
|
-
out = strip_ansi(proc.stdout)
|
|
215
|
-
# Check if UV_INSTALL_DIR is set in environment, otherwise expect default ./bin
|
|
216
|
-
expected_bin = f"{expected_uv_install_dir}/uvx"
|
|
217
|
-
assert f"Value of UVX_BIN:\n{expected_bin}" in out
|
|
218
|
-
|
|
219
180
|
def test_script_folder_is_github_scripts(self, logger):
|
|
220
181
|
"""`SCRIPTS_FOLDER` should point to `.rhiza/scripts`."""
|
|
221
182
|
proc = run_make(logger, ["print-SCRIPTS_FOLDER"], dry_run=False)
|
|
@@ -8,12 +8,11 @@ Tests call the script from a temporary clone and use a small mock `uv`
|
|
|
8
8
|
to avoid external dependencies.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import shutil
|
|
12
11
|
import subprocess
|
|
13
12
|
|
|
14
13
|
# Get shell path once at module level
|
|
15
|
-
SHELL =
|
|
16
|
-
GIT =
|
|
14
|
+
SHELL = "/bin/sh"
|
|
15
|
+
GIT = "/usr/bin/git"
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
def test_release_creates_tag(git_repo):
|