python-package-folder 1.1.3__tar.gz → 1.2.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 (42) hide show
  1. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/PKG-INFO +97 -22
  2. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/README.md +96 -21
  3. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/coverage.svg +2 -2
  4. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/analyzer.py +43 -1
  5. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/manager.py +175 -57
  6. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/python_package_folder.py +2 -1
  7. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/subfolder_build.py +187 -13
  8. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/utils.py +7 -5
  9. python_package_folder-1.2.1/tests/test_subfolder_build.py +723 -0
  10. python_package_folder-1.1.3/tests/test_subfolder_build.py +0 -362
  11. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.copier-answers.yml +0 -0
  12. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.cursor/rules/general.mdc +0 -0
  13. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.cursor/rules/python.mdc +0 -0
  14. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.github/workflows/ci.yml +0 -0
  15. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.github/workflows/publish.yml +0 -0
  16. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.gitignore +0 -0
  17. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/.vscode/settings.json +0 -0
  18. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/LICENSE +0 -0
  19. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/Makefile +0 -0
  20. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/development.md +0 -0
  21. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/installation.md +0 -0
  22. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/publishing.md +0 -0
  23. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/pyproject.toml +0 -0
  24. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/__init__.py +0 -0
  25. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/__main__.py +0 -0
  26. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/finder.py +0 -0
  27. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/publisher.py +0 -0
  28. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/py.typed +0 -0
  29. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/types.py +0 -0
  30. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/src/python_package_folder/version.py +0 -0
  31. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/folder_structure/some_globals.py +0 -0
  32. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  33. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  34. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  35. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  36. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/test_build_with_external_deps.py +0 -0
  37. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/test_linting.py +0 -0
  38. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/test_publisher.py +0 -0
  39. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/test_utils.py +0 -0
  40. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/test_version_manager.py +0 -0
  41. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/tests/tests.py +0 -0
  42. {python_package_folder-1.1.3 → python_package_folder-1.2.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 1.1.3
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 project root detection
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
- When building from a subdirectory, you **must** specify `--version` because subfolders are built as separate packages with their own version.
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: creates a temporary `pyproject.toml` with:
278
- - Package name derived from the subfolder name (or use `--package-name` to override)
279
- - Version from `--version` argument
280
- - Proper package path configuration for hatchling
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
- Or use the convenience method:
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), you **must** specify `--version`:
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
- - **Version is required**: The tool will error if `--version` is not provided
409
- - **Package name**: Automatically derived from the subfolder name (e.g., `subfolder_to_build` → `subfolder-to-build`)
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`: Create temporary `pyproject.toml` with subfolder-specific configuration
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 creates `__init__.py` files if needed to make subfolders valid Python packages. It also handles README files:
745
- - If a README exists in the subfolder, it will be used instead of the parent README
746
- - If no README exists in the subfolder, a minimal README with just the folder name will be created
747
- - The original parent README is backed up and restored after the build completes
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
@@ -105,9 +105,11 @@ This package will automatically:
105
105
 
106
106
  ## Features
107
107
 
108
- - **Subfolder Build Support**: Build subfolders as separate packages with automatic project root detection
108
+ - **Subfolder Build Support**: Build subfolders as separate packages with automatic detection and configuration
109
+ - **Automatic subfolder detection**: Detects when building a subfolder (not the main `src/` directory)
109
110
  - 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
110
111
  - Automatic package name derivation from subfolder name
112
+ - Automatic temporary `pyproject.toml` creation with correct package structure
111
113
  - Dependency group selection: specify which dependency group from parent `pyproject.toml` to include.
112
114
 
113
115
  - **Smart Import Classification and analysis**:
@@ -225,6 +227,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
225
227
  - If found, copies the subfolder README to project root (backing up the original parent README)
226
228
  - If not found, creates a minimal README with just the folder name
227
229
  5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
230
+ - `[build-system]` section using hatchling (replaces any existing build-system configuration)
228
231
  - Subfolder-specific package name (derived or custom)
229
232
  - Specified version
230
233
  - Correct package path for hatchling
@@ -248,16 +251,21 @@ cd my_project/subfolder_to_build
248
251
  python-package-folder --version "1.0.0" --publish pypi
249
252
  ```
250
253
 
251
- When building from a subdirectory, you **must** specify `--version` because subfolders are built as separate packages with their own version.
254
+ 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.
252
255
 
253
256
  The tool automatically:
257
+ - **Detects subfolder builds**: Automatically identifies when building from a subdirectory
254
258
  - Finds the project root by looking for `pyproject.toml` in parent directories
255
259
  - Uses the current directory as the source directory if it contains Python files
256
260
  - Falls back to `project_root/src` if the current directory isn't suitable
257
- - For subfolder builds: creates a temporary `pyproject.toml` with:
258
- - Package name derived from the subfolder name (or use `--package-name` to override)
259
- - Version from `--version` argument
260
- - Proper package path configuration for hatchling
261
+ - **For subfolder builds**: Handles `pyproject.toml` configuration:
262
+ - **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
263
+ - **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
264
+ - `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
265
+ - Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
266
+ - Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
267
+ - Proper package path configuration for hatchling
268
+ - Dependency groups from parent `pyproject.toml` if specified
261
269
  - Creates temporary `__init__.py` files if needed to make subfolders valid Python packages
262
270
  - **README handling for subfolder builds**:
263
271
  - 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
@@ -265,6 +273,8 @@ The tool automatically:
265
273
  - Restores the original `pyproject.toml` after build (unless `--no-restore-versioning` is used)
266
274
  - Cleans up temporary `__init__.py` files after build
267
275
 
276
+ **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.
277
+
268
278
  **Subfolder Build Example:**
269
279
  ```bash
270
280
  # Build a subfolder as a separate package
@@ -273,6 +283,11 @@ python-package-folder --version "0.1.0" --package-name "my-subfolder-package" --
273
283
 
274
284
  # Build with a specific dependency group from parent pyproject.toml
275
285
  python-package-folder --version "0.1.0" --dependency-group "dev" --publish pypi
286
+
287
+ # If subfolder has its own pyproject.toml, it will be used automatically
288
+ # (package-name and version arguments are ignored in this case)
289
+ cd src/integration/my_package # assuming my_package/pyproject.toml exists
290
+ python-package-folder --publish pypi
276
291
  ```
277
292
 
278
293
  **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:
@@ -288,6 +303,8 @@ The specified dependency group will be copied from the parent `pyproject.toml`'s
288
303
 
289
304
  You can also use the package programmatically:
290
305
 
306
+ ### Basic Usage
307
+
291
308
  ```python
292
309
  from pathlib import Path
293
310
  from python_package_folder import BuildManager
@@ -308,11 +325,11 @@ for dep in external_deps:
308
325
  # Run your build process here
309
326
  # ...
310
327
 
311
- # Cleanup copied files
328
+ # Cleanup copied files (also restores pyproject.toml if subfolder build)
312
329
  manager.cleanup()
313
330
  ```
314
331
 
315
- Or use the convenience method:
332
+ ### Using the Convenience Method
316
333
 
317
334
  ```python
318
335
  from pathlib import Path
@@ -328,6 +345,55 @@ def build_command():
328
345
  manager.run_build(build_command)
329
346
  ```
330
347
 
348
+ ### Subfolder Builds (Automatic Detection)
349
+
350
+ The tool automatically detects when you're building a subfolder and sets up the appropriate configuration:
351
+
352
+ ```python
353
+ from pathlib import Path
354
+ from python_package_folder import BuildManager
355
+ import subprocess
356
+
357
+ # Building a subfolder - automatic detection!
358
+ manager = BuildManager(
359
+ project_root=Path("."),
360
+ src_dir=Path("src/integration/empty_drawing_detection")
361
+ )
362
+
363
+ def build_command():
364
+ subprocess.run(["uv", "build"], check=True)
365
+
366
+ # prepare_build() automatically:
367
+ # - Detects this is a subfolder build
368
+ # - If pyproject.toml exists in subfolder: uses that file
369
+ # - If no pyproject.toml in subfolder: creates temporary one with package name "empty-drawing-detection"
370
+ # - Uses version "0.0.0" (or pass version="1.0.0" to override) if creating temporary pyproject.toml
371
+ external_deps = manager.prepare_build(version="1.0.0")
372
+
373
+ # Run build - uses the pyproject.toml (either from subfolder or temporary)
374
+ build_command()
375
+
376
+ # Cleanup restores original pyproject.toml and removes copied files
377
+ manager.cleanup()
378
+ ```
379
+
380
+ **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.
381
+
382
+ Or use the convenience method:
383
+
384
+ ```python
385
+ manager = BuildManager(
386
+ project_root=Path("."),
387
+ src_dir=Path("src/integration/empty_drawing_detection")
388
+ )
389
+
390
+ def build_command():
391
+ subprocess.run(["uv", "build"], check=True)
392
+
393
+ # All handled automatically: subfolder detection, pyproject.toml setup, build, cleanup
394
+ manager.run_build(build_command, version="1.0.0", package_name="my-custom-name")
395
+ ```
396
+
331
397
  ## Working with sysappend
332
398
 
333
399
  This package works well with projects using [sysappend](https://pypi.org/project/sysappend/) for flexible import management. When you have imports like:
@@ -373,20 +439,28 @@ The `--version` option:
373
439
 
374
440
  ### Subfolder Versioning
375
441
 
376
- When building from a subdirectory (not the main `src/` directory), you **must** specify `--version`:
442
+ When building from a subdirectory (not the main `src/` directory), the tool automatically detects the subfolder and sets up the build configuration:
377
443
 
378
444
  ```bash
379
- # Build a subfolder as a separate package
445
+ # Build a subfolder as a separate package (version recommended but not required)
380
446
  cd my_project/subfolder_to_build
381
447
  python-package-folder --version "1.0.0" --publish pypi
382
448
 
383
449
  # With custom package name
384
450
  python-package-folder --version "1.0.0" --package-name "my-custom-name" --publish pypi
451
+
452
+ # Version defaults to "0.0.0" if not specified (with a warning)
453
+ python-package-folder --publish pypi
385
454
  ```
386
455
 
387
456
  For subfolder builds:
388
- - **Version is required**: The tool will error if `--version` is not provided
389
- - **Package name**: Automatically derived from the subfolder name (e.g., `subfolder_to_build` → `subfolder-to-build`)
457
+ - **Automatic detection**: The tool automatically detects subfolder builds
458
+ - **pyproject.toml handling**:
459
+ - If `pyproject.toml` exists in subfolder: Uses that file (copied to project root temporarily)
460
+ - If no `pyproject.toml` in subfolder: Creates temporary one with correct package structure
461
+ - **Version**: Recommended but not required when creating temporary pyproject.toml. If not provided, defaults to `0.0.0` with a warning. Ignored if subfolder has its own `pyproject.toml`.
462
+ - **Package name**: Automatically derived from the subfolder name (e.g., `subfolder_to_build` → `subfolder-to-build`). Only used when creating temporary pyproject.toml.
463
+ - **Restoration**: Original `pyproject.toml` is restored after build
390
464
  - **Temporary configuration**: Creates a temporary `pyproject.toml` with:
391
465
  - Custom package name (from `--package-name` or derived)
392
466
  - Specified version
@@ -695,7 +769,8 @@ version_manager.restore_dynamic_versioning()
695
769
 
696
770
  ### SubfolderBuildConfig
697
771
 
698
- Manages temporary build configuration for subfolder builds.
772
+ Manages temporary build configuration for subfolder builds. If a `pyproject.toml` exists
773
+ in the subfolder, it will be used instead of creating a new one.
699
774
 
700
775
  ```python
701
776
  from python_package_folder import SubfolderBuildConfig
@@ -704,11 +779,11 @@ from pathlib import Path
704
779
  config = SubfolderBuildConfig(
705
780
  project_root=Path("."),
706
781
  src_dir=Path("subfolder"),
707
- package_name="my-subfolder",
708
- version="1.0.0"
782
+ package_name="my-subfolder", # Only used if subfolder has no pyproject.toml
783
+ version="1.0.0" # Only used if subfolder has no pyproject.toml
709
784
  )
710
785
 
711
- # Create temporary pyproject.toml
786
+ # Create temporary pyproject.toml (or use subfolder's if it exists)
712
787
  config.create_temp_pyproject()
713
788
 
714
789
  # ... build process ...
@@ -718,13 +793,13 @@ config.restore()
718
793
  ```
719
794
 
720
795
  **Methods:**
721
- - `create_temp_pyproject() -> Path`: Create temporary `pyproject.toml` with subfolder-specific configuration
796
+ - `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
722
797
  - `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
723
798
 
724
- **Note**: This class automatically creates `__init__.py` files if needed to make subfolders valid Python packages. It also handles README files:
725
- - If a README exists in the subfolder, it will be used instead of the parent README
726
- - If no README exists in the subfolder, a minimal README with just the folder name will be created
727
- - The original parent README is backed up and restored after the build completes
799
+ **Note**: This class automatically:
800
+ - **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.
801
+ - **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.
802
+ - **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
728
803
 
729
804
 
730
805
  ## Development
@@ -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">65%</text>
18
- <text x="81" y="14">65%</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>
@@ -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
- return [path for path in directory.rglob("*.py") if path.is_file()]
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
  """