python-package-folder 8.3.0__tar.gz → 9.0.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.3.0 → python_package_folder-9.0.0}/PKG-INFO +1 -1
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/coverage.svg +2 -2
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/pyproject.toml +1 -1
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/analyzer.py +64 -5
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/manager.py +219 -22
- python_package_folder-9.0.0/tests/test_subfolder_build.py +3848 -0
- python_package_folder-8.3.0/tests/test_subfolder_build.py +0 -2101
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.copier-answers.yml +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.github/workflows/ci.yml +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.github/workflows/publish.yml +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.gitignore +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/.vscode/settings.json +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/LICENSE +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/MANIFEST.in +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/Makefile +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/README.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/development.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/docs/INSTALLATION.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/docs/PUBLISHING.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/docs/REFERENCE.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/docs/USAGE.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/installation.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/publishing.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/conftest.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_linting.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_publisher.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_utils.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_version_calculator.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/tests/tests.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-9.0.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0.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">67%</text>
|
|
18
|
+
<text x="81" y="14">67%</text>
|
|
19
19
|
</g>
|
|
20
20
|
</svg>
|
{python_package_folder-8.3.0 → python_package_folder-9.0.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.3.0 → python_package_folder-9.0.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,65 @@ 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_dir = file_path.parent
|
|
841
|
+
# If module_path has a file extension (.py), treat it as a file and use its parent
|
|
842
|
+
# Otherwise, treat it as a directory
|
|
843
|
+
# This handles cases where the file doesn't exist yet (is_file() would return False)
|
|
844
|
+
if module_path.suffix == ".py" or (module_path.is_file() if module_path.exists() else False):
|
|
845
|
+
module_dir = module_path.parent
|
|
846
|
+
else:
|
|
847
|
+
module_dir = module_path
|
|
848
|
+
|
|
849
|
+
# Normalize both to be relative to src_dir
|
|
850
|
+
try:
|
|
851
|
+
file_rel = file_dir.relative_to(src_dir)
|
|
852
|
+
module_rel = module_dir.relative_to(src_dir)
|
|
853
|
+
except ValueError:
|
|
854
|
+
# If not relative to src_dir, fall back to single dot
|
|
855
|
+
return "."
|
|
856
|
+
|
|
857
|
+
# Calculate depth difference
|
|
858
|
+
# If paths are ".", depth is 0
|
|
859
|
+
file_depth = len(file_rel.parts) if file_rel.parts and file_rel.parts != (".",) else 0
|
|
860
|
+
module_depth = len(module_rel.parts) if module_rel.parts and module_rel.parts != (".",) else 0
|
|
861
|
+
|
|
862
|
+
depth_diff = file_depth - module_depth
|
|
863
|
+
|
|
864
|
+
if depth_diff == 0:
|
|
865
|
+
# Same depth - check if they're the same directory or siblings
|
|
866
|
+
if file_rel == module_rel:
|
|
867
|
+
return "." # Same directory
|
|
868
|
+
else:
|
|
869
|
+
# Sibling directories at same depth - need to go up one level
|
|
870
|
+
# e.g., PytorchCoco/ and _shared/ both at depth 1, need ..
|
|
871
|
+
return ".."
|
|
872
|
+
elif depth_diff > 0:
|
|
873
|
+
# File is deeper than module, need to go up
|
|
874
|
+
# e.g., file at depth 2, module at depth 0, need ...
|
|
875
|
+
return "." * (depth_diff + 1) # Go up depth_diff levels
|
|
876
|
+
else:
|
|
877
|
+
# Module is deeper than file, use single dot
|
|
878
|
+
return "."
|
|
879
|
+
except (ValueError, AttributeError):
|
|
880
|
+
# If paths are not relative to src_dir, fall back to single dot
|
|
881
|
+
return "."
|
|
882
|
+
|
|
768
883
|
def _convert_imports_to_relative(
|
|
769
884
|
self, python_files: list[Path], external_deps: list[ExternalDependency]
|
|
770
885
|
) -> None:
|
|
@@ -844,7 +959,24 @@ class BuildManager:
|
|
|
844
959
|
if node.module is None:
|
|
845
960
|
continue
|
|
846
961
|
|
|
847
|
-
#
|
|
962
|
+
# Extract root module (first part before the first dot)
|
|
963
|
+
root_module = node.module.split(".")[0]
|
|
964
|
+
|
|
965
|
+
# Classify the root module first to check if it's third-party/ambiguous/stdlib
|
|
966
|
+
# If root is third-party/ambiguous/stdlib, skip ALL its submodules
|
|
967
|
+
root_import_info = ImportInfo(
|
|
968
|
+
module_name=root_module,
|
|
969
|
+
import_type="from",
|
|
970
|
+
line_number=node.lineno,
|
|
971
|
+
file_path=file_path,
|
|
972
|
+
)
|
|
973
|
+
analyzer.classify_import(root_import_info, self.src_dir)
|
|
974
|
+
|
|
975
|
+
# If root is third-party/ambiguous/stdlib, skip ALL submodules
|
|
976
|
+
if root_import_info.classification in ("third_party", "ambiguous", "stdlib"):
|
|
977
|
+
continue
|
|
978
|
+
|
|
979
|
+
# Now classify the full import to determine if it should be converted
|
|
848
980
|
import_info = ImportInfo(
|
|
849
981
|
module_name=node.module,
|
|
850
982
|
import_type="from",
|
|
@@ -860,7 +992,6 @@ class BuildManager:
|
|
|
860
992
|
|
|
861
993
|
# Check if this import matches a copied dependency or a local file
|
|
862
994
|
# (additional safety check, but classification takes precedence)
|
|
863
|
-
root_module = node.module.split(".")[0]
|
|
864
995
|
is_copied_dependency = (
|
|
865
996
|
root_module in copied_import_names or node.module in copied_import_names
|
|
866
997
|
)
|
|
@@ -880,11 +1011,36 @@ class BuildManager:
|
|
|
880
1011
|
if original_line.strip().startswith("from ."):
|
|
881
1012
|
continue
|
|
882
1013
|
|
|
1014
|
+
# Find the target path for the imported module
|
|
1015
|
+
module_target_path = None
|
|
1016
|
+
for dep in external_deps:
|
|
1017
|
+
if dep.import_name == node.module or node.module.startswith(dep.import_name + "."):
|
|
1018
|
+
module_target_path = dep.target_path
|
|
1019
|
+
break
|
|
1020
|
+
|
|
1021
|
+
# If not found in external deps, check if it's a local file
|
|
1022
|
+
if module_target_path is None:
|
|
1023
|
+
# Try to find it as a local file
|
|
1024
|
+
module_parts = node.module.split(".")
|
|
1025
|
+
potential_path = self.src_dir / "/".join(module_parts)
|
|
1026
|
+
if (potential_path / "__init__.py").exists():
|
|
1027
|
+
module_target_path = potential_path / "__init__.py"
|
|
1028
|
+
elif potential_path.with_suffix(".py").exists():
|
|
1029
|
+
module_target_path = potential_path.with_suffix(".py")
|
|
1030
|
+
else:
|
|
1031
|
+
# Fallback: assume same level
|
|
1032
|
+
module_target_path = file_path.parent
|
|
1033
|
+
|
|
1034
|
+
# Calculate the correct relative import depth
|
|
1035
|
+
dots = self._calculate_relative_import_depth(
|
|
1036
|
+
file_path, module_target_path, self.src_dir
|
|
1037
|
+
)
|
|
1038
|
+
|
|
883
1039
|
# Convert absolute import to relative import
|
|
884
|
-
# from _shared.image_utils import ... -> from
|
|
1040
|
+
# from _shared.image_utils import ... -> from .._shared.image_utils import ...
|
|
885
1041
|
new_line = re.sub(
|
|
886
1042
|
rf"^(\s*)from\s+{re.escape(node.module)}\s+import",
|
|
887
|
-
rf"\1from
|
|
1043
|
+
rf"\1from {dots}{node.module} import",
|
|
888
1044
|
original_line,
|
|
889
1045
|
)
|
|
890
1046
|
|
|
@@ -895,7 +1051,24 @@ class BuildManager:
|
|
|
895
1051
|
elif isinstance(node, ast.Import):
|
|
896
1052
|
# Handle "import X" statements
|
|
897
1053
|
for alias in node.names:
|
|
898
|
-
#
|
|
1054
|
+
# Extract root module (first part before the first dot)
|
|
1055
|
+
root_module = alias.name.split(".")[0]
|
|
1056
|
+
|
|
1057
|
+
# Classify the root module first to check if it's third-party/ambiguous/stdlib
|
|
1058
|
+
# If root is third-party/ambiguous/stdlib, skip ALL its submodules
|
|
1059
|
+
root_import_info = ImportInfo(
|
|
1060
|
+
module_name=root_module,
|
|
1061
|
+
import_type="import",
|
|
1062
|
+
line_number=node.lineno,
|
|
1063
|
+
file_path=file_path,
|
|
1064
|
+
)
|
|
1065
|
+
analyzer.classify_import(root_import_info, self.src_dir)
|
|
1066
|
+
|
|
1067
|
+
# If root is third-party/ambiguous/stdlib, skip ALL submodules
|
|
1068
|
+
if root_import_info.classification in ("third_party", "ambiguous", "stdlib"):
|
|
1069
|
+
continue
|
|
1070
|
+
|
|
1071
|
+
# Now classify the full import to determine if it should be converted
|
|
899
1072
|
import_info = ImportInfo(
|
|
900
1073
|
module_name=alias.name,
|
|
901
1074
|
import_type="import",
|
|
@@ -911,7 +1084,6 @@ class BuildManager:
|
|
|
911
1084
|
|
|
912
1085
|
# Check if this import matches a copied dependency or a local file
|
|
913
1086
|
# (additional safety check, but classification takes precedence)
|
|
914
|
-
root_module = alias.name.split(".")[0]
|
|
915
1087
|
is_copied_dependency = root_module in copied_import_names
|
|
916
1088
|
is_local_file = root_module in local_file_names
|
|
917
1089
|
|
|
@@ -928,11 +1100,36 @@ class BuildManager:
|
|
|
928
1100
|
if original_line.strip().startswith("import ."):
|
|
929
1101
|
continue
|
|
930
1102
|
|
|
931
|
-
#
|
|
1103
|
+
# Find the target path for the imported module
|
|
1104
|
+
module_target_path = None
|
|
1105
|
+
for dep in external_deps:
|
|
1106
|
+
if dep.import_name == alias.name or alias.name.startswith(dep.import_name + "."):
|
|
1107
|
+
module_target_path = dep.target_path
|
|
1108
|
+
break
|
|
1109
|
+
|
|
1110
|
+
# If not found in external deps, check if it's a local file
|
|
1111
|
+
if module_target_path is None:
|
|
1112
|
+
# Try to find it as a local file
|
|
1113
|
+
module_parts = alias.name.split(".")
|
|
1114
|
+
potential_path = self.src_dir / "/".join(module_parts)
|
|
1115
|
+
if (potential_path / "__init__.py").exists():
|
|
1116
|
+
module_target_path = potential_path / "__init__.py"
|
|
1117
|
+
elif potential_path.with_suffix(".py").exists():
|
|
1118
|
+
module_target_path = potential_path.with_suffix(".py")
|
|
1119
|
+
else:
|
|
1120
|
+
# Fallback: assume same level
|
|
1121
|
+
module_target_path = file_path.parent
|
|
1122
|
+
|
|
1123
|
+
# Calculate the correct relative import depth
|
|
1124
|
+
dots = self._calculate_relative_import_depth(
|
|
1125
|
+
file_path, module_target_path, self.src_dir
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
# Convert "import _shared" to "from .. import _shared" (with correct depth)
|
|
932
1129
|
# This is more complex, so we'll use a regex replacement
|
|
933
1130
|
new_line = re.sub(
|
|
934
1131
|
rf"^(\s*)import\s+{re.escape(alias.name)}\b",
|
|
935
|
-
rf"\1from
|
|
1132
|
+
rf"\1from {dots} import {alias.name}",
|
|
936
1133
|
original_line,
|
|
937
1134
|
)
|
|
938
1135
|
|