python-package-folder 2.0.2__py3-none-any.whl → 2.0.4__py3-none-any.whl

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.
@@ -394,6 +394,88 @@ class BuildManager:
394
394
  elif src_item.is_dir():
395
395
  self._copytree_excluding(src_item, dst_item)
396
396
 
397
+ def _get_package_name_from_import(self, module_name: str) -> str | None:
398
+ """
399
+ Get the actual PyPI package name from an import module name.
400
+
401
+ This handles cases where the import name differs from the package name
402
+ (e.g., 'import fitz' from 'pymupdf' package).
403
+
404
+ Args:
405
+ module_name: The module name from the import statement
406
+
407
+ Returns:
408
+ The actual package name, or None if not found
409
+ """
410
+ root_module = module_name.split(".")[0]
411
+ try:
412
+ # Try Python 3.10+ first (has packages_distributions)
413
+ import importlib.metadata as importlib_metadata
414
+
415
+ # Use packages_distributions() if available (Python 3.10+)
416
+ if hasattr(importlib_metadata, "packages_distributions"):
417
+ packages_map = importlib_metadata.packages_distributions()
418
+ # packages_map is a dict mapping module names to list of distribution names
419
+ if root_module in packages_map:
420
+ # Return the first distribution name (usually there's only one)
421
+ dist_names = packages_map[root_module]
422
+ if dist_names:
423
+ return dist_names[0]
424
+
425
+ # Fallback: search all distributions
426
+ for dist in importlib_metadata.distributions():
427
+ 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"]
445
+ except Exception:
446
+ continue
447
+
448
+ except ImportError:
449
+ try:
450
+ # Fallback for older Python versions
451
+ import importlib_metadata
452
+
453
+ # Search all distributions
454
+ for dist in importlib_metadata.distributions():
455
+ try:
456
+ files = dist.files or []
457
+ for file in files:
458
+ file_str = str(file)
459
+ if (
460
+ file.suffix == ".py"
461
+ and (file.stem == root_module or file.stem == "__init__")
462
+ ) or (
463
+ "/" in file_str
464
+ and (
465
+ file_str.startswith(f"{root_module}/")
466
+ or file_str.startswith(f"{root_module.replace('_', '-')}/")
467
+ )
468
+ ):
469
+ return dist.metadata["Name"]
470
+ except Exception:
471
+ continue
472
+ except ImportError:
473
+ pass
474
+ except Exception:
475
+ pass
476
+
477
+ return None
478
+
397
479
  def _extract_third_party_dependencies(
398
480
  self, python_files: list[Path], analyzer: ImportAnalyzer
399
481
  ) -> list[str]:
@@ -401,14 +483,15 @@ class BuildManager:
401
483
  Extract third-party package dependencies from Python files.
402
484
 
403
485
  Analyzes all Python files to find imports classified as "third_party"
404
- and returns a list of unique package names.
486
+ and returns a list of unique package names. Handles cases where the
487
+ import name differs from the package name (e.g., 'fitz' -> 'pymupdf').
405
488
 
406
489
  Args:
407
490
  python_files: List of Python file paths to analyze
408
491
  analyzer: ImportAnalyzer instance to use for classification
409
492
 
410
493
  Returns:
411
- List of unique third-party package names (e.g., ["pypdf", "requests"])
494
+ List of unique third-party package names (e.g., ["pypdf", "requests", "pymupdf"])
412
495
  """
413
496
  third_party_packages: set[str] = set()
414
497
 
@@ -425,17 +508,28 @@ class BuildManager:
425
508
  if root_module in stdlib_modules:
426
509
  continue
427
510
 
428
- # If classified as third_party, add it
511
+ # If classified as third_party, try to get actual package name
429
512
  if imp.classification == "third_party":
430
- third_party_packages.add(root_module)
513
+ # Try to get the actual package name from metadata
514
+ actual_package = self._get_package_name_from_import(imp.module_name)
515
+ if actual_package:
516
+ third_party_packages.add(actual_package)
517
+ else:
518
+ # Fallback to using the import name
519
+ third_party_packages.add(root_module)
431
520
  # If it's ambiguous or unresolved, and not stdlib/local/external,
432
521
  # it's likely a third-party package that needs to be declared
433
522
  elif imp.classification == "ambiguous" or imp.classification is None:
434
523
  # Check if it's not a local or external module
435
524
  if not imp.resolved_path:
436
- # This is likely a third-party package that's not installed
437
- # in the build environment but needs to be declared
438
- third_party_packages.add(root_module)
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)
528
+ if actual_package:
529
+ third_party_packages.add(actual_package)
530
+ else:
531
+ # Fallback: use import name (will be normalized later)
532
+ third_party_packages.add(root_module)
439
533
 
440
534
  return sorted(list(third_party_packages))
441
535
 
@@ -590,17 +590,36 @@ class SubfolderBuildConfig:
590
590
  updated_content = self._add_dependencies_to_pyproject(content, dependencies)
591
591
  self.temp_pyproject.write_text(updated_content, encoding="utf-8")
592
592
 
593
+ def _normalize_package_name(self, package_name: str) -> str:
594
+ """
595
+ Normalize package name for PyPI.
596
+
597
+ Converts underscores to hyphens, as PyPI package names typically use hyphens
598
+ while Python import names use underscores (e.g., 'better_enum' -> 'better-enum').
599
+
600
+ Args:
601
+ package_name: Package name from import statement
602
+
603
+ Returns:
604
+ Normalized package name for PyPI
605
+ """
606
+ # Convert underscores to hyphens for PyPI package names
607
+ # This handles the common case where import names use underscores
608
+ # but PyPI package names use hyphens
609
+ return package_name.replace("_", "-")
610
+
593
611
  def _add_dependencies_to_pyproject(self, content: str, dependencies: list[str]) -> str:
594
612
  """
595
613
  Add dependencies to pyproject.toml content.
596
614
 
597
615
  Adds the specified dependencies to the [project] section's dependencies list.
598
616
  If dependencies already exist, merges them. If no dependencies section exists,
599
- creates one.
617
+ creates one. Package names are normalized (underscores -> hyphens) to match
618
+ PyPI naming conventions.
600
619
 
601
620
  Args:
602
621
  content: Current pyproject.toml content
603
- dependencies: List of dependency names to add
622
+ dependencies: List of dependency names to add (will be normalized)
604
623
 
605
624
  Returns:
606
625
  Updated pyproject.toml content with dependencies added
@@ -608,6 +627,9 @@ class SubfolderBuildConfig:
608
627
  if not dependencies:
609
628
  return content
610
629
 
630
+ # Normalize package names (convert underscores to hyphens for PyPI)
631
+ normalized_deps = [self._normalize_package_name(dep) for dep in dependencies]
632
+
611
633
  lines = content.split("\n")
612
634
  result = []
613
635
  in_project = False
@@ -631,8 +653,8 @@ class SubfolderBuildConfig:
631
653
  if line.strip().endswith("]"):
632
654
  in_dependencies = False
633
655
 
634
- # Merge with new dependencies
635
- all_deps = sorted(existing_deps | set(dependencies))
656
+ # Merge with new dependencies (normalized)
657
+ all_deps = sorted(existing_deps | set(normalized_deps))
636
658
 
637
659
  # Second pass: build result with dependencies
638
660
  in_project = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 2.0.2
3
+ Version: 2.0.4
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>
@@ -2,16 +2,16 @@ python_package_folder/__init__.py,sha256=DQt-uldOEKfh0MUqCvKdeNKOnpuOvpb7blYvXMy
2
2
  python_package_folder/__main__.py,sha256=a-__-VLhYw-J7S7CsHdhtEvQr3RiAZxiYDvKhKTgMX4,291
3
3
  python_package_folder/analyzer.py,sha256=w7hc2oyOoPK7tvlwcJDXnB3eiJsuGZc4BkOpTfZP7Vo,12257
4
4
  python_package_folder/finder.py,sha256=_LvJ9xBVKv41UK5sbwbNyKmuYjAOqUbzvZhK7NCYQF8,9130
5
- python_package_folder/manager.py,sha256=t3mbTnn42QCLWSOC5P_XRJPi1bL9hyYaCZVdITENHXk,33764
5
+ python_package_folder/manager.py,sha256=C3atdBMZqZTFnDMuKSTtb7q2gL90AHo7Wu7u3l8nLLI,38085
6
6
  python_package_folder/publisher.py,sha256=TSjdOvxvnWLbJCnduTK_xZBRfvsrq9kpEH-sfebeWkU,13507
7
7
  python_package_folder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  python_package_folder/python_package_folder.py,sha256=RPsqRcIy_LjzzTHdp4qdtFJ4-4xhtR_0YLIC0RlUxFo,8841
9
- python_package_folder/subfolder_build.py,sha256=IfnXW5Xx8-WMuBc8hTdbCImDIuUAiHvVe1k5V2FbwoY,34339
9
+ python_package_folder/subfolder_build.py,sha256=LMiJ4Ck6PW1x3hmiSK-9fDH4Q6RrHqot4drx4lise3E,35310
10
10
  python_package_folder/types.py,sha256=3yeSRR5p_3PDKEAaehW_RJ7NwJHexOIeA08bGaT1iSY,2368
11
11
  python_package_folder/utils.py,sha256=lIkWsFKeAYAJ9TDUM99T4pUBHJVbUvCdUgkWQN-LUho,3111
12
12
  python_package_folder/version.py,sha256=kIDP6S9trEfs9gj7lBYGxrWm4RPssRla24UtlO9Jkh4,9111
13
- python_package_folder-2.0.2.dist-info/METADATA,sha256=1AFTide6hdpSNzjVFpLai2Mo-UYyfSSlA9hcx66QJBM,33282
14
- python_package_folder-2.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- python_package_folder-2.0.2.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
16
- python_package_folder-2.0.2.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
17
- python_package_folder-2.0.2.dist-info/RECORD,,
13
+ python_package_folder-2.0.4.dist-info/METADATA,sha256=4PIl6MNJWiM4Laz4Rs36zLr5O9T34BnqN9RjWDmR_VQ,33282
14
+ python_package_folder-2.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ python_package_folder-2.0.4.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
16
+ python_package_folder-2.0.4.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
17
+ python_package_folder-2.0.4.dist-info/RECORD,,