python-package-folder 3.1.3__tar.gz → 4.1.0__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.
- python_package_folder-4.1.0/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +123 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.github/workflows/ci.yml +4 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.github/workflows/publish.yml +39 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/PKG-INFO +80 -9
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/README.md +79 -8
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/coverage.svg +2 -2
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/pyproject.toml +6 -16
- python_package_folder-4.1.0/python_package_folder/scripts/get-next-version.cjs +553 -0
- python_package_folder-4.1.0/scripts/get-next-version.cjs +383 -0
- python_package_folder-4.1.0/src/python_package_folder/_hatch_build.py +39 -0
- python_package_folder-4.1.0/src/python_package_folder/python_package_folder.py +450 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_linting.py +15 -4
- python_package_folder-3.1.3/src/python_package_folder/python_package_folder.py +0 -240
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.copier-answers.yml +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.gitignore +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/.vscode/settings.json +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/LICENSE +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/Makefile +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/development.md +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/installation.md +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/publishing.md +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/manager.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/conftest.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_publisher.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_subfolder_build.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_utils.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/tests/tests.py +0 -0
- {python_package_folder-3.1.3 → python_package_folder-4.1.0}/uv.lock +0 -0
python_package_folder-4.1.0/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Optional --version with semantic-release (both workflows)
|
|
2
|
+
|
|
3
|
+
## Scope: two workflows
|
|
4
|
+
|
|
5
|
+
1. **Workflow 1 – Publishing a subfolder** (monorepo): Build and publish a subfolder of `src/` as its own package. Version today is required via `--version`.
|
|
6
|
+
2. **Workflow 2 – Building packages with shared code**: Build the main package (e.g. `src/my_package/`) that imports from `shared/`. Version today comes from `pyproject.toml` (dynamic or static) or `--version` when publishing.
|
|
7
|
+
|
|
8
|
+
Both workflows should support **optional `--version**`: when omitted, resolve the next version via semantic-release and use it for the build/publish.
|
|
9
|
+
|
|
10
|
+
## Current behavior
|
|
11
|
+
|
|
12
|
+
- **CLI** ([`python_package_folder.py`](src/python_package_folder/python_package_folder.py)): For subfolder builds, `--version` is required; the tool exits with an error if it is missing (lines 158–164). For main-package builds, `--version` is optional; version comes from `pyproject.toml` or user.
|
|
13
|
+
- **Manager** ([`manager.py`](src/python_package_folder/manager.py)): `prepare_build` defaults version to `"0.0.0"` with a warning when `version` is `None` for subfolders (lines 232–239). `build_and_publish` raises `ValueError` if `version` is missing for a subfolder build (lines 1254–1258). For main package, `version` can be None (no error); then publish uses whatever the build produced (dynamic versioning or static from pyproject).
|
|
14
|
+
- **Publisher** ([`publisher.py`](src/python_package_folder/publisher.py)): Filters dist files by `package_name` and `version`; both are required for reliable filtering.
|
|
15
|
+
|
|
16
|
+
## Target behavior
|
|
17
|
+
|
|
18
|
+
- `**--version` optional for both workflows**: If `--version` is not provided and a version is needed (subfolder build, or main-package publish), compute the next version using semantic-release, then proceed with that version. If provided, keep current behavior (explicit version).
|
|
19
|
+
- **Workflow 1 (subfolder)**: Per-package tags `{package-name}-v{version}` and commits filtered to the subfolder path.
|
|
20
|
+
- **Workflow 2 (main package)**: Repo-level tags (e.g. `v{version}`), no path filter; run semantic-release from project root.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart LR
|
|
26
|
+
subgraph Workflows
|
|
27
|
+
W1[Workflow 1: Subfolder build]
|
|
28
|
+
W2[Workflow 2: Main package with shared code]
|
|
29
|
+
end
|
|
30
|
+
subgraph CLI
|
|
31
|
+
A[Build or publish without --version]
|
|
32
|
+
B[Resolve version via semantic-release]
|
|
33
|
+
C[Build and publish with resolved version]
|
|
34
|
+
end
|
|
35
|
+
W1 --> A
|
|
36
|
+
W2 --> A
|
|
37
|
+
A --> B --> C
|
|
38
|
+
B --> Node[Node: get-next-version script]
|
|
39
|
+
Node --> SR[semantic-release dry-run]
|
|
40
|
+
SR --> NextVer[Next version]
|
|
41
|
+
NextVer --> C
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **Version resolution**: When `--version` is missing and needed (subfolder build, or main-package publish), call a Node script that runs semantic-release in dry-run and prints the next version to stdout.
|
|
45
|
+
- **Workflow 1**: Script runs with subfolder path and package name → per-package tag format and path-filtered commits (semantic-release-commit-filter).
|
|
46
|
+
- **Workflow 2**: Script runs from project root, no path filter → default tag format `v{version}`; package name from `pyproject.toml` for Publisher filtering only.
|
|
47
|
+
- **Fallback**: If Node/semantic-release is unavailable or semantic-release decides there is no release, fail with a clear message and suggest installing semantic-release (and commit-filter for subfolders) or passing `--version` explicitly.
|
|
48
|
+
|
|
49
|
+
## Implementation options for “get next version”
|
|
50
|
+
|
|
51
|
+
- **Option A (recommended): Small Node script using semantic-release API**
|
|
52
|
+
|
|
53
|
+
Add a script (e.g. `scripts/get-next-version.cjs` or under `.release/`) that:
|
|
54
|
+
|
|
55
|
+
- Takes args: project root, subfolder path (relative or absolute), package name.
|
|
56
|
+
- Ensures a minimal `package.json` exists in the subfolder (or in a temp location with correct `name`) so that semantic-release-commit-filter can use `package.name` for `tagFormat` and filter commits by cwd.
|
|
57
|
+
- Requires semantic-release and semantic-release-commit-filter, runs semantic-release programmatically with `dryRun: true`, and prints `nextRelease.version` (or “none”) to stdout.
|
|
58
|
+
|
|
59
|
+
This avoids parsing human-oriented dry-run output and gives a single, stable contract.
|
|
60
|
+
|
|
61
|
+
- **Option B: Parse `npx semantic-release --dry-run` output**
|
|
62
|
+
|
|
63
|
+
Run the CLI in dry-run and parse stdout. Possible but brittle (format can change, localization, etc.). Not recommended.
|
|
64
|
+
|
|
65
|
+
## Key implementation details
|
|
66
|
+
|
|
67
|
+
1. **Where to run semantic-release**
|
|
68
|
+
|
|
69
|
+
Run from the **subfolder** directory so that semantic-release-commit-filter’s “current directory” is the subfolder and commits are filtered to that path. Tag format will be `{package.name}-v${version}` from the `package.json` in that directory.
|
|
70
|
+
|
|
71
|
+
2. **Temporary `package.json` in subfolder**
|
|
72
|
+
|
|
73
|
+
Python subfolders usually have no `package.json`. Create a temporary one for the version resolution only: `{"name": "<package_name>"}` (same name as used for the Python package). Run semantic-release from the subfolder, then remove the temp file (or overwrite only if we created it). Document that the script may create/remove `package.json` in the subfolder so users are not surprised.
|
|
74
|
+
|
|
75
|
+
3. **Dependencies**
|
|
76
|
+
|
|
77
|
+
- No new Python dependencies.
|
|
78
|
+
- Document that **Node.js** and **npm** (or **npx**) must be available when using auto-versioning.
|
|
79
|
+
- Document (and optionally script) install of semantic-release and semantic-release-commit-filter, e.g. `npm install -g semantic-release semantic-release-commit-filter` or per-repo `package.json` with these as devDependencies.
|
|
80
|
+
|
|
81
|
+
4. **CLI flow**
|
|
82
|
+
|
|
83
|
+
- If subfolder build and `args.version` is None:
|
|
84
|
+
- Call the version resolver (subprocess: `node scripts/get-next-version.cjs <project_root> <subfolder_path> <package_name>`).
|
|
85
|
+
- If resolver returns a version string: use it for the rest of the flow.
|
|
86
|
+
- If resolver returns “none” or fails (no release / semantic-release not found / Node error): exit with a clear error suggesting to pass `--version` or to install and configure semantic-release.
|
|
87
|
+
- Pass the resolved or explicit version into `build_and_publish` / `prepare_build` as today.
|
|
88
|
+
|
|
89
|
+
5. **Manager / Publisher**
|
|
90
|
+
|
|
91
|
+
No change to the contract: they still receive a concrete `version` (either from CLI or from the resolver). Only the CLI and the new resolution step change.
|
|
92
|
+
|
|
93
|
+
6. **Convention**
|
|
94
|
+
|
|
95
|
+
Rely on default Angular/conventional commit rules (e.g. `fix:` → patch, `feat:` → minor, `BREAKING CHANGE:` → major). Document that conventional commits are required for auto-versioning; no change to commit format inside this repo unless you add a config file for semantic-release.
|
|
96
|
+
|
|
97
|
+
## Files to add or touch
|
|
98
|
+
|
|
99
|
+
| Item | Action |
|
|
100
|
+
|
|
101
|
+
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
|
|
103
|
+
| New script | Add `scripts/get-next-version.cjs` (or similar) that runs semantic-release in dry-run with commit-filter and prints next version. |
|
|
104
|
+
|
|
105
|
+
| CLI | In [`python_package_folder.py`](src/python_package_folder/python_package_folder.py): when `is_subfolder and not args.version`, call the resolver; on success set `args.version` (or a local variable) to the resolved version; on failure exit with error. Remove the “version required” error for this case. |
|
|
106
|
+
|
|
107
|
+
| Manager | In [`manager.py`](src/python_package_folder/manager.py): keep the `ValueError` when `version` is None for subfolder in `build_and_publish` (CLI will always pass a version after resolution). Optionally keep or adjust the “default 0.0.0” in `prepare_build` for programmatic callers who still omit version. |
|
|
108
|
+
|
|
109
|
+
| Docs | Update README (and any publishing doc) to describe: `--version` optional for subfolders when semantic-release is used, per-package tags, conventional commits, and Node/npm + semantic-release (and commit-filter) setup. |
|
|
110
|
+
|
|
111
|
+
| Tests | Add tests for: CLI with subfolder and no `--version` (mock or skip if Node/semantic-release missing), and for the resolver helper (or script) when given a fixture repo with tags and conventional commits. |
|
|
112
|
+
|
|
113
|
+
## Open decisions
|
|
114
|
+
|
|
115
|
+
- **Script location**: Ship `get-next-version.cjs` inside this repo under `scripts/` (or `.release/`) so that `python-package-folder` can invoke it without requiring the user to add it. The script will `require('semantic-release')` and `require('semantic-release-commit-filter')`; users must have these installed (globally or in a local `package.json` at project root or subfolder).
|
|
116
|
+
- **First release / no tag**: If there is no tag for this package yet, semantic-release will use an initial version (e.g. 1.0.0). Confirm desired behavior (e.g. configurable first version or always 1.0.0).
|
|
117
|
+
- **No release (no relevant commits)**: If semantic-release determines there is no release, the script should output something like “none” and the CLI should exit with a clear message rather than defaulting to 0.0.0.
|
|
118
|
+
|
|
119
|
+
## Summary
|
|
120
|
+
|
|
121
|
+
- Make `--version` optional for subfolder builds by resolving the next version via Node.js semantic-release with per-package tags and path-filtered commits.
|
|
122
|
+
- Add a small Node script that runs semantic-release in dry-run and prints the next version; wire it from the CLI when `--version` is omitted.
|
|
123
|
+
- Document Node/npm and semantic-release (and semantic-release-commit-filter) as requirements for this mode, and keep explicit `--version` as the fallback when auto-versioning is not available or not desired.
|
|
@@ -4,6 +4,11 @@ on:
|
|
|
4
4
|
release:
|
|
5
5
|
types: [published]
|
|
6
6
|
workflow_dispatch: # Enable manual trigger.
|
|
7
|
+
inputs:
|
|
8
|
+
version:
|
|
9
|
+
description: 'Package version to publish (e.g., 4.0.1). Required for manual triggers.'
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
7
12
|
|
|
8
13
|
jobs:
|
|
9
14
|
build-and-publish:
|
|
@@ -58,6 +63,40 @@ jobs:
|
|
|
58
63
|
rm -rf dist/
|
|
59
64
|
echo "✓ Cleaned dist/ directory"
|
|
60
65
|
|
|
66
|
+
- name: Extract version from release tag or input
|
|
67
|
+
id: version
|
|
68
|
+
run: |
|
|
69
|
+
if [ "${{ github.event_name }}" = "release" ]; then
|
|
70
|
+
# Extract from release tag
|
|
71
|
+
VERSION="${{ github.event.release.tag_name }}"
|
|
72
|
+
# Strip 'v' prefix if present
|
|
73
|
+
VERSION="${VERSION#v}"
|
|
74
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
75
|
+
echo "Extracted version from release tag: $VERSION"
|
|
76
|
+
else
|
|
77
|
+
# For workflow_dispatch, use the provided input
|
|
78
|
+
VERSION="${{ github.event.inputs.version }}"
|
|
79
|
+
if [ -z "$VERSION" ]; then
|
|
80
|
+
echo "Error: Version is required for manual workflow_dispatch triggers"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
# Strip 'v' prefix if present (user might include it)
|
|
84
|
+
VERSION="${VERSION#v}"
|
|
85
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
86
|
+
echo "Using version from workflow input: $VERSION"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
- name: Set version for release
|
|
90
|
+
if: steps.version.outputs.version != ''
|
|
91
|
+
run: |
|
|
92
|
+
python -c "
|
|
93
|
+
from pathlib import Path
|
|
94
|
+
from python_package_folder.version import VersionManager
|
|
95
|
+
version_manager = VersionManager(project_root=Path('.'))
|
|
96
|
+
version_manager.set_version('${{ steps.version.outputs.version }}')
|
|
97
|
+
print(f'✓ Set version to ${{ steps.version.outputs.version }}')
|
|
98
|
+
"
|
|
99
|
+
|
|
61
100
|
- name: Build package
|
|
62
101
|
run: uv build
|
|
63
102
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
|
|
5
5
|
Project-URL: Repository, https://github.com/alelom/python-package-folder
|
|
6
6
|
Author-email: Alessio Lombardi <work@alelom.com>
|
|
@@ -75,6 +75,9 @@ cd src/api_package
|
|
|
75
75
|
# Build and publish to TestPyPI with version 1.2.0
|
|
76
76
|
python-package-folder --publish testpypi --version 1.2.0
|
|
77
77
|
|
|
78
|
+
# Or publish to PyPI with automatic version resolution via semantic-release
|
|
79
|
+
python-package-folder --publish pypi
|
|
80
|
+
|
|
78
81
|
# Or publish to PyPI with a custom package name
|
|
79
82
|
python-package-folder --publish pypi --version 1.2.0 --package-name "my-api-package"
|
|
80
83
|
|
|
@@ -169,6 +172,13 @@ uv add twine
|
|
|
169
172
|
|
|
170
173
|
**For secure credential storage**: `keyring` is optional but recommended (install with `pip install keyring`)
|
|
171
174
|
|
|
175
|
+
**For automatic version resolution**: When using `--version` optional mode (automatic version resolution via semantic-release), you'll need:
|
|
176
|
+
- Node.js and npm (or npx)
|
|
177
|
+
- semantic-release: `npm install -g semantic-release`
|
|
178
|
+
- For subfolder builds: semantic-release-commit-filter: `npm install -g semantic-release-commit-filter`
|
|
179
|
+
|
|
180
|
+
Alternatively, install these as devDependencies in your project's `package.json`.
|
|
181
|
+
|
|
172
182
|
|
|
173
183
|
## Quick Start
|
|
174
184
|
|
|
@@ -182,9 +192,13 @@ Useful for monorepos containing many subfolders that may need publishing as stan
|
|
|
182
192
|
# First cd to the specific subfolder
|
|
183
193
|
cd src/subfolder_to_build_and_publish
|
|
184
194
|
|
|
185
|
-
# Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
|
|
195
|
+
# Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
|
|
196
|
+
# Version can be provided explicitly or resolved automatically via semantic-release
|
|
186
197
|
python-package-folder --publish testpypi --version 0.0.2
|
|
187
198
|
|
|
199
|
+
# Or let semantic-release determine the next version automatically (requires semantic-release setup)
|
|
200
|
+
python-package-folder --publish testpypi
|
|
201
|
+
|
|
188
202
|
# Only analyse (no building)
|
|
189
203
|
cd src/subfolder_to_build_and_publish
|
|
190
204
|
python-package-folder --analyze-only
|
|
@@ -457,33 +471,89 @@ The `--version` option:
|
|
|
457
471
|
**Version Format**: Versions must follow PEP 440 (e.g., `1.2.3`, `1.2.3a1`, `1.2.3.post1`, `1.2.3.dev1`)
|
|
458
472
|
|
|
459
473
|
|
|
474
|
+
### Automatic Version Resolution (semantic-release)
|
|
475
|
+
|
|
476
|
+
When `--version` is not provided, the tool can automatically determine the next version using semantic-release. This requires Node.js, npm, and semantic-release to be installed.
|
|
477
|
+
|
|
478
|
+
**Version Detection:**
|
|
479
|
+
- **Baseline version**:
|
|
480
|
+
- **Registry Query (Preferred)**: When publishing to a repository (PyPI, TestPyPI, or Azure Artifacts), the tool queries the target registry for the latest published version and uses it as the baseline for version calculation. This ensures version calculations are based on what's actually published, not just git tags.
|
|
481
|
+
- **Git Tags (Fallback)**: If the package doesn't exist on the registry yet (first release) or if registry query fails, the tool falls back to using git tags to determine the starting version.
|
|
482
|
+
- **New version to publish**: After determining the baseline version, [`semantic-release`](https://semantic-release.gitbook.io/semantic-release/) analyzes commits since that version to calculate the next version bump (major, minor, or patch) based on [_conventional commit_](https://www.conventionalcommits.org/en/v1.0.0/) messages.
|
|
483
|
+
|
|
484
|
+
**For subfolder builds (Workflow 1):**
|
|
485
|
+
- Uses per-package tags: `{package-name}-v{version}` (e.g., `my-package-v1.2.3`)
|
|
486
|
+
- Queries the target registry for the latest published version of the subfolder package
|
|
487
|
+
- Filters commits to only those affecting the subfolder path
|
|
488
|
+
- **Commit filtering behavior**: Only commits that modify files within the subfolder path are considered for version calculation. Commits that only target files outside the subfolder are excluded. For example:
|
|
489
|
+
- `fix: update my_subfolder/foo.py` → **Included** (affects subfolder)
|
|
490
|
+
- `feat: add feature to other_package/bar.py` → **Excluded** (doesn't affect subfolder)
|
|
491
|
+
- `fix: update my_subfolder/baz.py and shared/utils.py` → **Included** (affects subfolder, even if it also touches files outside)
|
|
492
|
+
- Requires `semantic-release-commit-filter` plugin
|
|
493
|
+
|
|
494
|
+
**For main package builds (Workflow 2):**
|
|
495
|
+
- Uses repo-level tags: `v{version}` (e.g., `v1.2.3`)
|
|
496
|
+
- Queries the target registry for the latest published version when publishing
|
|
497
|
+
- Analyzes all commits in the repository
|
|
498
|
+
|
|
499
|
+
**Registry Support:**
|
|
500
|
+
- **PyPI**: Fully supported via JSON API (`https://pypi.org/pypi/{package-name}/json`)
|
|
501
|
+
- **TestPyPI**: Fully supported via JSON API (`https://test.pypi.org/pypi/{package-name}/json`)
|
|
502
|
+
- **Azure Artifacts**: Basic support with fallback to git tags. Azure Artifacts uses a different API format and may require authentication, so if the query fails, the tool automatically falls back to git tags.
|
|
503
|
+
|
|
504
|
+
**Setup:**
|
|
505
|
+
```bash
|
|
506
|
+
# Install semantic-release globally
|
|
507
|
+
npm install -g semantic-release
|
|
508
|
+
|
|
509
|
+
# For subfolder builds, also install semantic-release-commit-filter
|
|
510
|
+
npm install -g semantic-release-commit-filter
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Usage:**
|
|
514
|
+
```bash
|
|
515
|
+
# Subfolder build - version resolved automatically
|
|
516
|
+
cd src/my_subfolder
|
|
517
|
+
python-package-folder --publish pypi
|
|
518
|
+
|
|
519
|
+
# Main package - version resolved automatically
|
|
520
|
+
python-package-folder --publish pypi
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Requirements:**
|
|
524
|
+
- Conventional commits (e.g., `fix:`, `feat:`, `BREAKING CHANGE:`) are required for semantic-release to determine version bumps
|
|
525
|
+
- The tool will fall back to requiring `--version` explicitly if semantic-release is not available or determines no release is needed
|
|
526
|
+
|
|
460
527
|
### Subfolder Versioning
|
|
461
528
|
|
|
462
529
|
When building from a subdirectory (not the main `src/` directory), the tool automatically detects the subfolder and sets up the build configuration:
|
|
463
530
|
|
|
464
531
|
```bash
|
|
465
|
-
# Build a subfolder as a separate package
|
|
532
|
+
# Build a subfolder as a separate package with explicit version
|
|
466
533
|
cd my_project/subfolder_to_build
|
|
467
534
|
python-package-folder --version "1.0.0" --publish pypi
|
|
468
535
|
|
|
536
|
+
# Or let semantic-release determine the version automatically
|
|
537
|
+
python-package-folder --publish pypi
|
|
538
|
+
|
|
469
539
|
# With custom package name
|
|
470
540
|
python-package-folder --version "1.0.0" --package-name "my-custom-name" --publish pypi
|
|
471
|
-
|
|
472
|
-
# Version defaults to "0.0.0" if not specified (with a warning)
|
|
473
|
-
python-package-folder --publish pypi
|
|
474
541
|
```
|
|
475
542
|
|
|
476
543
|
For subfolder builds:
|
|
477
544
|
- **Automatic detection**: The tool automatically detects subfolder builds
|
|
545
|
+
- **Version resolution**:
|
|
546
|
+
- If `--version` is provided: Uses the explicit version
|
|
547
|
+
- If `--version` is omitted: Attempts to resolve via semantic-release (requires setup)
|
|
548
|
+
- If semantic-release is unavailable or determines no release: Requires `--version` explicitly
|
|
478
549
|
- **pyproject.toml handling**:
|
|
479
550
|
- If `pyproject.toml` exists in subfolder: Uses that file (copied to project root temporarily)
|
|
480
551
|
- If no `pyproject.toml` in subfolder: Creates temporary one with correct package structure
|
|
481
|
-
- **Version**: Recommended but not required when creating temporary pyproject.toml. If not provided, defaults to `0.0.0` with a warning. Ignored if subfolder has its own `pyproject.toml`.
|
|
482
552
|
- **Package name**: Automatically derived from the subfolder name (e.g., `subfolder_to_build` → `subfolder-to-build`). Only used when creating temporary pyproject.toml.
|
|
483
553
|
- **Restoration**: Original `pyproject.toml` is restored after build
|
|
484
554
|
- **Temporary configuration**: Creates a temporary `pyproject.toml` with:
|
|
485
555
|
- Custom package name (from `--package-name` or derived)
|
|
486
|
-
- Specified version
|
|
556
|
+
- Specified or resolved version
|
|
487
557
|
- Correct package path for hatchling
|
|
488
558
|
- Dependency group from parent (if `--dependency-group` is specified)
|
|
489
559
|
- **Package initialization**: Automatically creates `__init__.py` if the subfolder doesn't have one (required for hatchling)
|
|
@@ -686,7 +756,8 @@ options:
|
|
|
686
756
|
--password PASSWORD Password/token for publishing (will prompt if not provided)
|
|
687
757
|
--skip-existing Skip files that already exist on the repository
|
|
688
758
|
--version VERSION Set a specific version before building (PEP 440 format).
|
|
689
|
-
|
|
759
|
+
Optional: if omitted, version will be resolved via
|
|
760
|
+
semantic-release (requires Node.js and semantic-release setup).
|
|
690
761
|
--package-name PACKAGE_NAME
|
|
691
762
|
Package name for subfolder builds (default: derived from
|
|
692
763
|
source directory name)
|
|
@@ -55,6 +55,9 @@ cd src/api_package
|
|
|
55
55
|
# Build and publish to TestPyPI with version 1.2.0
|
|
56
56
|
python-package-folder --publish testpypi --version 1.2.0
|
|
57
57
|
|
|
58
|
+
# Or publish to PyPI with automatic version resolution via semantic-release
|
|
59
|
+
python-package-folder --publish pypi
|
|
60
|
+
|
|
58
61
|
# Or publish to PyPI with a custom package name
|
|
59
62
|
python-package-folder --publish pypi --version 1.2.0 --package-name "my-api-package"
|
|
60
63
|
|
|
@@ -149,6 +152,13 @@ uv add twine
|
|
|
149
152
|
|
|
150
153
|
**For secure credential storage**: `keyring` is optional but recommended (install with `pip install keyring`)
|
|
151
154
|
|
|
155
|
+
**For automatic version resolution**: When using `--version` optional mode (automatic version resolution via semantic-release), you'll need:
|
|
156
|
+
- Node.js and npm (or npx)
|
|
157
|
+
- semantic-release: `npm install -g semantic-release`
|
|
158
|
+
- For subfolder builds: semantic-release-commit-filter: `npm install -g semantic-release-commit-filter`
|
|
159
|
+
|
|
160
|
+
Alternatively, install these as devDependencies in your project's `package.json`.
|
|
161
|
+
|
|
152
162
|
|
|
153
163
|
## Quick Start
|
|
154
164
|
|
|
@@ -162,9 +172,13 @@ Useful for monorepos containing many subfolders that may need publishing as stan
|
|
|
162
172
|
# First cd to the specific subfolder
|
|
163
173
|
cd src/subfolder_to_build_and_publish
|
|
164
174
|
|
|
165
|
-
# Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
|
|
175
|
+
# Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
|
|
176
|
+
# Version can be provided explicitly or resolved automatically via semantic-release
|
|
166
177
|
python-package-folder --publish testpypi --version 0.0.2
|
|
167
178
|
|
|
179
|
+
# Or let semantic-release determine the next version automatically (requires semantic-release setup)
|
|
180
|
+
python-package-folder --publish testpypi
|
|
181
|
+
|
|
168
182
|
# Only analyse (no building)
|
|
169
183
|
cd src/subfolder_to_build_and_publish
|
|
170
184
|
python-package-folder --analyze-only
|
|
@@ -437,33 +451,89 @@ The `--version` option:
|
|
|
437
451
|
**Version Format**: Versions must follow PEP 440 (e.g., `1.2.3`, `1.2.3a1`, `1.2.3.post1`, `1.2.3.dev1`)
|
|
438
452
|
|
|
439
453
|
|
|
454
|
+
### Automatic Version Resolution (semantic-release)
|
|
455
|
+
|
|
456
|
+
When `--version` is not provided, the tool can automatically determine the next version using semantic-release. This requires Node.js, npm, and semantic-release to be installed.
|
|
457
|
+
|
|
458
|
+
**Version Detection:**
|
|
459
|
+
- **Baseline version**:
|
|
460
|
+
- **Registry Query (Preferred)**: When publishing to a repository (PyPI, TestPyPI, or Azure Artifacts), the tool queries the target registry for the latest published version and uses it as the baseline for version calculation. This ensures version calculations are based on what's actually published, not just git tags.
|
|
461
|
+
- **Git Tags (Fallback)**: If the package doesn't exist on the registry yet (first release) or if registry query fails, the tool falls back to using git tags to determine the starting version.
|
|
462
|
+
- **New version to publish**: After determining the baseline version, [`semantic-release`](https://semantic-release.gitbook.io/semantic-release/) analyzes commits since that version to calculate the next version bump (major, minor, or patch) based on [_conventional commit_](https://www.conventionalcommits.org/en/v1.0.0/) messages.
|
|
463
|
+
|
|
464
|
+
**For subfolder builds (Workflow 1):**
|
|
465
|
+
- Uses per-package tags: `{package-name}-v{version}` (e.g., `my-package-v1.2.3`)
|
|
466
|
+
- Queries the target registry for the latest published version of the subfolder package
|
|
467
|
+
- Filters commits to only those affecting the subfolder path
|
|
468
|
+
- **Commit filtering behavior**: Only commits that modify files within the subfolder path are considered for version calculation. Commits that only target files outside the subfolder are excluded. For example:
|
|
469
|
+
- `fix: update my_subfolder/foo.py` → **Included** (affects subfolder)
|
|
470
|
+
- `feat: add feature to other_package/bar.py` → **Excluded** (doesn't affect subfolder)
|
|
471
|
+
- `fix: update my_subfolder/baz.py and shared/utils.py` → **Included** (affects subfolder, even if it also touches files outside)
|
|
472
|
+
- Requires `semantic-release-commit-filter` plugin
|
|
473
|
+
|
|
474
|
+
**For main package builds (Workflow 2):**
|
|
475
|
+
- Uses repo-level tags: `v{version}` (e.g., `v1.2.3`)
|
|
476
|
+
- Queries the target registry for the latest published version when publishing
|
|
477
|
+
- Analyzes all commits in the repository
|
|
478
|
+
|
|
479
|
+
**Registry Support:**
|
|
480
|
+
- **PyPI**: Fully supported via JSON API (`https://pypi.org/pypi/{package-name}/json`)
|
|
481
|
+
- **TestPyPI**: Fully supported via JSON API (`https://test.pypi.org/pypi/{package-name}/json`)
|
|
482
|
+
- **Azure Artifacts**: Basic support with fallback to git tags. Azure Artifacts uses a different API format and may require authentication, so if the query fails, the tool automatically falls back to git tags.
|
|
483
|
+
|
|
484
|
+
**Setup:**
|
|
485
|
+
```bash
|
|
486
|
+
# Install semantic-release globally
|
|
487
|
+
npm install -g semantic-release
|
|
488
|
+
|
|
489
|
+
# For subfolder builds, also install semantic-release-commit-filter
|
|
490
|
+
npm install -g semantic-release-commit-filter
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**Usage:**
|
|
494
|
+
```bash
|
|
495
|
+
# Subfolder build - version resolved automatically
|
|
496
|
+
cd src/my_subfolder
|
|
497
|
+
python-package-folder --publish pypi
|
|
498
|
+
|
|
499
|
+
# Main package - version resolved automatically
|
|
500
|
+
python-package-folder --publish pypi
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Requirements:**
|
|
504
|
+
- Conventional commits (e.g., `fix:`, `feat:`, `BREAKING CHANGE:`) are required for semantic-release to determine version bumps
|
|
505
|
+
- The tool will fall back to requiring `--version` explicitly if semantic-release is not available or determines no release is needed
|
|
506
|
+
|
|
440
507
|
### Subfolder Versioning
|
|
441
508
|
|
|
442
509
|
When building from a subdirectory (not the main `src/` directory), the tool automatically detects the subfolder and sets up the build configuration:
|
|
443
510
|
|
|
444
511
|
```bash
|
|
445
|
-
# Build a subfolder as a separate package
|
|
512
|
+
# Build a subfolder as a separate package with explicit version
|
|
446
513
|
cd my_project/subfolder_to_build
|
|
447
514
|
python-package-folder --version "1.0.0" --publish pypi
|
|
448
515
|
|
|
516
|
+
# Or let semantic-release determine the version automatically
|
|
517
|
+
python-package-folder --publish pypi
|
|
518
|
+
|
|
449
519
|
# With custom package name
|
|
450
520
|
python-package-folder --version "1.0.0" --package-name "my-custom-name" --publish pypi
|
|
451
|
-
|
|
452
|
-
# Version defaults to "0.0.0" if not specified (with a warning)
|
|
453
|
-
python-package-folder --publish pypi
|
|
454
521
|
```
|
|
455
522
|
|
|
456
523
|
For subfolder builds:
|
|
457
524
|
- **Automatic detection**: The tool automatically detects subfolder builds
|
|
525
|
+
- **Version resolution**:
|
|
526
|
+
- If `--version` is provided: Uses the explicit version
|
|
527
|
+
- If `--version` is omitted: Attempts to resolve via semantic-release (requires setup)
|
|
528
|
+
- If semantic-release is unavailable or determines no release: Requires `--version` explicitly
|
|
458
529
|
- **pyproject.toml handling**:
|
|
459
530
|
- If `pyproject.toml` exists in subfolder: Uses that file (copied to project root temporarily)
|
|
460
531
|
- If no `pyproject.toml` in subfolder: Creates temporary one with correct package structure
|
|
461
|
-
- **Version**: Recommended but not required when creating temporary pyproject.toml. If not provided, defaults to `0.0.0` with a warning. Ignored if subfolder has its own `pyproject.toml`.
|
|
462
532
|
- **Package name**: Automatically derived from the subfolder name (e.g., `subfolder_to_build` → `subfolder-to-build`). Only used when creating temporary pyproject.toml.
|
|
463
533
|
- **Restoration**: Original `pyproject.toml` is restored after build
|
|
464
534
|
- **Temporary configuration**: Creates a temporary `pyproject.toml` with:
|
|
465
535
|
- Custom package name (from `--package-name` or derived)
|
|
466
|
-
- Specified version
|
|
536
|
+
- Specified or resolved version
|
|
467
537
|
- Correct package path for hatchling
|
|
468
538
|
- Dependency group from parent (if `--dependency-group` is specified)
|
|
469
539
|
- **Package initialization**: Automatically creates `__init__.py` if the subfolder doesn't have one (required for hatchling)
|
|
@@ -666,7 +736,8 @@ options:
|
|
|
666
736
|
--password PASSWORD Password/token for publishing (will prompt if not provided)
|
|
667
737
|
--skip-existing Skip files that already exist on the repository
|
|
668
738
|
--version VERSION Set a specific version before building (PEP 440 format).
|
|
669
|
-
|
|
739
|
+
Optional: if omitted, version will be resolved via
|
|
740
|
+
semantic-release (requires Node.js and semantic-release setup).
|
|
670
741
|
--package-name PACKAGE_NAME
|
|
671
742
|
Package name for subfolder builds (default: derived from
|
|
672
743
|
source directory name)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
15
15
|
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
|
16
16
|
<text x="31.5" y="14">coverage</text>
|
|
17
|
-
<text x="81" y="15" fill="#010101" fill-opacity=".3">
|
|
18
|
-
<text x="81" y="14">
|
|
17
|
+
<text x="81" y="15" fill="#010101" fill-opacity=".3">65%</text>
|
|
18
|
+
<text x="81" y="14">65%</text>
|
|
19
19
|
</g>
|
|
20
20
|
</svg>
|
|
@@ -14,7 +14,6 @@ authors = [
|
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
license = "MIT"
|
|
16
16
|
requires-python = ">=3.11,<4.0"
|
|
17
|
-
dynamic = ["version"]
|
|
18
17
|
|
|
19
18
|
# https://pypi.org/classifiers/
|
|
20
19
|
# Adjust as needed:
|
|
@@ -43,6 +42,7 @@ dependencies = [
|
|
|
43
42
|
|
|
44
43
|
# ---- Dev dependencies ----
|
|
45
44
|
|
|
45
|
+
version = "4.1.0"
|
|
46
46
|
[dependency-groups]
|
|
47
47
|
dev = [
|
|
48
48
|
"pytest>=8.3.5",
|
|
@@ -71,20 +71,8 @@ python-package-folder = "python_package_folder.python_package_folder:main"
|
|
|
71
71
|
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
72
72
|
build-backend = "hatchling.build"
|
|
73
73
|
|
|
74
|
-
[tool.hatch.
|
|
75
|
-
|
|
76
|
-
# Note JSON schemas don't seem to be right for tool.hatch.version.source so
|
|
77
|
-
# this may cause false warnings in IDEs.
|
|
78
|
-
# https://github.com/ninoseki/uv-dynamic-versioning/issues/21
|
|
79
|
-
|
|
80
|
-
[tool.uv-dynamic-versioning]
|
|
81
|
-
vcs = "git"
|
|
82
|
-
style = "pep440"
|
|
83
|
-
bump = true
|
|
84
|
-
|
|
85
|
-
[tool.hatch.build.targets.wheel]
|
|
86
|
-
# The source location for the package.
|
|
87
|
-
packages = ["src/python_package_folder"]
|
|
74
|
+
[tool.hatch.build.hooks.custom]
|
|
75
|
+
path = "src/python_package_folder/_hatch_build.py"
|
|
88
76
|
|
|
89
77
|
|
|
90
78
|
# ---- Settings ----
|
|
@@ -164,12 +152,14 @@ reportUnreachable = false
|
|
|
164
152
|
# skip = "foo.py,bar.py"
|
|
165
153
|
|
|
166
154
|
[tool.pytest.ini_options]
|
|
167
|
-
python_files = ["*.py"]
|
|
155
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
168
156
|
python_classes = ["Test*"]
|
|
169
157
|
python_functions = ["test_*"]
|
|
170
158
|
testpaths = [
|
|
171
159
|
"src",
|
|
172
160
|
"tests",
|
|
173
161
|
]
|
|
162
|
+
# Ignore build hooks - they're not test files and require hatchling which isn't in test deps
|
|
163
|
+
ignore = ["_hatch_build.py"]
|
|
174
164
|
norecursedirs = []
|
|
175
165
|
filterwarnings = []
|