python-package-folder 1.4.1__py3-none-any.whl → 2.0.2__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.
@@ -268,6 +268,19 @@ class BuildManager:
268
268
  for dep in external_deps:
269
269
  self._copy_dependency(dep)
270
270
 
271
+ # For subfolder builds, extract third-party dependencies and add to pyproject.toml
272
+ if self._is_subfolder_build() and self.subfolder_config:
273
+ # Re-analyze all Python files (including copied dependencies) to find third-party imports
274
+ all_python_files = analyzer.find_all_python_files(self.src_dir)
275
+ third_party_deps = self._extract_third_party_dependencies(all_python_files, analyzer)
276
+ if third_party_deps:
277
+ print(
278
+ f"Found {len(third_party_deps)} third-party dependencies: {', '.join(third_party_deps)}"
279
+ )
280
+ self.subfolder_config.add_third_party_dependencies(third_party_deps)
281
+ else:
282
+ print("No third-party dependencies found in subfolder code")
283
+
271
284
  # Report ambiguous imports
272
285
  self._report_ambiguous_imports(python_files)
273
286
 
@@ -381,6 +394,51 @@ class BuildManager:
381
394
  elif src_item.is_dir():
382
395
  self._copytree_excluding(src_item, dst_item)
383
396
 
397
+ def _extract_third_party_dependencies(
398
+ self, python_files: list[Path], analyzer: ImportAnalyzer
399
+ ) -> list[str]:
400
+ """
401
+ Extract third-party package dependencies from Python files.
402
+
403
+ Analyzes all Python files to find imports classified as "third_party"
404
+ and returns a list of unique package names.
405
+
406
+ Args:
407
+ python_files: List of Python file paths to analyze
408
+ analyzer: ImportAnalyzer instance to use for classification
409
+
410
+ Returns:
411
+ List of unique third-party package names (e.g., ["pypdf", "requests"])
412
+ """
413
+ third_party_packages: set[str] = set()
414
+
415
+ for file_path in python_files:
416
+ imports = analyzer.extract_imports(file_path)
417
+ for imp in imports:
418
+ analyzer.classify_import(imp, self.src_dir)
419
+
420
+ # Extract the root package name (first part of module name)
421
+ root_module = imp.module_name.split(".")[0]
422
+
423
+ # Skip if it's a standard library module
424
+ stdlib_modules = analyzer.get_stdlib_modules()
425
+ if root_module in stdlib_modules:
426
+ continue
427
+
428
+ # If classified as third_party, add it
429
+ if imp.classification == "third_party":
430
+ third_party_packages.add(root_module)
431
+ # If it's ambiguous or unresolved, and not stdlib/local/external,
432
+ # it's likely a third-party package that needs to be declared
433
+ elif imp.classification == "ambiguous" or imp.classification is None:
434
+ # Check if it's not a local or external module
435
+ 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)
439
+
440
+ return sorted(list(third_party_packages))
441
+
384
442
  def _report_ambiguous_imports(self, python_files: list[Path]) -> None:
385
443
  """
386
444
  Report any ambiguous imports that couldn't be resolved.
@@ -254,6 +254,9 @@ class SubfolderBuildConfig:
254
254
  # If original pyproject.toml exists, temporarily move it
255
255
  if original_pyproject.exists():
256
256
  backup_path = self.project_root / "pyproject.toml.original"
257
+ # Remove backup if it already exists (from previous failed test or run)
258
+ if backup_path.exists():
259
+ backup_path.unlink()
257
260
  original_pyproject.rename(backup_path)
258
261
  self.original_pyproject_backup = backup_path
259
262
 
@@ -292,6 +295,9 @@ class SubfolderBuildConfig:
292
295
 
293
296
  # Temporarily move original to backup location
294
297
  backup_path = self.project_root / "pyproject.toml.original"
298
+ # Remove backup if it already exists (from previous failed test or run)
299
+ if backup_path.exists():
300
+ backup_path.unlink()
295
301
  original_pyproject.rename(backup_path)
296
302
  self.original_pyproject_backup = backup_path
297
303
 
@@ -559,6 +565,117 @@ class SubfolderBuildConfig:
559
565
 
560
566
  return "\n".join(result)
561
567
 
568
+ def add_third_party_dependencies(self, dependencies: list[str]) -> None:
569
+ """
570
+ Add third-party dependencies to the temporary pyproject.toml.
571
+
572
+ This method updates the pyproject.toml file that was created for the subfolder
573
+ build by adding the specified dependencies to the [project.dependencies] section.
574
+
575
+ Args:
576
+ dependencies: List of third-party package names to add (e.g., ["pypdf", "requests"])
577
+ """
578
+ if not self.temp_pyproject or not self.temp_pyproject.exists():
579
+ print(
580
+ f"Warning: Cannot add third-party dependencies - pyproject.toml not found at {self.temp_pyproject}",
581
+ file=sys.stderr,
582
+ )
583
+ return
584
+
585
+ if not dependencies:
586
+ return
587
+
588
+ print(f"Adding third-party dependencies to pyproject.toml: {', '.join(dependencies)}")
589
+ content = self.temp_pyproject.read_text(encoding="utf-8")
590
+ updated_content = self._add_dependencies_to_pyproject(content, dependencies)
591
+ self.temp_pyproject.write_text(updated_content, encoding="utf-8")
592
+
593
+ def _add_dependencies_to_pyproject(self, content: str, dependencies: list[str]) -> str:
594
+ """
595
+ Add dependencies to pyproject.toml content.
596
+
597
+ Adds the specified dependencies to the [project] section's dependencies list.
598
+ If dependencies already exist, merges them. If no dependencies section exists,
599
+ creates one.
600
+
601
+ Args:
602
+ content: Current pyproject.toml content
603
+ dependencies: List of dependency names to add
604
+
605
+ Returns:
606
+ Updated pyproject.toml content with dependencies added
607
+ """
608
+ if not dependencies:
609
+ return content
610
+
611
+ lines = content.split("\n")
612
+ result = []
613
+ in_project = False
614
+ in_dependencies = False
615
+ dependencies_added = False
616
+ existing_deps: set[str] = set()
617
+
618
+ # First pass: find existing dependencies
619
+ for line in lines:
620
+ if line.strip().startswith("[project]"):
621
+ in_project = True
622
+ elif line.strip().startswith("[") and in_project:
623
+ in_project = False
624
+ elif in_project and re.match(r"^\s*dependencies\s*=\s*\[", line):
625
+ in_dependencies = True
626
+ elif in_dependencies:
627
+ # Extract existing dependency names
628
+ dep_match = re.search(r'["\']([^"\']+)["\']', line)
629
+ if dep_match:
630
+ existing_deps.add(dep_match.group(1))
631
+ if line.strip().endswith("]"):
632
+ in_dependencies = False
633
+
634
+ # Merge with new dependencies
635
+ all_deps = sorted(existing_deps | set(dependencies))
636
+
637
+ # Second pass: build result with dependencies
638
+ in_project = False
639
+ in_dependencies = False
640
+ for line in lines:
641
+ if line.strip().startswith("[project]"):
642
+ in_project = True
643
+ result.append(line)
644
+ elif line.strip().startswith("[") and in_project:
645
+ # End of [project] section, add dependencies if not already present
646
+ if not dependencies_added:
647
+ result.append("dependencies = [")
648
+ for dep in all_deps:
649
+ result.append(f' "{dep}",')
650
+ result.append("]")
651
+ result.append("")
652
+ in_project = False
653
+ result.append(line)
654
+ elif in_project and re.match(r"^\s*dependencies\s*=\s*\[", line):
655
+ # Replace existing dependencies section
656
+ result.append("dependencies = [")
657
+ for dep in all_deps:
658
+ result.append(f' "{dep}",')
659
+ result.append("]")
660
+ dependencies_added = True
661
+ in_dependencies = True
662
+ elif in_dependencies:
663
+ # Skip lines in existing dependencies section (already replaced)
664
+ if line.strip().endswith("]"):
665
+ in_dependencies = False
666
+ else:
667
+ result.append(line)
668
+
669
+ # If [project] section exists but no dependencies were added, add them
670
+ if in_project and not dependencies_added:
671
+ result.append("dependencies = [")
672
+ for dep in all_deps:
673
+ result.append(f' "{dep}",')
674
+ result.append("]")
675
+ result.append("")
676
+
677
+ return "\n".join(result)
678
+
562
679
  def _handle_readme(self) -> None:
563
680
  """
564
681
  Handle README file for subfolder builds.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 1.4.1
3
+ Version: 2.0.2
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=cQRBLdru8bSovenR5hXFRKmjrBdTmjl0owJdqbXyHjg,30989
5
+ python_package_folder/manager.py,sha256=t3mbTnn42QCLWSOC5P_XRJPi1bL9hyYaCZVdITENHXk,33764
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=JRctqCaaax76sI7qvjRxR3M9Otz27_tUVYKCCN-Gjyw,29535
9
+ python_package_folder/subfolder_build.py,sha256=IfnXW5Xx8-WMuBc8hTdbCImDIuUAiHvVe1k5V2FbwoY,34339
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-1.4.1.dist-info/METADATA,sha256=E6egWuU3bFmFJeM8s74ra7If-P69qNeGNS6Qnz8YRyc,33282
14
- python_package_folder-1.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- python_package_folder-1.4.1.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
16
- python_package_folder-1.4.1.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
17
- python_package_folder-1.4.1.dist-info/RECORD,,
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,,