python-package-folder 2.0.4__tar.gz → 2.0.5__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 (45) hide show
  1. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/PKG-INFO +1 -1
  2. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/coverage.svg +2 -2
  3. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/manager.py +68 -25
  4. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.copier-answers.yml +0 -0
  5. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.cursor/rules/general.mdc +0 -0
  6. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.cursor/rules/python.mdc +0 -0
  7. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.github/workflows/ci.yml +0 -0
  8. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.github/workflows/publish.yml +0 -0
  9. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.gitignore +0 -0
  10. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/.vscode/settings.json +0 -0
  11. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/LICENSE +0 -0
  12. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/Makefile +0 -0
  13. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/README.md +0 -0
  14. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/development.md +0 -0
  15. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/installation.md +0 -0
  16. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/publishing.md +0 -0
  17. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/pyproject.toml +0 -0
  18. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/__init__.py +0 -0
  19. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/__main__.py +0 -0
  20. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/analyzer.py +0 -0
  21. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/finder.py +0 -0
  22. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/publisher.py +0 -0
  23. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/py.typed +0 -0
  24. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/python_package_folder.py +0 -0
  25. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/subfolder_build.py +0 -0
  26. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/types.py +0 -0
  27. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/utils.py +0 -0
  28. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/src/python_package_folder/version.py +0 -0
  29. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/conftest.py +0 -0
  30. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/some_globals.py +0 -0
  31. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  32. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  33. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  34. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  35. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  36. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  37. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_build_with_external_deps.py +0 -0
  38. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_linting.py +0 -0
  39. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_publisher.py +0 -0
  40. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_subfolder_build.py +0 -0
  41. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_third_party_dependencies.py +0 -0
  42. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_utils.py +0 -0
  43. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/test_version_manager.py +0 -0
  44. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/tests/tests.py +0 -0
  45. {python_package_folder-2.0.4 → python_package_folder-2.0.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 2.0.4
3
+ Version: 2.0.5
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">69%</text>
18
- <text x="81" y="14">69%</text>
17
+ <text x="81" y="15" fill="#010101" fill-opacity=".3">68%</text>
18
+ <text x="81" y="14">68%</text>
19
19
  </g>
20
20
  </svg>
@@ -86,6 +86,8 @@ class BuildManager:
86
86
  self.project_root, self.src_dir, exclude_patterns=exclude_patterns
87
87
  )
88
88
  self.subfolder_config: SubfolderBuildConfig | None = None
89
+ # Cache for package name lookups (expensive operation)
90
+ self._packages_distributions_cache: dict[str, list[str]] | None = None
89
91
 
90
92
  # Check if it's a valid Python package directory
91
93
  if not any(self.src_dir.glob("*.py")) and not (self.src_dir / "__init__.py").exists():
@@ -271,7 +273,9 @@ class BuildManager:
271
273
  # For subfolder builds, extract third-party dependencies and add to pyproject.toml
272
274
  if self._is_subfolder_build() and self.subfolder_config:
273
275
  # Re-analyze all Python files (including copied dependencies) to find third-party imports
276
+ print("Analyzing Python files for third-party dependencies...")
274
277
  all_python_files = analyzer.find_all_python_files(self.src_dir)
278
+ print(f"Found {len(all_python_files)} Python files to analyze")
275
279
  third_party_deps = self._extract_third_party_dependencies(all_python_files, analyzer)
276
280
  if third_party_deps:
277
281
  print(
@@ -413,8 +417,12 @@ class BuildManager:
413
417
  import importlib.metadata as importlib_metadata
414
418
 
415
419
  # Use packages_distributions() if available (Python 3.10+)
420
+ # Cache the result since it's expensive to call
416
421
  if hasattr(importlib_metadata, "packages_distributions"):
417
- packages_map = importlib_metadata.packages_distributions()
422
+ if self._packages_distributions_cache is None:
423
+ # Cache the packages_distributions() result
424
+ self._packages_distributions_cache = importlib_metadata.packages_distributions()
425
+ packages_map = self._packages_distributions_cache
418
426
  # packages_map is a dict mapping module names to list of distribution names
419
427
  if root_module in packages_map:
420
428
  # Return the first distribution name (usually there's only one)
@@ -422,26 +430,45 @@ class BuildManager:
422
430
  if dist_names:
423
431
  return dist_names[0]
424
432
 
425
- # Fallback: search all distributions
433
+ # Fallback: search all distributions (this can be slow, so limit search)
434
+ # Only check top-level package matches to speed up search
435
+ dist_count = 0
436
+ max_distributions_to_check = 1000 # Limit to prevent excessive searching
426
437
  for dist in importlib_metadata.distributions():
438
+ dist_count += 1
439
+ if dist_count > max_distributions_to_check:
440
+ # Too many distributions, give up to avoid hanging
441
+ break
427
442
  try:
428
- # Check if this distribution provides the module
429
- # by looking at its files
430
- files = dist.files or []
431
- for file in files:
432
- file_str = str(file)
433
- # Check if file is the module itself or in a package directory
434
- if (
435
- file.suffix == ".py"
436
- and (file.stem == root_module or file.stem == "__init__")
437
- ) or (
438
- "/" in file_str
439
- and (
440
- file_str.startswith(f"{root_module}/")
441
- or file_str.startswith(f"{root_module.replace('_', '-')}/")
442
- )
443
- ):
444
- return dist.metadata["Name"]
443
+ # Check distribution name first (fast check)
444
+ dist_name = dist.metadata.get("Name", "")
445
+ # If distribution name matches or contains the module name, check files
446
+ if dist_name.lower().replace(
447
+ "-", "_"
448
+ ) == root_module.lower() or root_module.lower() in dist_name.lower().replace(
449
+ "-", "_"
450
+ ):
451
+ # Check if this distribution provides the module by looking at its files
452
+ files = dist.files or []
453
+ # Limit file checking to first 100 files per distribution
454
+ file_count = 0
455
+ for file in files:
456
+ file_count += 1
457
+ if file_count > 100:
458
+ break
459
+ file_str = str(file)
460
+ # Check if file is the module itself or in a package directory
461
+ if (
462
+ file.suffix == ".py"
463
+ and (file.stem == root_module or file.stem == "__init__")
464
+ ) or (
465
+ "/" in file_str
466
+ and (
467
+ file_str.startswith(f"{root_module}/")
468
+ or file_str.startswith(f"{root_module.replace('_', '-')}/")
469
+ )
470
+ ):
471
+ return dist.metadata["Name"]
445
472
  except Exception:
446
473
  continue
447
474
 
@@ -494,8 +521,14 @@ class BuildManager:
494
521
  List of unique third-party package names (e.g., ["pypdf", "requests", "pymupdf"])
495
522
  """
496
523
  third_party_packages: set[str] = set()
524
+ # Cache package name lookups to avoid repeated expensive searches
525
+ package_name_cache: dict[str, str | None] = {}
526
+
527
+ total_files = len(python_files)
528
+ for idx, file_path in enumerate(python_files):
529
+ if idx > 0 and idx % 50 == 0:
530
+ print(f" Analyzing file {idx}/{total_files}...", end="\r", flush=True)
497
531
 
498
- for file_path in python_files:
499
532
  imports = analyzer.extract_imports(file_path)
500
533
  for imp in imports:
501
534
  analyzer.classify_import(imp, self.src_dir)
@@ -510,8 +543,12 @@ class BuildManager:
510
543
 
511
544
  # If classified as third_party, try to get actual package name
512
545
  if imp.classification == "third_party":
513
- # Try to get the actual package name from metadata
514
- actual_package = self._get_package_name_from_import(imp.module_name)
546
+ # Check cache first
547
+ if root_module not in package_name_cache:
548
+ package_name_cache[root_module] = self._get_package_name_from_import(
549
+ imp.module_name
550
+ )
551
+ actual_package = package_name_cache[root_module]
515
552
  if actual_package:
516
553
  third_party_packages.add(actual_package)
517
554
  else:
@@ -522,15 +559,21 @@ class BuildManager:
522
559
  elif imp.classification == "ambiguous" or imp.classification is None:
523
560
  # Check if it's not a local or external module
524
561
  if not imp.resolved_path:
525
- # Try to get the actual package name from metadata
526
- # (might work if package is installed but module path is unusual)
527
- actual_package = self._get_package_name_from_import(imp.module_name)
562
+ # Check cache first
563
+ if root_module not in package_name_cache:
564
+ package_name_cache[root_module] = self._get_package_name_from_import(
565
+ imp.module_name
566
+ )
567
+ actual_package = package_name_cache[root_module]
528
568
  if actual_package:
529
569
  third_party_packages.add(actual_package)
530
570
  else:
531
571
  # Fallback: use import name (will be normalized later)
532
572
  third_party_packages.add(root_module)
533
573
 
574
+ if total_files > 50:
575
+ print() # New line after progress indicator
576
+
534
577
  return sorted(list(third_party_packages))
535
578
 
536
579
  def _report_ambiguous_imports(self, python_files: list[Path]) -> None: