python-package-folder 9.1.0__tar.gz → 9.2.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 (66) hide show
  1. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/PKG-INFO +1 -1
  2. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/pyproject.toml +1 -1
  3. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/analyzer.py +16 -0
  4. python_package_folder-9.2.0/tests/test_subfolder_build.py +62 -0
  5. python_package_folder-9.2.0/tests/test_subfolder_build_config.py +399 -0
  6. python_package_folder-9.2.0/tests/test_subfolder_imports.py +600 -0
  7. python_package_folder-9.2.0/tests/test_subfolder_pyproject_toml.py +1145 -0
  8. python_package_folder-9.2.0/tests/test_subfolder_temp_directory.py +413 -0
  9. python_package_folder-9.2.0/tests/test_subfolder_wheel_packaging.py +124 -0
  10. python_package_folder-9.1.0/tests/test_subfolder_build.py +0 -4474
  11. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.copier-answers.yml +0 -0
  12. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  13. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
  14. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.cursor/rules/general.mdc +0 -0
  15. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.cursor/rules/python.mdc +0 -0
  16. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.github/workflows/ci.yml +0 -0
  17. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.github/workflows/publish.yml +0 -0
  18. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.gitignore +0 -0
  19. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/.vscode/settings.json +0 -0
  20. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/LICENSE +0 -0
  21. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/MANIFEST.in +0 -0
  22. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/Makefile +0 -0
  23. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/README.md +0 -0
  24. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/coverage.svg +0 -0
  25. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/development.md +0 -0
  26. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/docs/DEVELOPMENT.md +0 -0
  27. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/docs/INSTALLATION.md +0 -0
  28. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/docs/PUBLISHING.md +0 -0
  29. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/docs/REFERENCE.md +0 -0
  30. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/docs/USAGE.md +0 -0
  31. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/docs/VERSION_RESOLUTION.md +0 -0
  32. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/installation.md +0 -0
  33. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/publishing.md +0 -0
  34. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/__init__.py +0 -0
  35. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/__main__.py +0 -0
  36. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/finder.py +0 -0
  37. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/manager.py +0 -0
  38. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/publisher.py +0 -0
  39. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/py.typed +0 -0
  40. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/python_package_folder.py +0 -0
  41. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/subfolder_build.py +0 -0
  42. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/types.py +0 -0
  43. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/utils.py +0 -0
  44. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/version.py +0 -0
  45. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/src/python_package_folder/version_calculator.py +0 -0
  46. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/conftest.py +0 -0
  47. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/some_globals.py +0 -0
  48. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  49. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  50. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  51. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  52. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  53. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  54. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_build_with_external_deps.py +0 -0
  55. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_exclude_patterns.py +0 -0
  56. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_linting.py +0 -0
  57. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_preserve_directory_structure.py +0 -0
  58. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_publisher.py +0 -0
  59. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_shared_subdirectory_imports.py +0 -0
  60. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_spreadsheet_creation_imports.py +0 -0
  61. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_third_party_dependencies.py +0 -0
  62. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_utils.py +0 -0
  63. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_version_calculator.py +0 -0
  64. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/test_version_manager.py +0 -0
  65. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/tests/tests.py +0 -0
  66. {python_package_folder-9.1.0 → python_package_folder-9.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 9.1.0
3
+ Version: 9.2.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>
@@ -43,7 +43,7 @@ dependencies = [
43
43
 
44
44
  # ---- Dev dependencies ----
45
45
 
46
- version = "9.1.0"
46
+ version = "9.2.0"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pytest>=8.3.5",
@@ -310,6 +310,15 @@ class ImportAnalyzer:
310
310
  self.project_root / module_path_str / "__init__.py",
311
311
  (self.project_root / module_path_str).with_suffix(".py"),
312
312
  ]
313
+
314
+ # Also check project_root/src/ for files at src/ root (like _globals.py)
315
+ # This is important when building subfolders where src_dir points to temp directory
316
+ src_base = self.project_root / "src"
317
+ if src_base.exists():
318
+ potential_paths.extend([
319
+ src_base / module_path_str / "__init__.py",
320
+ (src_base / module_path_str).with_suffix(".py"),
321
+ ])
313
322
 
314
323
  for path in potential_paths:
315
324
  if path.exists():
@@ -335,6 +344,13 @@ class ImportAnalyzer:
335
344
  potential_file = parent / f"{module_name.split('.')[-1]}.py"
336
345
  if potential_file.exists():
337
346
  return potential_file
347
+
348
+ # Also check project_root/src/ for files at src/ root
349
+ # This handles cases like _globals.py at src/_globals.py when building subfolders
350
+ if parent == self.project_root and src_base.exists():
351
+ src_file = src_base / f"{module_name.split('.')[-1]}.py"
352
+ if src_file.exists():
353
+ return src_file
338
354
 
339
355
  # Check all subdirectories in parent (not just common ones)
340
356
  # This handles cases like src/data/spreadsheet_creation/spreadsheet_formatting_dataclasses.py
@@ -0,0 +1,62 @@
1
+ """Tests for subfolder build functionality."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ import venv
9
+ import zipfile
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ from python_package_folder import BuildManager, SubfolderBuildConfig
15
+
16
+
17
+ @pytest.fixture
18
+ def test_project_with_pyproject(tmp_path: Path) -> Path:
19
+ """Create a test project with pyproject.toml."""
20
+ project_root = tmp_path / "test_project"
21
+ project_root.mkdir()
22
+
23
+ # Create pyproject.toml
24
+ pyproject_content = """[project]
25
+ name = "test-package"
26
+ version = "0.1.0"
27
+ dynamic = ["version"]
28
+
29
+ [tool.hatch.version]
30
+ source = "uv-dynamic-versioning"
31
+
32
+ [tool.uv-dynamic-versioning]
33
+ vcs = "git"
34
+ style = "pep440"
35
+ bump = true
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/test_package"]
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest>=8.0.0",
43
+ "pytest-cov>=4.0.0",
44
+ ]
45
+ test = [
46
+ "pytest>=8.0.0",
47
+ "mypy>=1.0.0",
48
+ ]
49
+ """
50
+ (project_root / "pyproject.toml").write_text(pyproject_content)
51
+
52
+ # Create subfolder
53
+ subfolder = project_root / "subfolder"
54
+ subfolder.mkdir(exist_ok=True)
55
+ (subfolder / "module.py").write_text("def func(): pass")
56
+
57
+ return project_root
58
+
59
+
60
+
61
+ # This file has been split into multiple test files.
62
+ # See test_subfolder_*.py files for the actual tests.
@@ -0,0 +1,399 @@
1
+ """Tests for subfolder build functionality."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+ import tempfile
8
+ import venv
9
+ import zipfile
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ from python_package_folder import BuildManager, SubfolderBuildConfig
15
+
16
+
17
+ @pytest.fixture
18
+ def test_project_with_pyproject(tmp_path: Path) -> Path:
19
+ """Create a test project with pyproject.toml."""
20
+ project_root = tmp_path / "test_project"
21
+ project_root.mkdir()
22
+
23
+ # Create pyproject.toml
24
+ pyproject_content = """[project]
25
+ name = "test-package"
26
+ version = "0.1.0"
27
+ dynamic = ["version"]
28
+
29
+ [tool.hatch.version]
30
+ source = "uv-dynamic-versioning"
31
+
32
+ [tool.uv-dynamic-versioning]
33
+ vcs = "git"
34
+ style = "pep440"
35
+ bump = true
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/test_package"]
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest>=8.0.0",
43
+ "pytest-cov>=4.0.0",
44
+ ]
45
+ test = [
46
+ "pytest>=8.0.0",
47
+ "mypy>=1.0.0",
48
+ ]
49
+ """
50
+ (project_root / "pyproject.toml").write_text(pyproject_content)
51
+
52
+ # Create subfolder
53
+ subfolder = project_root / "subfolder"
54
+ subfolder.mkdir(exist_ok=True)
55
+ (subfolder / "module.py").write_text("def func(): pass")
56
+
57
+ return project_root
58
+
59
+
60
+ class TestSubfolderBuildConfig:
61
+ """
62
+ Tests for SubfolderBuildConfig class.
63
+
64
+ This class tests the core functionality of SubfolderBuildConfig, including:
65
+ - Initialization with default and custom package names
66
+ - Temporary pyproject.toml creation and configuration
67
+ - Dependency group handling
68
+ - __init__.py file creation
69
+
70
+ File: test_subfolder_build_config.py
71
+ When to add tests here: Tests for SubfolderBuildConfig initialization, configuration,
72
+ and temporary file management should go in this class.
73
+ """
74
+
75
+ def test_init_with_defaults(self, test_project_with_pyproject: Path) -> None:
76
+ """Test initialization with default package name."""
77
+ config = SubfolderBuildConfig(
78
+ project_root=test_project_with_pyproject,
79
+ src_dir=test_project_with_pyproject / "subfolder",
80
+ version="1.0.0",
81
+ )
82
+
83
+ assert config.package_name == "test-package-subfolder"
84
+ assert config.version == "1.0.0"
85
+ assert config.dependency_group is None
86
+
87
+ def test_init_with_custom_name(self, test_project_with_pyproject: Path) -> None:
88
+ """Test initialization with custom package name."""
89
+ config = SubfolderBuildConfig(
90
+ project_root=test_project_with_pyproject,
91
+ src_dir=test_project_with_pyproject / "subfolder",
92
+ package_name="custom-package",
93
+ version="1.0.0",
94
+ )
95
+
96
+ assert config.package_name == "custom-package"
97
+ assert config.version == "1.0.0"
98
+
99
+ def test_init_with_dependency_group(self, test_project_with_pyproject: Path) -> None:
100
+ """Test initialization with dependency group."""
101
+ config = SubfolderBuildConfig(
102
+ project_root=test_project_with_pyproject,
103
+ src_dir=test_project_with_pyproject / "subfolder",
104
+ version="1.0.0",
105
+ dependency_group="dev",
106
+ )
107
+
108
+ assert config.dependency_group == "dev"
109
+
110
+ def test_create_temp_pyproject(self, test_project_with_pyproject: Path) -> None:
111
+ """Test creating temporary pyproject.toml."""
112
+ config = SubfolderBuildConfig(
113
+ project_root=test_project_with_pyproject,
114
+ src_dir=test_project_with_pyproject / "subfolder",
115
+ version="2.0.0",
116
+ )
117
+
118
+ pyproject_path = config.create_temp_pyproject()
119
+
120
+ assert pyproject_path.exists()
121
+ content = pyproject_path.read_text()
122
+
123
+ # Check package name and version are set
124
+ assert 'name = "test-package-subfolder"' in content
125
+ assert 'version = "2.0.0"' in content
126
+
127
+ # Check dynamic versioning is removed
128
+ assert 'dynamic = ["version"]' not in content
129
+ assert "[tool.hatch.version]" not in content
130
+ assert "[tool.uv-dynamic-versioning]" not in content
131
+
132
+ def test_create_temp_pyproject_with_dependency_group(
133
+ self, test_project_with_pyproject: Path
134
+ ) -> None:
135
+ """Test creating temporary pyproject.toml with dependency group."""
136
+ config = SubfolderBuildConfig(
137
+ project_root=test_project_with_pyproject,
138
+ src_dir=test_project_with_pyproject / "subfolder",
139
+ version="2.0.0",
140
+ dependency_group="dev",
141
+ )
142
+
143
+ pyproject_path = config.create_temp_pyproject()
144
+ content = pyproject_path.read_text()
145
+
146
+ # Check dependency group is included
147
+ assert "[dependency-groups]" in content
148
+ assert "dev = [" in content
149
+ assert '"pytest>=8.0.0"' in content
150
+
151
+ def test_create_temp_pyproject_creates_init(self, test_project_with_pyproject: Path) -> None:
152
+ """Test that __init__.py is created if missing."""
153
+ subfolder = test_project_with_pyproject / "subfolder"
154
+ config = SubfolderBuildConfig(
155
+ project_root=test_project_with_pyproject,
156
+ src_dir=subfolder,
157
+ version="1.0.0",
158
+ )
159
+
160
+ # Ensure __init__.py doesn't exist
161
+ init_file = subfolder / "__init__.py"
162
+ if init_file.exists():
163
+ init_file.unlink()
164
+
165
+ config.create_temp_pyproject()
166
+
167
+ # Check __init__.py was created
168
+ assert init_file.exists()
169
+ assert config._temp_init_created
170
+
171
+ def test_restore_pyproject(self, test_project_with_pyproject: Path) -> None:
172
+ """Test restoring original pyproject.toml."""
173
+ original_content = (test_project_with_pyproject / "pyproject.toml").read_text()
174
+
175
+ config = SubfolderBuildConfig(
176
+ project_root=test_project_with_pyproject,
177
+ src_dir=test_project_with_pyproject / "subfolder",
178
+ version="1.0.0",
179
+ )
180
+
181
+ config.create_temp_pyproject()
182
+ config.restore()
183
+
184
+ # Check original content is restored
185
+ restored_content = (test_project_with_pyproject / "pyproject.toml").read_text()
186
+ assert restored_content == original_content
187
+
188
+ # Check backup is removed
189
+ assert not (test_project_with_pyproject / "pyproject.toml.original").exists()
190
+
191
+ def test_restore_removes_temp_init(self, test_project_with_pyproject: Path) -> None:
192
+ """Test that restore removes temporary __init__.py."""
193
+ subfolder = test_project_with_pyproject / "subfolder"
194
+ init_file = subfolder / "__init__.py"
195
+
196
+ # Ensure __init__.py doesn't exist
197
+ if init_file.exists():
198
+ init_file.unlink()
199
+
200
+ config = SubfolderBuildConfig(
201
+ project_root=test_project_with_pyproject,
202
+ src_dir=subfolder,
203
+ version="1.0.0",
204
+ )
205
+
206
+ config.create_temp_pyproject()
207
+ assert init_file.exists()
208
+
209
+ config.restore()
210
+
211
+ # Check __init__.py was removed
212
+ assert not init_file.exists()
213
+
214
+ def test_restore_preserves_existing_init(self, test_project_with_pyproject: Path) -> None:
215
+ """Test that restore preserves existing __init__.py."""
216
+ subfolder = test_project_with_pyproject / "subfolder"
217
+ init_file = subfolder / "__init__.py"
218
+
219
+ # Create existing __init__.py
220
+ init_file.write_text("# Original content")
221
+
222
+ config = SubfolderBuildConfig(
223
+ project_root=test_project_with_pyproject,
224
+ src_dir=subfolder,
225
+ version="1.0.0",
226
+ )
227
+
228
+ config.create_temp_pyproject()
229
+ config.restore()
230
+
231
+ # Check original __init__.py is preserved
232
+ assert init_file.exists()
233
+ assert init_file.read_text() == "# Original content"
234
+
235
+ def test_context_manager(self, test_project_with_pyproject: Path) -> None:
236
+ """Test using SubfolderBuildConfig as context manager."""
237
+ original_content = (test_project_with_pyproject / "pyproject.toml").read_text()
238
+
239
+ with SubfolderBuildConfig(
240
+ project_root=test_project_with_pyproject,
241
+ src_dir=test_project_with_pyproject / "subfolder",
242
+ version="1.0.0",
243
+ ) as config:
244
+ config.create_temp_pyproject()
245
+ content = (test_project_with_pyproject / "pyproject.toml").read_text()
246
+ assert 'name = "test-package-subfolder"' in content
247
+
248
+ # Check restore happened automatically
249
+ restored_content = (test_project_with_pyproject / "pyproject.toml").read_text()
250
+ assert restored_content == original_content
251
+
252
+ def test_missing_dependency_group_warning(
253
+ self, test_project_with_pyproject: Path, capsys: pytest.CaptureFixture[str]
254
+ ) -> None:
255
+ """Test warning when dependency group doesn't exist."""
256
+ config = SubfolderBuildConfig(
257
+ project_root=test_project_with_pyproject,
258
+ src_dir=test_project_with_pyproject / "subfolder",
259
+ version="1.0.0",
260
+ dependency_group="nonexistent",
261
+ )
262
+
263
+ # Create temp pyproject - this should print a warning
264
+ config.create_temp_pyproject()
265
+
266
+ # The warning is printed to stderr during create_temp_pyproject
267
+ # Since capsys might not capture it properly, we'll just verify
268
+ # that the build still works (warning is non-fatal)
269
+ assert config.temp_pyproject is not None
270
+
271
+ def test_version_required(self, test_project_with_pyproject: Path) -> None:
272
+ """Test that version is required."""
273
+ config = SubfolderBuildConfig(
274
+ project_root=test_project_with_pyproject,
275
+ src_dir=test_project_with_pyproject / "subfolder",
276
+ )
277
+
278
+ with pytest.raises(ValueError, match="Version is required"):
279
+ config.create_temp_pyproject()
280
+
281
+ def test_package_name_derivation(self, test_project_with_pyproject: Path) -> None:
282
+ """Test package name derivation from root project name and directory name."""
283
+ # Test with underscores
284
+ subfolder = test_project_with_pyproject / "subfolder_to_build"
285
+ subfolder.mkdir(exist_ok=True)
286
+ config = SubfolderBuildConfig(
287
+ project_root=test_project_with_pyproject,
288
+ src_dir=subfolder,
289
+ version="1.0.0",
290
+ )
291
+ assert config.package_name == "test-package-subfolder-to-build"
292
+
293
+ # Test with spaces
294
+ subfolder2 = test_project_with_pyproject / "subfolder with spaces"
295
+ subfolder2.mkdir()
296
+ config2 = SubfolderBuildConfig(
297
+ project_root=test_project_with_pyproject,
298
+ src_dir=subfolder2,
299
+ version="1.0.0",
300
+ )
301
+ assert config2.package_name == "test-package-subfolder-with-spaces"
302
+
303
+ def test_package_name_derivation_no_root_project(self, tmp_path: Path) -> None:
304
+ """Test package name derivation when root project name is not found (fallback)."""
305
+ # Create a project without pyproject.toml
306
+ project_root = tmp_path / "test_project_no_pyproject"
307
+ project_root.mkdir()
308
+
309
+ subfolder = project_root / "subfolder"
310
+ subfolder.mkdir(exist_ok=True)
311
+
312
+ config = SubfolderBuildConfig(
313
+ project_root=project_root,
314
+ src_dir=subfolder,
315
+ version="1.0.0",
316
+ )
317
+ # Should fallback to just subfolder name when root project name not found
318
+ assert config.package_name == "subfolder"
319
+
320
+
321
+ def test_readme_handling_with_existing_readme(test_project_with_pyproject: Path):
322
+ """Test that subfolder README is used when it exists."""
323
+ project_root = test_project_with_pyproject
324
+ subfolder = project_root / "subfolder"
325
+
326
+ # Create README in subfolder
327
+ subfolder_readme = subfolder / "README.md"
328
+ subfolder_readme.write_text("# Subfolder Package\n\nThis is the subfolder README.")
329
+
330
+ # Create README in project root
331
+ project_readme = project_root / "README.md"
332
+ project_readme.write_text("# Parent Package\n\nThis is the parent README.")
333
+
334
+ config = SubfolderBuildConfig(
335
+ project_root=project_root,
336
+ src_dir=subfolder,
337
+ version="1.0.0",
338
+ )
339
+
340
+ try:
341
+ config.create_temp_pyproject()
342
+
343
+ # Check that subfolder README was copied to project root
344
+ assert (project_root / "README.md").exists()
345
+ content = (project_root / "README.md").read_text()
346
+ assert "Subfolder Package" in content
347
+ assert "This is the subfolder README" in content
348
+ assert "Parent Package" not in content
349
+
350
+ # Check that backup was created
351
+ assert (project_root / "README.md.backup").exists()
352
+ backup_content = (project_root / "README.md.backup").read_text()
353
+ assert "Parent Package" in backup_content
354
+ finally:
355
+ config.restore()
356
+
357
+ # Verify original README was restored
358
+ assert (project_root / "README.md").exists()
359
+ restored_content = (project_root / "README.md").read_text()
360
+ assert "Parent Package" in restored_content
361
+ assert "Subfolder Package" not in restored_content
362
+ assert not (project_root / "README.md.backup").exists()
363
+
364
+
365
+ def test_readme_handling_without_readme(test_project_with_pyproject: Path):
366
+ """Test that minimal README is created when subfolder has no README."""
367
+ project_root = test_project_with_pyproject
368
+ subfolder = project_root / "subfolder"
369
+
370
+ # Ensure no README exists
371
+ assert not (subfolder / "README.md").exists()
372
+ assert not (subfolder / "README.rst").exists()
373
+
374
+ config = SubfolderBuildConfig(
375
+ project_root=project_root,
376
+ src_dir=subfolder,
377
+ version="1.0.0",
378
+ )
379
+
380
+ try:
381
+ config.create_temp_pyproject()
382
+
383
+ # Check that minimal README was created
384
+ assert (project_root / "README.md").exists()
385
+ content = (project_root / "README.md").read_text()
386
+ assert content.strip() == f"# {subfolder.name}"
387
+ finally:
388
+ config.restore()
389
+
390
+ # Verify README was removed if it didn't exist before
391
+ if not (project_root / "README.md.backup").exists():
392
+ # No backup means no original README, so temp should be removed
393
+ assert (
394
+ not (project_root / "README.md").exists()
395
+ or (project_root / "README.md").read_text() != f"# {subfolder.name}\n"
396
+ )
397
+
398
+
399
+