python-package-folder 4.3.6__tar.gz → 5.0.1__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.
Files changed (66) hide show
  1. python_package_folder-5.0.1/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +268 -0
  2. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.github/workflows/ci.yml +0 -3
  3. python_package_folder-5.0.1/PKG-INFO +192 -0
  4. python_package_folder-5.0.1/README.md +171 -0
  5. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/coverage.svg +2 -2
  6. python_package_folder-5.0.1/docs/DEVELOPMENT.md +38 -0
  7. python_package_folder-5.0.1/docs/INSTALLATION.md +23 -0
  8. python_package_folder-5.0.1/docs/PUBLISHING.md +121 -0
  9. python_package_folder-5.0.1/docs/REFERENCE.md +177 -0
  10. python_package_folder-5.0.1/docs/USAGE.md +224 -0
  11. python_package_folder-5.0.1/docs/VERSION_RESOLUTION.md +191 -0
  12. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/pyproject.toml +2 -8
  13. python_package_folder-5.0.1/src/python_package_folder/python_package_folder.py +318 -0
  14. python_package_folder-5.0.1/src/python_package_folder/version_calculator.py +597 -0
  15. python_package_folder-5.0.1/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  16. python_package_folder-5.0.1/tests/test_version_calculator.py +494 -0
  17. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/uv.lock +5 -17
  18. python_package_folder-4.3.6/MANIFEST.in +0 -2
  19. python_package_folder-4.3.6/PKG-INFO +0 -952
  20. python_package_folder-4.3.6/README.md +0 -932
  21. python_package_folder-4.3.6/python_package_folder/scripts/get-next-version.cjs +0 -668
  22. python_package_folder-4.3.6/scripts/get-next-version.cjs +0 -383
  23. python_package_folder-4.3.6/src/python_package_folder/python_package_folder.py +0 -579
  24. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.copier-answers.yml +0 -0
  25. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  26. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.cursor/rules/general.mdc +0 -0
  27. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.cursor/rules/python.mdc +0 -0
  28. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.github/workflows/publish.yml +0 -0
  29. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.gitignore +0 -0
  30. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/.vscode/settings.json +0 -0
  31. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/LICENSE +0 -0
  32. /python_package_folder-4.3.6/src/python_package_folder/py.typed → /python_package_folder-5.0.1/MANIFEST.in +0 -0
  33. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/Makefile +0 -0
  34. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/development.md +0 -0
  35. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/installation.md +0 -0
  36. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/publishing.md +0 -0
  37. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/__init__.py +0 -0
  38. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/__main__.py +0 -0
  39. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/_hatch_build.py +0 -0
  40. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/analyzer.py +0 -0
  41. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/finder.py +0 -0
  42. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/manager.py +0 -0
  43. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/publisher.py +0 -0
  44. /python_package_folder-4.3.6/tests/folder_structure/utility_folder/_SS/some_superseded_file.py → /python_package_folder-5.0.1/src/python_package_folder/py.typed +0 -0
  45. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/subfolder_build.py +0 -0
  46. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/types.py +0 -0
  47. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/utils.py +0 -0
  48. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/src/python_package_folder/version.py +0 -0
  49. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/conftest.py +0 -0
  50. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/folder_structure/some_globals.py +0 -0
  51. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  52. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  53. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  54. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  55. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  56. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_build_with_external_deps.py +0 -0
  57. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_linting.py +0 -0
  58. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_preserve_directory_structure.py +0 -0
  59. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_publisher.py +0 -0
  60. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_shared_subdirectory_imports.py +0 -0
  61. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_spreadsheet_creation_imports.py +0 -0
  62. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_subfolder_build.py +0 -0
  63. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_third_party_dependencies.py +0 -0
  64. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_utils.py +0 -0
  65. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/test_version_manager.py +0 -0
  66. {python_package_folder-4.3.6 → python_package_folder-5.0.1}/tests/tests.py +0 -0
@@ -0,0 +1,268 @@
1
+ ---
2
+ name: Replace Node.js semantic-release with custom Python implementation
3
+ overview: Replace the Node.js semantic-release dependency with a pure Python implementation that calculates versions from conventional commits, queries registries for baseline versions, and filters commits by subfolder path. This eliminates the push permission requirement and Node.js dependency.
4
+ todos: []
5
+ ---
6
+
7
+ # Replace Node.js semantic-release with Custom Python Implementation
8
+
9
+ ## Overview
10
+
11
+ Replace the Node.js `semantic-release` implementation with a pure Python solution that:
12
+
13
+ - Calculates next version from conventional commits (no push permissions needed)
14
+ - Queries registries (PyPI/TestPyPI/Azure) for baseline versions
15
+ - Filters commits by subfolder path for monorepo support
16
+ - Eliminates Node.js dependency
17
+
18
+ ## Architecture
19
+
20
+ ```
21
+ ┌─────────────────────────────────────────────────────────────┐
22
+ │ Version Resolution Flow │
23
+ └─────────────────────────────────────────────────────────────┘
24
+
25
+
26
+ ┌─────────────────────────────────────┐
27
+ │ Query Registry (PyPI/Azure/etc) │
28
+ │ for latest published version │
29
+ └─────────────────────────────────────┘
30
+
31
+ ┌─────────────┴─────────────┐
32
+ │ │
33
+ ▼ ▼
34
+ Success Failure
35
+ │ │
36
+ ▼ ▼
37
+ Use registry Query Git Tags
38
+ version (v1.2.3 or
39
+ │ package-v1.2.3)
40
+ │ │
41
+ └─────────────┬─────────────┘
42
+
43
+
44
+ ┌─────────────────────────────┐
45
+ │ Get commits since baseline │
46
+ │ (filter by subfolder path) │
47
+ └─────────────────────────────┘
48
+
49
+
50
+ ┌─────────────────────────────┐
51
+ │ Parse conventional commits │
52
+ │ (feat/fix/BREAKING CHANGE) │
53
+ └─────────────────────────────┘
54
+
55
+
56
+ ┌─────────────────────────────┐
57
+ │ Determine bump type │
58
+ │ (major/minor/patch) │
59
+ └─────────────────────────────┘
60
+
61
+
62
+ ┌─────────────────────────────┐
63
+ │ Calculate next version │
64
+ │ (baseline + bump) │
65
+ └─────────────────────────────┘
66
+ ```
67
+
68
+ ## Implementation Details
69
+
70
+ ### 1. Create New Python Module: `src/python_package_folder/version_calculator.py`
71
+
72
+ **Functions to implement:**
73
+
74
+ - `query_registry_version(package_name: str, repository: str, repository_url: str | None = None) -> str | None`
75
+ - Query PyPI/TestPyPI JSON API (`https://pypi.org/pypi/{package}/json`)
76
+ - Query Azure Artifacts (basic support, fallback on failure)
77
+ - Return latest version string or None
78
+
79
+ - `get_latest_git_tag(project_root: Path, package_name: str | None = None, is_subfolder: bool = False) -> str | None`
80
+ - For main package: look for tags matching `v{version}` (e.g., `v1.2.3`)
81
+ - For subfolder: look for tags matching `{package-name}-v{version}` (e.g., `my-package-v1.2.3`)
82
+ - Use `git tag --list` and parse version from tag
83
+ - Return latest version or None
84
+
85
+ - `get_commits_since(project_root: Path, baseline_version: str, subfolder_path: Path | None = None) -> list[str]`
86
+ - Use `git log --oneline` to get commits since baseline
87
+ - If `subfolder_path` provided, use `git log -- <subfolder_path>` to filter
88
+ - Return list of commit messages
89
+
90
+ - `parse_commit_for_bump(commit_message: str) -> str | None`
91
+ - Parse conventional commit format following [Angular Commit Message Conventions](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) as used by semantic-release
92
+ - Return `"major"`, `"minor"`, `"patch"`, or `None`
93
+ - **Breaking Change Detection (Major bump):**
94
+ - `BREAKING CHANGE:` in commit footer (body)
95
+ - `!` after type/scope: `feat!:`, `feat(scope)!:`, `fix!:`, etc.
96
+ - **Version Bump Rules:**
97
+ - `feat:` → `minor` (new feature)
98
+ - `fix:` → `patch` (bug fix)
99
+ - `perf:` → `patch` (performance improvement)
100
+ - **No version bump** (ignored): `docs:`, `style:`, `refactor:`, `test:`, `build:`, `ci:`, `chore:`, `revert:`
101
+ - All commit types supported by semantic-release Angular preset are recognized, but only `feat`, `fix`, and `perf` affect version bumps
102
+
103
+ - `calculate_next_version(baseline_version: str, commits: list[str], subfolder_path: Path | None = None) -> str | None`
104
+ - Get commits since baseline (filtered by subfolder if needed)
105
+ - Parse each commit for bump type
106
+ - Determine highest bump (major > minor > patch)
107
+ - Increment version: `1.2.3` + `minor` → `1.3.0`
108
+ - Return next version or None if no changes
109
+
110
+ - `resolve_version(project_root: Path, package_name: str | None = None, subfolder_path: Path | None = None, repository: str | None = None, repository_url: str | None = None) -> tuple[str | None, str | None]`
111
+ - Main entry point (replaces `resolve_version_via_semantic_release`)
112
+ - Try registry query first, fallback to git tags
113
+ - Calculate next version from commits
114
+ - Return `(version, error_message)` tuple
115
+
116
+ ### 2. Update `src/python_package_folder/python_package_folder.py`
117
+
118
+ **Changes:**
119
+
120
+ - Replace `resolve_version_via_semantic_release()` call with `resolve_version()` from `version_calculator`
121
+ - Remove `check_node_available()` function (no longer needed)
122
+ - Remove Node.js error messages
123
+ - Update imports: `from python_package_folder.version_calculator import resolve_version`
124
+
125
+ ### 3. Remove Node.js Dependencies
126
+
127
+ **Files to delete:**
128
+
129
+ - `src/python_package_folder/scripts/get-next-version.cjs`
130
+ - `scripts/get-next-version.cjs` (if exists)
131
+
132
+ **Files to update:**
133
+
134
+ - `pyproject.toml`: Remove any references to Node.js scripts in build hooks
135
+ - `src/python_package_folder/_hatch_build.py`: Remove script inclusion logic (if present)
136
+ - `MANIFEST.in`: Remove script inclusion (if present)
137
+
138
+ ### 4. Update Documentation: `README.md`
139
+
140
+ **Sections to update:**
141
+
142
+ - **"Automatic Version Resolution" section:**
143
+ - Remove Node.js/semantic-release setup instructions
144
+ - Update to describe Python-native implementation
145
+ - Keep registry query explanation
146
+ - Update commit filtering explanation (now native Python)
147
+ - **Add architecture diagram** (mermaid format) showing version resolution flow:
148
+ ```mermaid
149
+ flowchart TD
150
+ Start[Version Resolution] --> QueryRegistry[Query Registry<br/>PyPI/Azure/etc]
151
+ QueryRegistry -->|Success| UseRegistry[Use Registry Version]
152
+ QueryRegistry -->|Failure| QueryGitTags[Query Git Tags<br/>v1.2.3 or package-v1.2.3]
153
+ UseRegistry --> GetCommits[Get Commits Since Baseline<br/>filter by subfolder path]
154
+ QueryGitTags --> GetCommits
155
+ GetCommits --> ParseCommits[Parse Conventional Commits<br/>feat/fix/perf/BREAKING CHANGE]
156
+ ParseCommits --> DetermineBump[Determine Bump Type<br/>major/minor/patch]
157
+ DetermineBump --> CalculateVersion[Calculate Next Version<br/>baseline + bump]
158
+ CalculateVersion --> End[Return Version]
159
+ ```
160
+
161
+ - **Add "Supported Commit Types" subsection:**
162
+ - Document all Angular commit types supported (following [Angular Commit Message Conventions](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) as used by [semantic-release](https://semantic-release.gitbook.io/semantic-release/))
163
+ - **Version Bump Types:**
164
+ - `feat:` → **Minor** (new feature)
165
+ - `fix:` → **Patch** (bug fix)
166
+ - `perf:` → **Patch** (performance improvement)
167
+ - Breaking changes (any type with `!` or `BREAKING CHANGE:` footer) → **Major**
168
+ - **Ignored Types** (no version bump): `docs:`, `style:`, `refactor:`, `test:`, `build:`, `ci:`, `chore:`, `revert:`
169
+ - **Breaking Change Detection:**
170
+ - `BREAKING CHANGE:` in commit footer/body
171
+ - `!` after type: `feat!:`, `fix!:`, `feat(scope)!:`
172
+ - Include examples for each bump type with actual commit message examples
173
+ - Reference: "This implementation follows the same conventions as [semantic-release](https://semantic-release.gitbook.io/semantic-release/) using the Angular preset"
174
+
175
+ - **"Requirements" section:**
176
+ - Remove: "Install semantic-release globally"
177
+ - Remove: "Install semantic-release-commit-filter"
178
+ - Keep: "Conventional commits required"
179
+ - Add note: "Follows [Angular Commit Message Conventions](https://github.com/angular/angular/blob/main/contributing-docs/commit-message-guidelines.md) as used by [semantic-release](https://semantic-release.gitbook.io/semantic-release/)"
180
+
181
+ - **"Subfolder Versioning" section:**
182
+ - Update to reflect Python-native commit filtering
183
+ - Remove references to `semantic-release-commit-filter`
184
+
185
+ ### 5. Add Tests: `tests/test_version_calculator.py`
186
+
187
+ **Test cases:**
188
+
189
+ - `test_query_pypi_version()` - Mock HTTP request to PyPI API
190
+ - `test_query_testpypi_version()` - Mock HTTP request to TestPyPI API
191
+ - `test_query_azure_artifacts_version()` - Mock Azure Artifacts request (basic)
192
+ - `test_get_latest_git_tag_main_package()` - Test `v1.2.3` tag parsing
193
+ - `test_get_latest_git_tag_subfolder()` - Test `package-v1.2.3` tag parsing
194
+ - `test_get_commits_since()` - Test git log filtering
195
+ - `test_get_commits_since_with_subfolder()` - Test subfolder path filtering
196
+ - `test_parse_commit_for_bump_major_breaking_footer()` - Test `BREAKING CHANGE:` in footer
197
+ - `test_parse_commit_for_bump_major_exclamation()` - Test `feat!:`, `fix!:` syntax
198
+ - `test_parse_commit_for_bump_major_exclamation_with_scope()` - Test `feat(scope)!:` syntax
199
+ - `test_parse_commit_for_bump_minor()` - Test `feat:` detection
200
+ - `test_parse_commit_for_bump_patch_fix()` - Test `fix:` detection
201
+ - `test_parse_commit_for_bump_patch_perf()` - Test `perf:` detection
202
+ - `test_parse_commit_for_bump_none()` - Test ignored commit types (docs, style, refactor, test, build, ci, chore, revert)
203
+ - `test_calculate_next_version_patch()` - Test version increment (1.2.3 → 1.2.4)
204
+ - `test_calculate_next_version_minor()` - Test version increment (1.2.3 → 1.3.0)
205
+ - `test_calculate_next_version_major()` - Test version increment (1.2.3 → 2.0.0)
206
+ - `test_calculate_next_version_no_changes()` - Test None when no relevant commits
207
+ - `test_resolve_version_registry_first()` - Integration test with registry query
208
+ - `test_resolve_version_git_fallback()` - Integration test with git tag fallback
209
+ - `test_resolve_version_subfolder()` - Integration test for subfolder builds
210
+
211
+ **Test utilities:**
212
+
213
+ - Use `pytest` fixtures for temporary git repositories
214
+ - Use `unittest.mock` to mock HTTP requests and git commands
215
+ - Use `subprocess` mocking for git commands
216
+
217
+ ### 6. Update Integration Tests
218
+
219
+ **Files to check:**
220
+
221
+ - `tests/test_subfolder_build.py` - May need updates if it tests version resolution
222
+ - Any other tests that mock or test `resolve_version_via_semantic_release`
223
+
224
+ ### 7. Dependencies
225
+
226
+ **Add to `pyproject.toml` (if not already present):**
227
+
228
+ - `requests>=2.0.0` (for registry queries - already in lock file)
229
+
230
+ **Remove:**
231
+
232
+ - Any Node.js/npm related dependencies or build hooks
233
+
234
+ ## Testing Strategy
235
+
236
+ 1. **Unit tests**: Test each function in isolation with mocks
237
+ 2. **Integration tests**: Test full version resolution flow with temporary git repos
238
+ 3. **Manual testing**: Test against this repository's actual git history
239
+ 4. **CI testing**: Ensure tests pass in GitHub Actions (no Node.js needed)
240
+
241
+ ## Migration Notes
242
+
243
+ - **Backward compatibility**: The function signature `resolve_version()` matches the old `resolve_version_via_semantic_release()` signature, so CLI code doesn't need changes
244
+ - **Error messages**: Update error messages to remove Node.js references
245
+ - **Documentation**: Update all docs to reflect Python-native approach
246
+
247
+ ## Files to Create/Modify
248
+
249
+ **New files:**
250
+
251
+ - `src/python_package_folder/version_calculator.py` (~300-400 lines)
252
+
253
+ **Modified files:**
254
+
255
+ - `src/python_package_folder/python_package_folder.py` (replace function call, remove Node.js checks)
256
+ - `README.md` (update documentation)
257
+ - `tests/test_version_calculator.py` (new test file)
258
+
259
+ **Deleted files:**
260
+
261
+ - `src/python_package_folder/scripts/get-next-version.cjs`
262
+ - `scripts/get-next-version.cjs` (if exists)
263
+
264
+ **Potentially modified:**
265
+
266
+ - `pyproject.toml` (remove build hooks for scripts)
267
+ - `src/python_package_folder/_hatch_build.py` (remove script inclusion)
268
+ - `MANIFEST.in` (remove script inclusion)
@@ -56,9 +56,6 @@ jobs:
56
56
  - name: Install all dependencies
57
57
  run: uv sync --all-extras
58
58
 
59
- - name: Run linting
60
- run: uv run ruff check .
61
-
62
59
  - name: Check formatting
63
60
  continue-on-error: true
64
61
  run: uv run ruff format --check .
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-package-folder
3
+ Version: 5.0.1
4
+ Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
5
+ Project-URL: Repository, https://github.com/alelom/python-package-folder
6
+ Author-email: Alessio Lombardi <work@alelom.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: <4.0,>=3.11
19
+ Requires-Dist: requests>=2.0.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ # python-package-folder <!-- omit from toc -->
23
+
24
+ [![Tests](https://github.com/alelom/python-package-folder/actions/workflows/ci.yml/badge.svg)](https://github.com/alelom/python-package-folder/actions/workflows/ci.yml)
25
+ [![Coverage](https://raw.githubusercontent.com/alelom/python-package-folder/main/coverage.svg)](https://github.com/alelom/python-package-folder)
26
+
27
+ Easily build and publish any target folder in a repository, including subfolders of a monorepo.
28
+ Together with [sysappend](https://pypi.org/project/sysappend/), this library makes relative imports, flexible import management, and package publishing a breeze.
29
+
30
+ ## Documentation
31
+
32
+ - [Installation and Requirements](docs/INSTALLATION.md)
33
+ - [Usage Guide](docs/USAGE.md) - How it works, Python API, subfolder builds
34
+ - [Version Resolution](docs/VERSION_RESOLUTION.md) - Manual and automatic versioning with conventional commits
35
+ - [Publishing Packages](docs/PUBLISHING.md) - Publishing to PyPI, TestPyPI, and Azure Artifacts
36
+ - [API Reference](docs/REFERENCE.md) - Command-line options and Python API
37
+ - [Development](docs/DEVELOPMENT.md) - Contributing and project structure
38
+
39
+ ## Use Cases
40
+
41
+ ### 1) Publishing a Subfolder from src/ in a Monorepo
42
+
43
+ If you have a monorepo structure with multiple packages in `src/`:
44
+
45
+ ```
46
+ project/
47
+ ├── src/
48
+ │ ├── core_package/
49
+ │ │ ├── __init__.py
50
+ │ │ ├── core.py
51
+ │ │ └── README.md
52
+ │ ├── api_package/
53
+ │ │ ├── __init__.py
54
+ │ │ ├── api.py
55
+ │ │ └── README.md
56
+ │ └── utils_package/
57
+ │ ├── __init__.py
58
+ │ ├── utils.py
59
+ │ └── README.md
60
+ ├── shared/
61
+ │ └── common.py
62
+ └── pyproject.toml
63
+ ```
64
+
65
+ You can build and publish any subfolder from `src/` as a standalone package:
66
+
67
+ ```bash
68
+ # Navigate to the subfolder you want to publish
69
+ cd src/api_package
70
+
71
+ # Build and publish to TestPyPI with version 1.2.0
72
+ python-package-folder --publish testpypi --version 1.2.0
73
+
74
+ # Or publish to PyPI with automatic version resolution via conventional commits
75
+ python-package-folder --publish pypi
76
+
77
+ # Or publish to PyPI with a custom package name
78
+ python-package-folder --publish pypi --version 1.2.0 --package-name "my-api-package"
79
+
80
+ # Include a specific dependency group from the parent pyproject.toml
81
+ python-package-folder --publish pypi --version 1.2.0 --dependency-group "dev"
82
+ ```
83
+
84
+ The tool will automatically:
85
+ 1. Detect the project root (where `pyproject.toml` is located)
86
+ 2. Use `src/api_package` as the source directory
87
+ 3. Copy any external dependencies (like `shared/common.py`) into the package before building
88
+ 4. Use the subfolder's README if present, or create a minimal one
89
+ 5. Create a temporary `pyproject.toml` with the subfolder's package name and version
90
+ 6. Build and publish the package
91
+ 7. Clean up all temporary files and restore the original `pyproject.toml`
92
+
93
+ This is especially useful for monorepos where you want to publish individual packages independently while sharing common code.
94
+
95
+
96
+ ### 2) Building Packages with Shared Code
97
+
98
+ If your project structure looks like this:
99
+
100
+ ```
101
+ project/
102
+ ├── src/
103
+ │ └── my_package/
104
+ │ └── main.py
105
+ ├── shared/
106
+ │ ├── utils.py
107
+ │ └── helpers.py
108
+ └── pyproject.toml
109
+ ```
110
+
111
+ And `main.py` imports from `shared/`:
112
+
113
+ ```python
114
+ from shared.utils import some_function
115
+ from shared.helpers import Helper
116
+ ```
117
+
118
+ This package will automatically:
119
+ 1. Detect that `shared/` is outside `src/`
120
+ 2. Copy `shared/` into `src/` before building
121
+ 3. Build your package with all dependencies included
122
+ 4. Clean up the copied files after build
123
+
124
+ ## Features
125
+
126
+ - **Subfolder Build Support**: Build subfolders as separate packages with automatic detection and configuration
127
+ - **Automatic subfolder detection**: Detects when building a subfolder (not the main `src/` directory)
128
+ - Creates any needed file for publishing automatically, cleaning up if not originally in the subfolder after the build/publish process. E.g. copies external dependencies into the source directory before build and cleans them up afterward; temporary `__init__.py` creation for non-package subfolders; uses subfolder README if present, otherwise creates minimal README
129
+ - Automatic package name derivation from subfolder name
130
+ - Automatic temporary `pyproject.toml` creation with correct package structure
131
+ - Dependency group selection: specify which dependency group from parent `pyproject.toml` to include.
132
+
133
+ - **Smart Import Classification and analysis**:
134
+ - Recursively parses all `.py` files to detect `import` and `from ... import ...` statements
135
+ - Handles external dependencies (modules and files that originate from outside the main package directory), and distinguishes standard library imports, 3rd-party packages (from site-packages), local/external/relative/ambiguous imports.
136
+
137
+ - **Idempotent Operations**: Safely handles repeated runs without duplicating files
138
+ - **Build Integration**: Seamlessly integrates with build tools like `uv build`, `pip build`, etc.
139
+ - **Version Management**:
140
+ - Set static versions for publishing (PEP 440 compliant)
141
+ - Temporarily override dynamic versioning during builds
142
+ - Automatic restoration of dynamic versioning after build
143
+ - **Automatic version resolution** via conventional commits (Python-native, no Node.js required)
144
+ - **Package Publishing**:
145
+ - Uses twine to publish the built folder/subfolder
146
+ - Handles publishing to to PyPI, TestPyPI, or Azure Artifacts, with interactive credential prompts, secure storage support
147
+
148
+ ## Quick Start
149
+
150
+ The simplest way to use this package is via the command-line interface
151
+
152
+ **Build/publish a specific subfolder in a repository**
153
+
154
+ Useful for monorepos containing many subfolders that may need publishing as stand-alone packages for external usage.
155
+
156
+ ```bash
157
+ # First cd to the specific subfolder
158
+ cd src/subfolder_to_build_and_publish
159
+
160
+ # Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
161
+ # Version can be provided explicitly or resolved automatically via conventional commits
162
+ python-package-folder --publish testpypi --version 0.0.2
163
+
164
+ # Or let the tool determine the next version automatically from conventional commits
165
+ python-package-folder --publish testpypi
166
+
167
+ # Only analyse (no building)
168
+ cd src/subfolder_to_build_and_publish
169
+ python-package-folder --analyze-only
170
+
171
+ # Only build
172
+ cd src/subfolder_to_build_and_publish
173
+ python-package-folder
174
+
175
+ # Build with automatic dependency management
176
+ python-package-folder --build-command "uv build"
177
+ ```
178
+
179
+ You can also target a specific subfolder via commandline, rather than `cd`ing there:
180
+
181
+ ```bash
182
+ # Specify custom project root and source directory
183
+ python-package-folder --project-root /path/to/project --src-dir /path/to/src --build-command "pip build"
184
+ ```
185
+
186
+ ## License <!-- omit from toc -->
187
+
188
+ MIT License - see LICENSE file for details
189
+
190
+ ## Contributing <!-- omit from toc -->
191
+
192
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,171 @@
1
+ # python-package-folder <!-- omit from toc -->
2
+
3
+ [![Tests](https://github.com/alelom/python-package-folder/actions/workflows/ci.yml/badge.svg)](https://github.com/alelom/python-package-folder/actions/workflows/ci.yml)
4
+ [![Coverage](https://raw.githubusercontent.com/alelom/python-package-folder/main/coverage.svg)](https://github.com/alelom/python-package-folder)
5
+
6
+ Easily build and publish any target folder in a repository, including subfolders of a monorepo.
7
+ Together with [sysappend](https://pypi.org/project/sysappend/), this library makes relative imports, flexible import management, and package publishing a breeze.
8
+
9
+ ## Documentation
10
+
11
+ - [Installation and Requirements](docs/INSTALLATION.md)
12
+ - [Usage Guide](docs/USAGE.md) - How it works, Python API, subfolder builds
13
+ - [Version Resolution](docs/VERSION_RESOLUTION.md) - Manual and automatic versioning with conventional commits
14
+ - [Publishing Packages](docs/PUBLISHING.md) - Publishing to PyPI, TestPyPI, and Azure Artifacts
15
+ - [API Reference](docs/REFERENCE.md) - Command-line options and Python API
16
+ - [Development](docs/DEVELOPMENT.md) - Contributing and project structure
17
+
18
+ ## Use Cases
19
+
20
+ ### 1) Publishing a Subfolder from src/ in a Monorepo
21
+
22
+ If you have a monorepo structure with multiple packages in `src/`:
23
+
24
+ ```
25
+ project/
26
+ ├── src/
27
+ │ ├── core_package/
28
+ │ │ ├── __init__.py
29
+ │ │ ├── core.py
30
+ │ │ └── README.md
31
+ │ ├── api_package/
32
+ │ │ ├── __init__.py
33
+ │ │ ├── api.py
34
+ │ │ └── README.md
35
+ │ └── utils_package/
36
+ │ ├── __init__.py
37
+ │ ├── utils.py
38
+ │ └── README.md
39
+ ├── shared/
40
+ │ └── common.py
41
+ └── pyproject.toml
42
+ ```
43
+
44
+ You can build and publish any subfolder from `src/` as a standalone package:
45
+
46
+ ```bash
47
+ # Navigate to the subfolder you want to publish
48
+ cd src/api_package
49
+
50
+ # Build and publish to TestPyPI with version 1.2.0
51
+ python-package-folder --publish testpypi --version 1.2.0
52
+
53
+ # Or publish to PyPI with automatic version resolution via conventional commits
54
+ python-package-folder --publish pypi
55
+
56
+ # Or publish to PyPI with a custom package name
57
+ python-package-folder --publish pypi --version 1.2.0 --package-name "my-api-package"
58
+
59
+ # Include a specific dependency group from the parent pyproject.toml
60
+ python-package-folder --publish pypi --version 1.2.0 --dependency-group "dev"
61
+ ```
62
+
63
+ The tool will automatically:
64
+ 1. Detect the project root (where `pyproject.toml` is located)
65
+ 2. Use `src/api_package` as the source directory
66
+ 3. Copy any external dependencies (like `shared/common.py`) into the package before building
67
+ 4. Use the subfolder's README if present, or create a minimal one
68
+ 5. Create a temporary `pyproject.toml` with the subfolder's package name and version
69
+ 6. Build and publish the package
70
+ 7. Clean up all temporary files and restore the original `pyproject.toml`
71
+
72
+ This is especially useful for monorepos where you want to publish individual packages independently while sharing common code.
73
+
74
+
75
+ ### 2) Building Packages with Shared Code
76
+
77
+ If your project structure looks like this:
78
+
79
+ ```
80
+ project/
81
+ ├── src/
82
+ │ └── my_package/
83
+ │ └── main.py
84
+ ├── shared/
85
+ │ ├── utils.py
86
+ │ └── helpers.py
87
+ └── pyproject.toml
88
+ ```
89
+
90
+ And `main.py` imports from `shared/`:
91
+
92
+ ```python
93
+ from shared.utils import some_function
94
+ from shared.helpers import Helper
95
+ ```
96
+
97
+ This package will automatically:
98
+ 1. Detect that `shared/` is outside `src/`
99
+ 2. Copy `shared/` into `src/` before building
100
+ 3. Build your package with all dependencies included
101
+ 4. Clean up the copied files after build
102
+
103
+ ## Features
104
+
105
+ - **Subfolder Build Support**: Build subfolders as separate packages with automatic detection and configuration
106
+ - **Automatic subfolder detection**: Detects when building a subfolder (not the main `src/` directory)
107
+ - Creates any needed file for publishing automatically, cleaning up if not originally in the subfolder after the build/publish process. E.g. copies external dependencies into the source directory before build and cleans them up afterward; temporary `__init__.py` creation for non-package subfolders; uses subfolder README if present, otherwise creates minimal README
108
+ - Automatic package name derivation from subfolder name
109
+ - Automatic temporary `pyproject.toml` creation with correct package structure
110
+ - Dependency group selection: specify which dependency group from parent `pyproject.toml` to include.
111
+
112
+ - **Smart Import Classification and analysis**:
113
+ - Recursively parses all `.py` files to detect `import` and `from ... import ...` statements
114
+ - Handles external dependencies (modules and files that originate from outside the main package directory), and distinguishes standard library imports, 3rd-party packages (from site-packages), local/external/relative/ambiguous imports.
115
+
116
+ - **Idempotent Operations**: Safely handles repeated runs without duplicating files
117
+ - **Build Integration**: Seamlessly integrates with build tools like `uv build`, `pip build`, etc.
118
+ - **Version Management**:
119
+ - Set static versions for publishing (PEP 440 compliant)
120
+ - Temporarily override dynamic versioning during builds
121
+ - Automatic restoration of dynamic versioning after build
122
+ - **Automatic version resolution** via conventional commits (Python-native, no Node.js required)
123
+ - **Package Publishing**:
124
+ - Uses twine to publish the built folder/subfolder
125
+ - Handles publishing to to PyPI, TestPyPI, or Azure Artifacts, with interactive credential prompts, secure storage support
126
+
127
+ ## Quick Start
128
+
129
+ The simplest way to use this package is via the command-line interface
130
+
131
+ **Build/publish a specific subfolder in a repository**
132
+
133
+ Useful for monorepos containing many subfolders that may need publishing as stand-alone packages for external usage.
134
+
135
+ ```bash
136
+ # First cd to the specific subfolder
137
+ cd src/subfolder_to_build_and_publish
138
+
139
+ # Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
140
+ # Version can be provided explicitly or resolved automatically via conventional commits
141
+ python-package-folder --publish testpypi --version 0.0.2
142
+
143
+ # Or let the tool determine the next version automatically from conventional commits
144
+ python-package-folder --publish testpypi
145
+
146
+ # Only analyse (no building)
147
+ cd src/subfolder_to_build_and_publish
148
+ python-package-folder --analyze-only
149
+
150
+ # Only build
151
+ cd src/subfolder_to_build_and_publish
152
+ python-package-folder
153
+
154
+ # Build with automatic dependency management
155
+ python-package-folder --build-command "uv build"
156
+ ```
157
+
158
+ You can also target a specific subfolder via commandline, rather than `cd`ing there:
159
+
160
+ ```bash
161
+ # Specify custom project root and source directory
162
+ python-package-folder --project-root /path/to/project --src-dir /path/to/src --build-command "pip build"
163
+ ```
164
+
165
+ ## License <!-- omit from toc -->
166
+
167
+ MIT License - see LICENSE file for details
168
+
169
+ ## Contributing <!-- omit from toc -->
170
+
171
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -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">64%</text>
18
- <text x="81" y="14">64%</text>
17
+ <text x="81" y="15" fill="#010101" fill-opacity=".3">68%</text>
18
+ <text x="81" y="14">68%</text>
19
19
  </g>
20
20
  </svg>