python-package-folder 1.2.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_package_folder-1.2.3/PKG-INFO +23 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/manager.py +75 -2
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/publisher.py +39 -34
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/subfolder_build.py +59 -24
- python_package_folder-1.2.3/tests/folder_structure/subfolder_to_build/README.md +3 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_build_with_external_deps.py +30 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_subfolder_build.py +48 -7
- python_package_folder-1.2.1/PKG-INFO +0 -870
- python_package_folder-1.2.1/README.md +0 -850
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.copier-answers.yml +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.github/workflows/ci.yml +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.github/workflows/publish.yml +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.gitignore +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/.vscode/settings.json +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/LICENSE +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/Makefile +0 -0
- {python_package_folder-1.2.1/tests/folder_structure/subfolder_to_build → python_package_folder-1.2.3}/README.md +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/coverage.svg +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/development.md +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/installation.md +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/publishing.md +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/pyproject.toml +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/types.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/version.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_linting.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_publisher.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_utils.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_version_manager.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/tests.py +0 -0
- {python_package_folder-1.2.1 → python_package_folder-1.2.3}/uv.lock +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-package-folder
|
|
3
|
+
Version: 1.2.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
|
+
# Example subfolder
|
|
22
|
+
|
|
23
|
+
Example subfolder built and published with https://github.com/alelom/python-package-folder.
|
{python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/manager.py
RENAMED
|
@@ -415,7 +415,9 @@ class BuildManager:
|
|
|
415
415
|
|
|
416
416
|
This method removes all files and directories that were copied during prepare_build().
|
|
417
417
|
It also restores the original pyproject.toml if a temporary one was created for a
|
|
418
|
-
subfolder build.
|
|
418
|
+
subfolder build. Additionally, it removes all .egg-info directories created during
|
|
419
|
+
the build process and cleans up any empty directories that remain after removing
|
|
420
|
+
copied files. It handles errors gracefully and clears the internal tracking lists.
|
|
419
421
|
|
|
420
422
|
This is automatically called by run_build(), but you can call it manually if you
|
|
421
423
|
use prepare_build() directly.
|
|
@@ -425,7 +427,7 @@ class BuildManager:
|
|
|
425
427
|
manager = BuildManager(project_root=Path("."), src_dir=Path("src"))
|
|
426
428
|
deps = manager.prepare_build()
|
|
427
429
|
# ... do your build ...
|
|
428
|
-
manager.cleanup() # Restores pyproject.toml
|
|
430
|
+
manager.cleanup() # Restores pyproject.toml, removes copied files, .egg-info dirs, and empty dirs
|
|
429
431
|
```
|
|
430
432
|
"""
|
|
431
433
|
# Restore subfolder config if it was created
|
|
@@ -458,6 +460,77 @@ class BuildManager:
|
|
|
458
460
|
self.copied_files.clear()
|
|
459
461
|
self.copied_dirs.clear()
|
|
460
462
|
|
|
463
|
+
# Remove all .egg-info directories in src_dir and project_root
|
|
464
|
+
self._cleanup_egg_info_dirs()
|
|
465
|
+
|
|
466
|
+
# Remove empty directories that may remain after cleanup
|
|
467
|
+
self._cleanup_empty_dirs()
|
|
468
|
+
|
|
469
|
+
def _cleanup_egg_info_dirs(self) -> None:
|
|
470
|
+
"""
|
|
471
|
+
Remove all .egg-info directories in the source directory and project root.
|
|
472
|
+
|
|
473
|
+
These directories are created by setuptools during the build process and
|
|
474
|
+
should be cleaned up after the build completes.
|
|
475
|
+
"""
|
|
476
|
+
# Search in src_dir and project_root
|
|
477
|
+
search_dirs = [self.src_dir, self.project_root]
|
|
478
|
+
|
|
479
|
+
for search_dir in search_dirs:
|
|
480
|
+
if not search_dir.exists():
|
|
481
|
+
continue
|
|
482
|
+
|
|
483
|
+
# Find all .egg-info directories
|
|
484
|
+
for egg_info_dir in search_dir.rglob("*.egg-info"):
|
|
485
|
+
if egg_info_dir.is_dir():
|
|
486
|
+
try:
|
|
487
|
+
shutil.rmtree(egg_info_dir)
|
|
488
|
+
print(f"Removed .egg-info directory: {egg_info_dir}")
|
|
489
|
+
except Exception as e:
|
|
490
|
+
print(
|
|
491
|
+
f"Warning: Could not remove .egg-info directory {egg_info_dir}: {e}",
|
|
492
|
+
file=sys.stderr,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
def _cleanup_empty_dirs(self) -> None:
|
|
496
|
+
"""
|
|
497
|
+
Remove empty directories in the source directory after cleanup.
|
|
498
|
+
|
|
499
|
+
After removing copied files and directories, some parent directories may
|
|
500
|
+
become empty. This method recursively removes empty directories starting
|
|
501
|
+
from the deepest level.
|
|
502
|
+
"""
|
|
503
|
+
if not self.src_dir.exists():
|
|
504
|
+
return
|
|
505
|
+
|
|
506
|
+
# Collect all directories in src_dir, sorted by depth (deepest first)
|
|
507
|
+
all_dirs: list[Path] = []
|
|
508
|
+
for item in self.src_dir.rglob("*"):
|
|
509
|
+
if item.is_dir():
|
|
510
|
+
all_dirs.append(item)
|
|
511
|
+
|
|
512
|
+
# Sort by path depth (deepest first) so we remove children before parents
|
|
513
|
+
all_dirs.sort(key=lambda p: len(p.parts), reverse=True)
|
|
514
|
+
|
|
515
|
+
# Remove empty directories (but not src_dir itself)
|
|
516
|
+
for dir_path in all_dirs:
|
|
517
|
+
if dir_path == self.src_dir:
|
|
518
|
+
continue
|
|
519
|
+
|
|
520
|
+
try:
|
|
521
|
+
# Check if directory is empty
|
|
522
|
+
if dir_path.exists() and not any(dir_path.iterdir()):
|
|
523
|
+
dir_path.rmdir()
|
|
524
|
+
print(f"Removed empty directory: {dir_path}")
|
|
525
|
+
except (OSError, PermissionError):
|
|
526
|
+
# Directory not empty or permission error - skip it
|
|
527
|
+
pass
|
|
528
|
+
except Exception as e:
|
|
529
|
+
print(
|
|
530
|
+
f"Warning: Could not remove directory {dir_path}: {e}",
|
|
531
|
+
file=sys.stderr,
|
|
532
|
+
)
|
|
533
|
+
|
|
461
534
|
def run_build(
|
|
462
535
|
self,
|
|
463
536
|
build_command: Callable[[], None],
|
{python_package_folder-1.2.1 → python_package_folder-1.2.3}/src/python_package_folder/publisher.py
RENAMED
|
@@ -41,12 +41,15 @@ class Publisher:
|
|
|
41
41
|
This class manages the publishing process, including credential handling
|
|
42
42
|
and repository configuration. It uses twine under the hood for actual publishing.
|
|
43
43
|
|
|
44
|
+
Credentials are not stored - they must be provided via command-line arguments
|
|
45
|
+
or will be prompted each time. This ensures credentials are not persisted.
|
|
46
|
+
|
|
44
47
|
Attributes:
|
|
45
48
|
repository: Target repository for publishing
|
|
46
49
|
dist_dir: Directory containing built distribution files
|
|
47
50
|
repository_url: Custom repository URL (for Azure or custom PyPI servers)
|
|
48
|
-
username: Username for authentication (optional,
|
|
49
|
-
password: Password/token for authentication (optional,
|
|
51
|
+
username: Username for authentication (optional, will be prompted if not provided)
|
|
52
|
+
password: Password/token for authentication (optional, will be prompted if not provided)
|
|
50
53
|
"""
|
|
51
54
|
|
|
52
55
|
def __init__(
|
|
@@ -109,8 +112,9 @@ class Publisher:
|
|
|
109
112
|
"""
|
|
110
113
|
Get credentials for publishing.
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
Always prompts for username and password/token if not already provided.
|
|
116
|
+
Does not use keyring to store/retrieve credentials - credentials must be
|
|
117
|
+
provided via command-line arguments or will be prompted each time.
|
|
114
118
|
|
|
115
119
|
Returns:
|
|
116
120
|
Tuple of (username, password/token)
|
|
@@ -118,24 +122,8 @@ class Publisher:
|
|
|
118
122
|
username = self.username
|
|
119
123
|
password = self.password
|
|
120
124
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
username = keyring.get_password(
|
|
125
|
-
f"python-package-folder-{self.repository.value}", "username"
|
|
126
|
-
)
|
|
127
|
-
except Exception:
|
|
128
|
-
pass
|
|
129
|
-
|
|
130
|
-
if keyring and not password:
|
|
131
|
-
try:
|
|
132
|
-
password = keyring.get_password(
|
|
133
|
-
f"python-package-folder-{self.repository.value}", username or "token"
|
|
134
|
-
)
|
|
135
|
-
except Exception:
|
|
136
|
-
pass
|
|
137
|
-
|
|
138
|
-
# Prompt if still not available
|
|
125
|
+
# Always prompt if not provided via command-line arguments
|
|
126
|
+
# We don't use keyring to avoid storing credentials
|
|
139
127
|
if not username:
|
|
140
128
|
username = input(f"Enter username for {self.repository.value}: ").strip()
|
|
141
129
|
if not username:
|
|
@@ -160,18 +148,7 @@ class Publisher:
|
|
|
160
148
|
)
|
|
161
149
|
username = "__token__"
|
|
162
150
|
|
|
163
|
-
#
|
|
164
|
-
if keyring:
|
|
165
|
-
try:
|
|
166
|
-
keyring.set_password(
|
|
167
|
-
f"python-package-folder-{self.repository.value}", "username", username
|
|
168
|
-
)
|
|
169
|
-
keyring.set_password(
|
|
170
|
-
f"python-package-folder-{self.repository.value}", username, password
|
|
171
|
-
)
|
|
172
|
-
except Exception:
|
|
173
|
-
# Keyring storage is optional, continue if it fails
|
|
174
|
-
pass
|
|
151
|
+
# Do not store in keyring - credentials are not persisted
|
|
175
152
|
|
|
176
153
|
return username, password
|
|
177
154
|
|
|
@@ -253,6 +230,9 @@ class Publisher:
|
|
|
253
230
|
cmd = ["twine", "upload"]
|
|
254
231
|
if skip_existing:
|
|
255
232
|
cmd.append("--skip-existing")
|
|
233
|
+
# Always use verbose for Azure Artifacts to get better error details
|
|
234
|
+
if self.repository == Repository.AZURE:
|
|
235
|
+
cmd.append("--verbose")
|
|
256
236
|
cmd.extend(["--repository-url", repo_url])
|
|
257
237
|
cmd.extend(["--username", username])
|
|
258
238
|
cmd.extend(["--password", password])
|
|
@@ -284,6 +264,31 @@ class Publisher:
|
|
|
284
264
|
self.password = None
|
|
285
265
|
self.publish(skip_existing=skip_existing)
|
|
286
266
|
|
|
267
|
+
def clear_stored_credentials(self) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Clear any stored credentials from keyring for this repository.
|
|
270
|
+
|
|
271
|
+
This method can be used to remove previously stored credentials.
|
|
272
|
+
Note: The current implementation does not store credentials, but this
|
|
273
|
+
method is provided for compatibility and to clear any old stored credentials.
|
|
274
|
+
"""
|
|
275
|
+
if keyring:
|
|
276
|
+
try:
|
|
277
|
+
service_name = f"python-package-folder-{self.repository.value}"
|
|
278
|
+
# Try to get and delete stored username
|
|
279
|
+
stored_username = keyring.get_password(service_name, "username")
|
|
280
|
+
if stored_username:
|
|
281
|
+
try:
|
|
282
|
+
keyring.delete_password(service_name, stored_username)
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
try:
|
|
286
|
+
keyring.delete_password(service_name, "username")
|
|
287
|
+
except Exception:
|
|
288
|
+
pass
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
|
|
287
292
|
|
|
288
293
|
def get_repository_help() -> str:
|
|
289
294
|
"""
|
|
@@ -36,6 +36,8 @@ class SubfolderBuildConfig:
|
|
|
36
36
|
- Always ensures [build-system] section uses hatchling (replaces any existing build-system
|
|
37
37
|
configuration from parent or subfolder)
|
|
38
38
|
- Handles README files similarly (uses subfolder README if present)
|
|
39
|
+
- **Never modifies the root pyproject.toml**: The original file is temporarily moved to
|
|
40
|
+
pyproject.toml.original and restored after the build, ensuring the original is never modified
|
|
39
41
|
"""
|
|
40
42
|
|
|
41
43
|
def __init__(
|
|
@@ -66,6 +68,7 @@ class SubfolderBuildConfig:
|
|
|
66
68
|
self.dependency_group = dependency_group
|
|
67
69
|
self.temp_pyproject: Path | None = None
|
|
68
70
|
self.original_pyproject_backup: Path | None = None
|
|
71
|
+
self.original_pyproject_path: Path | None = None
|
|
69
72
|
self._temp_init_created = False
|
|
70
73
|
self.temp_readme: Path | None = None
|
|
71
74
|
self.original_readme_backup: Path | None = None
|
|
@@ -205,6 +208,10 @@ class SubfolderBuildConfig:
|
|
|
205
208
|
The [build-system] section is always set to use hatchling, even if the parent or
|
|
206
209
|
subfolder pyproject.toml uses a different build backend (e.g., setuptools).
|
|
207
210
|
|
|
211
|
+
**Important**: The root pyproject.toml is never modified. Instead, it is temporarily
|
|
212
|
+
moved to pyproject.toml.original and restored after the build completes. This ensures
|
|
213
|
+
the original file remains unchanged.
|
|
214
|
+
|
|
208
215
|
Returns:
|
|
209
216
|
Path to the pyproject.toml file (either from subfolder or created temporary),
|
|
210
217
|
or None if no parent pyproject.toml exists (in which case subfolder config is skipped)
|
|
@@ -228,20 +235,30 @@ class SubfolderBuildConfig:
|
|
|
228
235
|
print(f"Using existing pyproject.toml from subfolder: {subfolder_pyproject}")
|
|
229
236
|
self._used_subfolder_pyproject = True
|
|
230
237
|
|
|
231
|
-
#
|
|
238
|
+
# Store reference to original project root pyproject.toml
|
|
232
239
|
original_pyproject = self.project_root / "pyproject.toml"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
self.original_pyproject_path = original_pyproject
|
|
241
|
+
|
|
242
|
+
# Create temporary pyproject.toml file
|
|
243
|
+
temp_pyproject_path = self.project_root / "pyproject.toml.temp"
|
|
237
244
|
|
|
238
245
|
# Read and adjust the subfolder pyproject.toml
|
|
239
246
|
subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
|
|
240
247
|
# Adjust packages path to be relative to project root
|
|
241
248
|
adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
|
|
242
249
|
|
|
243
|
-
# Write adjusted content to
|
|
244
|
-
|
|
250
|
+
# Write adjusted content to temporary file
|
|
251
|
+
temp_pyproject_path.write_text(adjusted_content, encoding="utf-8")
|
|
252
|
+
self.temp_pyproject = temp_pyproject_path
|
|
253
|
+
|
|
254
|
+
# If original pyproject.toml exists, temporarily move it
|
|
255
|
+
if original_pyproject.exists():
|
|
256
|
+
backup_path = self.project_root / "pyproject.toml.original"
|
|
257
|
+
original_pyproject.rename(backup_path)
|
|
258
|
+
self.original_pyproject_backup = backup_path
|
|
259
|
+
|
|
260
|
+
# Move temp file to pyproject.toml for the build
|
|
261
|
+
temp_pyproject_path.rename(original_pyproject)
|
|
245
262
|
self.temp_pyproject = original_pyproject
|
|
246
263
|
|
|
247
264
|
# Handle README file
|
|
@@ -270,9 +287,12 @@ class SubfolderBuildConfig:
|
|
|
270
287
|
|
|
271
288
|
original_content = original_pyproject.read_text(encoding="utf-8")
|
|
272
289
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
290
|
+
# Store reference to original
|
|
291
|
+
self.original_pyproject_path = original_pyproject
|
|
292
|
+
|
|
293
|
+
# Temporarily move original to backup location
|
|
294
|
+
backup_path = self.project_root / "pyproject.toml.original"
|
|
295
|
+
original_pyproject.rename(backup_path)
|
|
276
296
|
self.original_pyproject_backup = backup_path
|
|
277
297
|
|
|
278
298
|
# Parse and modify the pyproject.toml
|
|
@@ -327,8 +347,12 @@ class SubfolderBuildConfig:
|
|
|
327
347
|
original_content, parent_dependency_group
|
|
328
348
|
)
|
|
329
349
|
|
|
330
|
-
# Write the modified content
|
|
331
|
-
|
|
350
|
+
# Write the modified content to a temporary file
|
|
351
|
+
temp_pyproject_path = self.project_root / "pyproject.toml.temp"
|
|
352
|
+
temp_pyproject_path.write_text(modified_content, encoding="utf-8")
|
|
353
|
+
|
|
354
|
+
# Move temp file to pyproject.toml for the build
|
|
355
|
+
temp_pyproject_path.rename(original_pyproject)
|
|
332
356
|
self.temp_pyproject = original_pyproject
|
|
333
357
|
|
|
334
358
|
# Handle README file
|
|
@@ -585,9 +609,10 @@ class SubfolderBuildConfig:
|
|
|
585
609
|
"""
|
|
586
610
|
Restore the original pyproject.toml and remove temporary __init__.py if created.
|
|
587
611
|
|
|
588
|
-
|
|
589
|
-
pyproject.toml
|
|
590
|
-
restores the original
|
|
612
|
+
The root pyproject.toml is never modified during subfolder builds. Instead, it is
|
|
613
|
+
temporarily moved to pyproject.toml.original and then restored after the build.
|
|
614
|
+
This method removes the temporary pyproject.toml and restores the original from
|
|
615
|
+
the backup location, ensuring the original file is never modified.
|
|
591
616
|
"""
|
|
592
617
|
# Remove temporary __init__.py if we created it
|
|
593
618
|
if self._temp_init_created:
|
|
@@ -630,16 +655,26 @@ class SubfolderBuildConfig:
|
|
|
630
655
|
self.temp_readme = None
|
|
631
656
|
|
|
632
657
|
# Restore original pyproject.toml (only if we created/used one)
|
|
633
|
-
if
|
|
634
|
-
self.
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
658
|
+
if self.temp_pyproject and self.original_pyproject_path:
|
|
659
|
+
original_pyproject = self.original_pyproject_path
|
|
660
|
+
|
|
661
|
+
# Remove the temporary pyproject.toml we created
|
|
662
|
+
if original_pyproject.exists():
|
|
663
|
+
try:
|
|
664
|
+
original_pyproject.unlink()
|
|
665
|
+
except Exception as e:
|
|
666
|
+
print(
|
|
667
|
+
f"Warning: Could not remove temporary pyproject.toml: {e}",
|
|
668
|
+
file=sys.stderr,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
# Restore the original pyproject.toml from backup if it existed
|
|
672
|
+
if self.original_pyproject_backup and self.original_pyproject_backup.exists():
|
|
673
|
+
self.original_pyproject_backup.rename(original_pyproject)
|
|
674
|
+
self.original_pyproject_backup = None
|
|
675
|
+
|
|
642
676
|
self.temp_pyproject = None
|
|
677
|
+
self.original_pyproject_path = None
|
|
643
678
|
self._used_subfolder_pyproject = False
|
|
644
679
|
|
|
645
680
|
def __enter__(self) -> Self:
|
{python_package_folder-1.2.1 → python_package_folder-1.2.3}/tests/test_build_with_external_deps.py
RENAMED
|
@@ -277,6 +277,36 @@ class TestBuildManager:
|
|
|
277
277
|
assert len(manager.copied_files) == 0
|
|
278
278
|
assert len(manager.copied_dirs) == 0
|
|
279
279
|
|
|
280
|
+
def test_cleanup_removes_egg_info_dirs(self, test_project_root: Path) -> None:
|
|
281
|
+
"""Test that cleanup removes .egg-info directories."""
|
|
282
|
+
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
283
|
+
manager = BuildManager(test_project_root, src_dir)
|
|
284
|
+
|
|
285
|
+
# Create a fake .egg-info directory
|
|
286
|
+
egg_info_dir = src_dir / "package.egg-info"
|
|
287
|
+
egg_info_dir.mkdir()
|
|
288
|
+
(egg_info_dir / "PKG-INFO").write_text("test")
|
|
289
|
+
|
|
290
|
+
manager.cleanup()
|
|
291
|
+
|
|
292
|
+
# Verify .egg-info directory was removed
|
|
293
|
+
assert not egg_info_dir.exists()
|
|
294
|
+
|
|
295
|
+
def test_cleanup_removes_empty_dirs(self, test_project_root: Path) -> None:
|
|
296
|
+
"""Test that cleanup removes empty directories."""
|
|
297
|
+
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
298
|
+
manager = BuildManager(test_project_root, src_dir)
|
|
299
|
+
|
|
300
|
+
# Create a nested empty directory structure
|
|
301
|
+
empty_dir = src_dir / "empty_parent" / "empty_child"
|
|
302
|
+
empty_dir.mkdir(parents=True)
|
|
303
|
+
|
|
304
|
+
manager.cleanup()
|
|
305
|
+
|
|
306
|
+
# Verify empty directories were removed
|
|
307
|
+
assert not empty_dir.exists()
|
|
308
|
+
assert not (src_dir / "empty_parent").exists()
|
|
309
|
+
|
|
280
310
|
def test_cleanup_handles_missing_files(self, test_project_root: Path) -> None:
|
|
281
311
|
"""Test that cleanup handles already-removed files gracefully."""
|
|
282
312
|
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
@@ -169,7 +169,7 @@ class TestSubfolderBuildConfig:
|
|
|
169
169
|
assert restored_content == original_content
|
|
170
170
|
|
|
171
171
|
# Check backup is removed
|
|
172
|
-
assert not (test_project_with_pyproject / "pyproject.toml.
|
|
172
|
+
assert not (test_project_with_pyproject / "pyproject.toml.original").exists()
|
|
173
173
|
|
|
174
174
|
def test_restore_removes_temp_init(self, test_project_with_pyproject: Path) -> None:
|
|
175
175
|
"""Test that restore removes temporary __init__.py."""
|
|
@@ -405,9 +405,9 @@ requests = ">=2.0.0"
|
|
|
405
405
|
assert 'name = "test-package"' not in content
|
|
406
406
|
assert 'name = "subfolder"' not in content
|
|
407
407
|
|
|
408
|
-
# Verify
|
|
409
|
-
assert (project_root / "pyproject.toml.
|
|
410
|
-
backup_content = (project_root / "pyproject.toml.
|
|
408
|
+
# Verify original was moved to backup location
|
|
409
|
+
assert (project_root / "pyproject.toml.original").exists()
|
|
410
|
+
backup_content = (project_root / "pyproject.toml.original").read_text()
|
|
411
411
|
assert 'name = "test-package"' in backup_content
|
|
412
412
|
|
|
413
413
|
# Verify flag is set
|
|
@@ -434,6 +434,11 @@ requests = ">=2.0.0"
|
|
|
434
434
|
)
|
|
435
435
|
|
|
436
436
|
config.create_temp_pyproject()
|
|
437
|
+
|
|
438
|
+
# Verify original content is preserved in backup (not modified)
|
|
439
|
+
backup_content = (project_root / "pyproject.toml.original").read_text()
|
|
440
|
+
assert backup_content == original_content
|
|
441
|
+
|
|
437
442
|
config.restore()
|
|
438
443
|
|
|
439
444
|
# Verify original is restored
|
|
@@ -441,7 +446,43 @@ requests = ">=2.0.0"
|
|
|
441
446
|
assert restored_content == original_content
|
|
442
447
|
|
|
443
448
|
# Verify backup is removed
|
|
444
|
-
assert not (project_root / "pyproject.toml.
|
|
449
|
+
assert not (project_root / "pyproject.toml.original").exists()
|
|
450
|
+
|
|
451
|
+
def test_root_pyproject_toml_never_modified(self, test_project_with_pyproject: Path) -> None:
|
|
452
|
+
"""Test that root pyproject.toml is never modified, only moved and restored."""
|
|
453
|
+
project_root = test_project_with_pyproject
|
|
454
|
+
subfolder = project_root / "subfolder"
|
|
455
|
+
original_pyproject = project_root / "pyproject.toml"
|
|
456
|
+
original_content = original_pyproject.read_text()
|
|
457
|
+
|
|
458
|
+
# Create pyproject.toml in subfolder
|
|
459
|
+
(subfolder / "pyproject.toml").write_text(
|
|
460
|
+
'[project]\nname = "subfolder-package"\nversion = "3.0.0"\n'
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
config = SubfolderBuildConfig(
|
|
464
|
+
project_root=project_root,
|
|
465
|
+
src_dir=subfolder,
|
|
466
|
+
version="1.0.0",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
config.create_temp_pyproject()
|
|
470
|
+
|
|
471
|
+
# Verify original was moved (not modified in place)
|
|
472
|
+
assert not original_pyproject.exists() or original_pyproject.read_text() != original_content
|
|
473
|
+
assert (project_root / "pyproject.toml.original").exists()
|
|
474
|
+
backup_content = (project_root / "pyproject.toml.original").read_text()
|
|
475
|
+
assert backup_content == original_content # Original content preserved exactly
|
|
476
|
+
|
|
477
|
+
config.restore()
|
|
478
|
+
|
|
479
|
+
# Verify original is restored with exact same content
|
|
480
|
+
assert original_pyproject.exists()
|
|
481
|
+
restored_content = original_pyproject.read_text()
|
|
482
|
+
assert restored_content == original_content
|
|
483
|
+
|
|
484
|
+
# Verify backup is removed
|
|
485
|
+
assert not (project_root / "pyproject.toml.original").exists()
|
|
445
486
|
|
|
446
487
|
def test_subfolder_pyproject_toml_without_parent_backup(
|
|
447
488
|
self, test_project_with_pyproject: Path
|
|
@@ -477,7 +518,7 @@ version = "3.0.0"
|
|
|
477
518
|
assert 'name = "subfolder-package"' in content
|
|
478
519
|
|
|
479
520
|
# No backup should be created since parent didn't exist
|
|
480
|
-
assert not (project_root / "pyproject.toml.
|
|
521
|
+
assert not (project_root / "pyproject.toml.original").exists()
|
|
481
522
|
|
|
482
523
|
# Restore original for cleanup
|
|
483
524
|
parent_pyproject.write_text(original_content)
|
|
@@ -596,7 +637,7 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
|
|
|
596
637
|
assert 'packages = ["subfolder"]' in content or '"subfolder"' in content
|
|
597
638
|
|
|
598
639
|
# Verify backup was created
|
|
599
|
-
assert (project_root / "pyproject.toml.
|
|
640
|
+
assert (project_root / "pyproject.toml.original").exists()
|
|
600
641
|
|
|
601
642
|
# Cleanup
|
|
602
643
|
config.restore()
|