python-package-folder 9.0.0__tar.gz → 9.2.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-9.0.0 → python_package_folder-9.2.0}/PKG-INFO +1 -1
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/coverage.svg +2 -2
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/docs/PUBLISHING.md +11 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/docs/USAGE.md +29 -2
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/pyproject.toml +1 -1
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/analyzer.py +16 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/manager.py +35 -17
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/publisher.py +33 -2
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/subfolder_build.py +417 -0
- python_package_folder-9.2.0/tests/test_subfolder_build.py +62 -0
- python_package_folder-9.2.0/tests/test_subfolder_build_config.py +399 -0
- python_package_folder-9.2.0/tests/test_subfolder_imports.py +600 -0
- python_package_folder-9.2.0/tests/test_subfolder_pyproject_toml.py +1145 -0
- python_package_folder-9.2.0/tests/test_subfolder_temp_directory.py +413 -0
- python_package_folder-9.2.0/tests/test_subfolder_wheel_packaging.py +124 -0
- python_package_folder-9.0.0/tests/test_subfolder_build.py +0 -3848
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.copier-answers.yml +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.github/workflows/ci.yml +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.github/workflows/publish.yml +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.gitignore +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/.vscode/settings.json +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/LICENSE +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/MANIFEST.in +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/Makefile +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/README.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/development.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/docs/INSTALLATION.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/docs/REFERENCE.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/installation.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/publishing.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/conftest.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_linting.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_publisher.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_utils.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_version_calculator.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/tests/tests.py +0 -0
- {python_package_folder-9.0.0 → python_package_folder-9.2.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.2.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>
|
|
@@ -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>
|
|
@@ -51,6 +51,17 @@ When publishing, the tool automatically filters distribution files to only uploa
|
|
|
51
51
|
|
|
52
52
|
This ensures that when building a subfolder package, only that package's distribution files are uploaded, not files from previous builds of other packages.
|
|
53
53
|
|
|
54
|
+
## Version Mismatch Detection
|
|
55
|
+
|
|
56
|
+
If there's a version mismatch between the built package and the expected version (e.g., when a subfolder's `pyproject.toml` has a different version than the derived version), the error message will show:
|
|
57
|
+
|
|
58
|
+
- What version was actually built
|
|
59
|
+
- What version is expected for publishing
|
|
60
|
+
- An explanation of the mismatch
|
|
61
|
+
- A solution suggestion
|
|
62
|
+
|
|
63
|
+
The tool automatically updates the version in the subfolder's `pyproject.toml` to match the derived version, so this error should only occur if the build process fails before the version update takes effect.
|
|
64
|
+
|
|
54
65
|
To get a PyPI API token:
|
|
55
66
|
1. Go to https://pypi.org/manage/account/token/
|
|
56
67
|
2. Create a new API token
|
|
@@ -75,7 +75,13 @@ The tool automatically:
|
|
|
75
75
|
- Uses the current directory as the source directory if it contains Python files
|
|
76
76
|
- Falls back to `project_root/src` if the current directory isn't suitable
|
|
77
77
|
- **For subfolder builds**: Handles `pyproject.toml` configuration:
|
|
78
|
-
- **If `pyproject.toml` exists in subfolder**:
|
|
78
|
+
- **If `pyproject.toml` exists in subfolder**:
|
|
79
|
+
- Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
|
|
80
|
+
- **Version handling**: The version in the subfolder's `pyproject.toml` is automatically updated to match the derived version (from conventional commits or `--version` argument). A warning is shown if versions differ.
|
|
81
|
+
- **Name handling**: If the subfolder's `pyproject.toml` has a `name` field that differs from the derived name, a warning is shown but the subfolder's name is used (not the derived one).
|
|
82
|
+
- **Dependencies handling**: If the subfolder's `pyproject.toml` has a non-empty `dependencies` field, automatic dependency detection is skipped with a warning. To enable automatic detection, remove or empty the `dependencies` field.
|
|
83
|
+
- **Field merging**: Missing fields (like `description`, `authors`, `keywords`, `classifiers`, `license`, `urls`, etc.) are automatically filled from the parent `pyproject.toml` if available.
|
|
84
|
+
- **Exclude patterns**: Exclude patterns from the parent `pyproject.toml` are merged into the subfolder's configuration.
|
|
79
85
|
- **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
|
|
80
86
|
- `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
|
|
81
87
|
- Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
|
|
@@ -101,11 +107,32 @@ python-package-folder --version "0.1.0" --package-name "my-subfolder-package" --
|
|
|
101
107
|
python-package-folder --version "0.1.0" --dependency-group "dev" --publish pypi
|
|
102
108
|
|
|
103
109
|
# If subfolder has its own pyproject.toml, it will be used automatically
|
|
104
|
-
#
|
|
110
|
+
# The version will be updated to match the derived version (from conventional commits)
|
|
111
|
+
# The package name from the subfolder toml will be used (even if it differs from derived)
|
|
105
112
|
cd src/integration/my_package # assuming my_package/pyproject.toml exists
|
|
106
113
|
python-package-folder --publish pypi
|
|
107
114
|
```
|
|
108
115
|
|
|
116
|
+
**Subfolder `pyproject.toml` Behavior:**
|
|
117
|
+
|
|
118
|
+
When a subfolder has its own `pyproject.toml`, the tool intelligently merges it with derived information:
|
|
119
|
+
|
|
120
|
+
1. **Version**: Always updated to match the derived version (from conventional commits or `--version`). If the subfolder's version differs, a warning is shown but the derived version is used.
|
|
121
|
+
|
|
122
|
+
2. **Name**: If the subfolder's `pyproject.toml` has a `name` field, it takes precedence over the derived name. A warning is shown if they differ.
|
|
123
|
+
|
|
124
|
+
3. **Dependencies**:
|
|
125
|
+
- If the subfolder's `pyproject.toml` has a non-empty `dependencies` field, automatic dependency detection is **skipped** (with a warning).
|
|
126
|
+
- To enable automatic dependency detection, remove or empty the `dependencies` field in the subfolder's `pyproject.toml`.
|
|
127
|
+
- If the `dependencies` field is empty or missing, automatic detection proceeds normally.
|
|
128
|
+
|
|
129
|
+
4. **Field Merging**: Missing fields are automatically filled from the parent `pyproject.toml`:
|
|
130
|
+
- `description`, `readme`, `requires-python`, `authors`, `keywords`, `classifiers`, `license`
|
|
131
|
+
- `[project.urls]` section
|
|
132
|
+
- Other `[tool.*]` sections (except `tool.hatch.build.*` and `tool.python-package-folder.*`)
|
|
133
|
+
|
|
134
|
+
5. **Exclude Patterns**: Automatically merged from parent `pyproject.toml`.
|
|
135
|
+
|
|
109
136
|
**Dependency Groups**: When building a subfolder, you can specify a dependency group from the parent `pyproject.toml` to include in the subfolder's build configuration. This allows subfolders to inherit specific dependencies from the parent project:
|
|
110
137
|
|
|
111
138
|
```bash
|
{python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/analyzer.py
RENAMED
|
@@ -310,6 +310,15 @@ class ImportAnalyzer:
|
|
|
310
310
|
self.project_root / module_path_str / "__init__.py",
|
|
311
311
|
(self.project_root / module_path_str).with_suffix(".py"),
|
|
312
312
|
]
|
|
313
|
+
|
|
314
|
+
# Also check project_root/src/ for files at src/ root (like _globals.py)
|
|
315
|
+
# This is important when building subfolders where src_dir points to temp directory
|
|
316
|
+
src_base = self.project_root / "src"
|
|
317
|
+
if src_base.exists():
|
|
318
|
+
potential_paths.extend([
|
|
319
|
+
src_base / module_path_str / "__init__.py",
|
|
320
|
+
(src_base / module_path_str).with_suffix(".py"),
|
|
321
|
+
])
|
|
313
322
|
|
|
314
323
|
for path in potential_paths:
|
|
315
324
|
if path.exists():
|
|
@@ -335,6 +344,13 @@ class ImportAnalyzer:
|
|
|
335
344
|
potential_file = parent / f"{module_name.split('.')[-1]}.py"
|
|
336
345
|
if potential_file.exists():
|
|
337
346
|
return potential_file
|
|
347
|
+
|
|
348
|
+
# Also check project_root/src/ for files at src/ root
|
|
349
|
+
# This handles cases like _globals.py at src/_globals.py when building subfolders
|
|
350
|
+
if parent == self.project_root and src_base.exists():
|
|
351
|
+
src_file = src_base / f"{module_name.split('.')[-1]}.py"
|
|
352
|
+
if src_file.exists():
|
|
353
|
+
return src_file
|
|
338
354
|
|
|
339
355
|
# Check all subdirectories in parent (not just common ones)
|
|
340
356
|
# This handles cases like src/data/spreadsheet_creation/spreadsheet_formatting_dataclasses.py
|
{python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/manager.py
RENAMED
|
@@ -255,28 +255,46 @@ class BuildManager:
|
|
|
255
255
|
# Fallback to just subfolder name if root project name not found
|
|
256
256
|
package_name = subfolder_name
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
# Check if subfolder_config already exists with the same parameters
|
|
259
|
+
# This makes prepare_build() idempotent - safe to call multiple times
|
|
260
|
+
if (
|
|
261
|
+
self.subfolder_config
|
|
262
|
+
and self.subfolder_config.package_name == package_name
|
|
263
|
+
and self.subfolder_config.version == version
|
|
264
|
+
and self.subfolder_config.dependency_group == dependency_group
|
|
265
|
+
and self.subfolder_config._temp_package_dir
|
|
266
|
+
and self.subfolder_config._temp_package_dir.exists()
|
|
267
|
+
):
|
|
268
|
+
print(
|
|
269
|
+
f"Subfolder config already exists for '{package_name}' version '{version}', reusing it..."
|
|
270
|
+
)
|
|
271
|
+
# Still need to find external dependencies, so continue with that
|
|
272
|
+
# temp_pyproject should already exist from previous call
|
|
273
|
+
temp_pyproject = self.subfolder_config.temp_pyproject
|
|
273
274
|
else:
|
|
275
|
+
print(
|
|
276
|
+
f"Detected subfolder build. Setting up package '{package_name}' version '{version}'..."
|
|
277
|
+
)
|
|
278
|
+
self.subfolder_config = SubfolderBuildConfig(
|
|
279
|
+
project_root=self.project_root,
|
|
280
|
+
src_dir=self.src_dir,
|
|
281
|
+
package_name=package_name,
|
|
282
|
+
version=version,
|
|
283
|
+
dependency_group=dependency_group,
|
|
284
|
+
)
|
|
285
|
+
temp_pyproject = self.subfolder_config.create_temp_pyproject()
|
|
286
|
+
# If temp_pyproject is None, it means no parent pyproject.toml exists
|
|
287
|
+
# This is acceptable for tests or dependency-only operations
|
|
288
|
+
if temp_pyproject is None:
|
|
289
|
+
self.subfolder_config = None
|
|
290
|
+
|
|
291
|
+
# If we have a subfolder_config (either newly created or reused), use temp package dir
|
|
292
|
+
if self.subfolder_config:
|
|
274
293
|
# If temporary package directory was created, use it for all operations
|
|
275
294
|
# This ensures dependencies are copied to the correct location and
|
|
276
295
|
# imports are fixed in the files that will actually be packaged
|
|
277
296
|
if (
|
|
278
|
-
self.subfolder_config
|
|
279
|
-
and self.subfolder_config._temp_package_dir
|
|
297
|
+
self.subfolder_config._temp_package_dir
|
|
280
298
|
and self.subfolder_config._temp_package_dir.exists()
|
|
281
299
|
):
|
|
282
300
|
# Update src_dir to point to temp package directory
|
{python_package_folder-9.0.0 → python_package_folder-9.2.0}/src/python_package_folder/publisher.py
RENAMED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import getpass
|
|
11
11
|
import os
|
|
12
|
+
import re
|
|
12
13
|
import subprocess
|
|
13
14
|
import sys
|
|
14
15
|
from enum import Enum
|
|
@@ -284,10 +285,40 @@ class Publisher:
|
|
|
284
285
|
|
|
285
286
|
if not dist_files:
|
|
286
287
|
if self.package_name and self.version:
|
|
287
|
-
|
|
288
|
+
# Try to find what version was actually built
|
|
289
|
+
built_versions = set()
|
|
290
|
+
for dist_file in all_dist_files:
|
|
291
|
+
# Extract version from filename: package-name-version-*.whl
|
|
292
|
+
# Pattern: {package_name}-{version}-{...}
|
|
293
|
+
if self.package_name.replace("-", "_") in dist_file.name or self.package_name in dist_file.name:
|
|
294
|
+
# Try to extract version from filename
|
|
295
|
+
parts = dist_file.stem.split("-")
|
|
296
|
+
# Look for version-like pattern (e.g., 1.2.3)
|
|
297
|
+
for i, part in enumerate(parts):
|
|
298
|
+
if re.match(r"^\d+\.\d+\.\d+", part):
|
|
299
|
+
built_versions.add(part)
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
error_msg = (
|
|
288
303
|
f"No distribution files found matching package '{self.package_name}' "
|
|
289
|
-
f"version '{self.version}' in {self.dist_dir}"
|
|
304
|
+
f"version '{self.version}' in {self.dist_dir}\n"
|
|
290
305
|
)
|
|
306
|
+
|
|
307
|
+
if built_versions:
|
|
308
|
+
error_msg += (
|
|
309
|
+
f" - Built package version(s): {', '.join(sorted(built_versions))}\n"
|
|
310
|
+
f" - Expected version for publishing: {self.version}\n"
|
|
311
|
+
f" - This usually indicates a version mismatch between the built package and the expected version.\n"
|
|
312
|
+
f" - Solution: The version in the subfolder's pyproject.toml will be automatically updated to match the derived version."
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
error_msg += (
|
|
316
|
+
f" - No distribution files found for package '{self.package_name}' in {self.dist_dir}\n"
|
|
317
|
+
f" - Available files: {[f.name for f in all_dist_files[:5]]}"
|
|
318
|
+
+ (f" (and {len(all_dist_files) - 5} more)" if len(all_dist_files) > 5 else "")
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
raise ValueError(error_msg)
|
|
291
322
|
else:
|
|
292
323
|
raise ValueError(f"No distribution files found in {self.dist_dir}")
|
|
293
324
|
|