python-package-folder 8.2.0__tar.gz → 8.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/PKG-INFO +1 -1
  2. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/coverage.svg +2 -2
  3. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/pyproject.toml +1 -1
  4. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/manager.py +37 -0
  5. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_subfolder_build.py +190 -1
  6. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.copier-answers.yml +0 -0
  7. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  8. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
  9. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.cursor/rules/general.mdc +0 -0
  10. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.cursor/rules/python.mdc +0 -0
  11. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.github/workflows/ci.yml +0 -0
  12. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.github/workflows/publish.yml +0 -0
  13. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.gitignore +0 -0
  14. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/.vscode/settings.json +0 -0
  15. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/LICENSE +0 -0
  16. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/MANIFEST.in +0 -0
  17. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/Makefile +0 -0
  18. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/README.md +0 -0
  19. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/development.md +0 -0
  20. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/docs/DEVELOPMENT.md +0 -0
  21. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/docs/INSTALLATION.md +0 -0
  22. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/docs/PUBLISHING.md +0 -0
  23. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/docs/REFERENCE.md +0 -0
  24. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/docs/USAGE.md +0 -0
  25. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/docs/VERSION_RESOLUTION.md +0 -0
  26. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/installation.md +0 -0
  27. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/publishing.md +0 -0
  28. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/__init__.py +0 -0
  29. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/__main__.py +0 -0
  30. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/analyzer.py +0 -0
  31. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/finder.py +0 -0
  32. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/publisher.py +0 -0
  33. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/py.typed +0 -0
  34. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/python_package_folder.py +0 -0
  35. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/subfolder_build.py +0 -0
  36. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/types.py +0 -0
  37. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/utils.py +0 -0
  38. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/version.py +0 -0
  39. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/src/python_package_folder/version_calculator.py +0 -0
  40. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/conftest.py +0 -0
  41. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/some_globals.py +0 -0
  42. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  43. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  44. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  45. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  46. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  47. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  48. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_build_with_external_deps.py +0 -0
  49. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_exclude_patterns.py +0 -0
  50. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_linting.py +0 -0
  51. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_preserve_directory_structure.py +0 -0
  52. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_publisher.py +0 -0
  53. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_shared_subdirectory_imports.py +0 -0
  54. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_spreadsheet_creation_imports.py +0 -0
  55. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_third_party_dependencies.py +0 -0
  56. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_utils.py +0 -0
  57. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_version_calculator.py +0 -0
  58. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/test_version_manager.py +0 -0
  59. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/tests/tests.py +0 -0
  60. {python_package_folder-8.2.0 → python_package_folder-8.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 8.2.0
3
+ Version: 8.3.0
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">67%</text>
18
- <text x="81" y="14">67%</text>
17
+ <text x="81" y="15" fill="#010101" fill-opacity=".3">66%</text>
18
+ <text x="81" y="14">66%</text>
19
19
  </g>
20
20
  </svg>
@@ -43,7 +43,7 @@ dependencies = [
43
43
 
44
44
  # ---- Dev dependencies ----
45
45
 
46
- version = "8.2.0"
46
+ version = "8.3.0"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pytest>=8.3.5",
@@ -777,6 +777,9 @@ class BuildManager:
777
777
  1. Imports of copied dependencies (e.g., `from _shared.image_utils` -> `from ._shared.image_utils`)
778
778
  2. Imports of local files within the subfolder (e.g., `from detect_empty_drawings_utils` -> `from .detect_empty_drawings_utils`)
779
779
 
780
+ Only imports classified as "external" or "local" are converted. Imports classified
781
+ as "ambiguous", "third_party", or "stdlib" are left unchanged.
782
+
780
783
  Args:
781
784
  python_files: List of Python files in the source directory
782
785
  external_deps: List of external dependencies that were copied
@@ -784,6 +787,9 @@ class BuildManager:
784
787
  import ast
785
788
  import re
786
789
 
790
+ # Create analyzer for classifying imports
791
+ analyzer = ImportAnalyzer(self.project_root)
792
+
787
793
  # Build a set of import names that were copied
788
794
  copied_import_names: set[str] = set()
789
795
  for dep in external_deps:
@@ -838,7 +844,22 @@ class BuildManager:
838
844
  if node.module is None:
839
845
  continue
840
846
 
847
+ # Classify the import to determine if it should be converted
848
+ import_info = ImportInfo(
849
+ module_name=node.module,
850
+ import_type="from",
851
+ line_number=node.lineno,
852
+ file_path=file_path,
853
+ )
854
+ analyzer.classify_import(import_info, self.src_dir)
855
+
856
+ # Only convert imports classified as "external" or "local"
857
+ # Skip ambiguous, third_party, and stdlib imports
858
+ if import_info.classification not in ("external", "local"):
859
+ continue
860
+
841
861
  # Check if this import matches a copied dependency or a local file
862
+ # (additional safety check, but classification takes precedence)
842
863
  root_module = node.module.split(".")[0]
843
864
  is_copied_dependency = (
844
865
  root_module in copied_import_names or node.module in copied_import_names
@@ -874,6 +895,22 @@ class BuildManager:
874
895
  elif isinstance(node, ast.Import):
875
896
  # Handle "import X" statements
876
897
  for alias in node.names:
898
+ # Classify the import to determine if it should be converted
899
+ import_info = ImportInfo(
900
+ module_name=alias.name,
901
+ import_type="import",
902
+ line_number=node.lineno,
903
+ file_path=file_path,
904
+ )
905
+ analyzer.classify_import(import_info, self.src_dir)
906
+
907
+ # Only convert imports classified as "external" or "local"
908
+ # Skip ambiguous, third_party, and stdlib imports
909
+ if import_info.classification not in ("external", "local"):
910
+ continue
911
+
912
+ # Check if this import matches a copied dependency or a local file
913
+ # (additional safety check, but classification takes precedence)
877
914
  root_module = alias.name.split(".")[0]
878
915
  is_copied_dependency = root_module in copied_import_names
879
916
  is_local_file = root_module in local_file_names
@@ -1909,4 +1909,193 @@ only-include = ["src/data", "pyproject.toml", "README.md"]
1909
1909
  # Installation or import failed - this is acceptable if dependencies are missing
1910
1910
  # The main verification (wheel contents) has already passed
1911
1911
  print(f"Note: Installation/import test skipped due to: {e}")
1912
- # The wheel packaging verification above is the main test
1912
+ # The wheel packaging verification above is the main test
1913
+
1914
+
1915
+ class TestImportConversion:
1916
+ """Tests to verify that import conversion respects classification."""
1917
+
1918
+ def test_third_party_imports_not_converted_to_relative(
1919
+ self, test_project_with_pyproject: Path
1920
+ ) -> None:
1921
+ """
1922
+ Test that third-party imports (like torch, torchvision) are NOT converted
1923
+ to relative imports, even if they match local file names.
1924
+ """
1925
+ project_root = test_project_with_pyproject
1926
+ subfolder = project_root / "subfolder"
1927
+
1928
+ # Create a module that imports third-party packages
1929
+ (subfolder / "__init__.py").write_text("# Package init")
1930
+ (subfolder / "module.py").write_text(
1931
+ """import torch
1932
+ import torch.utils.data
1933
+ from torchvision import datasets
1934
+ import numpy as np
1935
+ from PIL import Image
1936
+ """
1937
+ )
1938
+
1939
+ # Build the subfolder
1940
+ manager = BuildManager(project_root=project_root, src_dir=subfolder)
1941
+
1942
+ try:
1943
+ manager.prepare_build(version="1.0.0", package_name="my-package")
1944
+
1945
+ # Verify the temp package directory exists
1946
+ assert manager.subfolder_config is not None
1947
+ temp_dir = manager.subfolder_config._temp_package_dir
1948
+ assert temp_dir is not None and temp_dir.exists()
1949
+
1950
+ # Read the modified file
1951
+ modified_content = (temp_dir / "module.py").read_text(encoding="utf-8")
1952
+
1953
+ # Verify third-party imports were NOT converted to relative imports
1954
+ assert "import torch" in modified_content, (
1955
+ "torch import should remain absolute, not converted to relative"
1956
+ )
1957
+ assert "import torch.utils.data" in modified_content or "from torch.utils import data" in modified_content, (
1958
+ "torch.utils.data import should remain absolute"
1959
+ )
1960
+ assert "from torchvision import datasets" in modified_content, (
1961
+ "torchvision import should remain absolute, not converted to relative"
1962
+ )
1963
+ assert "import numpy as np" in modified_content, (
1964
+ "numpy import should remain absolute"
1965
+ )
1966
+ assert "from PIL import Image" in modified_content, (
1967
+ "PIL import should remain absolute"
1968
+ )
1969
+
1970
+ # Verify NO relative imports were added for these third-party packages
1971
+ assert "from . import torch" not in modified_content, (
1972
+ "torch should NOT be converted to relative import"
1973
+ )
1974
+ assert "from .torchvision import" not in modified_content, (
1975
+ "torchvision should NOT be converted to relative import"
1976
+ )
1977
+ assert "from . import numpy" not in modified_content, (
1978
+ "numpy should NOT be converted to relative import"
1979
+ )
1980
+
1981
+ finally:
1982
+ manager.cleanup()
1983
+
1984
+ def test_ambiguous_imports_not_converted_to_relative(
1985
+ self, test_project_with_pyproject: Path
1986
+ ) -> None:
1987
+ """
1988
+ Test that ambiguous imports (like time, math) are NOT converted
1989
+ to relative imports, even if they match local file names.
1990
+ """
1991
+ project_root = test_project_with_pyproject
1992
+ subfolder = project_root / "subfolder"
1993
+
1994
+ # Create a module that imports standard library modules
1995
+ # (which may be classified as ambiguous if not in stdlib list)
1996
+ (subfolder / "__init__.py").write_text("# Package init")
1997
+ (subfolder / "module.py").write_text(
1998
+ """import time
1999
+ import math
2000
+ from datetime import datetime
2001
+ import os
2002
+ import sys
2003
+ """
2004
+ )
2005
+
2006
+ # Build the subfolder
2007
+ manager = BuildManager(project_root=project_root, src_dir=subfolder)
2008
+
2009
+ try:
2010
+ manager.prepare_build(version="1.0.0", package_name="my-package")
2011
+
2012
+ # Verify the temp package directory exists
2013
+ assert manager.subfolder_config is not None
2014
+ temp_dir = manager.subfolder_config._temp_package_dir
2015
+ assert temp_dir is not None and temp_dir.exists()
2016
+
2017
+ # Read the modified file
2018
+ modified_content = (temp_dir / "module.py").read_text(encoding="utf-8")
2019
+
2020
+ # Verify stdlib/ambiguous imports were NOT converted to relative imports
2021
+ assert "import time" in modified_content, (
2022
+ "time import should remain absolute, not converted to relative"
2023
+ )
2024
+ assert "import math" in modified_content, (
2025
+ "math import should remain absolute, not converted to relative"
2026
+ )
2027
+ assert "from datetime import datetime" in modified_content, (
2028
+ "datetime import should remain absolute"
2029
+ )
2030
+ assert "import os" in modified_content, (
2031
+ "os import should remain absolute"
2032
+ )
2033
+ assert "import sys" in modified_content, (
2034
+ "sys import should remain absolute"
2035
+ )
2036
+
2037
+ # Verify NO relative imports were added for these stdlib modules
2038
+ assert "from . import time" not in modified_content, (
2039
+ "time should NOT be converted to relative import"
2040
+ )
2041
+ assert "from . import math" not in modified_content, (
2042
+ "math should NOT be converted to relative import"
2043
+ )
2044
+ assert "from .datetime import" not in modified_content, (
2045
+ "datetime should NOT be converted to relative import"
2046
+ )
2047
+
2048
+ finally:
2049
+ manager.cleanup()
2050
+
2051
+ def test_external_imports_are_converted_to_relative(
2052
+ self, test_project_with_pyproject: Path
2053
+ ) -> None:
2054
+ """
2055
+ Test that external imports (from copied dependencies) ARE converted
2056
+ to relative imports.
2057
+ """
2058
+ project_root = test_project_with_pyproject
2059
+ subfolder = project_root / "subfolder"
2060
+
2061
+ # Create an external dependency
2062
+ external_dir = project_root / "src" / "_shared"
2063
+ external_dir.mkdir(parents=True)
2064
+ (external_dir / "__init__.py").write_text("# External shared module")
2065
+ (external_dir / "utils.py").write_text("def helper(): return 'help'")
2066
+
2067
+ # Create a module that imports the external dependency
2068
+ (subfolder / "__init__.py").write_text("# Package init")
2069
+ (subfolder / "module.py").write_text(
2070
+ "from _shared.utils import helper\n\ndef func(): return helper()"
2071
+ )
2072
+
2073
+ # Build the subfolder
2074
+ manager = BuildManager(project_root=project_root, src_dir=subfolder)
2075
+
2076
+ try:
2077
+ external_deps = manager.prepare_build(version="1.0.0", package_name="my-package")
2078
+
2079
+ # Verify external dependency was found and copied
2080
+ assert len(external_deps) > 0, "External dependency should be found"
2081
+
2082
+ # Verify the temp package directory exists
2083
+ assert manager.subfolder_config is not None
2084
+ temp_dir = manager.subfolder_config._temp_package_dir
2085
+ assert temp_dir is not None and temp_dir.exists()
2086
+
2087
+ # Read the modified file
2088
+ modified_content = (temp_dir / "module.py").read_text(encoding="utf-8")
2089
+
2090
+ # Verify external import WAS converted to relative import
2091
+ assert "from ._shared.utils import helper" in modified_content, (
2092
+ "External import should be converted to relative import"
2093
+ )
2094
+ assert "from _shared.utils import helper" not in modified_content or (
2095
+ "from ._shared.utils import helper" in modified_content
2096
+ ), (
2097
+ "Original absolute import should be replaced with relative import"
2098
+ )
2099
+
2100
+ finally:
2101
+ manager.cleanup()