python-package-folder 3.1.0__tar.gz → 3.1.2__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.1.0 → python_package_folder-3.1.2}/PKG-INFO +1 -1
  2. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/coverage.svg +2 -2
  3. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/manager.py +160 -0
  4. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/subfolder_build.py +44 -74
  5. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_build_with_external_deps.py +88 -0
  6. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_subfolder_build.py +27 -34
  7. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.copier-answers.yml +0 -0
  8. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.cursor/rules/general.mdc +0 -0
  9. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.cursor/rules/python.mdc +0 -0
  10. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.github/workflows/ci.yml +0 -0
  11. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.github/workflows/publish.yml +0 -0
  12. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.gitignore +0 -0
  13. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/.vscode/settings.json +0 -0
  14. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/LICENSE +0 -0
  15. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/Makefile +0 -0
  16. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/README.md +0 -0
  17. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/development.md +0 -0
  18. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/installation.md +0 -0
  19. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/publishing.md +0 -0
  20. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/pyproject.toml +0 -0
  21. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/__init__.py +0 -0
  22. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/__main__.py +0 -0
  23. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/analyzer.py +0 -0
  24. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/finder.py +0 -0
  25. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/publisher.py +0 -0
  26. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/py.typed +0 -0
  27. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/python_package_folder.py +0 -0
  28. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/types.py +0 -0
  29. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/utils.py +0 -0
  30. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/src/python_package_folder/version.py +0 -0
  31. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/conftest.py +0 -0
  32. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/some_globals.py +0 -0
  33. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  34. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  35. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  36. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  37. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  38. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  39. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_linting.py +0 -0
  40. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_preserve_directory_structure.py +0 -0
  41. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_publisher.py +0 -0
  42. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_shared_subdirectory_imports.py +0 -0
  43. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_spreadsheet_creation_imports.py +0 -0
  44. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_third_party_dependencies.py +0 -0
  45. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_utils.py +0 -0
  46. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/test_version_manager.py +0 -0
  47. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/tests/tests.py +0 -0
  48. {python_package_folder-3.1.0 → python_package_folder-3.1.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 3.1.0
3
+ Version: 3.1.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>
@@ -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>
@@ -88,6 +88,8 @@ class BuildManager:
88
88
  self.subfolder_config: SubfolderBuildConfig | None = None
89
89
  # Cache for package name lookups (expensive operation)
90
90
  self._packages_distributions_cache: dict[str, list[str]] | None = None
91
+ # Track files with modified imports and their original content
92
+ self._modified_import_files: dict[Path, str] = {}
91
93
 
92
94
  # Check if it's a valid Python package directory
93
95
  if not any(self.src_dir.glob("*.py")) and not (self.src_dir / "__init__.py").exists():
@@ -270,6 +272,10 @@ class BuildManager:
270
272
  for dep in external_deps:
271
273
  self._copy_dependency(dep)
272
274
 
275
+ # For subfolder builds, convert absolute imports of copied dependencies to relative imports
276
+ if self._is_subfolder_build() and external_deps:
277
+ self._convert_copied_dependency_imports_to_relative(python_files, external_deps)
278
+
273
279
  # For subfolder builds, extract third-party dependencies and add to pyproject.toml
274
280
  if self._is_subfolder_build() and self.subfolder_config:
275
281
  # Re-analyze all Python files (including copied dependencies) to find third-party imports
@@ -629,6 +635,147 @@ class BuildManager:
629
635
 
630
636
  return sorted(list(third_party_packages))
631
637
 
638
+ def _convert_copied_dependency_imports_to_relative(
639
+ self, python_files: list[Path], external_deps: list[ExternalDependency]
640
+ ) -> None:
641
+ """
642
+ Convert absolute imports of copied dependencies to relative imports.
643
+
644
+ For subfolder builds, when external dependencies are copied into the subfolder,
645
+ imports in the subfolder's files need to be converted from absolute to relative
646
+ so they work correctly when the package is installed.
647
+
648
+ Args:
649
+ python_files: List of Python files in the source directory
650
+ external_deps: List of external dependencies that were copied
651
+ """
652
+ import ast
653
+ import re
654
+
655
+ # Build a set of import names that were copied (e.g., "_shared", "_globals", "empty_drawing_detection_config")
656
+ copied_import_names: set[str] = set()
657
+ for dep in external_deps:
658
+ # Get the root module name (first part of the import)
659
+ root_module = dep.import_name.split(".")[0]
660
+ copied_import_names.add(root_module)
661
+ # Also add the full module name for nested imports
662
+ copied_import_names.add(dep.import_name)
663
+
664
+ if not copied_import_names:
665
+ return
666
+
667
+ # Only modify files that are in the original subfolder (not the copied dependencies)
668
+ for file_path in python_files:
669
+ # Skip files that are part of copied dependencies
670
+ is_copied_file = any(file_path.is_relative_to(dep.target_path) for dep in external_deps)
671
+ if is_copied_file:
672
+ continue
673
+
674
+ # Skip if file is not in src_dir (shouldn't happen, but safety check)
675
+ if not file_path.is_relative_to(self.src_dir):
676
+ continue
677
+
678
+ try:
679
+ content = file_path.read_text(encoding="utf-8")
680
+ original_content = content
681
+ lines = content.split("\n")
682
+ modified = False
683
+
684
+ # Parse the file with AST to find imports accurately
685
+ try:
686
+ tree = ast.parse(content, filename=str(file_path))
687
+ except SyntaxError:
688
+ # Skip files with syntax errors
689
+ continue
690
+
691
+ # Track which lines need to be modified
692
+ lines_to_modify: dict[int, str] = {}
693
+
694
+ for node in ast.walk(tree):
695
+ if isinstance(node, ast.ImportFrom):
696
+ if node.module is None:
697
+ continue
698
+
699
+ # Check if this import matches a copied dependency
700
+ root_module = node.module.split(".")[0]
701
+ if (
702
+ root_module not in copied_import_names
703
+ and node.module not in copied_import_names
704
+ ):
705
+ continue
706
+
707
+ # Get the line content
708
+ line_num = node.lineno - 1 # Convert to 0-based index
709
+ if line_num < 0 or line_num >= len(lines):
710
+ continue
711
+
712
+ original_line = lines[line_num]
713
+
714
+ # Skip if already a relative import
715
+ if original_line.strip().startswith("from ."):
716
+ continue
717
+
718
+ # Convert absolute import to relative import
719
+ # from _shared.image_utils import ... -> from ._shared.image_utils import ...
720
+ new_line = re.sub(
721
+ rf"^(\s*)from\s+{re.escape(node.module)}\s+import",
722
+ rf"\1from .{node.module} import",
723
+ original_line,
724
+ )
725
+
726
+ if new_line != original_line:
727
+ lines_to_modify[line_num] = new_line
728
+ modified = True
729
+
730
+ elif isinstance(node, ast.Import):
731
+ # Handle "import X" statements
732
+ for alias in node.names:
733
+ root_module = alias.name.split(".")[0]
734
+ if root_module not in copied_import_names:
735
+ continue
736
+
737
+ line_num = node.lineno - 1
738
+ if line_num < 0 or line_num >= len(lines):
739
+ continue
740
+
741
+ original_line = lines[line_num]
742
+
743
+ # Skip if already a relative import
744
+ if original_line.strip().startswith("import ."):
745
+ continue
746
+
747
+ # Convert "import _shared" to "from . import _shared"
748
+ # This is more complex, so we'll use a regex replacement
749
+ new_line = re.sub(
750
+ rf"^(\s*)import\s+{re.escape(alias.name)}\b",
751
+ rf"\1from . import {alias.name}",
752
+ original_line,
753
+ )
754
+
755
+ if new_line != original_line:
756
+ lines_to_modify[line_num] = new_line
757
+ modified = True
758
+
759
+ # Apply modifications
760
+ if modified:
761
+ for line_num, new_line in lines_to_modify.items():
762
+ lines[line_num] = new_line
763
+
764
+ new_content = "\n".join(lines)
765
+ # Store original content for restoration
766
+ if file_path not in self._modified_import_files:
767
+ self._modified_import_files[file_path] = original_content
768
+
769
+ # Write modified content
770
+ file_path.write_text(new_content, encoding="utf-8")
771
+ print(f"Converted imports to relative in: {file_path}")
772
+
773
+ except Exception as e:
774
+ print(
775
+ f"Warning: Could not modify imports in {file_path}: {e}",
776
+ file=sys.stderr,
777
+ )
778
+
632
779
  def _report_ambiguous_imports(self, python_files: list[Path]) -> None:
633
780
  """
634
781
  Report any ambiguous imports that couldn't be resolved.
@@ -708,6 +855,19 @@ class BuildManager:
708
855
  self.copied_files.clear()
709
856
  self.copied_dirs.clear()
710
857
 
858
+ # Restore files with modified imports
859
+ for file_path, original_content in self._modified_import_files.items():
860
+ if file_path.exists():
861
+ try:
862
+ file_path.write_text(original_content, encoding="utf-8")
863
+ print(f"Restored original imports in: {file_path}")
864
+ except Exception as e:
865
+ print(
866
+ f"Warning: Could not restore imports in {file_path}: {e}",
867
+ file=sys.stderr,
868
+ )
869
+ self._modified_import_files.clear()
870
+
711
871
  # Remove all .egg-info directories in src_dir and project_root
712
872
  self._cleanup_egg_info_dirs()
713
873
 
@@ -141,8 +141,9 @@ class SubfolderBuildConfig:
141
141
  result = []
142
142
  in_hatch_build = False
143
143
  in_hatch_build_section = False
144
+ in_sdist_section = False
144
145
  packages_set = False
145
- paths_exclude_set = False
146
+ only_include_set = False
146
147
 
147
148
  for line in lines:
148
149
  # Detect hatch build section
@@ -165,10 +166,20 @@ class SubfolderBuildConfig:
165
166
  # End of hatch build section
166
167
  in_hatch_build_section = False
167
168
  result.append(line)
169
+ elif line.strip().startswith("[tool.hatch.build.targets.sdist]"):
170
+ in_sdist_section = True
171
+ result.append(line)
172
+ continue
173
+ elif line.strip().startswith("[") and in_sdist_section:
174
+ # End of sdist section
175
+ in_sdist_section = False
176
+ result.append(line)
177
+ elif in_sdist_section:
178
+ # Track if only-include already exists
179
+ if re.match(r"^\s*only-include\s*=", line):
180
+ only_include_set = True
181
+ result.append(line)
168
182
  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
183
  result.append(line)
173
184
  elif in_hatch_build:
174
185
  # Modify packages path if found
@@ -209,42 +220,21 @@ class SubfolderBuildConfig:
209
220
  packages_str = f'"{correct_packages_path}"'
210
221
  result.append(f"packages = [{packages_str}]")
211
222
 
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:
223
+ # Use only-include for source distributions to ensure only the subfolder is included
224
+ # This prevents including files from the project root
225
+ if correct_packages_path and not only_include_set:
215
226
  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("]")
227
+ result.append("[tool.hatch.build.targets.sdist]")
228
+ # Include only the subfolder directory and necessary files
229
+ only_include_paths = [correct_packages_path]
230
+ # Also include pyproject.toml and README if they exist
231
+ only_include_paths.append("pyproject.toml")
232
+ only_include_paths.append("README.md")
233
+ only_include_paths.append("README.rst")
234
+ only_include_paths.append("README.txt")
235
+ only_include_paths.append("README")
236
+ only_include_str = ", ".join(f'"{p}"' for p in only_include_paths)
237
+ result.append(f"only-include = [{only_include_str}]")
248
238
 
249
239
  return "\n".join(result)
250
240
 
@@ -584,41 +574,21 @@ class SubfolderBuildConfig:
584
574
  packages_str = ", ".join(f'"{p}"' for p in package_dirs)
585
575
  result.append(f"packages = [{packages_str}]")
586
576
 
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("]")
577
+ # Use only-include for source distributions to ensure only the subfolder is included
578
+ # This prevents including files from the project root
579
+ if package_dirs:
580
+ result.append("")
581
+ result.append("[tool.hatch.build.targets.sdist]")
582
+ # Include only the subfolder directory and necessary files
583
+ only_include_paths = [package_dirs[0]]
584
+ # Also include pyproject.toml and README if they exist
585
+ only_include_paths.append("pyproject.toml")
586
+ only_include_paths.append("README.md")
587
+ only_include_paths.append("README.rst")
588
+ only_include_paths.append("README.txt")
589
+ only_include_paths.append("README")
590
+ only_include_str = ", ".join(f'"{p}"' for p in only_include_paths)
591
+ result.append(f"only-include = [{only_include_str}]")
622
592
 
623
593
  # Add dependency group if specified
624
594
  if dependency_group:
@@ -363,6 +363,94 @@ class TestBuildManager:
363
363
  assert len(manager.copied_files) == 0
364
364
  assert len(manager.copied_dirs) == 0
365
365
 
366
+ def test_convert_copied_dependency_imports_to_relative(self, tmp_path: Path) -> None:
367
+ """Test that absolute imports of copied dependencies are converted to relative imports for subfolder builds."""
368
+ project_root = tmp_path / "test_project"
369
+ project_root.mkdir()
370
+
371
+ # Create pyproject.toml
372
+ (project_root / "pyproject.toml").write_text(
373
+ """[project]
374
+ name = "test-package"
375
+ version = "0.1.0"
376
+ """
377
+ )
378
+
379
+ # Create external dependency _shared
380
+ shared_dir = project_root / "_shared"
381
+ shared_dir.mkdir()
382
+ (shared_dir / "__init__.py").write_text("")
383
+ (shared_dir / "image_utils.py").write_text("def save_PIL_image(): pass")
384
+ (shared_dir / "file_utils.py").write_text("def get_filepaths_config(): pass")
385
+
386
+ # Create external dependency _globals
387
+ (project_root / "_globals.py").write_text("is_testing = False")
388
+
389
+ # Create subfolder to build
390
+ subfolder = project_root / "src" / "integration" / "empty_drawing_detection"
391
+ subfolder.mkdir(parents=True)
392
+ (subfolder / "__init__.py").write_text("")
393
+
394
+ # Create a file in subfolder that imports copied dependencies with absolute imports
395
+ test_file = subfolder / "detect_empty_drawings.py"
396
+ original_content = """from pathlib import Path
397
+ from _shared.image_utils import save_PIL_image
398
+ from _shared.file_utils import get_filepaths_config
399
+ from _globals import is_testing
400
+
401
+ def analyze_folder():
402
+ save_PIL_image()
403
+ get_filepaths_config()
404
+ return is_testing
405
+ """
406
+ test_file.write_text(original_content)
407
+
408
+ # Create another file with different import style
409
+ test_file2 = subfolder / "config.py"
410
+ original_content2 = """import _globals
411
+
412
+ def get_config():
413
+ return _globals.is_testing
414
+ """
415
+ test_file2.write_text(original_content2)
416
+
417
+ manager = BuildManager(project_root, subfolder)
418
+
419
+ try:
420
+ # Prepare build - this should copy dependencies and convert imports
421
+ external_deps = manager.prepare_build(version="1.0.0", package_name="test-package")
422
+
423
+ # Verify dependencies were copied
424
+ assert len(external_deps) >= 2
425
+ assert (subfolder / "_shared").exists()
426
+ assert (subfolder / "_globals.py").exists()
427
+
428
+ # Verify imports were converted to relative
429
+ modified_content = test_file.read_text()
430
+ assert "from ._shared.image_utils import save_PIL_image" in modified_content
431
+ assert "from ._shared.file_utils import get_filepaths_config" in modified_content
432
+ assert "from ._globals import is_testing" in modified_content
433
+ # Verify stdlib import was not changed
434
+ assert "from pathlib import Path" in modified_content
435
+
436
+ # Verify import statement conversion
437
+ modified_content2 = test_file2.read_text()
438
+ assert "from . import _globals" in modified_content2
439
+
440
+ # Cleanup should restore original imports
441
+ manager.cleanup()
442
+
443
+ restored_content = test_file.read_text()
444
+ assert restored_content == original_content
445
+
446
+ restored_content2 = test_file2.read_text()
447
+ assert restored_content2 == original_content2
448
+
449
+ finally:
450
+ # Ensure cleanup even if test fails
451
+ if manager._modified_import_files:
452
+ manager.cleanup()
453
+
366
454
 
367
455
  class TestRealFolderStructure:
368
456
  """Tests using the real folder_structure from tests directory."""
@@ -711,30 +711,24 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
711
711
  assert pyproject_path.exists()
712
712
  content = pyproject_path.read_text()
713
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
714
+ # Verify [tool.hatch.build.targets.sdist] section exists
715
+ assert "[tool.hatch.build.targets.sdist]" in content
716
+
717
+ # Verify only-include is present
718
+ assert "only-include = [" in content
719
+
720
+ # Verify the subfolder is included
721
+ assert '"subfolder"' in content
722
+
723
+ # Verify necessary files are included
724
+ assert '"pyproject.toml"' in content
725
+ assert '"README.md"' in content
726
+
727
+ # Verify non-package directories are NOT explicitly included
728
+ assert '".cursor"' not in content or '".cursor"' not in content.split("only-include")[1]
729
+ assert '".github"' not in content or '".github"' not in content.split("only-include")[1]
730
+ assert '"data"' not in content or '"data"' not in content.split("only-include")[1]
731
+ assert '"docs"' not in content or '"docs"' not in content.split("only-include")[1]
738
732
 
739
733
  # Cleanup
740
734
  config.restore()
@@ -771,18 +765,17 @@ description = "Subfolder package"
771
765
  assert pyproject_path.exists()
772
766
  content = pyproject_path.read_text()
773
767
 
774
- # Verify [tool.hatch.build] section exists
775
- assert "[tool.hatch.build]" in content
768
+ # Verify [tool.hatch.build.targets.sdist] section exists
769
+ assert "[tool.hatch.build.targets.sdist]" in content
770
+
771
+ # Verify only-include is present
772
+ assert "only-include = [" in content
776
773
 
777
- # Verify paths-exclude is present
778
- assert "paths-exclude = [" in content
774
+ # Verify the subfolder is included
775
+ assert '"subfolder-package"' in content or '"subfolder"' in content
779
776
 
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
777
+ # Verify necessary files are included
778
+ assert '"pyproject.toml"' in content
786
779
 
787
780
  # Cleanup
788
781
  config.restore()