python-package-folder 1.1.3__py3-none-any.whl → 1.2.1__py3-none-any.whl
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/analyzer.py +43 -1
- python_package_folder/manager.py +175 -57
- python_package_folder/python_package_folder.py +2 -1
- python_package_folder/subfolder_build.py +187 -13
- python_package_folder/utils.py +7 -5
- {python_package_folder-1.1.3.dist-info → python_package_folder-1.2.1.dist-info}/METADATA +97 -22
- python_package_folder-1.2.1.dist-info/RECORD +17 -0
- python_package_folder-1.1.3.dist-info/RECORD +0 -17
- {python_package_folder-1.1.3.dist-info → python_package_folder-1.2.1.dist-info}/WHEEL +0 -0
- {python_package_folder-1.1.3.dist-info → python_package_folder-1.2.1.dist-info}/entry_points.txt +0 -0
- {python_package_folder-1.1.3.dist-info → python_package_folder-1.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -45,13 +45,55 @@ class ImportAnalyzer:
|
|
|
45
45
|
"""
|
|
46
46
|
Recursively find all Python files in a directory.
|
|
47
47
|
|
|
48
|
+
Excludes common directories like .venv, venv, __pycache__, etc.
|
|
49
|
+
|
|
48
50
|
Args:
|
|
49
51
|
directory: Directory to search for Python files
|
|
50
52
|
|
|
51
53
|
Returns:
|
|
52
54
|
List of paths to all .py files found in the directory tree
|
|
53
55
|
"""
|
|
54
|
-
|
|
56
|
+
exclude_patterns = {
|
|
57
|
+
".venv",
|
|
58
|
+
"venv",
|
|
59
|
+
"__pycache__",
|
|
60
|
+
".git",
|
|
61
|
+
".pytest_cache",
|
|
62
|
+
".mypy_cache",
|
|
63
|
+
"node_modules",
|
|
64
|
+
".tox",
|
|
65
|
+
"dist",
|
|
66
|
+
"build",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
python_files = []
|
|
70
|
+
for path in directory.rglob("*.py"):
|
|
71
|
+
if not path.is_file():
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Check if any part of the path matches exclusion patterns
|
|
75
|
+
should_exclude = False
|
|
76
|
+
for part in path.parts:
|
|
77
|
+
# Check exact matches
|
|
78
|
+
if part in exclude_patterns:
|
|
79
|
+
should_exclude = True
|
|
80
|
+
break
|
|
81
|
+
# Check if part starts with excluded pattern or contains .egg-info
|
|
82
|
+
for pattern in exclude_patterns:
|
|
83
|
+
if part.startswith(pattern):
|
|
84
|
+
should_exclude = True
|
|
85
|
+
break
|
|
86
|
+
# Also exclude .egg-info directories
|
|
87
|
+
if ".egg-info" in part:
|
|
88
|
+
should_exclude = True
|
|
89
|
+
break
|
|
90
|
+
if should_exclude:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
if not should_exclude:
|
|
94
|
+
python_files.append(path)
|
|
95
|
+
|
|
96
|
+
return python_files
|
|
55
97
|
|
|
56
98
|
def extract_imports(self, file_path: Path) -> list[ImportInfo]:
|
|
57
99
|
"""
|
python_package_folder/manager.py
CHANGED
|
@@ -85,15 +85,13 @@ class BuildManager:
|
|
|
85
85
|
self.finder = ExternalDependencyFinder(
|
|
86
86
|
self.project_root, self.src_dir, exclude_patterns=exclude_patterns
|
|
87
87
|
)
|
|
88
|
+
self.subfolder_config: SubfolderBuildConfig | None = None
|
|
88
89
|
|
|
89
90
|
# Check if it's a valid Python package directory
|
|
90
91
|
if not any(self.src_dir.glob("*.py")) and not (self.src_dir / "__init__.py").exists():
|
|
91
92
|
# Allow empty directories for now, but warn
|
|
92
93
|
pass
|
|
93
94
|
|
|
94
|
-
self.copied_files: list[Path] = []
|
|
95
|
-
self.copied_dirs: list[Path] = []
|
|
96
|
-
|
|
97
95
|
def find_src_package_dir(self) -> Path | None:
|
|
98
96
|
"""
|
|
99
97
|
Find the main package directory within src/.
|
|
@@ -125,6 +123,21 @@ class BuildManager:
|
|
|
125
123
|
# Return the first one or src_dir itself
|
|
126
124
|
return package_dirs[0] if package_dirs else self.src_dir
|
|
127
125
|
|
|
126
|
+
def _is_subfolder_build(self) -> bool:
|
|
127
|
+
"""
|
|
128
|
+
Check if we're building a subfolder (not the main src/ directory).
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
True if this is a subfolder build, False otherwise
|
|
132
|
+
"""
|
|
133
|
+
# Check if src_dir is not the main src/ directory
|
|
134
|
+
main_src = self.project_root / "src"
|
|
135
|
+
return (
|
|
136
|
+
self.src_dir != main_src
|
|
137
|
+
and self.src_dir != self.project_root
|
|
138
|
+
and self.src_dir.is_relative_to(self.project_root)
|
|
139
|
+
)
|
|
140
|
+
|
|
128
141
|
def _get_project_name(self) -> str | None:
|
|
129
142
|
"""
|
|
130
143
|
Get the project name from pyproject.toml.
|
|
@@ -155,19 +168,94 @@ class BuildManager:
|
|
|
155
168
|
|
|
156
169
|
return None
|
|
157
170
|
|
|
158
|
-
def prepare_build(
|
|
171
|
+
def prepare_build(
|
|
172
|
+
self,
|
|
173
|
+
version: str | None = None,
|
|
174
|
+
package_name: str | None = None,
|
|
175
|
+
dependency_group: str | None = None,
|
|
176
|
+
) -> list[ExternalDependency]:
|
|
159
177
|
"""
|
|
160
178
|
Prepare for build by finding and copying external dependencies.
|
|
161
179
|
|
|
162
|
-
This method
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
This method automatically detects if you're building a subfolder (not the main src/
|
|
181
|
+
directory) and sets up pyproject.toml appropriately:
|
|
182
|
+
- If pyproject.toml exists in the subfolder, it will be used
|
|
183
|
+
- Otherwise, creates a temporary pyproject.toml with the correct package configuration
|
|
184
|
+
For subfolder builds without their own pyproject.toml, if no version is provided,
|
|
185
|
+
it defaults to "0.0.0" with a warning.
|
|
186
|
+
|
|
187
|
+
Process:
|
|
188
|
+
1. Detects if this is a subfolder build and sets up pyproject.toml if needed:
|
|
189
|
+
- If pyproject.toml exists in subfolder: uses that file
|
|
190
|
+
- If no pyproject.toml in subfolder: creates temporary one from parent
|
|
191
|
+
2. Finds all Python files in the source directory
|
|
192
|
+
3. Analyzes them for external dependencies
|
|
193
|
+
4. Copies external files/directories into the source directory
|
|
194
|
+
5. Reports any ambiguous imports
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
version: Version for subfolder builds. If building a subfolder and version is None,
|
|
198
|
+
defaults to "0.0.0" with a warning. Only used when creating temporary pyproject.toml
|
|
199
|
+
(ignored if subfolder has its own pyproject.toml). For regular builds, this parameter
|
|
200
|
+
is ignored.
|
|
201
|
+
package_name: Package name for subfolder builds. If None, derived from src_dir name
|
|
202
|
+
(e.g., "empty_drawing_detection" -> "empty-drawing-detection"). Only used when
|
|
203
|
+
creating temporary pyproject.toml (ignored if subfolder has its own pyproject.toml).
|
|
204
|
+
Ignored for regular builds.
|
|
205
|
+
dependency_group: Name of dependency group to copy from parent pyproject.toml.
|
|
206
|
+
Only used for subfolder builds when creating temporary pyproject.toml.
|
|
167
207
|
|
|
168
208
|
Returns:
|
|
169
209
|
List of ExternalDependency objects that were copied
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
```python
|
|
213
|
+
# Regular build (main src/ directory)
|
|
214
|
+
manager = BuildManager(project_root=Path("."), src_dir=Path("src"))
|
|
215
|
+
deps = manager.prepare_build() # No version needed
|
|
216
|
+
|
|
217
|
+
# Subfolder build (automatic detection)
|
|
218
|
+
manager = BuildManager(
|
|
219
|
+
project_root=Path("."),
|
|
220
|
+
src_dir=Path("src/integration/empty_drawing_detection")
|
|
221
|
+
)
|
|
222
|
+
# Version defaults to "0.0.0" if not provided
|
|
223
|
+
deps = manager.prepare_build(version="1.0.0", package_name="my-package")
|
|
224
|
+
```
|
|
170
225
|
"""
|
|
226
|
+
# Check if this is a subfolder build and set up config if needed
|
|
227
|
+
if self._is_subfolder_build():
|
|
228
|
+
# For subfolder builds, we need a version
|
|
229
|
+
# If not provided, use a default version
|
|
230
|
+
if not version:
|
|
231
|
+
version = "0.0.0"
|
|
232
|
+
print(
|
|
233
|
+
f"Warning: No version specified for subfolder build. Using default version '{version}'",
|
|
234
|
+
file=sys.stderr,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if not package_name:
|
|
238
|
+
# Derive package name from subfolder
|
|
239
|
+
package_name = (
|
|
240
|
+
self.src_dir.name.replace("_", "-").replace(" ", "-").lower().strip("-")
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
print(
|
|
244
|
+
f"Detected subfolder build. Setting up package '{package_name}' version '{version}'..."
|
|
245
|
+
)
|
|
246
|
+
self.subfolder_config = SubfolderBuildConfig(
|
|
247
|
+
project_root=self.project_root,
|
|
248
|
+
src_dir=self.src_dir,
|
|
249
|
+
package_name=package_name,
|
|
250
|
+
version=version,
|
|
251
|
+
dependency_group=dependency_group,
|
|
252
|
+
)
|
|
253
|
+
temp_pyproject = self.subfolder_config.create_temp_pyproject()
|
|
254
|
+
# If temp_pyproject is None, it means no parent pyproject.toml exists
|
|
255
|
+
# This is acceptable for tests or dependency-only operations
|
|
256
|
+
if temp_pyproject is None:
|
|
257
|
+
self.subfolder_config = None
|
|
258
|
+
|
|
171
259
|
analyzer = ImportAnalyzer(self.project_root)
|
|
172
260
|
|
|
173
261
|
# Find all Python files in src/
|
|
@@ -325,10 +413,30 @@ class BuildManager:
|
|
|
325
413
|
"""
|
|
326
414
|
Remove all copied files and directories.
|
|
327
415
|
|
|
328
|
-
This method removes all files and directories that were copied
|
|
329
|
-
|
|
330
|
-
the internal tracking lists.
|
|
416
|
+
This method removes all files and directories that were copied during prepare_build().
|
|
417
|
+
It also restores the original pyproject.toml if a temporary one was created for a
|
|
418
|
+
subfolder build. It handles errors gracefully and clears the internal tracking lists.
|
|
419
|
+
|
|
420
|
+
This is automatically called by run_build(), but you can call it manually if you
|
|
421
|
+
use prepare_build() directly.
|
|
422
|
+
|
|
423
|
+
Example:
|
|
424
|
+
```python
|
|
425
|
+
manager = BuildManager(project_root=Path("."), src_dir=Path("src"))
|
|
426
|
+
deps = manager.prepare_build()
|
|
427
|
+
# ... do your build ...
|
|
428
|
+
manager.cleanup() # Restores pyproject.toml and removes copied files
|
|
429
|
+
```
|
|
331
430
|
"""
|
|
431
|
+
# Restore subfolder config if it was created
|
|
432
|
+
if self.subfolder_config:
|
|
433
|
+
try:
|
|
434
|
+
self.subfolder_config.restore()
|
|
435
|
+
print("Restored original pyproject.toml")
|
|
436
|
+
except Exception as e:
|
|
437
|
+
print(f"Warning: Could not restore pyproject.toml: {e}", file=sys.stderr)
|
|
438
|
+
self.subfolder_config = None
|
|
439
|
+
|
|
332
440
|
# Remove copied directories first (they may contain files)
|
|
333
441
|
for dir_path in reversed(self.copied_dirs):
|
|
334
442
|
if dir_path.exists():
|
|
@@ -350,29 +458,59 @@ class BuildManager:
|
|
|
350
458
|
self.copied_files.clear()
|
|
351
459
|
self.copied_dirs.clear()
|
|
352
460
|
|
|
353
|
-
def run_build(
|
|
461
|
+
def run_build(
|
|
462
|
+
self,
|
|
463
|
+
build_command: Callable[[], None],
|
|
464
|
+
version: str | None = None,
|
|
465
|
+
package_name: str | None = None,
|
|
466
|
+
dependency_group: str | None = None,
|
|
467
|
+
) -> None:
|
|
354
468
|
"""
|
|
355
469
|
Run the build process with dependency management.
|
|
356
470
|
|
|
357
|
-
This is a convenience method that:
|
|
358
|
-
1. Calls prepare_build() to find and copy dependencies
|
|
471
|
+
This is a convenience method that automatically handles the full build lifecycle:
|
|
472
|
+
1. Calls prepare_build() to find and copy dependencies (with automatic subfolder detection)
|
|
359
473
|
2. Executes the provided build_command
|
|
360
474
|
3. Always calls cleanup() afterward, even if build fails
|
|
361
475
|
|
|
476
|
+
For subfolder builds, this method automatically detects the subfolder and creates a
|
|
477
|
+
temporary pyproject.toml with the correct package configuration. The build command
|
|
478
|
+
should be runnable from the project root (e.g., "uv build", "python -m build").
|
|
479
|
+
|
|
362
480
|
Args:
|
|
363
|
-
build_command: Callable that executes the build process
|
|
481
|
+
build_command: Callable that executes the build process. Should run from project root.
|
|
482
|
+
version: Version for subfolder builds. If building a subfolder and version is None,
|
|
483
|
+
defaults to "0.0.0" with a warning. Ignored for regular builds.
|
|
484
|
+
package_name: Package name for subfolder builds. If None, derived from src_dir name.
|
|
485
|
+
Ignored for regular builds.
|
|
486
|
+
dependency_group: Name of dependency group to copy from parent pyproject.toml.
|
|
487
|
+
Only used for subfolder builds.
|
|
364
488
|
|
|
365
489
|
Example:
|
|
366
490
|
```python
|
|
491
|
+
from pathlib import Path
|
|
492
|
+
from python_package_folder import BuildManager
|
|
493
|
+
import subprocess
|
|
494
|
+
|
|
495
|
+
# Regular build
|
|
496
|
+
manager = BuildManager(project_root=Path("."), src_dir=Path("src"))
|
|
367
497
|
def build():
|
|
368
498
|
subprocess.run(["uv", "build"], check=True)
|
|
369
|
-
|
|
370
499
|
manager.run_build(build)
|
|
500
|
+
|
|
501
|
+
# Subfolder build (automatic detection)
|
|
502
|
+
manager = BuildManager(
|
|
503
|
+
project_root=Path("."),
|
|
504
|
+
src_dir=Path("src/integration/empty_drawing_detection")
|
|
505
|
+
)
|
|
506
|
+
manager.run_build(build, version="1.0.0")
|
|
371
507
|
```
|
|
372
508
|
"""
|
|
373
509
|
try:
|
|
374
510
|
print("Analyzing project for external dependencies...")
|
|
375
|
-
external_deps = self.prepare_build(
|
|
511
|
+
external_deps = self.prepare_build(
|
|
512
|
+
version=version, package_name=package_name, dependency_group=dependency_group
|
|
513
|
+
)
|
|
376
514
|
|
|
377
515
|
if external_deps:
|
|
378
516
|
print(f"\nFound {len(external_deps)} external dependencies")
|
|
@@ -444,49 +582,37 @@ class BuildManager:
|
|
|
444
582
|
|
|
445
583
|
version_manager = None
|
|
446
584
|
original_version = None
|
|
447
|
-
subfolder_config = None
|
|
448
|
-
|
|
449
|
-
# Check if we're building a subfolder (not the main src/ directory)
|
|
450
|
-
is_subfolder_build = not self.src_dir.is_relative_to(self.project_root / "src") or (
|
|
451
|
-
self.src_dir != self.project_root / "src" and self.src_dir != self.project_root
|
|
452
|
-
)
|
|
453
585
|
|
|
454
586
|
try:
|
|
455
|
-
# For subfolder builds
|
|
456
|
-
if
|
|
457
|
-
if not package_name:
|
|
458
|
-
# Derive package name from subfolder
|
|
459
|
-
package_name = (
|
|
460
|
-
self.src_dir.name.replace("_", "-").replace(" ", "-").lower().strip("-")
|
|
461
|
-
)
|
|
462
|
-
print(f"Building subfolder as package '{package_name}' version '{version}'...")
|
|
463
|
-
subfolder_config = SubfolderBuildConfig(
|
|
464
|
-
project_root=self.project_root,
|
|
465
|
-
src_dir=self.src_dir,
|
|
466
|
-
package_name=package_name,
|
|
467
|
-
version=version,
|
|
468
|
-
dependency_group=dependency_group,
|
|
469
|
-
)
|
|
470
|
-
subfolder_config.create_temp_pyproject()
|
|
471
|
-
elif version:
|
|
472
|
-
# Regular build with version override
|
|
587
|
+
# For non-subfolder builds with version, use VersionManager
|
|
588
|
+
if version and not self._is_subfolder_build():
|
|
473
589
|
version_manager = VersionManager(self.project_root)
|
|
474
590
|
original_version = version_manager.get_current_version()
|
|
475
591
|
print(f"Setting version to {version}...")
|
|
476
592
|
version_manager.set_version(version)
|
|
477
593
|
|
|
478
|
-
# Build the package
|
|
479
|
-
self.run_build(
|
|
594
|
+
# Build the package (prepare_build will handle subfolder config if needed)
|
|
595
|
+
self.run_build(
|
|
596
|
+
build_command,
|
|
597
|
+
version=version,
|
|
598
|
+
package_name=package_name,
|
|
599
|
+
dependency_group=dependency_group,
|
|
600
|
+
)
|
|
480
601
|
|
|
481
602
|
# Publish if repository is specified
|
|
482
603
|
if repository:
|
|
483
604
|
# Determine package name and version for filtering
|
|
484
605
|
publish_package_name = None
|
|
485
606
|
publish_version = version
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
607
|
+
is_subfolder_build = self._is_subfolder_build()
|
|
608
|
+
|
|
609
|
+
if is_subfolder_build:
|
|
610
|
+
# Get package name from subfolder_config if it was created, otherwise use provided
|
|
611
|
+
if self.subfolder_config:
|
|
612
|
+
publish_package_name = self.subfolder_config.package_name
|
|
613
|
+
elif package_name:
|
|
614
|
+
publish_package_name = package_name
|
|
615
|
+
else:
|
|
490
616
|
# For regular builds, get package name from pyproject.toml
|
|
491
617
|
try:
|
|
492
618
|
import tomllib
|
|
@@ -515,15 +641,7 @@ class BuildManager:
|
|
|
515
641
|
)
|
|
516
642
|
publisher.publish(skip_existing=skip_existing)
|
|
517
643
|
finally:
|
|
518
|
-
# Restore subfolder config
|
|
519
|
-
if subfolder_config and restore_versioning:
|
|
520
|
-
try:
|
|
521
|
-
subfolder_config.restore()
|
|
522
|
-
print("Restored original pyproject.toml")
|
|
523
|
-
except Exception as e:
|
|
524
|
-
print(f"Warning: Could not restore pyproject.toml: {e}", file=sys.stderr)
|
|
525
|
-
|
|
526
|
-
# Restore versioning if needed
|
|
644
|
+
# Restore versioning if needed (subfolder config is handled by cleanup)
|
|
527
645
|
if version_manager and restore_versioning:
|
|
528
646
|
try:
|
|
529
647
|
if original_version:
|
|
@@ -122,7 +122,8 @@ def main() -> int:
|
|
|
122
122
|
src_dir = Path(args.src_dir).resolve()
|
|
123
123
|
else:
|
|
124
124
|
# Auto-detect: use current directory if it has Python files, otherwise use project_root/src
|
|
125
|
-
|
|
125
|
+
current_dir = Path.cwd()
|
|
126
|
+
src_dir = find_source_directory(project_root, current_dir=current_dir)
|
|
126
127
|
if src_dir:
|
|
127
128
|
print(f"Auto-detected source directory: {src_dir}")
|
|
128
129
|
else:
|
|
@@ -29,8 +29,13 @@ class SubfolderBuildConfig:
|
|
|
29
29
|
"""
|
|
30
30
|
Manages temporary build configuration for subfolder builds.
|
|
31
31
|
|
|
32
|
-
When building a subfolder as a separate package, this class
|
|
33
|
-
|
|
32
|
+
When building a subfolder as a separate package, this class:
|
|
33
|
+
- Uses the subfolder's pyproject.toml if it exists (adjusts package paths and ensures
|
|
34
|
+
[build-system] uses hatchling)
|
|
35
|
+
- Otherwise creates a temporary pyproject.toml with the appropriate package name and version
|
|
36
|
+
- Always ensures [build-system] section uses hatchling (replaces any existing build-system
|
|
37
|
+
configuration from parent or subfolder)
|
|
38
|
+
- Handles README files similarly (uses subfolder README if present)
|
|
34
39
|
"""
|
|
35
40
|
|
|
36
41
|
def __init__(
|
|
@@ -47,9 +52,12 @@ class SubfolderBuildConfig:
|
|
|
47
52
|
Args:
|
|
48
53
|
project_root: Root directory containing the main pyproject.toml
|
|
49
54
|
src_dir: Source directory being built (subfolder)
|
|
50
|
-
package_name: Name for the subfolder package (default: derived from src_dir name)
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
package_name: Name for the subfolder package (default: derived from src_dir name).
|
|
56
|
+
Only used if subfolder doesn't have its own pyproject.toml.
|
|
57
|
+
version: Version for the subfolder package (required if building subfolder).
|
|
58
|
+
Only used if subfolder doesn't have its own pyproject.toml.
|
|
59
|
+
dependency_group: Name of dependency group to copy from parent pyproject.toml.
|
|
60
|
+
Only used if subfolder doesn't have its own pyproject.toml.
|
|
53
61
|
"""
|
|
54
62
|
self.project_root = project_root.resolve()
|
|
55
63
|
self.src_dir = src_dir.resolve()
|
|
@@ -61,6 +69,7 @@ class SubfolderBuildConfig:
|
|
|
61
69
|
self._temp_init_created = False
|
|
62
70
|
self.temp_readme: Path | None = None
|
|
63
71
|
self.original_readme_backup: Path | None = None
|
|
72
|
+
self._used_subfolder_pyproject = False
|
|
64
73
|
|
|
65
74
|
def _derive_package_name(self) -> str:
|
|
66
75
|
"""Derive package name from source directory name."""
|
|
@@ -105,15 +114,100 @@ class SubfolderBuildConfig:
|
|
|
105
114
|
# If it's a package or has subpackages, return the path
|
|
106
115
|
return packages_path, [packages_path] if packages_path else []
|
|
107
116
|
|
|
108
|
-
def
|
|
117
|
+
def _adjust_subfolder_pyproject_packages_path(self, content: str) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Adjust packages path in subfolder pyproject.toml to be relative to project root.
|
|
120
|
+
|
|
121
|
+
When a subfolder's pyproject.toml is copied to project root, the packages path
|
|
122
|
+
needs to be adjusted to point to the subfolder relative to the project root.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
content: Content of the subfolder's pyproject.toml
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Adjusted content with correct packages path
|
|
129
|
+
"""
|
|
130
|
+
# Get the correct packages path relative to project root
|
|
131
|
+
_, package_dirs = self._get_package_structure()
|
|
132
|
+
if not package_dirs:
|
|
133
|
+
# No adjustment needed if we can't determine the path
|
|
134
|
+
return content
|
|
135
|
+
|
|
136
|
+
correct_packages_path = package_dirs[0]
|
|
137
|
+
lines = content.split("\n")
|
|
138
|
+
result = []
|
|
139
|
+
in_hatch_build = False
|
|
140
|
+
packages_set = False
|
|
141
|
+
|
|
142
|
+
for line in lines:
|
|
143
|
+
# Detect hatch build section
|
|
144
|
+
if line.strip().startswith("[tool.hatch.build.targets.wheel]"):
|
|
145
|
+
in_hatch_build = True
|
|
146
|
+
result.append(line)
|
|
147
|
+
continue
|
|
148
|
+
elif line.strip().startswith("[") and in_hatch_build:
|
|
149
|
+
# End of hatch build section, add packages if not set
|
|
150
|
+
if not packages_set and correct_packages_path:
|
|
151
|
+
packages_str = f'"{correct_packages_path}"'
|
|
152
|
+
result.append(f"packages = [{packages_str}]")
|
|
153
|
+
in_hatch_build = False
|
|
154
|
+
result.append(line)
|
|
155
|
+
elif in_hatch_build:
|
|
156
|
+
# Modify packages path if found
|
|
157
|
+
if re.match(r"^\s*packages\s*=", line):
|
|
158
|
+
packages_str = f'"{correct_packages_path}"'
|
|
159
|
+
result.append(f"packages = [{packages_str}]")
|
|
160
|
+
packages_set = True
|
|
161
|
+
continue
|
|
162
|
+
# Keep other lines in hatch build section
|
|
163
|
+
result.append(line)
|
|
164
|
+
else:
|
|
165
|
+
result.append(line)
|
|
166
|
+
|
|
167
|
+
# Add packages if we're still in hatch build section and haven't set it
|
|
168
|
+
if in_hatch_build and not packages_set and correct_packages_path:
|
|
169
|
+
packages_str = f'"{correct_packages_path}"'
|
|
170
|
+
result.append(f"packages = [{packages_str}]")
|
|
171
|
+
|
|
172
|
+
# Ensure build-system section exists (required for hatchling)
|
|
173
|
+
# Check if build-system section exists in the result
|
|
174
|
+
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
175
|
+
if not has_build_system:
|
|
176
|
+
# Insert build-system at the very beginning of the file
|
|
177
|
+
build_system_lines = [
|
|
178
|
+
"[build-system]",
|
|
179
|
+
'requires = ["hatchling"]',
|
|
180
|
+
'build-backend = "hatchling.build"',
|
|
181
|
+
"",
|
|
182
|
+
]
|
|
183
|
+
result = build_system_lines + result
|
|
184
|
+
|
|
185
|
+
# Ensure hatch build section exists if packages path is needed
|
|
186
|
+
if not packages_set and correct_packages_path:
|
|
187
|
+
# Check if we need to add the section
|
|
188
|
+
if "[tool.hatch.build.targets.wheel]" not in content:
|
|
189
|
+
result.append("")
|
|
190
|
+
result.append("[tool.hatch.build.targets.wheel]")
|
|
191
|
+
packages_str = f'"{correct_packages_path}"'
|
|
192
|
+
result.append(f"packages = [{packages_str}]")
|
|
193
|
+
|
|
194
|
+
return "\n".join(result)
|
|
195
|
+
|
|
196
|
+
def create_temp_pyproject(self) -> Path | None:
|
|
109
197
|
"""
|
|
110
198
|
Create a temporary pyproject.toml for the subfolder build.
|
|
111
199
|
|
|
112
|
-
|
|
113
|
-
|
|
200
|
+
If a pyproject.toml exists in the subfolder, it will be used (copied to project root
|
|
201
|
+
with adjusted package paths and ensuring [build-system] uses hatchling). Otherwise,
|
|
202
|
+
creates a pyproject.toml in the project root based on the parent pyproject.toml with
|
|
203
|
+
the appropriate package name and version.
|
|
204
|
+
|
|
205
|
+
The [build-system] section is always set to use hatchling, even if the parent or
|
|
206
|
+
subfolder pyproject.toml uses a different build backend (e.g., setuptools).
|
|
114
207
|
|
|
115
208
|
Returns:
|
|
116
|
-
Path to the
|
|
209
|
+
Path to the pyproject.toml file (either from subfolder or created temporary),
|
|
210
|
+
or None if no parent pyproject.toml exists (in which case subfolder config is skipped)
|
|
117
211
|
"""
|
|
118
212
|
if not self.version:
|
|
119
213
|
raise ValueError("Version is required for subfolder builds")
|
|
@@ -127,10 +221,52 @@ class SubfolderBuildConfig:
|
|
|
127
221
|
else:
|
|
128
222
|
self._temp_init_created = False
|
|
129
223
|
|
|
224
|
+
# Check if pyproject.toml exists in subfolder
|
|
225
|
+
subfolder_pyproject = self.src_dir / "pyproject.toml"
|
|
226
|
+
if subfolder_pyproject.exists():
|
|
227
|
+
# Use the subfolder's pyproject.toml
|
|
228
|
+
print(f"Using existing pyproject.toml from subfolder: {subfolder_pyproject}")
|
|
229
|
+
self._used_subfolder_pyproject = True
|
|
230
|
+
|
|
231
|
+
# Backup the original project root pyproject.toml if it exists
|
|
232
|
+
original_pyproject = self.project_root / "pyproject.toml"
|
|
233
|
+
if original_pyproject.exists():
|
|
234
|
+
backup_path = self.project_root / "pyproject.toml.backup"
|
|
235
|
+
shutil.copy2(original_pyproject, backup_path)
|
|
236
|
+
self.original_pyproject_backup = backup_path
|
|
237
|
+
|
|
238
|
+
# Read and adjust the subfolder pyproject.toml
|
|
239
|
+
subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
|
|
240
|
+
# Adjust packages path to be relative to project root
|
|
241
|
+
adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
|
|
242
|
+
|
|
243
|
+
# Write adjusted content to project root
|
|
244
|
+
original_pyproject.write_text(adjusted_content, encoding="utf-8")
|
|
245
|
+
self.temp_pyproject = original_pyproject
|
|
246
|
+
|
|
247
|
+
# Handle README file
|
|
248
|
+
self._handle_readme()
|
|
249
|
+
|
|
250
|
+
return original_pyproject
|
|
251
|
+
|
|
252
|
+
# No pyproject.toml in subfolder, create one from parent
|
|
253
|
+
self._used_subfolder_pyproject = False
|
|
254
|
+
print("No pyproject.toml found in subfolder, creating temporary one from parent")
|
|
255
|
+
|
|
130
256
|
# Read the original pyproject.toml
|
|
131
257
|
original_pyproject = self.project_root / "pyproject.toml"
|
|
132
258
|
if not original_pyproject.exists():
|
|
133
|
-
|
|
259
|
+
# If no parent pyproject.toml exists, we can't create a temporary one
|
|
260
|
+
# This is acceptable for tests or cases where only dependency copying is needed
|
|
261
|
+
print(
|
|
262
|
+
f"Warning: No pyproject.toml found in project root ({original_pyproject}). "
|
|
263
|
+
"Skipping subfolder build configuration. Only dependency copying will be performed.",
|
|
264
|
+
file=sys.stderr,
|
|
265
|
+
)
|
|
266
|
+
# Still handle README file
|
|
267
|
+
self._handle_readme()
|
|
268
|
+
# Return None to indicate no pyproject.toml was created
|
|
269
|
+
return None
|
|
134
270
|
|
|
135
271
|
original_content = original_pyproject.read_text(encoding="utf-8")
|
|
136
272
|
|
|
@@ -214,6 +350,7 @@ class SubfolderBuildConfig:
|
|
|
214
350
|
skip_uv_dynamic = False
|
|
215
351
|
in_hatch_build = False
|
|
216
352
|
packages_set = False
|
|
353
|
+
build_system_set = False
|
|
217
354
|
|
|
218
355
|
# Get package structure
|
|
219
356
|
packages_path, package_dirs = self._get_package_structure()
|
|
@@ -221,6 +358,19 @@ class SubfolderBuildConfig:
|
|
|
221
358
|
package_dirs = []
|
|
222
359
|
|
|
223
360
|
for _i, line in enumerate(lines):
|
|
361
|
+
# Skip build-system section - we'll add our own for subfolder builds
|
|
362
|
+
if line.strip().startswith("[build-system]"):
|
|
363
|
+
build_system_set = True
|
|
364
|
+
continue # Skip the [build-system] line
|
|
365
|
+
elif build_system_set and line.strip().startswith("["):
|
|
366
|
+
# End of build-system section
|
|
367
|
+
build_system_set = False
|
|
368
|
+
result.append(line)
|
|
369
|
+
continue
|
|
370
|
+
elif build_system_set:
|
|
371
|
+
# Skip build-system content - we'll add our own
|
|
372
|
+
continue
|
|
373
|
+
|
|
224
374
|
# Skip hatch versioning and uv-dynamic-versioning sections
|
|
225
375
|
if line.strip().startswith("[tool.hatch.version]"):
|
|
226
376
|
skip_hatch_version = True
|
|
@@ -316,6 +466,19 @@ class SubfolderBuildConfig:
|
|
|
316
466
|
packages_str = ", ".join(f'"{p}"' for p in package_dirs)
|
|
317
467
|
result.append(f"packages = [{packages_str}]")
|
|
318
468
|
|
|
469
|
+
# Ensure build-system section exists (required for hatchling)
|
|
470
|
+
# Check if build-system section exists in the result
|
|
471
|
+
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
472
|
+
if not has_build_system:
|
|
473
|
+
# Insert build-system at the very beginning of the file
|
|
474
|
+
build_system_lines = [
|
|
475
|
+
"[build-system]",
|
|
476
|
+
'requires = ["hatchling"]',
|
|
477
|
+
'build-backend = "hatchling.build"',
|
|
478
|
+
"",
|
|
479
|
+
]
|
|
480
|
+
result = build_system_lines + result
|
|
481
|
+
|
|
319
482
|
# Ensure packages is always set for subfolder builds
|
|
320
483
|
if not packages_set and package_dirs:
|
|
321
484
|
# Add the section if it doesn't exist
|
|
@@ -419,7 +582,13 @@ class SubfolderBuildConfig:
|
|
|
419
582
|
self.temp_readme = target_readme
|
|
420
583
|
|
|
421
584
|
def restore(self) -> None:
|
|
422
|
-
"""
|
|
585
|
+
"""
|
|
586
|
+
Restore the original pyproject.toml and remove temporary __init__.py if created.
|
|
587
|
+
|
|
588
|
+
If a subfolder pyproject.toml was used, it restores the original project root
|
|
589
|
+
pyproject.toml from backup. If a temporary pyproject.toml was created, it
|
|
590
|
+
restores the original as well.
|
|
591
|
+
"""
|
|
423
592
|
# Remove temporary __init__.py if we created it
|
|
424
593
|
if self._temp_init_created:
|
|
425
594
|
init_file = self.src_dir / "__init__.py"
|
|
@@ -460,13 +629,18 @@ class SubfolderBuildConfig:
|
|
|
460
629
|
pass # Ignore errors during cleanup
|
|
461
630
|
self.temp_readme = None
|
|
462
631
|
|
|
463
|
-
# Restore original pyproject.toml
|
|
464
|
-
if
|
|
632
|
+
# Restore original pyproject.toml (only if we created/used one)
|
|
633
|
+
if (
|
|
634
|
+
self.temp_pyproject
|
|
635
|
+
and self.original_pyproject_backup
|
|
636
|
+
and self.original_pyproject_backup.exists()
|
|
637
|
+
):
|
|
465
638
|
original_pyproject = self.project_root / "pyproject.toml"
|
|
466
639
|
shutil.copy2(self.original_pyproject_backup, original_pyproject)
|
|
467
640
|
self.original_pyproject_backup.unlink()
|
|
468
641
|
self.original_pyproject_backup = None
|
|
469
642
|
self.temp_pyproject = None
|
|
643
|
+
self._used_subfolder_pyproject = False
|
|
470
644
|
|
|
471
645
|
def __enter__(self) -> Self:
|
|
472
646
|
"""Context manager entry."""
|
python_package_folder/utils.py
CHANGED
|
@@ -63,7 +63,8 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
|
|
|
63
63
|
project_root = project_root.resolve()
|
|
64
64
|
|
|
65
65
|
# Check if current directory is a subdirectory with Python files
|
|
66
|
-
if
|
|
66
|
+
# Prioritize current directory if it's within the project and has Python files
|
|
67
|
+
if current_dir.is_relative_to(project_root) and current_dir != project_root:
|
|
67
68
|
python_files = list(current_dir.glob("*.py"))
|
|
68
69
|
if python_files:
|
|
69
70
|
# Current directory has Python files, use it as source
|
|
@@ -74,10 +75,11 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
|
|
|
74
75
|
if src_dir.exists() and src_dir.is_dir():
|
|
75
76
|
return src_dir
|
|
76
77
|
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
# Only check project_root if current_dir is the project_root
|
|
79
|
+
if current_dir == project_root:
|
|
80
|
+
python_files = list(project_root.glob("*.py"))
|
|
81
|
+
if python_files:
|
|
82
|
+
return project_root
|
|
81
83
|
|
|
82
84
|
return None
|
|
83
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
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>
|
|
@@ -125,9 +125,11 @@ This package will automatically:
|
|
|
125
125
|
|
|
126
126
|
## Features
|
|
127
127
|
|
|
128
|
-
- **Subfolder Build Support**: Build subfolders as separate packages with automatic
|
|
128
|
+
- **Subfolder Build Support**: Build subfolders as separate packages with automatic detection and configuration
|
|
129
|
+
- **Automatic subfolder detection**: Detects when building a subfolder (not the main `src/` directory)
|
|
129
130
|
- 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
|
|
130
131
|
- Automatic package name derivation from subfolder name
|
|
132
|
+
- Automatic temporary `pyproject.toml` creation with correct package structure
|
|
131
133
|
- Dependency group selection: specify which dependency group from parent `pyproject.toml` to include.
|
|
132
134
|
|
|
133
135
|
- **Smart Import Classification and analysis**:
|
|
@@ -245,6 +247,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
|
|
|
245
247
|
- If found, copies the subfolder README to project root (backing up the original parent README)
|
|
246
248
|
- If not found, creates a minimal README with just the folder name
|
|
247
249
|
5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
|
|
250
|
+
- `[build-system]` section using hatchling (replaces any existing build-system configuration)
|
|
248
251
|
- Subfolder-specific package name (derived or custom)
|
|
249
252
|
- Specified version
|
|
250
253
|
- Correct package path for hatchling
|
|
@@ -268,16 +271,21 @@ cd my_project/subfolder_to_build
|
|
|
268
271
|
python-package-folder --version "1.0.0" --publish pypi
|
|
269
272
|
```
|
|
270
273
|
|
|
271
|
-
|
|
274
|
+
The tool **automatically detects** when you're building a subfolder (any directory that's not the main `src/` directory) and sets up the appropriate build configuration.
|
|
272
275
|
|
|
273
276
|
The tool automatically:
|
|
277
|
+
- **Detects subfolder builds**: Automatically identifies when building from a subdirectory
|
|
274
278
|
- Finds the project root by looking for `pyproject.toml` in parent directories
|
|
275
279
|
- Uses the current directory as the source directory if it contains Python files
|
|
276
280
|
- Falls back to `project_root/src` if the current directory isn't suitable
|
|
277
|
-
- For subfolder builds
|
|
278
|
-
-
|
|
279
|
-
-
|
|
280
|
-
|
|
281
|
+
- **For subfolder builds**: Handles `pyproject.toml` configuration:
|
|
282
|
+
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
|
|
283
|
+
- **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
|
|
284
|
+
- `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
|
|
285
|
+
- Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
|
|
286
|
+
- Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
|
|
287
|
+
- Proper package path configuration for hatchling
|
|
288
|
+
- Dependency groups from parent `pyproject.toml` if specified
|
|
281
289
|
- Creates temporary `__init__.py` files if needed to make subfolders valid Python packages
|
|
282
290
|
- **README handling for subfolder builds**:
|
|
283
291
|
- If a README file (README.md, README.rst, README.txt, or README) exists in the subfolder, it will be used instead of the parent README
|
|
@@ -285,6 +293,8 @@ The tool automatically:
|
|
|
285
293
|
- Restores the original `pyproject.toml` after build (unless `--no-restore-versioning` is used)
|
|
286
294
|
- Cleans up temporary `__init__.py` files after build
|
|
287
295
|
|
|
296
|
+
**Note**: While version is not strictly required (defaults to `0.0.0`), it's recommended to specify `--version` for subfolder builds to ensure proper versioning.
|
|
297
|
+
|
|
288
298
|
**Subfolder Build Example:**
|
|
289
299
|
```bash
|
|
290
300
|
# Build a subfolder as a separate package
|
|
@@ -293,6 +303,11 @@ python-package-folder --version "0.1.0" --package-name "my-subfolder-package" --
|
|
|
293
303
|
|
|
294
304
|
# Build with a specific dependency group from parent pyproject.toml
|
|
295
305
|
python-package-folder --version "0.1.0" --dependency-group "dev" --publish pypi
|
|
306
|
+
|
|
307
|
+
# If subfolder has its own pyproject.toml, it will be used automatically
|
|
308
|
+
# (package-name and version arguments are ignored in this case)
|
|
309
|
+
cd src/integration/my_package # assuming my_package/pyproject.toml exists
|
|
310
|
+
python-package-folder --publish pypi
|
|
296
311
|
```
|
|
297
312
|
|
|
298
313
|
**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:
|
|
@@ -308,6 +323,8 @@ The specified dependency group will be copied from the parent `pyproject.toml`'s
|
|
|
308
323
|
|
|
309
324
|
You can also use the package programmatically:
|
|
310
325
|
|
|
326
|
+
### Basic Usage
|
|
327
|
+
|
|
311
328
|
```python
|
|
312
329
|
from pathlib import Path
|
|
313
330
|
from python_package_folder import BuildManager
|
|
@@ -328,11 +345,11 @@ for dep in external_deps:
|
|
|
328
345
|
# Run your build process here
|
|
329
346
|
# ...
|
|
330
347
|
|
|
331
|
-
# Cleanup copied files
|
|
348
|
+
# Cleanup copied files (also restores pyproject.toml if subfolder build)
|
|
332
349
|
manager.cleanup()
|
|
333
350
|
```
|
|
334
351
|
|
|
335
|
-
|
|
352
|
+
### Using the Convenience Method
|
|
336
353
|
|
|
337
354
|
```python
|
|
338
355
|
from pathlib import Path
|
|
@@ -348,6 +365,55 @@ def build_command():
|
|
|
348
365
|
manager.run_build(build_command)
|
|
349
366
|
```
|
|
350
367
|
|
|
368
|
+
### Subfolder Builds (Automatic Detection)
|
|
369
|
+
|
|
370
|
+
The tool automatically detects when you're building a subfolder and sets up the appropriate configuration:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from pathlib import Path
|
|
374
|
+
from python_package_folder import BuildManager
|
|
375
|
+
import subprocess
|
|
376
|
+
|
|
377
|
+
# Building a subfolder - automatic detection!
|
|
378
|
+
manager = BuildManager(
|
|
379
|
+
project_root=Path("."),
|
|
380
|
+
src_dir=Path("src/integration/empty_drawing_detection")
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def build_command():
|
|
384
|
+
subprocess.run(["uv", "build"], check=True)
|
|
385
|
+
|
|
386
|
+
# prepare_build() automatically:
|
|
387
|
+
# - Detects this is a subfolder build
|
|
388
|
+
# - If pyproject.toml exists in subfolder: uses that file
|
|
389
|
+
# - If no pyproject.toml in subfolder: creates temporary one with package name "empty-drawing-detection"
|
|
390
|
+
# - Uses version "0.0.0" (or pass version="1.0.0" to override) if creating temporary pyproject.toml
|
|
391
|
+
external_deps = manager.prepare_build(version="1.0.0")
|
|
392
|
+
|
|
393
|
+
# Run build - uses the pyproject.toml (either from subfolder or temporary)
|
|
394
|
+
build_command()
|
|
395
|
+
|
|
396
|
+
# Cleanup restores original pyproject.toml and removes copied files
|
|
397
|
+
manager.cleanup()
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Note**: If the subfolder has its own `pyproject.toml`, it will be used automatically. The `version` and `package_name` parameters are only used when creating a temporary `pyproject.toml` from the parent configuration.
|
|
401
|
+
|
|
402
|
+
Or use the convenience method:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
manager = BuildManager(
|
|
406
|
+
project_root=Path("."),
|
|
407
|
+
src_dir=Path("src/integration/empty_drawing_detection")
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def build_command():
|
|
411
|
+
subprocess.run(["uv", "build"], check=True)
|
|
412
|
+
|
|
413
|
+
# All handled automatically: subfolder detection, pyproject.toml setup, build, cleanup
|
|
414
|
+
manager.run_build(build_command, version="1.0.0", package_name="my-custom-name")
|
|
415
|
+
```
|
|
416
|
+
|
|
351
417
|
## Working with sysappend
|
|
352
418
|
|
|
353
419
|
This package works well with projects using [sysappend](https://pypi.org/project/sysappend/) for flexible import management. When you have imports like:
|
|
@@ -393,20 +459,28 @@ The `--version` option:
|
|
|
393
459
|
|
|
394
460
|
### Subfolder Versioning
|
|
395
461
|
|
|
396
|
-
When building from a subdirectory (not the main `src/` directory),
|
|
462
|
+
When building from a subdirectory (not the main `src/` directory), the tool automatically detects the subfolder and sets up the build configuration:
|
|
397
463
|
|
|
398
464
|
```bash
|
|
399
|
-
# Build a subfolder as a separate package
|
|
465
|
+
# Build a subfolder as a separate package (version recommended but not required)
|
|
400
466
|
cd my_project/subfolder_to_build
|
|
401
467
|
python-package-folder --version "1.0.0" --publish pypi
|
|
402
468
|
|
|
403
469
|
# With custom package name
|
|
404
470
|
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
|
|
405
474
|
```
|
|
406
475
|
|
|
407
476
|
For subfolder builds:
|
|
408
|
-
- **
|
|
409
|
-
- **
|
|
477
|
+
- **Automatic detection**: The tool automatically detects subfolder builds
|
|
478
|
+
- **pyproject.toml handling**:
|
|
479
|
+
- If `pyproject.toml` exists in subfolder: Uses that file (copied to project root temporarily)
|
|
480
|
+
- 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
|
+
- **Package name**: Automatically derived from the subfolder name (e.g., `subfolder_to_build` → `subfolder-to-build`). Only used when creating temporary pyproject.toml.
|
|
483
|
+
- **Restoration**: Original `pyproject.toml` is restored after build
|
|
410
484
|
- **Temporary configuration**: Creates a temporary `pyproject.toml` with:
|
|
411
485
|
- Custom package name (from `--package-name` or derived)
|
|
412
486
|
- Specified version
|
|
@@ -715,7 +789,8 @@ version_manager.restore_dynamic_versioning()
|
|
|
715
789
|
|
|
716
790
|
### SubfolderBuildConfig
|
|
717
791
|
|
|
718
|
-
Manages temporary build configuration for subfolder builds.
|
|
792
|
+
Manages temporary build configuration for subfolder builds. If a `pyproject.toml` exists
|
|
793
|
+
in the subfolder, it will be used instead of creating a new one.
|
|
719
794
|
|
|
720
795
|
```python
|
|
721
796
|
from python_package_folder import SubfolderBuildConfig
|
|
@@ -724,11 +799,11 @@ from pathlib import Path
|
|
|
724
799
|
config = SubfolderBuildConfig(
|
|
725
800
|
project_root=Path("."),
|
|
726
801
|
src_dir=Path("subfolder"),
|
|
727
|
-
package_name="my-subfolder",
|
|
728
|
-
version="1.0.0"
|
|
802
|
+
package_name="my-subfolder", # Only used if subfolder has no pyproject.toml
|
|
803
|
+
version="1.0.0" # Only used if subfolder has no pyproject.toml
|
|
729
804
|
)
|
|
730
805
|
|
|
731
|
-
# Create temporary pyproject.toml
|
|
806
|
+
# Create temporary pyproject.toml (or use subfolder's if it exists)
|
|
732
807
|
config.create_temp_pyproject()
|
|
733
808
|
|
|
734
809
|
# ... build process ...
|
|
@@ -738,13 +813,13 @@ config.restore()
|
|
|
738
813
|
```
|
|
739
814
|
|
|
740
815
|
**Methods:**
|
|
741
|
-
- `create_temp_pyproject() -> Path`:
|
|
816
|
+
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists (adjusting package paths and ensuring `[build-system]` uses hatchling), otherwise create temporary `pyproject.toml` with subfolder-specific configuration including `[build-system]` section using hatchling
|
|
742
817
|
- `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
|
|
743
818
|
|
|
744
|
-
**Note**: This class automatically
|
|
745
|
-
- If a
|
|
746
|
-
- If no README exists in the subfolder, a minimal README with just the folder name will be created
|
|
747
|
-
-
|
|
819
|
+
**Note**: This class automatically:
|
|
820
|
+
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily with adjusted package paths). Otherwise, creates a temporary one from the parent configuration. In both cases, the `[build-system]` section is always set to use hatchling, replacing any existing build-system configuration.
|
|
821
|
+
- **README handling**: If a README exists in the subfolder, it will be used instead of the parent README. If no README exists in the subfolder, a minimal README with just the folder name will be created. The original parent README is backed up and restored after the build completes.
|
|
822
|
+
- **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
|
|
748
823
|
|
|
749
824
|
|
|
750
825
|
## Development
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
python_package_folder/__init__.py,sha256=DQt-uldOEKfh0MUqCvKdeNKOnpuOvpb7blYvXMyO9Wc,719
|
|
2
|
+
python_package_folder/__main__.py,sha256=a-__-VLhYw-J7S7CsHdhtEvQr3RiAZxiYDvKhKTgMX4,291
|
|
3
|
+
python_package_folder/analyzer.py,sha256=w7hc2oyOoPK7tvlwcJDXnB3eiJsuGZc4BkOpTfZP7Vo,12257
|
|
4
|
+
python_package_folder/finder.py,sha256=_LvJ9xBVKv41UK5sbwbNyKmuYjAOqUbzvZhK7NCYQF8,9130
|
|
5
|
+
python_package_folder/manager.py,sha256=AlzEqI7q0Q2mVmc_HjIEBmomlT053qqLBMZZ52X7IDQ,26360
|
|
6
|
+
python_package_folder/publisher.py,sha256=1xa6PuduOXNVTTp4IrJcx4qOskugX2fTeJ9QsLDLuUM,11535
|
|
7
|
+
python_package_folder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
python_package_folder/python_package_folder.py,sha256=RPsqRcIy_LjzzTHdp4qdtFJ4-4xhtR_0YLIC0RlUxFo,8841
|
|
9
|
+
python_package_folder/subfolder_build.py,sha256=C_Hksc7JqeNfYvP1HUqXnxlaFFYde3ACXCAE-8SYh9M,27712
|
|
10
|
+
python_package_folder/types.py,sha256=3yeSRR5p_3PDKEAaehW_RJ7NwJHexOIeA08bGaT1iSY,2368
|
|
11
|
+
python_package_folder/utils.py,sha256=lIkWsFKeAYAJ9TDUM99T4pUBHJVbUvCdUgkWQN-LUho,3111
|
|
12
|
+
python_package_folder/version.py,sha256=kIDP6S9trEfs9gj7lBYGxrWm4RPssRla24UtlO9Jkh4,9111
|
|
13
|
+
python_package_folder-1.2.1.dist-info/METADATA,sha256=rkIOYmrjFZxN0AMgT6LshC4qxRbQW1s0CzBgWNsalaQ,32711
|
|
14
|
+
python_package_folder-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
python_package_folder-1.2.1.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
|
|
16
|
+
python_package_folder-1.2.1.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
|
|
17
|
+
python_package_folder-1.2.1.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
python_package_folder/__init__.py,sha256=DQt-uldOEKfh0MUqCvKdeNKOnpuOvpb7blYvXMyO9Wc,719
|
|
2
|
-
python_package_folder/__main__.py,sha256=a-__-VLhYw-J7S7CsHdhtEvQr3RiAZxiYDvKhKTgMX4,291
|
|
3
|
-
python_package_folder/analyzer.py,sha256=Iw5bdg9NahO57L3CZgGYbhU-m2mh0DpQQ-xqIINUfic,10976
|
|
4
|
-
python_package_folder/finder.py,sha256=_LvJ9xBVKv41UK5sbwbNyKmuYjAOqUbzvZhK7NCYQF8,9130
|
|
5
|
-
python_package_folder/manager.py,sha256=VZ2e6ydQUPlDnve-PlqGfa86XksSCCqFDZ46GDwZr2A,20549
|
|
6
|
-
python_package_folder/publisher.py,sha256=1xa6PuduOXNVTTp4IrJcx4qOskugX2fTeJ9QsLDLuUM,11535
|
|
7
|
-
python_package_folder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
python_package_folder/python_package_folder.py,sha256=3ECayVwjXQAnujr2e21xvsuwetcqgRF5Zawt3qgtI9M,8779
|
|
9
|
-
python_package_folder/subfolder_build.py,sha256=NIlQRMjBQvBJdxYpwYyOcQWqijPKmgRogsRyY9rElkk,19567
|
|
10
|
-
python_package_folder/types.py,sha256=3yeSRR5p_3PDKEAaehW_RJ7NwJHexOIeA08bGaT1iSY,2368
|
|
11
|
-
python_package_folder/utils.py,sha256=hoCNRiTHe_-zjAFztxyqjNEMKiEl7XCkSDl36iaMcjM,2966
|
|
12
|
-
python_package_folder/version.py,sha256=kIDP6S9trEfs9gj7lBYGxrWm4RPssRla24UtlO9Jkh4,9111
|
|
13
|
-
python_package_folder-1.1.3.dist-info/METADATA,sha256=7dD2K5qmYNEBJS8m6i-I5_lrZeAm_jab21rULqHp2mw,28134
|
|
14
|
-
python_package_folder-1.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
-
python_package_folder-1.1.3.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
|
|
16
|
-
python_package_folder-1.1.3.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
|
|
17
|
-
python_package_folder-1.1.3.dist-info/RECORD,,
|
|
File without changes
|
{python_package_folder-1.1.3.dist-info → python_package_folder-1.2.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_package_folder-1.1.3.dist-info → python_package_folder-1.2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|