python-package-folder 3.1.2__tar.gz → 3.1.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-3.1.2 → python_package_folder-3.1.3}/PKG-INFO +1 -1
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/coverage.svg +2 -2
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/manager.py +131 -17
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_build_with_external_deps.py +30 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.copier-answers.yml +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.github/workflows/ci.yml +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.github/workflows/publish.yml +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.gitignore +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/.vscode/settings.json +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/LICENSE +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/Makefile +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/README.md +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/development.md +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/installation.md +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/publishing.md +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/pyproject.toml +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/types.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/version.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/conftest.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_linting.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_publisher.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_subfolder_build.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_utils.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_version_manager.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/tests.py +0 -0
- {python_package_folder-3.1.2 → python_package_folder-3.1.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.3
|
|
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>
|
|
@@ -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">
|
|
18
|
-
<text x="81" y="14">
|
|
17
|
+
<text x="81" y="15" fill="#010101" fill-opacity=".3">69%</text>
|
|
18
|
+
<text x="81" y="14">69%</text>
|
|
19
19
|
</g>
|
|
20
20
|
</svg>
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/manager.py
RENAMED
|
@@ -272,9 +272,12 @@ class BuildManager:
|
|
|
272
272
|
for dep in external_deps:
|
|
273
273
|
self._copy_dependency(dep)
|
|
274
274
|
|
|
275
|
-
# For subfolder builds,
|
|
275
|
+
# For subfolder builds, fix imports
|
|
276
276
|
if self._is_subfolder_build() and external_deps:
|
|
277
|
-
|
|
277
|
+
# Fix relative imports in copied dependency files (convert to absolute)
|
|
278
|
+
self._fix_relative_imports_in_copied_files(external_deps)
|
|
279
|
+
# Convert absolute imports of copied dependencies and local files to relative imports
|
|
280
|
+
self._convert_imports_to_relative(python_files, external_deps)
|
|
278
281
|
|
|
279
282
|
# For subfolder builds, extract third-party dependencies and add to pyproject.toml
|
|
280
283
|
if self._is_subfolder_build() and self.subfolder_config:
|
|
@@ -635,15 +638,111 @@ class BuildManager:
|
|
|
635
638
|
|
|
636
639
|
return sorted(list(third_party_packages))
|
|
637
640
|
|
|
638
|
-
def
|
|
641
|
+
def _fix_relative_imports_in_copied_files(
|
|
642
|
+
self, external_deps: list[ExternalDependency]
|
|
643
|
+
) -> None:
|
|
644
|
+
"""
|
|
645
|
+
Fix relative imports in copied dependency files.
|
|
646
|
+
|
|
647
|
+
When files are copied into the subfolder, their relative imports (like
|
|
648
|
+
`from ._shared.shared_dataclasses import ...`) break because the file
|
|
649
|
+
structure has changed. Convert these to absolute imports based on the
|
|
650
|
+
target location.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
external_deps: List of external dependencies that were copied
|
|
654
|
+
"""
|
|
655
|
+
import ast
|
|
656
|
+
import re
|
|
657
|
+
|
|
658
|
+
# Find all Python files in copied dependencies
|
|
659
|
+
copied_files: list[Path] = []
|
|
660
|
+
for dep in external_deps:
|
|
661
|
+
if dep.target_path.is_file() and dep.target_path.suffix == ".py":
|
|
662
|
+
copied_files.append(dep.target_path)
|
|
663
|
+
elif dep.target_path.is_dir():
|
|
664
|
+
copied_files.extend(dep.target_path.rglob("*.py"))
|
|
665
|
+
|
|
666
|
+
for file_path in copied_files:
|
|
667
|
+
try:
|
|
668
|
+
content = file_path.read_text(encoding="utf-8")
|
|
669
|
+
original_content = content
|
|
670
|
+
lines = content.split("\n")
|
|
671
|
+
modified = False
|
|
672
|
+
|
|
673
|
+
try:
|
|
674
|
+
tree = ast.parse(content, filename=str(file_path))
|
|
675
|
+
except SyntaxError:
|
|
676
|
+
continue
|
|
677
|
+
|
|
678
|
+
lines_to_modify: dict[int, str] = {}
|
|
679
|
+
|
|
680
|
+
for node in ast.walk(tree):
|
|
681
|
+
if isinstance(node, ast.ImportFrom):
|
|
682
|
+
if node.module is None:
|
|
683
|
+
continue
|
|
684
|
+
|
|
685
|
+
# Check if this is a relative import (level > 0)
|
|
686
|
+
if node.level == 0:
|
|
687
|
+
continue
|
|
688
|
+
|
|
689
|
+
line_num = node.lineno - 1
|
|
690
|
+
if line_num < 0 or line_num >= len(lines):
|
|
691
|
+
continue
|
|
692
|
+
|
|
693
|
+
original_line = lines[line_num]
|
|
694
|
+
|
|
695
|
+
# Convert relative import to absolute based on target location
|
|
696
|
+
# When a file is copied to the package root, relative imports need to be absolute
|
|
697
|
+
# For example: from ._shared.shared_dataclasses -> from _shared.shared_dataclasses
|
|
698
|
+
if node.module:
|
|
699
|
+
# Remove the leading dots and convert to absolute
|
|
700
|
+
# If it was `from ._shared.shared_dataclasses`, it becomes `from _shared.shared_dataclasses`
|
|
701
|
+
absolute_module = node.module
|
|
702
|
+
new_line = re.sub(
|
|
703
|
+
rf"^(\s*)from\s+\.+{re.escape(node.module)}\s+import",
|
|
704
|
+
rf"\1from {absolute_module} import",
|
|
705
|
+
original_line,
|
|
706
|
+
)
|
|
707
|
+
else:
|
|
708
|
+
# from . import X -> from . import X (keep as relative, but at package root level)
|
|
709
|
+
# Actually, if we're at package root, this should work as-is
|
|
710
|
+
# But if the file was in a subdirectory, we need to adjust
|
|
711
|
+
# For now, keep it as relative import
|
|
712
|
+
continue
|
|
713
|
+
|
|
714
|
+
if new_line != original_line:
|
|
715
|
+
lines_to_modify[line_num] = new_line
|
|
716
|
+
modified = True
|
|
717
|
+
|
|
718
|
+
if modified:
|
|
719
|
+
for line_num, new_line in lines_to_modify.items():
|
|
720
|
+
lines[line_num] = new_line
|
|
721
|
+
|
|
722
|
+
new_content = "\n".join(lines)
|
|
723
|
+
if file_path not in self._modified_import_files:
|
|
724
|
+
self._modified_import_files[file_path] = original_content
|
|
725
|
+
|
|
726
|
+
file_path.write_text(new_content, encoding="utf-8")
|
|
727
|
+
print(f"Fixed relative imports in copied file: {file_path}")
|
|
728
|
+
|
|
729
|
+
except Exception as e:
|
|
730
|
+
print(
|
|
731
|
+
f"Warning: Could not fix imports in copied file {file_path}: {e}",
|
|
732
|
+
file=sys.stderr,
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
def _convert_imports_to_relative(
|
|
639
736
|
self, python_files: list[Path], external_deps: list[ExternalDependency]
|
|
640
737
|
) -> None:
|
|
641
738
|
"""
|
|
642
|
-
Convert absolute imports
|
|
739
|
+
Convert absolute imports to relative imports for subfolder builds.
|
|
643
740
|
|
|
644
741
|
For subfolder builds, when external dependencies are copied into the subfolder,
|
|
645
|
-
imports
|
|
646
|
-
|
|
742
|
+
imports need to be converted from absolute to relative so they work correctly
|
|
743
|
+
when the package is installed. This includes:
|
|
744
|
+
1. Imports of copied dependencies (e.g., `from _shared.image_utils` -> `from ._shared.image_utils`)
|
|
745
|
+
2. Imports of local files within the subfolder (e.g., `from detect_empty_drawings_utils` -> `from .detect_empty_drawings_utils`)
|
|
647
746
|
|
|
648
747
|
Args:
|
|
649
748
|
python_files: List of Python files in the source directory
|
|
@@ -652,17 +751,27 @@ class BuildManager:
|
|
|
652
751
|
import ast
|
|
653
752
|
import re
|
|
654
753
|
|
|
655
|
-
# Build a set of import names that were copied
|
|
754
|
+
# Build a set of import names that were copied
|
|
656
755
|
copied_import_names: set[str] = set()
|
|
657
756
|
for dep in external_deps:
|
|
658
|
-
# Get the root module name (first part of the import)
|
|
659
757
|
root_module = dep.import_name.split(".")[0]
|
|
660
758
|
copied_import_names.add(root_module)
|
|
661
|
-
# Also add the full module name for nested imports
|
|
662
759
|
copied_import_names.add(dep.import_name)
|
|
663
760
|
|
|
664
|
-
|
|
665
|
-
|
|
761
|
+
# Build a set of local file names in the subfolder (excluding copied dependencies)
|
|
762
|
+
local_file_names: set[str] = set()
|
|
763
|
+
for file_path in python_files:
|
|
764
|
+
# Skip files that are part of copied dependencies
|
|
765
|
+
is_copied_file = any(file_path.is_relative_to(dep.target_path) for dep in external_deps)
|
|
766
|
+
if is_copied_file:
|
|
767
|
+
continue
|
|
768
|
+
if not file_path.is_relative_to(self.src_dir):
|
|
769
|
+
continue
|
|
770
|
+
# Get the module name (filename without .py extension)
|
|
771
|
+
if file_path.suffix == ".py":
|
|
772
|
+
module_name = file_path.stem
|
|
773
|
+
if module_name != "__init__":
|
|
774
|
+
local_file_names.add(module_name)
|
|
666
775
|
|
|
667
776
|
# Only modify files that are in the original subfolder (not the copied dependencies)
|
|
668
777
|
for file_path in python_files:
|
|
@@ -696,12 +805,14 @@ class BuildManager:
|
|
|
696
805
|
if node.module is None:
|
|
697
806
|
continue
|
|
698
807
|
|
|
699
|
-
# Check if this import matches a copied dependency
|
|
808
|
+
# Check if this import matches a copied dependency or a local file
|
|
700
809
|
root_module = node.module.split(".")[0]
|
|
701
|
-
|
|
702
|
-
root_module
|
|
703
|
-
|
|
704
|
-
|
|
810
|
+
is_copied_dependency = (
|
|
811
|
+
root_module in copied_import_names or node.module in copied_import_names
|
|
812
|
+
)
|
|
813
|
+
is_local_file = root_module in local_file_names
|
|
814
|
+
|
|
815
|
+
if not is_copied_dependency and not is_local_file:
|
|
705
816
|
continue
|
|
706
817
|
|
|
707
818
|
# Get the line content
|
|
@@ -731,7 +842,10 @@ class BuildManager:
|
|
|
731
842
|
# Handle "import X" statements
|
|
732
843
|
for alias in node.names:
|
|
733
844
|
root_module = alias.name.split(".")[0]
|
|
734
|
-
|
|
845
|
+
is_copied_dependency = root_module in copied_import_names
|
|
846
|
+
is_local_file = root_module in local_file_names
|
|
847
|
+
|
|
848
|
+
if not is_copied_dependency and not is_local_file:
|
|
735
849
|
continue
|
|
736
850
|
|
|
737
851
|
line_num = node.lineno - 1
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_build_with_external_deps.py
RENAMED
|
@@ -391,12 +391,18 @@ version = "0.1.0"
|
|
|
391
391
|
subfolder.mkdir(parents=True)
|
|
392
392
|
(subfolder / "__init__.py").write_text("")
|
|
393
393
|
|
|
394
|
+
# Create a local file that will be imported
|
|
395
|
+
local_file = subfolder / "local_utils.py"
|
|
396
|
+
local_file.write_text("def local_function(): return 42")
|
|
397
|
+
|
|
394
398
|
# Create a file in subfolder that imports copied dependencies with absolute imports
|
|
395
399
|
test_file = subfolder / "detect_empty_drawings.py"
|
|
396
400
|
original_content = """from pathlib import Path
|
|
397
401
|
from _shared.image_utils import save_PIL_image
|
|
398
402
|
from _shared.file_utils import get_filepaths_config
|
|
399
403
|
from _globals import is_testing
|
|
404
|
+
from local_utils import local_function
|
|
405
|
+
from config import get_config
|
|
400
406
|
|
|
401
407
|
def analyze_folder():
|
|
402
408
|
save_PIL_image()
|
|
@@ -414,6 +420,18 @@ def get_config():
|
|
|
414
420
|
"""
|
|
415
421
|
test_file2.write_text(original_content2)
|
|
416
422
|
|
|
423
|
+
# Create a copied dependency file with relative imports (simulating detect_empty_drawings_utils.py)
|
|
424
|
+
# This will be created when _shared is copied
|
|
425
|
+
# First, let's create a file in _shared that will be copied
|
|
426
|
+
shared_utils = shared_dir / "shared_utils.py"
|
|
427
|
+
shared_utils.write_text(
|
|
428
|
+
"""from .file_utils import get_filepaths_config
|
|
429
|
+
|
|
430
|
+
def shared_function():
|
|
431
|
+
return get_filepaths_config()
|
|
432
|
+
"""
|
|
433
|
+
)
|
|
434
|
+
|
|
417
435
|
manager = BuildManager(project_root, subfolder)
|
|
418
436
|
|
|
419
437
|
try:
|
|
@@ -430,6 +448,9 @@ def get_config():
|
|
|
430
448
|
assert "from ._shared.image_utils import save_PIL_image" in modified_content
|
|
431
449
|
assert "from ._shared.file_utils import get_filepaths_config" in modified_content
|
|
432
450
|
assert "from ._globals import is_testing" in modified_content
|
|
451
|
+
# Verify local file imports were converted to relative
|
|
452
|
+
assert "from .local_utils import local_function" in modified_content
|
|
453
|
+
assert "from .config import get_config" in modified_content
|
|
433
454
|
# Verify stdlib import was not changed
|
|
434
455
|
assert "from pathlib import Path" in modified_content
|
|
435
456
|
|
|
@@ -437,6 +458,15 @@ def get_config():
|
|
|
437
458
|
modified_content2 = test_file2.read_text()
|
|
438
459
|
assert "from . import _globals" in modified_content2
|
|
439
460
|
|
|
461
|
+
# Verify relative imports in copied files were fixed
|
|
462
|
+
if (subfolder / "_shared" / "shared_utils.py").exists():
|
|
463
|
+
shared_utils_content = (subfolder / "_shared" / "shared_utils.py").read_text()
|
|
464
|
+
# The relative import should be converted to absolute
|
|
465
|
+
assert (
|
|
466
|
+
"from .file_utils import" not in shared_utils_content
|
|
467
|
+
or "from file_utils import" in shared_utils_content
|
|
468
|
+
)
|
|
469
|
+
|
|
440
470
|
# Cleanup should restore original imports
|
|
441
471
|
manager.cleanup()
|
|
442
472
|
|
|
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-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/analyzer.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/finder.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/publisher.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-3.1.2 → python_package_folder-3.1.3}/tests/test_third_party_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|