python-package-folder 8.2.0__tar.gz → 8.4.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.
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/PKG-INFO +1 -1
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/coverage.svg +2 -2
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/pyproject.toml +1 -1
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/analyzer.py +64 -5
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/manager.py +232 -19
- python_package_folder-8.4.0/tests/test_subfolder_build.py +3554 -0
- python_package_folder-8.2.0/tests/test_subfolder_build.py +0 -1912
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.copier-answers.yml +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.github/workflows/ci.yml +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.github/workflows/publish.yml +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.gitignore +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/.vscode/settings.json +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/LICENSE +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/MANIFEST.in +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/Makefile +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/README.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/development.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/docs/INSTALLATION.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/docs/PUBLISHING.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/docs/REFERENCE.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/docs/USAGE.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/installation.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/publishing.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/conftest.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_linting.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_publisher.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_utils.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_version_calculator.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/tests/tests.py +0 -0
- {python_package_folder-8.2.0 → python_package_folder-8.4.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.4.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">
|
|
18
|
-
<text x="81" y="14">
|
|
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>
|
{python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/analyzer.py
RENAMED
|
@@ -396,7 +396,9 @@ class ImportAnalyzer:
|
|
|
396
396
|
Check if a module is a third-party package.
|
|
397
397
|
|
|
398
398
|
Uses importlib to find the module and checks if its location
|
|
399
|
-
is in site-packages or dist-packages.
|
|
399
|
+
is in site-packages or dist-packages. Also tries to import the module
|
|
400
|
+
to check if it's available, which helps catch cases where packages
|
|
401
|
+
are installed but metadata lookup fails.
|
|
400
402
|
|
|
401
403
|
Args:
|
|
402
404
|
module_name: Name of the module to check
|
|
@@ -405,12 +407,69 @@ class ImportAnalyzer:
|
|
|
405
407
|
True if the module is a third-party package, False otherwise
|
|
406
408
|
"""
|
|
407
409
|
root_module = module_name.split(".")[0]
|
|
410
|
+
|
|
411
|
+
# Skip if already known as stdlib
|
|
412
|
+
stdlib_modules = self.get_stdlib_modules()
|
|
413
|
+
if root_module in stdlib_modules:
|
|
414
|
+
return False
|
|
415
|
+
|
|
408
416
|
try:
|
|
409
417
|
spec = importlib.util.find_spec(root_module)
|
|
410
418
|
if spec and spec.origin:
|
|
411
419
|
origin_path = Path(spec.origin)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
420
|
+
origin_str = str(origin_path)
|
|
421
|
+
|
|
422
|
+
# Check if it's in site-packages or dist-packages
|
|
423
|
+
if "site-packages" in origin_str or "dist-packages" in origin_str:
|
|
424
|
+
return True
|
|
425
|
+
|
|
426
|
+
# Check if it's outside the standard library directory
|
|
427
|
+
# Standard library is typically in the Python installation directory
|
|
428
|
+
# and not in site-packages
|
|
429
|
+
stdlib_paths = [
|
|
430
|
+
"lib/python",
|
|
431
|
+
"Lib\\python", # Windows
|
|
432
|
+
"lib64/python",
|
|
433
|
+
"Lib64\\python", # Windows
|
|
434
|
+
]
|
|
435
|
+
is_stdlib_path = any(stdlib_path in origin_str for stdlib_path in stdlib_paths)
|
|
436
|
+
if not is_stdlib_path and "site-packages" not in origin_str:
|
|
437
|
+
# If it's not in stdlib paths and not in site-packages,
|
|
438
|
+
# and it's not in the project directory, it might be third-party
|
|
439
|
+
# Check if it's outside the project directory
|
|
440
|
+
try:
|
|
441
|
+
origin_path.resolve().relative_to(self.project_root.resolve())
|
|
442
|
+
# It's in the project, so it's not third-party
|
|
443
|
+
return False
|
|
444
|
+
except ValueError:
|
|
445
|
+
# It's outside the project, might be third-party
|
|
446
|
+
# Try to import it to verify
|
|
447
|
+
try:
|
|
448
|
+
__import__(root_module)
|
|
449
|
+
# If import succeeds and it's outside project, likely third-party
|
|
450
|
+
return True
|
|
451
|
+
except ImportError:
|
|
452
|
+
pass
|
|
453
|
+
|
|
454
|
+
except (ImportError, ValueError, AttributeError, Exception):
|
|
455
|
+
# If importlib fails, try direct import as fallback
|
|
456
|
+
try:
|
|
457
|
+
imported_module = __import__(root_module)
|
|
458
|
+
# Check if the module's __file__ points to site-packages
|
|
459
|
+
if hasattr(imported_module, "__file__") and imported_module.__file__:
|
|
460
|
+
module_file = str(imported_module.__file__)
|
|
461
|
+
if "site-packages" in module_file or "dist-packages" in module_file:
|
|
462
|
+
return True
|
|
463
|
+
# Check if it's outside the project
|
|
464
|
+
try:
|
|
465
|
+
Path(imported_module.__file__).resolve().relative_to(
|
|
466
|
+
self.project_root.resolve()
|
|
467
|
+
)
|
|
468
|
+
return False # It's in the project
|
|
469
|
+
except ValueError:
|
|
470
|
+
# It's outside the project, likely third-party
|
|
471
|
+
return True
|
|
472
|
+
except ImportError:
|
|
473
|
+
pass
|
|
474
|
+
|
|
416
475
|
return False
|
{python_package_folder-8.2.0 → python_package_folder-8.4.0}/src/python_package_folder/manager.py
RENAMED
|
@@ -605,6 +605,51 @@ class BuildManager:
|
|
|
605
605
|
Returns:
|
|
606
606
|
List of unique third-party package names (e.g., ["pypdf", "requests", "pymupdf"])
|
|
607
607
|
"""
|
|
608
|
+
# Common third-party packages that are often misclassified as ambiguous
|
|
609
|
+
COMMON_THIRD_PARTY_PACKAGES = {
|
|
610
|
+
"torch",
|
|
611
|
+
"torchvision",
|
|
612
|
+
"numpy",
|
|
613
|
+
"pandas",
|
|
614
|
+
"matplotlib",
|
|
615
|
+
"scipy",
|
|
616
|
+
"sklearn",
|
|
617
|
+
"tensorflow",
|
|
618
|
+
"keras",
|
|
619
|
+
"pillow",
|
|
620
|
+
"PIL",
|
|
621
|
+
"cv2",
|
|
622
|
+
"opencv",
|
|
623
|
+
"requests",
|
|
624
|
+
"flask",
|
|
625
|
+
"django",
|
|
626
|
+
"pytest",
|
|
627
|
+
"pydantic",
|
|
628
|
+
"fastapi",
|
|
629
|
+
"sqlalchemy",
|
|
630
|
+
"pymongo",
|
|
631
|
+
"redis",
|
|
632
|
+
"celery",
|
|
633
|
+
"boto3",
|
|
634
|
+
"azure",
|
|
635
|
+
"google",
|
|
636
|
+
"openai",
|
|
637
|
+
"langchain",
|
|
638
|
+
"fiftyone",
|
|
639
|
+
"pycocotools",
|
|
640
|
+
"albumentations",
|
|
641
|
+
"loguru",
|
|
642
|
+
"tqdm",
|
|
643
|
+
"rich",
|
|
644
|
+
"pypdf",
|
|
645
|
+
"pymupdf",
|
|
646
|
+
"networkx",
|
|
647
|
+
"openpyxl",
|
|
648
|
+
"nltk",
|
|
649
|
+
"spacy",
|
|
650
|
+
"textdistance",
|
|
651
|
+
"IPython",
|
|
652
|
+
}
|
|
608
653
|
third_party_packages: set[str] = set()
|
|
609
654
|
# Cache package name lookups to avoid repeated expensive searches
|
|
610
655
|
package_name_cache: dict[str, str | None] = {}
|
|
@@ -648,23 +693,34 @@ class BuildManager:
|
|
|
648
693
|
# Fallback to using the import name
|
|
649
694
|
third_party_packages.add(root_module)
|
|
650
695
|
# If it's ambiguous or unresolved, and not stdlib/local/external,
|
|
651
|
-
#
|
|
696
|
+
# check if it's a common third-party package or can be verified
|
|
652
697
|
elif imp.classification == "ambiguous" or imp.classification is None:
|
|
653
698
|
# Check if it's not a local or external module
|
|
654
699
|
if not imp.resolved_path:
|
|
655
|
-
#
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
700
|
+
# Check if it's a common third-party package
|
|
701
|
+
if root_module in COMMON_THIRD_PARTY_PACKAGES:
|
|
702
|
+
# Try to get package name, or use module name as fallback
|
|
703
|
+
if root_module not in package_name_cache:
|
|
704
|
+
package_name_cache[root_module] = self._get_package_name_from_import(
|
|
705
|
+
imp.module_name
|
|
706
|
+
)
|
|
707
|
+
actual_package = package_name_cache[root_module]
|
|
708
|
+
if actual_package:
|
|
709
|
+
third_party_packages.add(actual_package)
|
|
710
|
+
else:
|
|
711
|
+
# Fallback: use module name (common packages like torch, numpy)
|
|
712
|
+
third_party_packages.add(root_module)
|
|
713
|
+
else:
|
|
714
|
+
# For other ambiguous imports, only add if verified
|
|
715
|
+
if root_module not in package_name_cache:
|
|
716
|
+
package_name_cache[root_module] = self._get_package_name_from_import(
|
|
717
|
+
imp.module_name
|
|
718
|
+
)
|
|
719
|
+
actual_package = package_name_cache[root_module]
|
|
720
|
+
if actual_package:
|
|
721
|
+
third_party_packages.add(actual_package)
|
|
722
|
+
# If we can't verify it's a package, don't add it
|
|
723
|
+
# (it's likely a local file that wasn't resolved properly)
|
|
668
724
|
|
|
669
725
|
if total_files > 50:
|
|
670
726
|
print() # New line after progress indicator
|
|
@@ -765,6 +821,44 @@ class BuildManager:
|
|
|
765
821
|
file=sys.stderr,
|
|
766
822
|
)
|
|
767
823
|
|
|
824
|
+
def _calculate_relative_import_depth(
|
|
825
|
+
self, file_path: Path, module_path: Path, src_dir: Path
|
|
826
|
+
) -> str:
|
|
827
|
+
"""
|
|
828
|
+
Calculate the relative import prefix (., .., ...) based on directory depth.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
file_path: Path to the file containing the import
|
|
832
|
+
module_path: Path to the imported module (file or directory)
|
|
833
|
+
src_dir: Source directory (package root)
|
|
834
|
+
|
|
835
|
+
Returns:
|
|
836
|
+
String with appropriate number of dots (e.g., ".", "..", "...")
|
|
837
|
+
"""
|
|
838
|
+
try:
|
|
839
|
+
# Get relative paths from src_dir
|
|
840
|
+
file_rel = file_path.parent.relative_to(src_dir)
|
|
841
|
+
module_rel = module_path.parent.relative_to(src_dir)
|
|
842
|
+
|
|
843
|
+
# Calculate depth difference
|
|
844
|
+
# If paths are ".", depth is 0
|
|
845
|
+
file_depth = len(file_rel.parts) if file_rel.parts != (".",) else 0
|
|
846
|
+
module_depth = len(module_rel.parts) if module_rel.parts != (".",) else 0
|
|
847
|
+
|
|
848
|
+
depth_diff = file_depth - module_depth
|
|
849
|
+
|
|
850
|
+
if depth_diff == 0:
|
|
851
|
+
return "." # Same level
|
|
852
|
+
elif depth_diff > 0:
|
|
853
|
+
# File is deeper than module, need to go up
|
|
854
|
+
return "." * (depth_diff + 1) # Go up depth_diff levels
|
|
855
|
+
else:
|
|
856
|
+
# Module is deeper than file, use single dot
|
|
857
|
+
return "."
|
|
858
|
+
except ValueError:
|
|
859
|
+
# If paths are not relative to src_dir, fall back to single dot
|
|
860
|
+
return "."
|
|
861
|
+
|
|
768
862
|
def _convert_imports_to_relative(
|
|
769
863
|
self, python_files: list[Path], external_deps: list[ExternalDependency]
|
|
770
864
|
) -> None:
|
|
@@ -777,6 +871,9 @@ class BuildManager:
|
|
|
777
871
|
1. Imports of copied dependencies (e.g., `from _shared.image_utils` -> `from ._shared.image_utils`)
|
|
778
872
|
2. Imports of local files within the subfolder (e.g., `from detect_empty_drawings_utils` -> `from .detect_empty_drawings_utils`)
|
|
779
873
|
|
|
874
|
+
Only imports classified as "external" or "local" are converted. Imports classified
|
|
875
|
+
as "ambiguous", "third_party", or "stdlib" are left unchanged.
|
|
876
|
+
|
|
780
877
|
Args:
|
|
781
878
|
python_files: List of Python files in the source directory
|
|
782
879
|
external_deps: List of external dependencies that were copied
|
|
@@ -784,6 +881,9 @@ class BuildManager:
|
|
|
784
881
|
import ast
|
|
785
882
|
import re
|
|
786
883
|
|
|
884
|
+
# Create analyzer for classifying imports
|
|
885
|
+
analyzer = ImportAnalyzer(self.project_root)
|
|
886
|
+
|
|
787
887
|
# Build a set of import names that were copied
|
|
788
888
|
copied_import_names: set[str] = set()
|
|
789
889
|
for dep in external_deps:
|
|
@@ -838,8 +938,39 @@ class BuildManager:
|
|
|
838
938
|
if node.module is None:
|
|
839
939
|
continue
|
|
840
940
|
|
|
841
|
-
#
|
|
941
|
+
# Extract root module (first part before the first dot)
|
|
842
942
|
root_module = node.module.split(".")[0]
|
|
943
|
+
|
|
944
|
+
# Classify the root module first to check if it's third-party/ambiguous/stdlib
|
|
945
|
+
# If root is third-party/ambiguous/stdlib, skip ALL its submodules
|
|
946
|
+
root_import_info = ImportInfo(
|
|
947
|
+
module_name=root_module,
|
|
948
|
+
import_type="from",
|
|
949
|
+
line_number=node.lineno,
|
|
950
|
+
file_path=file_path,
|
|
951
|
+
)
|
|
952
|
+
analyzer.classify_import(root_import_info, self.src_dir)
|
|
953
|
+
|
|
954
|
+
# If root is third-party/ambiguous/stdlib, skip ALL submodules
|
|
955
|
+
if root_import_info.classification in ("third_party", "ambiguous", "stdlib"):
|
|
956
|
+
continue
|
|
957
|
+
|
|
958
|
+
# Now classify the full import to determine if it should be converted
|
|
959
|
+
import_info = ImportInfo(
|
|
960
|
+
module_name=node.module,
|
|
961
|
+
import_type="from",
|
|
962
|
+
line_number=node.lineno,
|
|
963
|
+
file_path=file_path,
|
|
964
|
+
)
|
|
965
|
+
analyzer.classify_import(import_info, self.src_dir)
|
|
966
|
+
|
|
967
|
+
# Only convert imports classified as "external" or "local"
|
|
968
|
+
# Skip ambiguous, third_party, and stdlib imports
|
|
969
|
+
if import_info.classification not in ("external", "local"):
|
|
970
|
+
continue
|
|
971
|
+
|
|
972
|
+
# Check if this import matches a copied dependency or a local file
|
|
973
|
+
# (additional safety check, but classification takes precedence)
|
|
843
974
|
is_copied_dependency = (
|
|
844
975
|
root_module in copied_import_names or node.module in copied_import_names
|
|
845
976
|
)
|
|
@@ -859,11 +990,36 @@ class BuildManager:
|
|
|
859
990
|
if original_line.strip().startswith("from ."):
|
|
860
991
|
continue
|
|
861
992
|
|
|
993
|
+
# Find the target path for the imported module
|
|
994
|
+
module_target_path = None
|
|
995
|
+
for dep in external_deps:
|
|
996
|
+
if dep.import_name == node.module or node.module.startswith(dep.import_name + "."):
|
|
997
|
+
module_target_path = dep.target_path
|
|
998
|
+
break
|
|
999
|
+
|
|
1000
|
+
# If not found in external deps, check if it's a local file
|
|
1001
|
+
if module_target_path is None:
|
|
1002
|
+
# Try to find it as a local file
|
|
1003
|
+
module_parts = node.module.split(".")
|
|
1004
|
+
potential_path = self.src_dir / "/".join(module_parts)
|
|
1005
|
+
if (potential_path / "__init__.py").exists():
|
|
1006
|
+
module_target_path = potential_path / "__init__.py"
|
|
1007
|
+
elif potential_path.with_suffix(".py").exists():
|
|
1008
|
+
module_target_path = potential_path.with_suffix(".py")
|
|
1009
|
+
else:
|
|
1010
|
+
# Fallback: assume same level
|
|
1011
|
+
module_target_path = file_path.parent
|
|
1012
|
+
|
|
1013
|
+
# Calculate the correct relative import depth
|
|
1014
|
+
dots = self._calculate_relative_import_depth(
|
|
1015
|
+
file_path, module_target_path, self.src_dir
|
|
1016
|
+
)
|
|
1017
|
+
|
|
862
1018
|
# Convert absolute import to relative import
|
|
863
|
-
# from _shared.image_utils import ... -> from
|
|
1019
|
+
# from _shared.image_utils import ... -> from .._shared.image_utils import ...
|
|
864
1020
|
new_line = re.sub(
|
|
865
1021
|
rf"^(\s*)from\s+{re.escape(node.module)}\s+import",
|
|
866
|
-
rf"\1from
|
|
1022
|
+
rf"\1from {dots}{node.module} import",
|
|
867
1023
|
original_line,
|
|
868
1024
|
)
|
|
869
1025
|
|
|
@@ -874,7 +1030,39 @@ class BuildManager:
|
|
|
874
1030
|
elif isinstance(node, ast.Import):
|
|
875
1031
|
# Handle "import X" statements
|
|
876
1032
|
for alias in node.names:
|
|
1033
|
+
# Extract root module (first part before the first dot)
|
|
877
1034
|
root_module = alias.name.split(".")[0]
|
|
1035
|
+
|
|
1036
|
+
# Classify the root module first to check if it's third-party/ambiguous/stdlib
|
|
1037
|
+
# If root is third-party/ambiguous/stdlib, skip ALL its submodules
|
|
1038
|
+
root_import_info = ImportInfo(
|
|
1039
|
+
module_name=root_module,
|
|
1040
|
+
import_type="import",
|
|
1041
|
+
line_number=node.lineno,
|
|
1042
|
+
file_path=file_path,
|
|
1043
|
+
)
|
|
1044
|
+
analyzer.classify_import(root_import_info, self.src_dir)
|
|
1045
|
+
|
|
1046
|
+
# If root is third-party/ambiguous/stdlib, skip ALL submodules
|
|
1047
|
+
if root_import_info.classification in ("third_party", "ambiguous", "stdlib"):
|
|
1048
|
+
continue
|
|
1049
|
+
|
|
1050
|
+
# Now classify the full import to determine if it should be converted
|
|
1051
|
+
import_info = ImportInfo(
|
|
1052
|
+
module_name=alias.name,
|
|
1053
|
+
import_type="import",
|
|
1054
|
+
line_number=node.lineno,
|
|
1055
|
+
file_path=file_path,
|
|
1056
|
+
)
|
|
1057
|
+
analyzer.classify_import(import_info, self.src_dir)
|
|
1058
|
+
|
|
1059
|
+
# Only convert imports classified as "external" or "local"
|
|
1060
|
+
# Skip ambiguous, third_party, and stdlib imports
|
|
1061
|
+
if import_info.classification not in ("external", "local"):
|
|
1062
|
+
continue
|
|
1063
|
+
|
|
1064
|
+
# Check if this import matches a copied dependency or a local file
|
|
1065
|
+
# (additional safety check, but classification takes precedence)
|
|
878
1066
|
is_copied_dependency = root_module in copied_import_names
|
|
879
1067
|
is_local_file = root_module in local_file_names
|
|
880
1068
|
|
|
@@ -891,11 +1079,36 @@ class BuildManager:
|
|
|
891
1079
|
if original_line.strip().startswith("import ."):
|
|
892
1080
|
continue
|
|
893
1081
|
|
|
894
|
-
#
|
|
1082
|
+
# Find the target path for the imported module
|
|
1083
|
+
module_target_path = None
|
|
1084
|
+
for dep in external_deps:
|
|
1085
|
+
if dep.import_name == alias.name or alias.name.startswith(dep.import_name + "."):
|
|
1086
|
+
module_target_path = dep.target_path
|
|
1087
|
+
break
|
|
1088
|
+
|
|
1089
|
+
# If not found in external deps, check if it's a local file
|
|
1090
|
+
if module_target_path is None:
|
|
1091
|
+
# Try to find it as a local file
|
|
1092
|
+
module_parts = alias.name.split(".")
|
|
1093
|
+
potential_path = self.src_dir / "/".join(module_parts)
|
|
1094
|
+
if (potential_path / "__init__.py").exists():
|
|
1095
|
+
module_target_path = potential_path / "__init__.py"
|
|
1096
|
+
elif potential_path.with_suffix(".py").exists():
|
|
1097
|
+
module_target_path = potential_path.with_suffix(".py")
|
|
1098
|
+
else:
|
|
1099
|
+
# Fallback: assume same level
|
|
1100
|
+
module_target_path = file_path.parent
|
|
1101
|
+
|
|
1102
|
+
# Calculate the correct relative import depth
|
|
1103
|
+
dots = self._calculate_relative_import_depth(
|
|
1104
|
+
file_path, module_target_path, self.src_dir
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
# Convert "import _shared" to "from .. import _shared" (with correct depth)
|
|
895
1108
|
# This is more complex, so we'll use a regex replacement
|
|
896
1109
|
new_line = re.sub(
|
|
897
1110
|
rf"^(\s*)import\s+{re.escape(alias.name)}\b",
|
|
898
|
-
rf"\1from
|
|
1111
|
+
rf"\1from {dots} import {alias.name}",
|
|
899
1112
|
original_line,
|
|
900
1113
|
)
|
|
901
1114
|
|