python-package-folder 1.1.3__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.
@@ -0,0 +1,795 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-package-folder
3
+ Version: 1.1.3
4
+ Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
5
+ Project-URL: Repository, https://github.com/alelom/python-package-folder
6
+ Author-email: Alessio Lombardi <work@alelom.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: <4.0,>=3.11
19
+ Description-Content-Type: text/markdown
20
+
21
+ # python-package-folder <!-- omit from toc -->
22
+
23
+ [![Tests](https://github.com/alelom/python-package-folder/actions/workflows/ci.yml/badge.svg)](https://github.com/alelom/python-package-folder/actions/workflows/ci.yml)
24
+ [![Coverage](https://raw.githubusercontent.com/alelom/python-package-folder/main/coverage.svg)](https://github.com/alelom/python-package-folder)
25
+
26
+ Easily build and publish any target folder in a repository, including subfolders of a monorepo.
27
+ Together with [sysappend](https://pypi.org/project/sysappend/), this library makes relative imports, flexible import management, and package publishing a breeze.
28
+
29
+ - [Use Cases](#use-cases)
30
+ - [Features](#features)
31
+ - [Installation and requirements](#installation-and-requirements)
32
+ - [Quick Start](#quick-start)
33
+ - [How does `python-package-folder` work?](#how-does-python-package-folder-work)
34
+ - [Python API Usage](#python-api-usage)
35
+ - [Working with sysappend](#working-with-sysappend)
36
+ - [Publishing version Management](#publishing-version-management)
37
+ - [Publishing Packages](#publishing-packages)
38
+ - [Command Line Options](#command-line-options)
39
+ - [API Reference](#api-reference)
40
+ - [Development](#development)
41
+
42
+
43
+ ## Use Cases
44
+
45
+ ### 1) Publishing a Subfolder from src/ in a Monorepo
46
+
47
+ If you have a monorepo structure with multiple packages in `src/`:
48
+
49
+ ```
50
+ project/
51
+ ├── src/
52
+ │ ├── core_package/
53
+ │ │ ├── __init__.py
54
+ │ │ ├── core.py
55
+ │ │ └── README.md
56
+ │ ├── api_package/
57
+ │ │ ├── __init__.py
58
+ │ │ ├── api.py
59
+ │ │ └── README.md
60
+ │ └── utils_package/
61
+ │ ├── __init__.py
62
+ │ ├── utils.py
63
+ │ └── README.md
64
+ ├── shared/
65
+ │ └── common.py
66
+ └── pyproject.toml
67
+ ```
68
+
69
+ You can build and publish any subfolder from `src/` as a standalone package:
70
+
71
+ ```bash
72
+ # Navigate to the subfolder you want to publish
73
+ cd src/api_package
74
+
75
+ # Build and publish to TestPyPI with version 1.2.0
76
+ python-package-folder --publish testpypi --version 1.2.0
77
+
78
+ # Or publish to PyPI with a custom package name
79
+ python-package-folder --publish pypi --version 1.2.0 --package-name "my-api-package"
80
+
81
+ # Include a specific dependency group from the parent pyproject.toml
82
+ python-package-folder --publish pypi --version 1.2.0 --dependency-group "dev"
83
+ ```
84
+
85
+ The tool will automatically:
86
+ 1. Detect the project root (where `pyproject.toml` is located)
87
+ 2. Use `src/api_package` as the source directory
88
+ 3. Copy any external dependencies (like `shared/common.py`) into the package before building
89
+ 4. Use the subfolder's README if present, or create a minimal one
90
+ 5. Create a temporary `pyproject.toml` with the subfolder's package name and version
91
+ 6. Build and publish the package
92
+ 7. Clean up all temporary files and restore the original `pyproject.toml`
93
+
94
+ This is especially useful for monorepos where you want to publish individual packages independently while sharing common code.
95
+
96
+
97
+ ### 2) Building Packages with Shared Code
98
+
99
+ If your project structure looks like this:
100
+
101
+ ```
102
+ project/
103
+ ├── src/
104
+ │ └── my_package/
105
+ │ └── main.py
106
+ ├── shared/
107
+ │ ├── utils.py
108
+ │ └── helpers.py
109
+ └── pyproject.toml
110
+ ```
111
+
112
+ And `main.py` imports from `shared/`:
113
+
114
+ ```python
115
+ from shared.utils import some_function
116
+ from shared.helpers import Helper
117
+ ```
118
+
119
+ This package will automatically:
120
+ 1. Detect that `shared/` is outside `src/`
121
+ 2. Copy `shared/` into `src/` before building
122
+ 3. Build your package with all dependencies included
123
+ 4. Clean up the copied files after build
124
+
125
+
126
+ ## Features
127
+
128
+ - **Subfolder Build Support**: Build subfolders as separate packages with automatic project root detection
129
+ - 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
+ - Automatic package name derivation from subfolder name
131
+ - Dependency group selection: specify which dependency group from parent `pyproject.toml` to include.
132
+
133
+ - **Smart Import Classification and analysis**:
134
+ - Recursively parses all `.py` files to detect `import` and `from ... import ...` statements
135
+ - Handles external dependencies (modules and files that originate from outside the main package directory), and distinguishes standard library imports, 3rd-party packages (from site-packages), local/external/relative/ambiguous imports.
136
+
137
+ - **Idempotent Operations**: Safely handles repeated runs without duplicating files
138
+ - **Build Integration**: Seamlessly integrates with build tools like `uv build`, `pip build`, etc.
139
+ - **Version Management**:
140
+ - Set static versions for publishing (PEP 440 compliant)
141
+ - Temporarily override dynamic versioning during builds
142
+ - Automatic restoration of dynamic versioning after build
143
+ - **Package Publishing**:
144
+ - Uses twine to publish the built folder/subfolder
145
+ - Handles publishing to to PyPI, TestPyPI, or Azure Artifacts, with interactive credential prompts, secure storage support
146
+
147
+
148
+ ## Installation and requirements
149
+
150
+ Python >= 3.11 is required.
151
+
152
+ ```bash
153
+ uv add python-package-folder
154
+
155
+ # or
156
+
157
+ pip install python-package-folder
158
+ ```
159
+
160
+ **Note**: For publishing functionality, you'll also need `twine`:
161
+
162
+ ```bash
163
+ pip install twine
164
+ # or
165
+ uv add twine
166
+ ```
167
+
168
+ **For secure credential storage**: `keyring` is optional but recommended (install with `pip install keyring`)
169
+
170
+
171
+ ## Quick Start
172
+
173
+ The simplest way to use this package is via the command-line interface
174
+
175
+ **Build/publish a specific subfolder in a repository**
176
+
177
+ Useful for monorepos containing many subfolders that may need publishing as stand-alone packages for external usage.
178
+
179
+ ```bash
180
+ # First cd to the specific subfolder
181
+ cd src/subfolder_to_build_and_publish
182
+
183
+ # Build and publish any subdirectory of your repo to TestPyPi (https://test.pypi.org/)
184
+ python-package-folder --publish testpypi --version 0.0.2
185
+
186
+ # Only analyse (no building)
187
+ cd src/subfolder_to_build_and_publish
188
+ python-package-folder --analyze-only
189
+
190
+ # Only build
191
+ cd src/subfolder_to_build_and_publish
192
+ python-package-folder
193
+
194
+ # Build with automatic dependency management
195
+ python-package-folder --build-command "uv build"
196
+ ```
197
+
198
+ You can also target a specific subfolder via commandline, rather than `cd`ing there:
199
+
200
+ ```python
201
+ # Specify custom project root and source directory
202
+ python-package-folder --project-root /path/to/project --src-dir /path/to/src --build-command "pip build"
203
+ ```
204
+
205
+ ## How does `python-package-folder` work?
206
+
207
+
208
+ ### Build Process
209
+
210
+ 1. **Import Extraction**: Uses Python's AST module to parse all `.py` files and extract import statements
211
+ 2. **Classification**: Each import is classified as:
212
+ - **stdlib**: Standard library modules
213
+ - **third_party**: Packages installed in site-packages
214
+ - **local**: Modules within the source directory
215
+ - **external**: Modules outside source directory but in the project
216
+ - **ambiguous**: Cannot be resolved
217
+ 3. **Dependency Resolution**: For external imports, the tool resolves the file path by checking:
218
+ - Parent directories of the source directory
219
+ - Project root and its subdirectories
220
+ - Relative import paths
221
+ 4. **File Copying**: External dependencies are temporarily copied into the source directory
222
+ 5. **Build Execution**: Your build command runs with all dependencies in place
223
+ 6. **Cleanup**: All temporarily copied files are removed after build
224
+
225
+ ### Publishing Process
226
+
227
+ 1. **Build Verification**: Ensures distribution files exist in the `dist/` directory
228
+ 2. **File Filtering**: Automatically filters distribution files to only include those matching the current package name and version (prevents uploading old artifacts)
229
+ 3. **Credential Management**:
230
+ - Prompts for credentials if not provided
231
+ - Uses `keyring` for secure storage (if available)
232
+ - Supports both username/password and API tokens
233
+ - Auto-detects API tokens and uses `__token__` as username
234
+ 4. **Repository Configuration**: Configures the target repository (PyPI, TestPyPI, or Azure)
235
+ 5. **Upload**: Uses `twine` to upload distribution files to the repository
236
+ 6. **Verification**: Confirms successful upload
237
+
238
+ ### Subfolder Build Process
239
+
240
+ 1. **Project Root Detection**: Searches parent directories for `pyproject.toml`
241
+ 2. **Source Directory Detection**: Uses current directory if it contains Python files, otherwise falls back to `project_root/src`
242
+ 3. **Package Initialization**: Creates temporary `__init__.py` if subfolder doesn't have one (required for hatchling)
243
+ 4. **README Handling**:
244
+ - Checks for README files in the subfolder (README.md, README.rst, README.txt, or README)
245
+ - If found, copies the subfolder README to project root (backing up the original parent README)
246
+ - If not found, creates a minimal README with just the folder name
247
+ 5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
248
+ - Subfolder-specific package name (derived or custom)
249
+ - Specified version
250
+ - Correct package path for hatchling
251
+ 6. **Build Execution**: Runs build command with all dependencies in place
252
+ 7. **Cleanup**: Restores original `pyproject.toml` and removes temporary `__init__.py`
253
+
254
+ ### How does building from Subdirectories work?
255
+
256
+ This is useful for monorepos containing many subfolders that may need publishing as stand-alone packages for external usage.
257
+ The tool automatically detects the project root by searching for `pyproject.toml` in parent directories.
258
+ This allows you to build subfolders of a main project as separate packages:
259
+
260
+ ```bash
261
+ # From a subdirectory, the tool will:
262
+ # 1. Find pyproject.toml in parent directories (project root)
263
+ # 2. Use current directory as source if it contains Python files
264
+ # 3. Build with dependencies from the parent project
265
+ # 4. Create a temporary build config with subfolder-specific name and version
266
+
267
+ cd my_project/subfolder_to_build
268
+ python-package-folder --version "1.0.0" --publish pypi
269
+ ```
270
+
271
+ When building from a subdirectory, you **must** specify `--version` because subfolders are built as separate packages with their own version.
272
+
273
+ The tool automatically:
274
+ - Finds the project root by looking for `pyproject.toml` in parent directories
275
+ - Uses the current directory as the source directory if it contains Python files
276
+ - 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
+ - Creates temporary `__init__.py` files if needed to make subfolders valid Python packages
282
+ - **README handling for subfolder builds**:
283
+ - 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
284
+ - If no README exists in the subfolder, a minimal README with just the folder name will be created
285
+ - Restores the original `pyproject.toml` after build (unless `--no-restore-versioning` is used)
286
+ - Cleans up temporary `__init__.py` files after build
287
+
288
+ **Subfolder Build Example:**
289
+ ```bash
290
+ # Build a subfolder as a separate package
291
+ cd tests/folder_structure/subfolder_to_build
292
+ python-package-folder --version "0.1.0" --package-name "my-subfolder-package" --publish pypi
293
+
294
+ # Build with a specific dependency group from parent pyproject.toml
295
+ python-package-folder --version "0.1.0" --dependency-group "dev" --publish pypi
296
+ ```
297
+
298
+ **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:
299
+
300
+ ```bash
301
+ # Use the 'dev' dependency group from parent pyproject.toml
302
+ python-package-folder --version "1.0.0" --dependency-group "dev" --publish pypi
303
+ ```
304
+
305
+ The specified dependency group will be copied from the parent `pyproject.toml`'s `[dependency-groups]` section into the temporary `pyproject.toml` used for the subfolder build.
306
+
307
+ ## Python API Usage
308
+
309
+ You can also use the package programmatically:
310
+
311
+ ```python
312
+ from pathlib import Path
313
+ from python_package_folder import BuildManager
314
+
315
+ # Initialize the build manager
316
+ manager = BuildManager(
317
+ project_root=Path("."),
318
+ src_dir=Path("src")
319
+ )
320
+
321
+ # Prepare build (finds and copies external dependencies)
322
+ external_deps = manager.prepare_build()
323
+
324
+ print(f"Found {len(external_deps)} external dependencies")
325
+ for dep in external_deps:
326
+ print(f" {dep.import_name}: {dep.source_path} -> {dep.target_path}")
327
+
328
+ # Run your build process here
329
+ # ...
330
+
331
+ # Cleanup copied files
332
+ manager.cleanup()
333
+ ```
334
+
335
+ Or use the convenience method:
336
+
337
+ ```python
338
+ from pathlib import Path
339
+ from python_package_folder import BuildManager
340
+ import subprocess
341
+
342
+ manager = BuildManager(project_root=Path("."), src_dir=Path("src"))
343
+
344
+ def build_command():
345
+ subprocess.run(["uv", "build"], check=True)
346
+
347
+ # Automatically handles prepare, build, and cleanup
348
+ manager.run_build(build_command)
349
+ ```
350
+
351
+ ## Working with sysappend
352
+
353
+ This package works well with projects using [sysappend](https://pypi.org/project/sysappend/) for flexible import management. When you have imports like:
354
+
355
+ ```python
356
+ if True:
357
+ import sysappend; sysappend.all()
358
+
359
+ from some_globals import SOME_GLOBAL_VARIABLE
360
+ from folder_structure.utility_folder.some_utility import print_something
361
+ ```
362
+
363
+ The package will correctly identify and copy external dependencies even when they're referenced without full package paths.
364
+
365
+ ## Publishing version Management
366
+
367
+ The package supports both dynamic versioning (from git tags) and manual version specification.
368
+
369
+
370
+ ### Manual Version Setting
371
+
372
+ You can manually set a version before building and publishing:
373
+
374
+ ```bash
375
+ # Build with a specific version
376
+ python-package-folder --version "1.2.3"
377
+
378
+ # Build and publish with a specific version
379
+ python-package-folder --version "1.2.3" --publish pypi
380
+
381
+ # Keep the static version (don't restore dynamic versioning)
382
+ python-package-folder --version "1.2.3" --no-restore-versioning
383
+ ```
384
+
385
+ The `--version` option:
386
+ - Sets a static version in `pyproject.toml` before building
387
+ - Temporarily removes dynamic versioning configuration
388
+ - Restores the original configuration after build (unless `--no-restore-versioning` is used)
389
+ - Validates version format (must be PEP 440 compliant)
390
+
391
+ **Version Format**: Versions must follow PEP 440 (e.g., `1.2.3`, `1.2.3a1`, `1.2.3.post1`, `1.2.3.dev1`)
392
+
393
+
394
+ ### Subfolder Versioning
395
+
396
+ When building from a subdirectory (not the main `src/` directory), you **must** specify `--version`:
397
+
398
+ ```bash
399
+ # Build a subfolder as a separate package
400
+ cd my_project/subfolder_to_build
401
+ python-package-folder --version "1.0.0" --publish pypi
402
+
403
+ # With custom package name
404
+ python-package-folder --version "1.0.0" --package-name "my-custom-name" --publish pypi
405
+ ```
406
+
407
+ 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`)
410
+ - **Temporary configuration**: Creates a temporary `pyproject.toml` with:
411
+ - Custom package name (from `--package-name` or derived)
412
+ - Specified version
413
+ - Correct package path for hatchling
414
+ - Dependency group from parent (if `--dependency-group` is specified)
415
+ - **Package initialization**: Automatically creates `__init__.py` if the subfolder doesn't have one (required for hatchling)
416
+ - **README handling**:
417
+ - If a README file exists in the subfolder, it will be used instead of the parent README
418
+ - If no README exists in the subfolder, a minimal README with just the folder name will be created
419
+ - **Auto-restore**: Original `pyproject.toml` is restored after build, and temporary `__init__.py` files are removed
420
+
421
+
422
+ ### Python API for Version Management
423
+
424
+ ```python
425
+ from python_package_folder import VersionManager
426
+ from pathlib import Path
427
+
428
+ # Set a version
429
+ version_manager = VersionManager(project_root=Path("."))
430
+ version_manager.set_version("1.2.3")
431
+
432
+ # Get current version
433
+ current_version = version_manager.get_current_version()
434
+
435
+ # Restore dynamic versioning
436
+ version_manager.restore_dynamic_versioning()
437
+ ```
438
+
439
+ ### Dynamic Versioning
440
+
441
+ By default, the package uses `uv-dynamic-versioning` which derives versions from git tags. This is configured in `pyproject.toml`:
442
+
443
+ ```toml
444
+ [project]
445
+ dynamic = ["version"]
446
+
447
+ [tool.hatch.version]
448
+ source = "uv-dynamic-versioning"
449
+
450
+ [tool.uv-dynamic-versioning]
451
+ vcs = "git"
452
+ style = "pep440"
453
+ bump = true
454
+ ```
455
+
456
+ When you use `--version`, the package temporarily switches to static versioning for that build, then restores the dynamic configuration.
457
+
458
+ ## Publishing Packages
459
+
460
+ The package includes built-in support for publishing to PyPI, TestPyPI, and Azure Artifacts.
461
+
462
+
463
+ ### Command Line Publishing
464
+
465
+ Publish after building:
466
+
467
+ ```bash
468
+ # Publish to PyPI
469
+ python-package-folder --publish pypi
470
+
471
+ # Publish to PyPI with a specific version
472
+ python-package-folder --version "1.2.3" --publish pypi
473
+
474
+ # Publish to TestPyPI (for testing)
475
+ python-package-folder --publish testpypi
476
+
477
+ # Publish to Azure Artifacts
478
+ python-package-folder --publish azure --repository-url "https://pkgs.dev.azure.com/ORG/PROJECT/_packaging/FEED/pypi/upload"
479
+ ```
480
+
481
+ The command will prompt for credentials if not provided:
482
+
483
+ ```bash
484
+ # Provide credentials via command line (less secure)
485
+ python-package-folder --publish pypi --username __token__ --password pypi-xxxxx
486
+
487
+ # Skip existing files on repository
488
+ python-package-folder --publish pypi --skip-existing
489
+ ```
490
+
491
+
492
+ ### Credentials
493
+
494
+ **For PyPI/TestPyPI:**
495
+ - **Username**: Your PyPI username, or `__token__` for API tokens
496
+ - **Password**: Your PyPI password or API token (recommended)
497
+ - **Auto-detection**: If you provide an API token (starts with `pypi-`), the tool will automatically use `__token__` as the username, even if you entered a different username
498
+
499
+ **Common Authentication Issues:**
500
+ - **403 Forbidden**: Usually means you used your username instead of `__token__` with an API token. The tool now auto-detects this.
501
+ - **TestPyPI vs PyPI**: TestPyPI requires a separate account and token from https://test.pypi.org/manage/account/token/
502
+
503
+
504
+ ### Smart File Filtering
505
+
506
+ When publishing, the tool automatically filters distribution files to only upload those matching the current build:
507
+
508
+ - **Package name matching**: Only uploads files for the package being built
509
+ - **Version matching**: Only uploads files for the specified version
510
+ - **Automatic cleanup**: Old build artifacts in `dist/` are ignored, preventing accidental uploads
511
+
512
+ This ensures that when building a subfolder package, only that package's distribution files are uploaded, not files from previous builds of other packages.
513
+
514
+ To get a PyPI API token:
515
+ 1. Go to https://pypi.org/manage/account/token/
516
+ 2. Create a new API token
517
+ 3. Use `__token__` as username and the token as password
518
+
519
+ **For Azure Artifacts:**
520
+ - **Username**: Your Azure username or feed name
521
+ - **Password**: Personal Access Token (PAT) with packaging permissions
522
+ - **Repository URL**: Your Azure Artifacts feed URL
523
+
524
+
525
+ ### Python API Publishing
526
+
527
+ You can also publish programmatically:
528
+
529
+ ```python
530
+ from pathlib import Path
531
+ from python_package_folder import BuildManager, Publisher, Repository
532
+ import subprocess
533
+
534
+ # Build and publish in one step
535
+ manager = BuildManager(project_root=Path("."), src_dir=Path("src"))
536
+
537
+ def build():
538
+ subprocess.run(["uv", "build"], check=True)
539
+
540
+ manager.build_and_publish(
541
+ build,
542
+ repository="pypi",
543
+ username="__token__",
544
+ password="pypi-xxxxx",
545
+ version="1.2.3" # Optional: set specific version
546
+ )
547
+ ```
548
+ ```
549
+
550
+ Or publish separately:
551
+
552
+ ```python
553
+ from python_package_folder import Publisher, Repository
554
+
555
+ # Publish existing distribution
556
+ publisher = Publisher(
557
+ repository=Repository.PYPI,
558
+ dist_dir=Path("dist"),
559
+ username="__token__",
560
+ password="pypi-xxxxx"
561
+ )
562
+ publisher.publish()
563
+ ```
564
+
565
+
566
+ ### Credential Storage
567
+
568
+ The package uses the `keyring` library (if installed) to securely store credentials. Credentials are stored per repository and will be reused on subsequent runs.
569
+
570
+ Install keyring for secure credential storage:
571
+ ```bash
572
+ pip install keyring
573
+ ```
574
+
575
+ ## Command Line Options
576
+
577
+ ```
578
+ usage: python-package-folder [-h] [--project-root PROJECT_ROOT]
579
+ [--src-dir SRC_DIR] [--analyze-only]
580
+ [--build-command BUILD_COMMAND]
581
+ [--publish {pypi,testpypi,azure}]
582
+ [--repository-url REPOSITORY_URL]
583
+ [--username USERNAME] [--password PASSWORD]
584
+ [--skip-existing]
585
+
586
+ Build Python package with external dependency management
587
+
588
+ options:
589
+ -h, --help show this help message and exit
590
+ --project-root PROJECT_ROOT
591
+ Root directory of the project (default: current directory)
592
+ --src-dir SRC_DIR Source directory (default: project_root/src)
593
+ --analyze-only Only analyze imports, don't run build
594
+ --build-command BUILD_COMMAND
595
+ Command to run for building (default: 'uv build')
596
+ --publish {pypi,testpypi,azure}
597
+ Publish to repository after building
598
+ --repository-url REPOSITORY_URL
599
+ Custom repository URL (required for Azure Artifacts)
600
+ --username USERNAME Username for publishing (will prompt if not provided)
601
+ --password PASSWORD Password/token for publishing (will prompt if not provided)
602
+ --skip-existing Skip files that already exist on the repository
603
+ --version VERSION Set a specific version before building (PEP 440 format).
604
+ Required for subfolder builds.
605
+ --package-name PACKAGE_NAME
606
+ Package name for subfolder builds (default: derived from
607
+ source directory name)
608
+ --dependency-group DEPENDENCY_GROUP
609
+ Dependency group name from parent pyproject.toml to include
610
+ in subfolder build
611
+ --no-restore-versioning
612
+ Don't restore dynamic versioning after build
613
+ ```
614
+
615
+ ## API Reference
616
+
617
+ ### BuildManager
618
+
619
+ Main class for managing the build process with external dependency handling.
620
+
621
+ ```python
622
+ from python_package_folder import BuildManager
623
+ from pathlib import Path
624
+
625
+ manager = BuildManager(
626
+ project_root: Path, # Root directory of the project
627
+ src_dir: Path | None # Source directory (default: project_root/src)
628
+ )
629
+ ```
630
+
631
+ **Methods:**
632
+
633
+ - `prepare_build() -> list[ExternalDependency]`: Find and copy external dependencies
634
+ - `cleanup() -> None`: Remove all copied files and directories
635
+ - `run_build(build_command: Callable[[], None]) -> None`: Run build with automatic prepare and cleanup
636
+
637
+ ### ImportAnalyzer
638
+
639
+ Analyzes Python files to extract and classify import statements.
640
+
641
+ ```python
642
+ from python_package_folder import ImportAnalyzer
643
+ from pathlib import Path
644
+
645
+ analyzer = ImportAnalyzer(project_root=Path("."))
646
+ python_files = analyzer.find_all_python_files(Path("src"))
647
+ imports = analyzer.extract_imports(python_files[0])
648
+ analyzer.classify_import(imports[0], src_dir=Path("src"))
649
+ ```
650
+
651
+ ### ExternalDependencyFinder
652
+
653
+ Finds external dependencies that need to be copied.
654
+
655
+ ```python
656
+ from python_package_folder import ExternalDependencyFinder
657
+ from pathlib import Path
658
+
659
+ finder = ExternalDependencyFinder(
660
+ project_root=Path("."),
661
+ src_dir=Path("src")
662
+ )
663
+ dependencies = finder.find_external_dependencies(python_files)
664
+ ```
665
+
666
+ ### Publisher
667
+
668
+ Publishes built packages to PyPI, TestPyPI, or Azure Artifacts.
669
+
670
+ ```python
671
+ from python_package_folder import Publisher, Repository
672
+ from pathlib import Path
673
+
674
+ publisher = Publisher(
675
+ repository=Repository.PYPI,
676
+ dist_dir=Path("dist"),
677
+ username="__token__",
678
+ password="pypi-xxxxx",
679
+ package_name="my-package", # Optional: filter files by package name
680
+ version="1.2.3" # Optional: filter files by version
681
+ )
682
+ publisher.publish()
683
+ ```
684
+
685
+ **Methods:**
686
+ - `publish(skip_existing: bool = False) -> None`: Publish the package (automatically filters by package_name/version if provided)
687
+ - `publish_interactive(skip_existing: bool = False) -> None`: Publish with interactive credential prompts
688
+
689
+ **Note**: When `package_name` and `version` are provided, only distribution files matching those parameters are uploaded. This prevents uploading old build artifacts.
690
+
691
+ ### VersionManager
692
+
693
+ Manages package version in pyproject.toml.
694
+
695
+ ```python
696
+ from python_package_folder import VersionManager
697
+ from pathlib import Path
698
+
699
+ version_manager = VersionManager(project_root=Path("."))
700
+
701
+ # Set a static version
702
+ version_manager.set_version("1.2.3")
703
+
704
+ # Get current version
705
+ version = version_manager.get_current_version()
706
+
707
+ # Restore dynamic versioning
708
+ version_manager.restore_dynamic_versioning()
709
+ ```
710
+
711
+ **Methods:**
712
+ - `set_version(version: str) -> None`: Set a static version (validates PEP 440 format)
713
+ - `get_current_version() -> str | None`: Get current version from pyproject.toml
714
+ - `restore_dynamic_versioning() -> None`: Restore dynamic versioning configuration
715
+
716
+ ### SubfolderBuildConfig
717
+
718
+ Manages temporary build configuration for subfolder builds.
719
+
720
+ ```python
721
+ from python_package_folder import SubfolderBuildConfig
722
+ from pathlib import Path
723
+
724
+ config = SubfolderBuildConfig(
725
+ project_root=Path("."),
726
+ src_dir=Path("subfolder"),
727
+ package_name="my-subfolder",
728
+ version="1.0.0"
729
+ )
730
+
731
+ # Create temporary pyproject.toml
732
+ config.create_temp_pyproject()
733
+
734
+ # ... build process ...
735
+
736
+ # Restore original configuration
737
+ config.restore()
738
+ ```
739
+
740
+ **Methods:**
741
+ - `create_temp_pyproject() -> Path`: Create temporary `pyproject.toml` with subfolder-specific configuration
742
+ - `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
743
+
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
748
+
749
+
750
+ ## Development
751
+
752
+ ### Setup
753
+
754
+ ```bash
755
+ # Clone the repository
756
+ git clone https://github.com/alelom/python-package-folder.git
757
+ cd python-package-folder
758
+
759
+ # Install dependencies
760
+ uv sync --all-extras
761
+
762
+ # Run tests
763
+ uv run pytest
764
+
765
+ # Run linting
766
+ make lint
767
+ ```
768
+
769
+ ### Project Structure
770
+
771
+ ```
772
+ python-package-folder/
773
+ ├── src/
774
+ │ └── python_package_folder/
775
+ │ ├── __init__.py # Package exports
776
+ │ ├── types.py # Type definitions
777
+ │ ├── analyzer.py # Import analysis
778
+ │ ├── finder.py # Dependency finding
779
+ │ ├── manager.py # Build management
780
+ │ └── python_package_folder.py # CLI entry point
781
+ ├── tests/
782
+ │ ├── test_build_with_external_deps.py
783
+ │ └── folder_structure/ # Test fixtures
784
+ ├── devtools/
785
+ │ └── lint.py # Development tools
786
+ └── pyproject.toml
787
+ ```
788
+
789
+ ## License <!-- omit from toc -->
790
+
791
+ MIT License - see LICENSE file for details
792
+
793
+ ## Contributing <!-- omit from toc -->
794
+
795
+ Contributions are welcome! Please feel free to submit a Pull Request.