python-package-folder 3.0.1__tar.gz → 3.1.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.
Files changed (48) hide show
  1. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/PKG-INFO +1 -1
  2. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/coverage.svg +2 -2
  3. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/manager.py +43 -0
  4. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/subfolder_build.py +88 -0
  5. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_subfolder_build.py +112 -0
  6. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.copier-answers.yml +0 -0
  7. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.cursor/rules/general.mdc +0 -0
  8. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.cursor/rules/python.mdc +0 -0
  9. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.github/workflows/ci.yml +0 -0
  10. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.github/workflows/publish.yml +0 -0
  11. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.gitignore +0 -0
  12. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/.vscode/settings.json +0 -0
  13. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/LICENSE +0 -0
  14. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/Makefile +0 -0
  15. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/README.md +0 -0
  16. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/development.md +0 -0
  17. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/installation.md +0 -0
  18. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/publishing.md +0 -0
  19. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/pyproject.toml +0 -0
  20. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/__init__.py +0 -0
  21. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/__main__.py +0 -0
  22. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/analyzer.py +0 -0
  23. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/finder.py +0 -0
  24. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/publisher.py +0 -0
  25. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/py.typed +0 -0
  26. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/python_package_folder.py +0 -0
  27. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/types.py +0 -0
  28. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/utils.py +0 -0
  29. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/src/python_package_folder/version.py +0 -0
  30. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/conftest.py +0 -0
  31. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/some_globals.py +0 -0
  32. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  33. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  34. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  35. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  36. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  37. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  38. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_build_with_external_deps.py +0 -0
  39. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_linting.py +0 -0
  40. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_preserve_directory_structure.py +0 -0
  41. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_publisher.py +0 -0
  42. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_shared_subdirectory_imports.py +0 -0
  43. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_spreadsheet_creation_imports.py +0 -0
  44. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_third_party_dependencies.py +0 -0
  45. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_utils.py +0 -0
  46. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/test_version_manager.py +0 -0
  47. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/tests/tests.py +0 -0
  48. {python_package_folder-3.0.1 → python_package_folder-3.1.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 3.0.1
3
+ Version: 3.1.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">68%</text>
18
- <text x="81" y="14">68%</text>
17
+ <text x="81" y="15" fill="#010101" fill-opacity=".3">69%</text>
18
+ <text x="81" y="14">69%</text>
19
19
  </g>
20
20
  </svg>
@@ -339,6 +339,13 @@ class BuildManager:
339
339
  shutil.copy2(source, target)
340
340
  self.copied_files.append(target)
341
341
  print(f"Copied external file: {source} -> {target}")
342
+ # If copying a Python file, ensure parent directory has __init__.py
343
+ # This helps type checkers resolve imports correctly
344
+ if source.suffix == ".py" and target.parent != self.src_dir:
345
+ init_file = target.parent / "__init__.py"
346
+ if not init_file.exists():
347
+ init_file.write_text("", encoding="utf-8")
348
+ self.copied_files.append(init_file)
342
349
  elif source.is_dir():
343
350
  if target.exists():
344
351
  shutil.rmtree(target)
@@ -354,6 +361,8 @@ class BuildManager:
354
361
  Copy a directory tree, excluding certain patterns.
355
362
 
356
363
  Excludes directories matching patterns like _SS, __SS, _sandbox, etc.
364
+ Ensures __init__.py files exist in directories containing Python files
365
+ so that type checkers can resolve imports correctly.
357
366
 
358
367
  Args:
359
368
  src: Source directory
@@ -385,6 +394,9 @@ class BuildManager:
385
394
  # Create destination directory
386
395
  dst.mkdir(parents=True, exist_ok=True)
387
396
 
397
+ has_python_files = False
398
+ copied_python_files = []
399
+
388
400
  # Copy files and subdirectories, excluding patterns
389
401
  for item in src.iterdir():
390
402
  if should_exclude(item):
@@ -395,8 +407,39 @@ class BuildManager:
395
407
 
396
408
  if src_item.is_file():
397
409
  shutil.copy2(src_item, dst_item)
410
+ if src_item.suffix == ".py":
411
+ has_python_files = True
412
+ copied_python_files.append(dst_item)
398
413
  elif src_item.is_dir():
399
414
  self._copytree_excluding(src_item, dst_item)
415
+ # Check if the subdirectory has Python files
416
+ if any(dst_item.rglob("*.py")):
417
+ has_python_files = True
418
+
419
+ # Ensure __init__.py exists in directories containing Python files
420
+ # This is needed for type checkers to resolve imports correctly
421
+ # Also ensure all parent directories in the path have __init__.py
422
+ if has_python_files:
423
+ init_file = dst / "__init__.py"
424
+ if not init_file.exists():
425
+ # Create an empty __init__.py file
426
+ init_file.write_text("", encoding="utf-8")
427
+ self.copied_files.append(init_file)
428
+
429
+ # Ensure all parent directories up to src_dir also have __init__.py
430
+ # This helps type checkers resolve nested imports like:
431
+ # from empty_drawing_detection.models.Information_extraction._shared_ie.ie_enums import ...
432
+ current = dst
433
+ while current != self.src_dir and current.is_relative_to(self.src_dir):
434
+ parent_init = current.parent / "__init__.py"
435
+ if (
436
+ parent_init.parent != self.src_dir
437
+ and not parent_init.exists()
438
+ and any(current.parent.rglob("*.py"))
439
+ ):
440
+ parent_init.write_text("", encoding="utf-8")
441
+ self.copied_files.append(parent_init)
442
+ current = current.parent
400
443
 
401
444
  def _get_package_name_from_import(self, module_name: str) -> str | None:
402
445
  """
@@ -140,7 +140,9 @@ class SubfolderBuildConfig:
140
140
  lines = content.split("\n")
141
141
  result = []
142
142
  in_hatch_build = False
143
+ in_hatch_build_section = False
143
144
  packages_set = False
145
+ paths_exclude_set = False
144
146
 
145
147
  for line in lines:
146
148
  # Detect hatch build section
@@ -148,6 +150,10 @@ class SubfolderBuildConfig:
148
150
  in_hatch_build = True
149
151
  result.append(line)
150
152
  continue
153
+ elif line.strip().startswith("[tool.hatch.build]"):
154
+ in_hatch_build_section = True
155
+ result.append(line)
156
+ continue
151
157
  elif line.strip().startswith("[") and in_hatch_build:
152
158
  # End of hatch build section, add packages if not set
153
159
  if not packages_set and correct_packages_path:
@@ -155,6 +161,15 @@ class SubfolderBuildConfig:
155
161
  result.append(f"packages = [{packages_str}]")
156
162
  in_hatch_build = False
157
163
  result.append(line)
164
+ elif line.strip().startswith("[") and in_hatch_build_section:
165
+ # End of hatch build section
166
+ in_hatch_build_section = False
167
+ result.append(line)
168
+ elif in_hatch_build_section:
169
+ # Track if paths-exclude already exists
170
+ if re.match(r"^\s*paths-exclude\s*=", line):
171
+ paths_exclude_set = True
172
+ result.append(line)
158
173
  elif in_hatch_build:
159
174
  # Modify packages path if found
160
175
  if re.match(r"^\s*packages\s*=", line):
@@ -194,6 +209,43 @@ class SubfolderBuildConfig:
194
209
  packages_str = f'"{correct_packages_path}"'
195
210
  result.append(f"packages = [{packages_str}]")
196
211
 
212
+ # Add file exclusion patterns to prevent including non-package files
213
+ # Only add if paths-exclude wasn't already set (we merged it above)
214
+ if not paths_exclude_set:
215
+ result.append("")
216
+ result.append("[tool.hatch.build]")
217
+ result.append("paths-exclude = [")
218
+ result.append(' ".cursor/**",')
219
+ result.append(' ".github/**",')
220
+ result.append(' ".vscode/**",')
221
+ result.append(' ".idea/**",')
222
+ result.append(' "data/**",')
223
+ result.append(' "docs/**",')
224
+ result.append(' "references/**",')
225
+ result.append(' "reports/**",')
226
+ result.append(' "scripts/**",')
227
+ result.append(' "tests/**",')
228
+ result.append(' "test/**",')
229
+ result.append(' "dist/**",')
230
+ result.append(' "build/**",')
231
+ result.append(' "*.egg-info/**",')
232
+ result.append(' "__pycache__/**",')
233
+ result.append(' ".pytest_cache/**",')
234
+ result.append(' ".mypy_cache/**",')
235
+ result.append(' ".venv/**",')
236
+ result.append(' "venv/**",')
237
+ result.append(' ".git/**",')
238
+ result.append(' ".gitignore",')
239
+ result.append(' ".gitattributes",')
240
+ result.append(' "Dockerfile",')
241
+ result.append(' ".dockerignore",')
242
+ result.append(' ".pylintrc",')
243
+ result.append(' "pyrightconfig.json",')
244
+ result.append(' "git-filter-repo",')
245
+ result.append(' "pyproject.toml.original",')
246
+ result.append(' "README.md.backup",')
247
+ result.append("]")
248
+
197
249
  return "\n".join(result)
198
250
 
199
251
  def create_temp_pyproject(self) -> Path | None:
@@ -532,6 +584,42 @@ class SubfolderBuildConfig:
532
584
  packages_str = ", ".join(f'"{p}"' for p in package_dirs)
533
585
  result.append(f"packages = [{packages_str}]")
534
586
 
587
+ # Add file exclusion patterns to prevent including non-package files
588
+ # This ensures only the subfolder code is included, not project root files
589
+ result.append("")
590
+ result.append("[tool.hatch.build]")
591
+ result.append("paths-exclude = [")
592
+ result.append(' ".cursor/**",')
593
+ result.append(' ".github/**",')
594
+ result.append(' ".vscode/**",')
595
+ result.append(' ".idea/**",')
596
+ result.append(' "data/**",')
597
+ result.append(' "docs/**",')
598
+ result.append(' "references/**",')
599
+ result.append(' "reports/**",')
600
+ result.append(' "scripts/**",')
601
+ result.append(' "tests/**",')
602
+ result.append(' "test/**",')
603
+ result.append(' "dist/**",')
604
+ result.append(' "build/**",')
605
+ result.append(' "*.egg-info/**",')
606
+ result.append(' "__pycache__/**",')
607
+ result.append(' ".pytest_cache/**",')
608
+ result.append(' ".mypy_cache/**",')
609
+ result.append(' ".venv/**",')
610
+ result.append(' "venv/**",')
611
+ result.append(' ".git/**",')
612
+ result.append(' ".gitignore",')
613
+ result.append(' ".gitattributes",')
614
+ result.append(' "Dockerfile",')
615
+ result.append(' ".dockerignore",')
616
+ result.append(' ".pylintrc",')
617
+ result.append(' "pyrightconfig.json",')
618
+ result.append(' "git-filter-repo",')
619
+ result.append(' "pyproject.toml.original",')
620
+ result.append(' "README.md.backup",')
621
+ result.append("]")
622
+
535
623
  # Add dependency group if specified
536
624
  if dependency_group:
537
625
  # Find where to insert dependency-groups section
@@ -675,6 +675,118 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
675
675
  # Cleanup
676
676
  config.restore()
677
677
 
678
+ def test_file_exclusion_patterns_added(self, test_project_with_pyproject: Path) -> None:
679
+ """Test that file exclusion patterns are added to temporary pyproject.toml."""
680
+ project_root = test_project_with_pyproject
681
+ subfolder = project_root / "subfolder"
682
+
683
+ # Create various non-package files and directories that should be excluded
684
+ (project_root / ".cursor").mkdir()
685
+ (project_root / ".github").mkdir()
686
+ (project_root / ".vscode").mkdir()
687
+ (project_root / "data").mkdir()
688
+ (project_root / "docs").mkdir()
689
+ (project_root / "references").mkdir()
690
+ (project_root / "reports").mkdir()
691
+ (project_root / "scripts").mkdir()
692
+ (project_root / "tests").mkdir()
693
+ (project_root / "Dockerfile").write_text("# Dockerfile")
694
+ (project_root / ".gitignore").write_text("*.pyc")
695
+
696
+ # Ensure no pyproject.toml in subfolder
697
+ subfolder_pyproject = subfolder / "pyproject.toml"
698
+ if subfolder_pyproject.exists():
699
+ subfolder_pyproject.unlink()
700
+
701
+ config = SubfolderBuildConfig(
702
+ project_root=project_root,
703
+ src_dir=subfolder,
704
+ version="1.0.0",
705
+ package_name="test-package",
706
+ )
707
+
708
+ pyproject_path = config.create_temp_pyproject()
709
+
710
+ assert pyproject_path is not None
711
+ assert pyproject_path.exists()
712
+ content = pyproject_path.read_text()
713
+
714
+ # Verify [tool.hatch.build] section exists
715
+ assert "[tool.hatch.build]" in content
716
+
717
+ # Verify paths-exclude is present
718
+ assert "paths-exclude = [" in content
719
+
720
+ # Verify common exclusion patterns are included
721
+ assert '".cursor/**"' in content
722
+ assert '".github/**"' in content
723
+ assert '".vscode/**"' in content
724
+ assert '"data/**"' in content
725
+ assert '"docs/**"' in content
726
+ assert '"references/**"' in content
727
+ assert '"reports/**"' in content
728
+ assert '"scripts/**"' in content
729
+ assert '"tests/**"' in content
730
+ assert '"dist/**"' in content
731
+ assert '"build/**"' in content
732
+ assert '"*.egg-info/**"' in content
733
+ assert '"__pycache__/**"' in content
734
+ assert '".pytest_cache/**"' in content
735
+ assert '".git/**"' in content
736
+ assert '"Dockerfile"' in content
737
+ assert '".gitignore"' in content
738
+
739
+ # Cleanup
740
+ config.restore()
741
+
742
+ def test_file_exclusion_patterns_with_subfolder_pyproject(
743
+ self, test_project_with_pyproject: Path
744
+ ) -> None:
745
+ """Test that file exclusion patterns are added when subfolder has its own pyproject.toml."""
746
+ project_root = test_project_with_pyproject
747
+ subfolder = project_root / "subfolder"
748
+
749
+ # Create various non-package files that should be excluded
750
+ (project_root / ".cursor").mkdir()
751
+ (project_root / "data").mkdir()
752
+ (project_root / "docs").mkdir()
753
+
754
+ # Create pyproject.toml in subfolder
755
+ subfolder_pyproject_content = """[project]
756
+ name = "subfolder-package"
757
+ version = "1.0.0"
758
+ description = "Subfolder package"
759
+ """
760
+ (subfolder / "pyproject.toml").write_text(subfolder_pyproject_content)
761
+
762
+ config = SubfolderBuildConfig(
763
+ project_root=project_root,
764
+ src_dir=subfolder,
765
+ version="2.0.0", # Should be ignored since subfolder has its own
766
+ )
767
+
768
+ pyproject_path = config.create_temp_pyproject()
769
+
770
+ assert pyproject_path is not None
771
+ assert pyproject_path.exists()
772
+ content = pyproject_path.read_text()
773
+
774
+ # Verify [tool.hatch.build] section exists
775
+ assert "[tool.hatch.build]" in content
776
+
777
+ # Verify paths-exclude is present
778
+ assert "paths-exclude = [" in content
779
+
780
+ # Verify common exclusion patterns are included
781
+ assert '".cursor/**"' in content
782
+ assert '"data/**"' in content
783
+ assert '"docs/**"' in content
784
+ assert '"dist/**"' in content
785
+ assert '"build/**"' in content
786
+
787
+ # Cleanup
788
+ config.restore()
789
+
678
790
  def test_third_party_dependencies_added(self, test_project_with_pyproject: Path) -> None:
679
791
  """Test that third-party dependencies are added to temporary pyproject.toml."""
680
792
  project_root = test_project_with_pyproject