python-package-folder 8.3.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.3.0 → python_package_folder-8.4.0}/PKG-INFO +1 -1
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/pyproject.toml +1 -1
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/analyzer.py +64 -5
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/manager.py +198 -22
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_subfolder_build.py +1679 -226
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.copier-answers.yml +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-8.3.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.3.0 → python_package_folder-8.4.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.github/workflows/ci.yml +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.github/workflows/publish.yml +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.gitignore +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/.vscode/settings.json +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/LICENSE +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/MANIFEST.in +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/Makefile +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/README.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/coverage.svg +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/development.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/docs/INSTALLATION.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/docs/PUBLISHING.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/docs/REFERENCE.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/docs/USAGE.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/installation.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/publishing.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/subfolder_build.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/conftest.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_linting.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_publisher.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_utils.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_version_calculator.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-8.3.0 → python_package_folder-8.4.0}/tests/tests.py +0 -0
- {python_package_folder-8.3.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>
|
{python_package_folder-8.3.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.3.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:
|
|
@@ -844,7 +938,24 @@ class BuildManager:
|
|
|
844
938
|
if node.module is None:
|
|
845
939
|
continue
|
|
846
940
|
|
|
847
|
-
#
|
|
941
|
+
# Extract root module (first part before the first dot)
|
|
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
|
|
848
959
|
import_info = ImportInfo(
|
|
849
960
|
module_name=node.module,
|
|
850
961
|
import_type="from",
|
|
@@ -860,7 +971,6 @@ class BuildManager:
|
|
|
860
971
|
|
|
861
972
|
# Check if this import matches a copied dependency or a local file
|
|
862
973
|
# (additional safety check, but classification takes precedence)
|
|
863
|
-
root_module = node.module.split(".")[0]
|
|
864
974
|
is_copied_dependency = (
|
|
865
975
|
root_module in copied_import_names or node.module in copied_import_names
|
|
866
976
|
)
|
|
@@ -880,11 +990,36 @@ class BuildManager:
|
|
|
880
990
|
if original_line.strip().startswith("from ."):
|
|
881
991
|
continue
|
|
882
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
|
+
|
|
883
1018
|
# Convert absolute import to relative import
|
|
884
|
-
# from _shared.image_utils import ... -> from
|
|
1019
|
+
# from _shared.image_utils import ... -> from .._shared.image_utils import ...
|
|
885
1020
|
new_line = re.sub(
|
|
886
1021
|
rf"^(\s*)from\s+{re.escape(node.module)}\s+import",
|
|
887
|
-
rf"\1from
|
|
1022
|
+
rf"\1from {dots}{node.module} import",
|
|
888
1023
|
original_line,
|
|
889
1024
|
)
|
|
890
1025
|
|
|
@@ -895,7 +1030,24 @@ class BuildManager:
|
|
|
895
1030
|
elif isinstance(node, ast.Import):
|
|
896
1031
|
# Handle "import X" statements
|
|
897
1032
|
for alias in node.names:
|
|
898
|
-
#
|
|
1033
|
+
# Extract root module (first part before the first dot)
|
|
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
|
|
899
1051
|
import_info = ImportInfo(
|
|
900
1052
|
module_name=alias.name,
|
|
901
1053
|
import_type="import",
|
|
@@ -911,7 +1063,6 @@ class BuildManager:
|
|
|
911
1063
|
|
|
912
1064
|
# Check if this import matches a copied dependency or a local file
|
|
913
1065
|
# (additional safety check, but classification takes precedence)
|
|
914
|
-
root_module = alias.name.split(".")[0]
|
|
915
1066
|
is_copied_dependency = root_module in copied_import_names
|
|
916
1067
|
is_local_file = root_module in local_file_names
|
|
917
1068
|
|
|
@@ -928,11 +1079,36 @@ class BuildManager:
|
|
|
928
1079
|
if original_line.strip().startswith("import ."):
|
|
929
1080
|
continue
|
|
930
1081
|
|
|
931
|
-
#
|
|
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)
|
|
932
1108
|
# This is more complex, so we'll use a regex replacement
|
|
933
1109
|
new_line = re.sub(
|
|
934
1110
|
rf"^(\s*)import\s+{re.escape(alias.name)}\b",
|
|
935
|
-
rf"\1from
|
|
1111
|
+
rf"\1from {dots} import {alias.name}",
|
|
936
1112
|
original_line,
|
|
937
1113
|
)
|
|
938
1114
|
|