python-package-folder 1.2.0__tar.gz → 1.2.1__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 (41) hide show
  1. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/PKG-INFO +6 -4
  2. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/README.md +5 -3
  3. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/coverage.svg +2 -2
  4. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/analyzer.py +43 -1
  5. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/python_package_folder.py +2 -1
  6. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/subfolder_build.py +124 -6
  7. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/utils.py +7 -5
  8. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/test_subfolder_build.py +73 -0
  9. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.copier-answers.yml +0 -0
  10. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.cursor/rules/general.mdc +0 -0
  11. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.cursor/rules/python.mdc +0 -0
  12. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.github/workflows/ci.yml +0 -0
  13. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.github/workflows/publish.yml +0 -0
  14. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.gitignore +0 -0
  15. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/.vscode/settings.json +0 -0
  16. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/LICENSE +0 -0
  17. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/Makefile +0 -0
  18. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/development.md +0 -0
  19. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/installation.md +0 -0
  20. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/publishing.md +0 -0
  21. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/pyproject.toml +0 -0
  22. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/__init__.py +0 -0
  23. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/__main__.py +0 -0
  24. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/finder.py +0 -0
  25. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/manager.py +0 -0
  26. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/publisher.py +0 -0
  27. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/py.typed +0 -0
  28. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/types.py +0 -0
  29. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/src/python_package_folder/version.py +0 -0
  30. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/folder_structure/some_globals.py +0 -0
  31. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  32. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  33. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  34. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  35. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/test_build_with_external_deps.py +0 -0
  36. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/test_linting.py +0 -0
  37. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/test_publisher.py +0 -0
  38. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/test_utils.py +0 -0
  39. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/test_version_manager.py +0 -0
  40. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/tests/tests.py +0 -0
  41. {python_package_folder-1.2.0 → python_package_folder-1.2.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 1.2.0
3
+ Version: 1.2.1
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>
@@ -247,6 +247,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
247
247
  - If found, copies the subfolder README to project root (backing up the original parent README)
248
248
  - If not found, creates a minimal README with just the folder name
249
249
  5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
250
+ - `[build-system]` section using hatchling (replaces any existing build-system configuration)
250
251
  - Subfolder-specific package name (derived or custom)
251
252
  - Specified version
252
253
  - Correct package path for hatchling
@@ -278,8 +279,9 @@ The tool automatically:
278
279
  - Uses the current directory as the source directory if it contains Python files
279
280
  - Falls back to `project_root/src` if the current directory isn't suitable
280
281
  - **For subfolder builds**: Handles `pyproject.toml` configuration:
281
- - **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily)
282
+ - **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
282
283
  - **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
284
+ - `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
283
285
  - Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
284
286
  - Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
285
287
  - Proper package path configuration for hatchling
@@ -811,11 +813,11 @@ config.restore()
811
813
  ```
812
814
 
813
815
  **Methods:**
814
- - `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists, otherwise create temporary `pyproject.toml` with subfolder-specific configuration
816
+ - `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists (adjusting package paths and ensuring `[build-system]` uses hatchling), otherwise create temporary `pyproject.toml` with subfolder-specific configuration including `[build-system]` section using hatchling
815
817
  - `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
816
818
 
817
819
  **Note**: This class automatically:
818
- - **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily). Otherwise, creates a temporary one from the parent configuration.
820
+ - **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily with adjusted package paths). Otherwise, creates a temporary one from the parent configuration. In both cases, the `[build-system]` section is always set to use hatchling, replacing any existing build-system configuration.
819
821
  - **README handling**: If a README exists in the subfolder, it will be used instead of the parent README. If no README exists in the subfolder, a minimal README with just the folder name will be created. The original parent README is backed up and restored after the build completes.
820
822
  - **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
821
823
 
@@ -227,6 +227,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
227
227
  - If found, copies the subfolder README to project root (backing up the original parent README)
228
228
  - If not found, creates a minimal README with just the folder name
229
229
  5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
230
+ - `[build-system]` section using hatchling (replaces any existing build-system configuration)
230
231
  - Subfolder-specific package name (derived or custom)
231
232
  - Specified version
232
233
  - Correct package path for hatchling
@@ -258,8 +259,9 @@ The tool automatically:
258
259
  - Uses the current directory as the source directory if it contains Python files
259
260
  - Falls back to `project_root/src` if the current directory isn't suitable
260
261
  - **For subfolder builds**: Handles `pyproject.toml` configuration:
261
- - **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily)
262
+ - **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
262
263
  - **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
264
+ - `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
263
265
  - Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
264
266
  - Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
265
267
  - Proper package path configuration for hatchling
@@ -791,11 +793,11 @@ config.restore()
791
793
  ```
792
794
 
793
795
  **Methods:**
794
- - `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists, otherwise create temporary `pyproject.toml` with subfolder-specific configuration
796
+ - `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists (adjusting package paths and ensuring `[build-system]` uses hatchling), otherwise create temporary `pyproject.toml` with subfolder-specific configuration including `[build-system]` section using hatchling
795
797
  - `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
796
798
 
797
799
  **Note**: This class automatically:
798
- - **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily). Otherwise, creates a temporary one from the parent configuration.
800
+ - **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily with adjusted package paths). Otherwise, creates a temporary one from the parent configuration. In both cases, the `[build-system]` section is always set to use hatchling, replacing any existing build-system configuration.
799
801
  - **README handling**: If a README exists in the subfolder, it will be used instead of the parent README. If no README exists in the subfolder, a minimal README with just the folder name will be created. The original parent README is backed up and restored after the build completes.
800
802
  - **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
801
803
 
@@ -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">67%</text>
18
- <text x="81" y="14">67%</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>
@@ -45,13 +45,55 @@ class ImportAnalyzer:
45
45
  """
46
46
  Recursively find all Python files in a directory.
47
47
 
48
+ Excludes common directories like .venv, venv, __pycache__, etc.
49
+
48
50
  Args:
49
51
  directory: Directory to search for Python files
50
52
 
51
53
  Returns:
52
54
  List of paths to all .py files found in the directory tree
53
55
  """
54
- return [path for path in directory.rglob("*.py") if path.is_file()]
56
+ exclude_patterns = {
57
+ ".venv",
58
+ "venv",
59
+ "__pycache__",
60
+ ".git",
61
+ ".pytest_cache",
62
+ ".mypy_cache",
63
+ "node_modules",
64
+ ".tox",
65
+ "dist",
66
+ "build",
67
+ }
68
+
69
+ python_files = []
70
+ for path in directory.rglob("*.py"):
71
+ if not path.is_file():
72
+ continue
73
+
74
+ # Check if any part of the path matches exclusion patterns
75
+ should_exclude = False
76
+ for part in path.parts:
77
+ # Check exact matches
78
+ if part in exclude_patterns:
79
+ should_exclude = True
80
+ break
81
+ # Check if part starts with excluded pattern or contains .egg-info
82
+ for pattern in exclude_patterns:
83
+ if part.startswith(pattern):
84
+ should_exclude = True
85
+ break
86
+ # Also exclude .egg-info directories
87
+ if ".egg-info" in part:
88
+ should_exclude = True
89
+ break
90
+ if should_exclude:
91
+ break
92
+
93
+ if not should_exclude:
94
+ python_files.append(path)
95
+
96
+ return python_files
55
97
 
56
98
  def extract_imports(self, file_path: Path) -> list[ImportInfo]:
57
99
  """
@@ -122,7 +122,8 @@ def main() -> int:
122
122
  src_dir = Path(args.src_dir).resolve()
123
123
  else:
124
124
  # Auto-detect: use current directory if it has Python files, otherwise use project_root/src
125
- src_dir = find_source_directory(project_root)
125
+ current_dir = Path.cwd()
126
+ src_dir = find_source_directory(project_root, current_dir=current_dir)
126
127
  if src_dir:
127
128
  print(f"Auto-detected source directory: {src_dir}")
128
129
  else:
@@ -30,8 +30,11 @@ class SubfolderBuildConfig:
30
30
  Manages temporary build configuration for subfolder builds.
31
31
 
32
32
  When building a subfolder as a separate package, this class:
33
- - Uses the subfolder's pyproject.toml if it exists
33
+ - Uses the subfolder's pyproject.toml if it exists (adjusts package paths and ensures
34
+ [build-system] uses hatchling)
34
35
  - Otherwise creates a temporary pyproject.toml with the appropriate package name and version
36
+ - Always ensures [build-system] section uses hatchling (replaces any existing build-system
37
+ configuration from parent or subfolder)
35
38
  - Handles README files similarly (uses subfolder README if present)
36
39
  """
37
40
 
@@ -111,13 +114,96 @@ class SubfolderBuildConfig:
111
114
  # If it's a package or has subpackages, return the path
112
115
  return packages_path, [packages_path] if packages_path else []
113
116
 
117
+ def _adjust_subfolder_pyproject_packages_path(self, content: str) -> str:
118
+ """
119
+ Adjust packages path in subfolder pyproject.toml to be relative to project root.
120
+
121
+ When a subfolder's pyproject.toml is copied to project root, the packages path
122
+ needs to be adjusted to point to the subfolder relative to the project root.
123
+
124
+ Args:
125
+ content: Content of the subfolder's pyproject.toml
126
+
127
+ Returns:
128
+ Adjusted content with correct packages path
129
+ """
130
+ # Get the correct packages path relative to project root
131
+ _, package_dirs = self._get_package_structure()
132
+ if not package_dirs:
133
+ # No adjustment needed if we can't determine the path
134
+ return content
135
+
136
+ correct_packages_path = package_dirs[0]
137
+ lines = content.split("\n")
138
+ result = []
139
+ in_hatch_build = False
140
+ packages_set = False
141
+
142
+ for line in lines:
143
+ # Detect hatch build section
144
+ if line.strip().startswith("[tool.hatch.build.targets.wheel]"):
145
+ in_hatch_build = True
146
+ result.append(line)
147
+ continue
148
+ elif line.strip().startswith("[") and in_hatch_build:
149
+ # End of hatch build section, add packages if not set
150
+ if not packages_set and correct_packages_path:
151
+ packages_str = f'"{correct_packages_path}"'
152
+ result.append(f"packages = [{packages_str}]")
153
+ in_hatch_build = False
154
+ result.append(line)
155
+ elif in_hatch_build:
156
+ # Modify packages path if found
157
+ if re.match(r"^\s*packages\s*=", line):
158
+ packages_str = f'"{correct_packages_path}"'
159
+ result.append(f"packages = [{packages_str}]")
160
+ packages_set = True
161
+ continue
162
+ # Keep other lines in hatch build section
163
+ result.append(line)
164
+ else:
165
+ result.append(line)
166
+
167
+ # Add packages if we're still in hatch build section and haven't set it
168
+ if in_hatch_build and not packages_set and correct_packages_path:
169
+ packages_str = f'"{correct_packages_path}"'
170
+ result.append(f"packages = [{packages_str}]")
171
+
172
+ # Ensure build-system section exists (required for hatchling)
173
+ # Check if build-system section exists in the result
174
+ has_build_system = any(line.strip().startswith("[build-system]") for line in result)
175
+ if not has_build_system:
176
+ # Insert build-system at the very beginning of the file
177
+ build_system_lines = [
178
+ "[build-system]",
179
+ 'requires = ["hatchling"]',
180
+ 'build-backend = "hatchling.build"',
181
+ "",
182
+ ]
183
+ result = build_system_lines + result
184
+
185
+ # Ensure hatch build section exists if packages path is needed
186
+ if not packages_set and correct_packages_path:
187
+ # Check if we need to add the section
188
+ if "[tool.hatch.build.targets.wheel]" not in content:
189
+ result.append("")
190
+ result.append("[tool.hatch.build.targets.wheel]")
191
+ packages_str = f'"{correct_packages_path}"'
192
+ result.append(f"packages = [{packages_str}]")
193
+
194
+ return "\n".join(result)
195
+
114
196
  def create_temp_pyproject(self) -> Path | None:
115
197
  """
116
198
  Create a temporary pyproject.toml for the subfolder build.
117
199
 
118
- If a pyproject.toml exists in the subfolder, it will be used instead of creating
119
- a new one. Otherwise, creates a pyproject.toml in the project root based on the
120
- parent pyproject.toml with the appropriate package name and version.
200
+ If a pyproject.toml exists in the subfolder, it will be used (copied to project root
201
+ with adjusted package paths and ensuring [build-system] uses hatchling). Otherwise,
202
+ creates a pyproject.toml in the project root based on the parent pyproject.toml with
203
+ the appropriate package name and version.
204
+
205
+ The [build-system] section is always set to use hatchling, even if the parent or
206
+ subfolder pyproject.toml uses a different build backend (e.g., setuptools).
121
207
 
122
208
  Returns:
123
209
  Path to the pyproject.toml file (either from subfolder or created temporary),
@@ -149,8 +235,13 @@ class SubfolderBuildConfig:
149
235
  shutil.copy2(original_pyproject, backup_path)
150
236
  self.original_pyproject_backup = backup_path
151
237
 
152
- # Copy subfolder pyproject.toml to project root
153
- shutil.copy2(subfolder_pyproject, original_pyproject)
238
+ # Read and adjust the subfolder pyproject.toml
239
+ subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
240
+ # Adjust packages path to be relative to project root
241
+ adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
242
+
243
+ # Write adjusted content to project root
244
+ original_pyproject.write_text(adjusted_content, encoding="utf-8")
154
245
  self.temp_pyproject = original_pyproject
155
246
 
156
247
  # Handle README file
@@ -259,6 +350,7 @@ class SubfolderBuildConfig:
259
350
  skip_uv_dynamic = False
260
351
  in_hatch_build = False
261
352
  packages_set = False
353
+ build_system_set = False
262
354
 
263
355
  # Get package structure
264
356
  packages_path, package_dirs = self._get_package_structure()
@@ -266,6 +358,19 @@ class SubfolderBuildConfig:
266
358
  package_dirs = []
267
359
 
268
360
  for _i, line in enumerate(lines):
361
+ # Skip build-system section - we'll add our own for subfolder builds
362
+ if line.strip().startswith("[build-system]"):
363
+ build_system_set = True
364
+ continue # Skip the [build-system] line
365
+ elif build_system_set and line.strip().startswith("["):
366
+ # End of build-system section
367
+ build_system_set = False
368
+ result.append(line)
369
+ continue
370
+ elif build_system_set:
371
+ # Skip build-system content - we'll add our own
372
+ continue
373
+
269
374
  # Skip hatch versioning and uv-dynamic-versioning sections
270
375
  if line.strip().startswith("[tool.hatch.version]"):
271
376
  skip_hatch_version = True
@@ -361,6 +466,19 @@ class SubfolderBuildConfig:
361
466
  packages_str = ", ".join(f'"{p}"' for p in package_dirs)
362
467
  result.append(f"packages = [{packages_str}]")
363
468
 
469
+ # Ensure build-system section exists (required for hatchling)
470
+ # Check if build-system section exists in the result
471
+ has_build_system = any(line.strip().startswith("[build-system]") for line in result)
472
+ if not has_build_system:
473
+ # Insert build-system at the very beginning of the file
474
+ build_system_lines = [
475
+ "[build-system]",
476
+ 'requires = ["hatchling"]',
477
+ 'build-backend = "hatchling.build"',
478
+ "",
479
+ ]
480
+ result = build_system_lines + result
481
+
364
482
  # Ensure packages is always set for subfolder builds
365
483
  if not packages_set and package_dirs:
366
484
  # Add the section if it doesn't exist
@@ -63,7 +63,8 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
63
63
  project_root = project_root.resolve()
64
64
 
65
65
  # Check if current directory is a subdirectory with Python files
66
- if current_dir.is_relative_to(project_root) or current_dir == project_root:
66
+ # Prioritize current directory if it's within the project and has Python files
67
+ if current_dir.is_relative_to(project_root) and current_dir != project_root:
67
68
  python_files = list(current_dir.glob("*.py"))
68
69
  if python_files:
69
70
  # Current directory has Python files, use it as source
@@ -74,10 +75,11 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
74
75
  if src_dir.exists() and src_dir.is_dir():
75
76
  return src_dir
76
77
 
77
- # Check if project_root itself has Python files
78
- python_files = list(project_root.glob("*.py"))
79
- if python_files:
80
- return project_root
78
+ # Only check project_root if current_dir is the project_root
79
+ if current_dir == project_root:
80
+ python_files = list(project_root.glob("*.py"))
81
+ if python_files:
82
+ return project_root
81
83
 
82
84
  return None
83
85
 
@@ -584,6 +584,11 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
584
584
 
585
585
  # Verify dynamic versioning is removed
586
586
  assert 'dynamic = ["version"]' not in content
587
+
588
+ # Verify build-system section is added (required for hatchling)
589
+ assert "[build-system]" in content
590
+ assert 'requires = ["hatchling"]' in content
591
+ assert 'build-backend = "hatchling.build"' in content
587
592
  assert "[tool.hatch.version]" not in content
588
593
  assert "[tool.uv-dynamic-versioning]" not in content
589
594
 
@@ -648,3 +653,71 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
648
653
  restored_content = (project_root / "pyproject.toml").read_text()
649
654
  assert restored_content == original_content
650
655
  assert 'name = "test-package"' in restored_content
656
+
657
+ def test_build_system_section_replaces_setuptools(
658
+ self, test_project_with_pyproject: Path
659
+ ) -> None:
660
+ """Test that build-system section replaces existing setuptools configuration."""
661
+ project_root = test_project_with_pyproject
662
+ subfolder = project_root / "subfolder"
663
+
664
+ # Modify parent pyproject.toml to have setuptools build-system
665
+ pyproject_path = project_root / "pyproject.toml"
666
+ original_content = pyproject_path.read_text()
667
+ modified_content = (
668
+ original_content
669
+ + '\n[build-system]\nrequires = ["setuptools"]\nbuild-backend = "setuptools.build_meta"\n'
670
+ )
671
+ pyproject_path.write_text(modified_content)
672
+
673
+ try:
674
+ config = SubfolderBuildConfig(
675
+ project_root=project_root,
676
+ src_dir=subfolder,
677
+ version="1.0.0",
678
+ )
679
+
680
+ pyproject_path = config.create_temp_pyproject()
681
+ content = pyproject_path.read_text()
682
+
683
+ # Verify build-system section uses hatchling, not setuptools
684
+ assert "[build-system]" in content
685
+ assert 'requires = ["hatchling"]' in content
686
+ assert 'build-backend = "hatchling.build"' in content
687
+ assert "setuptools" not in content or 'build-backend = "setuptools' not in content
688
+
689
+ config.restore()
690
+ finally:
691
+ # Restore original content
692
+ pyproject_path.write_text(original_content)
693
+
694
+ def test_build_system_section_with_subfolder_pyproject(
695
+ self, test_project_with_pyproject: Path
696
+ ) -> None:
697
+ """Test that build-system section is added when using subfolder's pyproject.toml."""
698
+ project_root = test_project_with_pyproject
699
+ subfolder = project_root / "subfolder"
700
+
701
+ # Create pyproject.toml in subfolder without build-system
702
+ subfolder_pyproject_content = """[project]
703
+ name = "subfolder-package"
704
+ version = "3.0.0"
705
+ description = "Subfolder package"
706
+ """
707
+ (subfolder / "pyproject.toml").write_text(subfolder_pyproject_content)
708
+
709
+ config = SubfolderBuildConfig(
710
+ project_root=project_root,
711
+ src_dir=subfolder,
712
+ version="1.0.0",
713
+ )
714
+
715
+ pyproject_path = config.create_temp_pyproject()
716
+ content = pyproject_path.read_text()
717
+
718
+ # Verify build-system section is added
719
+ assert "[build-system]" in content
720
+ assert 'requires = ["hatchling"]' in content
721
+ assert 'build-backend = "hatchling.build"' in content
722
+
723
+ config.restore()