python-package-folder 1.1.0__tar.gz → 1.1.2__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.1.0 → python_package_folder-1.1.2}/PKG-INFO +1 -1
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/finder.py +15 -11
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/manager.py +6 -2
- python_package_folder-1.1.2/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/test_build_with_external_deps.py +175 -6
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.copier-answers.yml +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.github/workflows/ci.yml +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.github/workflows/publish.yml +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.gitignore +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/.vscode/settings.json +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/LICENSE +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/Makefile +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/README.md +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/coverage.svg +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/development.md +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/installation.md +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/publishing.md +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/pyproject.toml +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/types.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/version.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/test_publisher.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/test_subfolder_build.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/test_utils.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/test_version_manager.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/tests.py +0 -0
- {python_package_folder-1.1.0 → python_package_folder-1.1.2}/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
|
+
Version: 1.1.2
|
|
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>
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/finder.py
RENAMED
|
@@ -74,23 +74,25 @@ class ExternalDependencyFinder:
|
|
|
74
74
|
if self._should_exclude_path(source_path):
|
|
75
75
|
continue
|
|
76
76
|
|
|
77
|
-
# For files,
|
|
78
|
-
#
|
|
79
|
-
# 1. It's a package (has __init__.py), OR
|
|
80
|
-
# 2. Files from it are actually imported (which is the case here since source_path is a file)
|
|
77
|
+
# For files, only copy parent directory if it's a package
|
|
78
|
+
# Otherwise, copy just the individual file
|
|
81
79
|
if source_path.is_file():
|
|
82
80
|
parent_dir = source_path.parent
|
|
83
81
|
module_parts = imp.module_name.split(".")
|
|
84
82
|
|
|
85
|
-
#
|
|
83
|
+
# Only copy parent directory if:
|
|
84
|
+
# 1. It's a package (has __init__.py), OR
|
|
85
|
+
# 2. Files from it are actually imported (which is the case here)
|
|
86
|
+
# But only copy the immediate parent, not entire directory trees
|
|
86
87
|
parent_is_package = (parent_dir / "__init__.py").exists()
|
|
87
|
-
|
|
88
|
-
files_in_parent_are_used = True # Since we're processing an import of a file from this parent
|
|
88
|
+
files_are_imported = True # Always true when processing an import
|
|
89
89
|
|
|
90
|
+
# Only copy immediate parent directory, not grandparent directories
|
|
91
|
+
# This prevents copying entire trees like models/Information_extraction
|
|
92
|
+
# when we only need models/Information_extraction/_shared_ie
|
|
90
93
|
should_copy_dir = (
|
|
91
94
|
not self._should_exclude_path(parent_dir)
|
|
92
|
-
and (parent_is_package or
|
|
93
|
-
and len(module_parts) > 2 # Has at least package.module structure
|
|
95
|
+
and (parent_is_package or files_are_imported) # Package OR files imported
|
|
94
96
|
and not parent_dir.is_relative_to(self.src_dir)
|
|
95
97
|
and not self.src_dir.is_relative_to(parent_dir)
|
|
96
98
|
and parent_dir != self.project_root
|
|
@@ -190,8 +192,10 @@ class ExternalDependencyFinder:
|
|
|
190
192
|
"""
|
|
191
193
|
# Check each component of the path
|
|
192
194
|
for part in path.parts:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
for pattern in self.exclude_patterns:
|
|
196
|
+
# Match if part equals pattern or starts with pattern
|
|
197
|
+
if part == pattern or part.startswith(pattern):
|
|
198
|
+
return True
|
|
195
199
|
return False
|
|
196
200
|
|
|
197
201
|
def _find_main_package(self) -> Path | None:
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/manager.py
RENAMED
|
@@ -252,9 +252,13 @@ class BuildManager:
|
|
|
252
252
|
|
|
253
253
|
def should_exclude(path: Path) -> bool:
|
|
254
254
|
"""Check if a path should be excluded."""
|
|
255
|
+
# Check each component of the path
|
|
255
256
|
for part in path.parts:
|
|
256
|
-
if any
|
|
257
|
-
|
|
257
|
+
# Check if any part matches an exclusion pattern
|
|
258
|
+
for pattern in exclude_patterns:
|
|
259
|
+
# Match if part equals pattern or starts with pattern
|
|
260
|
+
if part == pattern or part.startswith(pattern):
|
|
261
|
+
return True
|
|
258
262
|
return False
|
|
259
263
|
|
|
260
264
|
# Create destination directory
|
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/test_build_with_external_deps.py
RENAMED
|
@@ -33,6 +33,12 @@ def test_project_root(tmp_path: Path) -> Path:
|
|
|
33
33
|
(utility_folder / "some_utility.py").write_text(
|
|
34
34
|
"def print_something(to_print: str):\n print(to_print)"
|
|
35
35
|
)
|
|
36
|
+
# Create _SS subdirectory with a file that should be excluded
|
|
37
|
+
ss_dir = utility_folder / "_SS"
|
|
38
|
+
ss_dir.mkdir()
|
|
39
|
+
(ss_dir / "some_superseded_file.py").write_text(
|
|
40
|
+
"def superseded_function():\n pass"
|
|
41
|
+
)
|
|
36
42
|
|
|
37
43
|
# Create subfolder_to_build (target directory)
|
|
38
44
|
subfolder_to_build = folder_structure / "subfolder_to_build"
|
|
@@ -67,11 +73,13 @@ class TestImportAnalyzer:
|
|
|
67
73
|
analyzer = ImportAnalyzer(test_project_root)
|
|
68
74
|
python_files = list(analyzer.find_all_python_files(test_project_root / "folder_structure"))
|
|
69
75
|
|
|
70
|
-
|
|
76
|
+
# Should find all Python files including those in _SS (exclusion happens during dependency finding)
|
|
77
|
+
assert len(python_files) >= 3
|
|
71
78
|
file_names = {f.name for f in python_files}
|
|
72
79
|
assert "some_globals.py" in file_names
|
|
73
80
|
assert "some_function.py" in file_names
|
|
74
81
|
assert "some_utility.py" in file_names
|
|
82
|
+
# Note: some_superseded_file.py in _SS will be found but excluded during dependency resolution
|
|
75
83
|
|
|
76
84
|
def test_extract_imports(self, test_project_root: Path) -> None:
|
|
77
85
|
"""Test extracting imports from a Python file."""
|
|
@@ -226,17 +234,23 @@ class TestBuildManager:
|
|
|
226
234
|
|
|
227
235
|
# First call
|
|
228
236
|
manager.prepare_build()
|
|
229
|
-
count1 = len(manager.copied_files) + len(manager.copied_dirs)
|
|
230
237
|
copied_paths1 = set(manager.copied_files + manager.copied_dirs)
|
|
231
238
|
|
|
232
239
|
# Second call (should not duplicate files, but may have fewer deps since files are now local)
|
|
233
240
|
manager.prepare_build()
|
|
234
|
-
count2 = len(manager.copied_files) + len(manager.copied_dirs)
|
|
235
241
|
copied_paths2 = set(manager.copied_files + manager.copied_dirs)
|
|
236
242
|
|
|
237
|
-
# Idempotency: should
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
# Idempotency: the set of copied paths should be the same (or very similar)
|
|
244
|
+
# Files that were copied in the first call should still be present
|
|
245
|
+
# (they may not be re-copied if idempotency check works, but they should be in the list)
|
|
246
|
+
# The key is that we don't want to see significant divergence
|
|
247
|
+
assert len(copied_paths1) > 0, "First call should copy some files"
|
|
248
|
+
assert len(copied_paths2) > 0, "Second call should have some files"
|
|
249
|
+
|
|
250
|
+
# The paths should be similar (allowing for some variation due to idempotency checks)
|
|
251
|
+
# At minimum, the unique set of paths should be consistent
|
|
252
|
+
assert copied_paths1 == copied_paths2 or copied_paths1.issubset(copied_paths2) or copied_paths2.issubset(copied_paths1), \
|
|
253
|
+
f"Copied paths should be consistent between calls. First: {copied_paths1}, Second: {copied_paths2}"
|
|
240
254
|
|
|
241
255
|
def test_cleanup_removes_copied_files(self, test_project_root: Path) -> None:
|
|
242
256
|
"""Test that cleanup removes all copied files."""
|
|
@@ -378,6 +392,161 @@ class TestRealFolderStructure:
|
|
|
378
392
|
assert (src_dir / "utility_folder") not in manager.copied_dirs
|
|
379
393
|
|
|
380
394
|
|
|
395
|
+
class TestExclusionPatterns:
|
|
396
|
+
"""Tests for exclusion pattern functionality."""
|
|
397
|
+
|
|
398
|
+
def test_exclude_ss_directories(self, test_project_root: Path) -> None:
|
|
399
|
+
"""Test that _SS directories are excluded from copying."""
|
|
400
|
+
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
401
|
+
manager = BuildManager(test_project_root, src_dir)
|
|
402
|
+
|
|
403
|
+
external_deps = manager.prepare_build()
|
|
404
|
+
|
|
405
|
+
# Verify utility_folder was copied
|
|
406
|
+
copied_utility = src_dir / "utility_folder"
|
|
407
|
+
assert copied_utility.exists(), "utility_folder should be copied"
|
|
408
|
+
assert (copied_utility / "some_utility.py").exists(), "some_utility.py should be copied"
|
|
409
|
+
|
|
410
|
+
# Verify _SS directory was NOT copied
|
|
411
|
+
copied_ss = copied_utility / "_SS"
|
|
412
|
+
assert not copied_ss.exists(), "_SS directory should be excluded"
|
|
413
|
+
|
|
414
|
+
manager.cleanup()
|
|
415
|
+
|
|
416
|
+
def test_exclude_ss_directories_real_structure(self, real_test_structure: Path) -> None:
|
|
417
|
+
"""Test exclusion with real folder structure."""
|
|
418
|
+
project_root = real_test_structure.parent.parent
|
|
419
|
+
src_dir = real_test_structure / "subfolder_to_build"
|
|
420
|
+
|
|
421
|
+
if not src_dir.exists():
|
|
422
|
+
pytest.skip("Real test structure not found")
|
|
423
|
+
|
|
424
|
+
manager = BuildManager(project_root, src_dir)
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
external_deps = manager.prepare_build()
|
|
428
|
+
|
|
429
|
+
# Verify utility_folder was copied
|
|
430
|
+
copied_utility = src_dir / "utility_folder"
|
|
431
|
+
if copied_utility.exists():
|
|
432
|
+
# Verify _SS directory was NOT copied
|
|
433
|
+
copied_ss = copied_utility / "_SS"
|
|
434
|
+
assert not copied_ss.exists(), "_SS directory should be excluded from real structure"
|
|
435
|
+
|
|
436
|
+
# Verify the superseded file was NOT copied
|
|
437
|
+
copied_superseded = copied_ss / "some_superseded_file.py"
|
|
438
|
+
assert not copied_superseded.exists(), "Files in _SS should be excluded"
|
|
439
|
+
finally:
|
|
440
|
+
manager.cleanup()
|
|
441
|
+
|
|
442
|
+
def test_exclude_custom_patterns(self, test_project_root: Path) -> None:
|
|
443
|
+
"""Test that custom exclusion patterns work."""
|
|
444
|
+
# Create a directory with a custom exclusion pattern
|
|
445
|
+
folder_structure = test_project_root / "folder_structure"
|
|
446
|
+
custom_excluded = folder_structure / "custom_skip"
|
|
447
|
+
custom_excluded.mkdir()
|
|
448
|
+
(custom_excluded / "skip_file.py").write_text("def skip(): pass")
|
|
449
|
+
|
|
450
|
+
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
451
|
+
manager = BuildManager(
|
|
452
|
+
test_project_root, src_dir, exclude_patterns=["custom_skip"]
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
external_deps = manager.prepare_build()
|
|
456
|
+
|
|
457
|
+
# Verify custom_skip was NOT copied
|
|
458
|
+
copied_custom = src_dir / "custom_skip"
|
|
459
|
+
assert not copied_custom.exists(), "custom_skip should be excluded"
|
|
460
|
+
|
|
461
|
+
manager.cleanup()
|
|
462
|
+
|
|
463
|
+
def test_exclude_multiple_patterns(self, test_project_root: Path) -> None:
|
|
464
|
+
"""Test that multiple exclusion patterns work."""
|
|
465
|
+
folder_structure = test_project_root / "folder_structure"
|
|
466
|
+
|
|
467
|
+
# Create directories with different exclusion patterns
|
|
468
|
+
sandbox_dir = folder_structure / "_sandbox"
|
|
469
|
+
sandbox_dir.mkdir()
|
|
470
|
+
(sandbox_dir / "sandbox_file.py").write_text("def sandbox(): pass")
|
|
471
|
+
|
|
472
|
+
skip_dir = folder_structure / "_skip"
|
|
473
|
+
skip_dir.mkdir()
|
|
474
|
+
(skip_dir / "skip_file.py").write_text("def skip(): pass")
|
|
475
|
+
|
|
476
|
+
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
477
|
+
manager = BuildManager(test_project_root, src_dir)
|
|
478
|
+
|
|
479
|
+
external_deps = manager.prepare_build()
|
|
480
|
+
|
|
481
|
+
# Verify excluded directories were NOT copied
|
|
482
|
+
copied_sandbox = src_dir / "_sandbox"
|
|
483
|
+
copied_skip = src_dir / "_skip"
|
|
484
|
+
|
|
485
|
+
assert not copied_sandbox.exists(), "_sandbox should be excluded"
|
|
486
|
+
assert not copied_skip.exists(), "_skip should be excluded"
|
|
487
|
+
|
|
488
|
+
manager.cleanup()
|
|
489
|
+
|
|
490
|
+
def test_exclude_nested_ss_directories(self, test_project_root: Path) -> None:
|
|
491
|
+
"""Test that _SS directories are excluded even when nested."""
|
|
492
|
+
folder_structure = test_project_root / "folder_structure"
|
|
493
|
+
|
|
494
|
+
# Create a nested structure with _SS
|
|
495
|
+
nested_package = folder_structure / "nested_package"
|
|
496
|
+
nested_package.mkdir()
|
|
497
|
+
(nested_package / "__init__.py").write_text("")
|
|
498
|
+
(nested_package / "module.py").write_text("def func(): pass")
|
|
499
|
+
|
|
500
|
+
# Create nested _SS directory
|
|
501
|
+
nested_ss = nested_package / "_SS"
|
|
502
|
+
nested_ss.mkdir()
|
|
503
|
+
(nested_ss / "nested_superseded.py").write_text("def nested(): pass")
|
|
504
|
+
|
|
505
|
+
# Update test file to import from nested_package
|
|
506
|
+
subfolder_to_build = folder_structure / "subfolder_to_build"
|
|
507
|
+
(subfolder_to_build / "some_function.py").write_text(
|
|
508
|
+
"""if True:
|
|
509
|
+
import sysappend; sysappend.all()
|
|
510
|
+
|
|
511
|
+
from folder_structure.nested_package.module import func
|
|
512
|
+
from some_globals import SOME_GLOBAL_VARIABLE
|
|
513
|
+
"""
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
src_dir = subfolder_to_build
|
|
517
|
+
manager = BuildManager(test_project_root, src_dir)
|
|
518
|
+
|
|
519
|
+
external_deps = manager.prepare_build()
|
|
520
|
+
|
|
521
|
+
# Verify nested_package was copied
|
|
522
|
+
copied_nested = src_dir / "nested_package"
|
|
523
|
+
assert copied_nested.exists(), "nested_package should be copied"
|
|
524
|
+
assert (copied_nested / "module.py").exists(), "module.py should be copied"
|
|
525
|
+
|
|
526
|
+
# Verify nested _SS directory was NOT copied
|
|
527
|
+
copied_nested_ss = copied_nested / "_SS"
|
|
528
|
+
assert not copied_nested_ss.exists(), "Nested _SS directory should be excluded"
|
|
529
|
+
|
|
530
|
+
manager.cleanup()
|
|
531
|
+
|
|
532
|
+
def test_finder_excludes_ss_paths(self, test_project_root: Path) -> None:
|
|
533
|
+
"""Test that ExternalDependencyFinder excludes _SS paths."""
|
|
534
|
+
src_dir = test_project_root / "folder_structure" / "subfolder_to_build"
|
|
535
|
+
finder = ExternalDependencyFinder(test_project_root, src_dir)
|
|
536
|
+
analyzer = ImportAnalyzer(test_project_root)
|
|
537
|
+
|
|
538
|
+
python_files = list(analyzer.find_all_python_files(src_dir))
|
|
539
|
+
external_deps = finder.find_external_dependencies(python_files)
|
|
540
|
+
|
|
541
|
+
# Verify no dependencies point to _SS directories
|
|
542
|
+
for dep in external_deps:
|
|
543
|
+
source_str = str(dep.source_path)
|
|
544
|
+
assert "_SS" not in source_str, f"Dependency should not include _SS: {dep.source_path}"
|
|
545
|
+
# Check all path components
|
|
546
|
+
for part in dep.source_path.parts:
|
|
547
|
+
assert not part.startswith("_SS"), f"Path component should not start with _SS: {part}"
|
|
548
|
+
|
|
549
|
+
|
|
381
550
|
class TestEdgeCases:
|
|
382
551
|
"""Tests for edge cases and error handling."""
|
|
383
552
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/analyzer.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/publisher.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
{python_package_folder-1.1.0 → python_package_folder-1.1.2}/tests/folder_structure/some_globals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|