python-package-folder 5.2.4__tar.gz → 5.4.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 (60) hide show
  1. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/PKG-INFO +1 -1
  2. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/coverage.svg +2 -2
  3. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/pyproject.toml +1 -1
  4. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/manager.py +37 -9
  5. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/python_package_folder.py +83 -10
  6. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/subfolder_build.py +37 -5
  7. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/version_calculator.py +24 -1
  8. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_subfolder_build.py +24 -7
  9. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.copier-answers.yml +0 -0
  10. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  11. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
  12. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.cursor/rules/general.mdc +0 -0
  13. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.cursor/rules/python.mdc +0 -0
  14. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.github/workflows/ci.yml +0 -0
  15. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.github/workflows/publish.yml +0 -0
  16. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.gitignore +0 -0
  17. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/.vscode/settings.json +0 -0
  18. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/LICENSE +0 -0
  19. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/MANIFEST.in +0 -0
  20. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/Makefile +0 -0
  21. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/README.md +0 -0
  22. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/development.md +0 -0
  23. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/docs/DEVELOPMENT.md +0 -0
  24. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/docs/INSTALLATION.md +0 -0
  25. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/docs/PUBLISHING.md +0 -0
  26. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/docs/REFERENCE.md +0 -0
  27. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/docs/USAGE.md +0 -0
  28. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/docs/VERSION_RESOLUTION.md +0 -0
  29. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/installation.md +0 -0
  30. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/publishing.md +0 -0
  31. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/__init__.py +0 -0
  32. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/__main__.py +0 -0
  33. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/analyzer.py +0 -0
  34. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/finder.py +0 -0
  35. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/publisher.py +0 -0
  36. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/py.typed +0 -0
  37. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/types.py +0 -0
  38. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/utils.py +0 -0
  39. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/src/python_package_folder/version.py +0 -0
  40. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/conftest.py +0 -0
  41. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/some_globals.py +0 -0
  42. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  43. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  44. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  45. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  46. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  47. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  48. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_build_with_external_deps.py +0 -0
  49. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_exclude_patterns.py +0 -0
  50. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_linting.py +0 -0
  51. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_preserve_directory_structure.py +0 -0
  52. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_publisher.py +0 -0
  53. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_shared_subdirectory_imports.py +0 -0
  54. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_spreadsheet_creation_imports.py +0 -0
  55. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_third_party_dependencies.py +0 -0
  56. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_utils.py +0 -0
  57. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_version_calculator.py +0 -0
  58. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/test_version_manager.py +0 -0
  59. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/tests/tests.py +0 -0
  60. {python_package_folder-5.2.4 → python_package_folder-5.4.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 5.2.4
3
+ Version: 5.4.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">67%</text>
18
- <text x="81" y="14">67%</text>
17
+ <text x="81" y="15" fill="#010101" fill-opacity=".3">65%</text>
18
+ <text x="81" y="14">65%</text>
19
19
  </g>
20
20
  </svg>
@@ -43,7 +43,7 @@ dependencies = [
43
43
 
44
44
  # ---- Dev dependencies ----
45
45
 
46
- version = "5.2.4"
46
+ version = "5.4.0"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pytest>=8.3.5",
@@ -239,10 +239,19 @@ class BuildManager:
239
239
  )
240
240
 
241
241
  if not package_name:
242
- # Derive package name from subfolder
243
- package_name = (
242
+ # Derive package name from subfolder: {root_project_name}-{subfolder_name}
243
+ root_project_name = self._get_project_name()
244
+ subfolder_name = (
244
245
  self.src_dir.name.replace("_", "-").replace(" ", "-").lower().strip("-")
245
246
  )
247
+
248
+ if root_project_name:
249
+ # Normalize root project name (replace underscores/hyphens consistently)
250
+ root_name_normalized = root_project_name.replace("_", "-").lower()
251
+ package_name = f"{root_name_normalized}-{subfolder_name}"
252
+ else:
253
+ # Fallback to just subfolder name if root project name not found
254
+ package_name = subfolder_name
246
255
 
247
256
  print(
248
257
  f"Detected subfolder build. Setting up package '{package_name}' version '{version}'..."
@@ -1191,13 +1200,23 @@ class BuildManager:
1191
1200
  captured_package_name = None
1192
1201
  if self._is_subfolder_build():
1193
1202
  # We need to get the package name before run_build cleans up subfolder_config
1194
- if not package_name:
1195
- # Derive from src_dir name (same logic as in prepare_build)
1196
- captured_package_name = (
1203
+ if package_name:
1204
+ # Use provided package name (from --package-name arg)
1205
+ captured_package_name = package_name
1206
+ else:
1207
+ # Derive from root project name + src_dir name (same logic as in prepare_build)
1208
+ root_project_name = self._get_project_name()
1209
+ subfolder_name = (
1197
1210
  self.src_dir.name.replace("_", "-").replace(" ", "-").lower().strip("-")
1198
1211
  )
1199
- else:
1200
- captured_package_name = package_name
1212
+
1213
+ if root_project_name:
1214
+ # Normalize root project name (replace underscores/hyphens consistently)
1215
+ root_name_normalized = root_project_name.replace("_", "-").lower()
1216
+ captured_package_name = f"{root_name_normalized}-{subfolder_name}"
1217
+ else:
1218
+ # Fallback to just subfolder name if root project name not found
1219
+ captured_package_name = subfolder_name
1201
1220
 
1202
1221
  self.run_build(
1203
1222
  build_command,
@@ -1223,10 +1242,19 @@ class BuildManager:
1223
1242
  elif package_name:
1224
1243
  publish_package_name = package_name
1225
1244
  else:
1226
- # Last resort: derive from src_dir name
1227
- publish_package_name = (
1245
+ # Last resort: derive from root project name + src_dir name
1246
+ root_project_name = self._get_project_name()
1247
+ subfolder_name = (
1228
1248
  self.src_dir.name.replace("_", "-").replace(" ", "-").lower().strip("-")
1229
1249
  )
1250
+
1251
+ if root_project_name:
1252
+ # Normalize root project name (replace underscores/hyphens consistently)
1253
+ root_name_normalized = root_project_name.replace("_", "-").lower()
1254
+ publish_package_name = f"{root_name_normalized}-{subfolder_name}"
1255
+ else:
1256
+ # Fallback to just subfolder name if root project name not found
1257
+ publish_package_name = subfolder_name
1230
1258
 
1231
1259
  # Log the package name being used for publishing
1232
1260
  import logging
@@ -30,6 +30,45 @@ logging.basicConfig(
30
30
 
31
31
  def is_github_actions() -> bool:
32
32
  """Check if running in GitHub Actions."""
33
+
34
+
35
+ def _get_root_project_name(project_root: Path) -> str | None:
36
+ """
37
+ Get the root project name from pyproject.toml.
38
+
39
+ Args:
40
+ project_root: Root directory of the project
41
+
42
+ Returns:
43
+ Project name from pyproject.toml, or None if not found
44
+ """
45
+ pyproject_path = project_root / "pyproject.toml"
46
+ if not pyproject_path.exists():
47
+ return None
48
+
49
+ try:
50
+ import tomllib
51
+ except ImportError:
52
+ try:
53
+ import tomli as tomllib
54
+ except ImportError:
55
+ tomllib = None
56
+
57
+ try:
58
+ if tomllib:
59
+ with open(pyproject_path, "rb") as f:
60
+ data = tomllib.load(f)
61
+ return data.get("project", {}).get("name")
62
+ else:
63
+ # Fallback: simple string parsing
64
+ content = pyproject_path.read_text(encoding="utf-8")
65
+ for line in content.split("\n"):
66
+ if line.strip().startswith("name ="):
67
+ return line.split("=", 1)[1].strip().strip('"').strip("'")
68
+ except Exception:
69
+ pass
70
+
71
+ return None
33
72
  return os.getenv("GITHUB_ACTIONS") == "true"
34
73
 
35
74
 
@@ -177,6 +216,27 @@ def main() -> int:
177
216
 
178
217
  # Resolve version via conventional commits if not provided and needed
179
218
  resolved_version = args.version
219
+
220
+ # Derive package name for subfolder builds (used for both version resolution and publishing)
221
+ derived_package_name = None
222
+ if is_subfolder:
223
+ if args.package_name:
224
+ derived_package_name = args.package_name
225
+ else:
226
+ # Derive package name: {root_project_name}-{subfolder_name}
227
+ root_project_name = _get_root_project_name(project_root)
228
+ subfolder_name = src_dir.name.replace("_", "-").replace(
229
+ " ", "-"
230
+ ).lower().strip("-")
231
+
232
+ if root_project_name:
233
+ # Normalize root project name (replace underscores/hyphens consistently)
234
+ root_name_normalized = root_project_name.replace("_", "-").lower()
235
+ derived_package_name = f"{root_name_normalized}-{subfolder_name}"
236
+ else:
237
+ # Fallback to just subfolder name if root project name not found
238
+ derived_package_name = subfolder_name
239
+
180
240
  if not resolved_version and not args.analyze_only:
181
241
  # Version is needed for subfolder builds or when publishing main package
182
242
  if is_subfolder or args.publish:
@@ -204,21 +264,18 @@ def main() -> int:
204
264
  if is_subfolder:
205
265
  # Workflow 1: subfolder build
206
266
  # src_dir is guaranteed to be relative to project_root due to is_subfolder check
207
- package_name = args.package_name or src_dir.name.replace("_", "-").replace(
208
- " ", "-"
209
- ).lower().strip("-")
210
267
  subfolder_rel_path = src_dir.relative_to(project_root)
211
268
 
212
269
  # Log the package name being used for version query
213
270
  logger = logging.getLogger(__name__)
214
271
  logger.info(
215
- f"Querying registry for package name: '{package_name}' "
216
- f"(derived from src_dir: '{src_dir.name}', args.package_name: {args.package_name})"
272
+ f"Querying registry for package name: '{derived_package_name}' "
273
+ f"(derived from src_dir: '{src_dir.name}', root_project: {_get_root_project_name(project_root)}, args.package_name: {args.package_name})"
217
274
  )
218
275
 
219
276
  resolved_version, error_details = resolve_version(
220
277
  project_root,
221
- package_name=package_name,
278
+ package_name=derived_package_name,
222
279
  subfolder_path=subfolder_rel_path,
223
280
  repository=repository,
224
281
  repository_url=repository_url,
@@ -279,7 +336,7 @@ def main() -> int:
279
336
  skip_existing=args.skip_existing,
280
337
  version=args.version,
281
338
  restore_versioning=not args.no_restore_versioning,
282
- package_name=args.package_name,
339
+ package_name=derived_package_name if is_subfolder else args.package_name,
283
340
  dependency_group=args.dependency_group,
284
341
  )
285
342
  else:
@@ -289,9 +346,25 @@ def main() -> int:
289
346
  if is_subfolder:
290
347
  from .subfolder_build import SubfolderBuildConfig
291
348
 
292
- package_name = args.package_name or src_dir.name.replace("_", "-").replace(
293
- " ", "-"
294
- ).lower().strip("-")
349
+ # Use derived_package_name if available, otherwise derive it again
350
+ if derived_package_name is not None:
351
+ package_name = derived_package_name
352
+ elif args.package_name:
353
+ package_name = args.package_name
354
+ else:
355
+ # Derive package name: {root_project_name}-{subfolder_name}
356
+ root_project_name = _get_root_project_name(project_root)
357
+ subfolder_name = src_dir.name.replace("_", "-").replace(
358
+ " ", "-"
359
+ ).lower().strip("-")
360
+
361
+ if root_project_name:
362
+ # Normalize root project name (replace underscores/hyphens consistently)
363
+ root_name_normalized = root_project_name.replace("_", "-").lower()
364
+ package_name = f"{root_name_normalized}-{subfolder_name}"
365
+ else:
366
+ # Fallback to just subfolder name if root project name not found
367
+ package_name = subfolder_name
295
368
  subfolder_config = SubfolderBuildConfig(
296
369
  project_root=project_root,
297
370
  src_dir=src_dir,
@@ -80,14 +80,46 @@ class SubfolderBuildConfig:
80
80
  self._exclude_temp_dir: Path | None = None
81
81
 
82
82
  def _derive_package_name(self) -> str:
83
- """Derive package name from source directory name."""
83
+ """
84
+ Derive package name from root project name and source directory name.
85
+
86
+ Format: {root_project_name}-{subfolder_name}
87
+ Falls back to just subfolder_name if root project name not found.
88
+ """
89
+ # Get root project name from pyproject.toml
90
+ root_project_name = None
91
+ pyproject_path = self.project_root / "pyproject.toml"
92
+ if pyproject_path.exists():
93
+ try:
94
+ if tomllib:
95
+ with open(pyproject_path, "rb") as f:
96
+ data = tomllib.load(f)
97
+ root_project_name = data.get("project", {}).get("name")
98
+ else:
99
+ # Fallback: simple string parsing
100
+ content = pyproject_path.read_text(encoding="utf-8")
101
+ for line in content.split("\n"):
102
+ if line.strip().startswith("name ="):
103
+ root_project_name = line.split("=", 1)[1].strip().strip('"').strip("'")
104
+ break
105
+ except Exception:
106
+ pass
107
+
84
108
  # Use the directory name, replacing invalid characters
85
- name = self.src_dir.name
109
+ subfolder_name = self.src_dir.name
86
110
  # Replace invalid characters with hyphens
87
- name = name.replace("_", "-").replace(" ", "-").lower()
111
+ subfolder_name = subfolder_name.replace("_", "-").replace(" ", "-").lower()
88
112
  # Remove any leading/trailing hyphens
89
- name = name.strip("-")
90
- return name
113
+ subfolder_name = subfolder_name.strip("-")
114
+
115
+ # Combine with root project name if available
116
+ if root_project_name:
117
+ # Normalize root project name (replace underscores/hyphens consistently)
118
+ root_name_normalized = root_project_name.replace("_", "-").lower()
119
+ return f"{root_name_normalized}-{subfolder_name}"
120
+ else:
121
+ # Fallback to just subfolder name
122
+ return subfolder_name
91
123
 
92
124
  def _get_package_structure(self) -> tuple[str, list[str]]:
93
125
  """
@@ -820,6 +820,7 @@ def parse_commit_for_bump(commit_message: str) -> str | None:
820
820
  def calculate_next_version(
821
821
  baseline_version: str,
822
822
  commits: list[str],
823
+ auto_bump_minor: bool = False,
823
824
  ) -> str | None:
824
825
  """
825
826
  Calculate next version from baseline and commits.
@@ -827,11 +828,21 @@ def calculate_next_version(
827
828
  Args:
828
829
  baseline_version: Current baseline version (e.g., "1.2.3")
829
830
  commits: List of commit messages since baseline
831
+ auto_bump_minor: If True and no conventional commits found, bump minor version
830
832
 
831
833
  Returns:
832
834
  Next version string or None if no changes require a version bump
833
835
  """
834
836
  if not commits:
837
+ # If no commits and auto_bump_minor is enabled, bump minor version
838
+ if auto_bump_minor:
839
+ try:
840
+ parts = baseline_version.split(".")
841
+ major = int(parts[0])
842
+ minor = int(parts[1]) if len(parts) > 1 else 0
843
+ return f"{major}.{minor + 1}.0"
844
+ except (ValueError, IndexError):
845
+ return None
835
846
  return None
836
847
 
837
848
  # Parse each commit to determine bump type
@@ -842,6 +853,16 @@ def calculate_next_version(
842
853
  bump_types.append(bump)
843
854
 
844
855
  if not bump_types:
856
+ # No conventional commits found
857
+ # If auto_bump_minor is enabled, bump minor version
858
+ if auto_bump_minor:
859
+ try:
860
+ parts = baseline_version.split(".")
861
+ major = int(parts[0])
862
+ minor = int(parts[1]) if len(parts) > 1 else 0
863
+ return f"{major}.{minor + 1}.0"
864
+ except (ValueError, IndexError):
865
+ return None
845
866
  return None
846
867
 
847
868
  # Determine highest bump (major > minor > patch)
@@ -975,7 +996,9 @@ def resolve_version(
975
996
 
976
997
  # Step 5: Calculate next version
977
998
  logger.info(f"Calculating next version from baseline {baseline_version} and {len(commits)} commits")
978
- next_version = calculate_next_version(baseline_version, commits)
999
+ # Auto-bump minor version if no conventional commits found (but baseline exists)
1000
+ auto_bump_minor = baseline_version is not None and baseline_version != "0.0.0"
1001
+ next_version = calculate_next_version(baseline_version, commits, auto_bump_minor=auto_bump_minor)
979
1002
 
980
1003
  if next_version:
981
1004
  logger.info(f"Calculated next version: {next_version}")
@@ -63,7 +63,7 @@ class TestSubfolderBuildConfig:
63
63
  version="1.0.0",
64
64
  )
65
65
 
66
- assert config.package_name == "subfolder"
66
+ assert config.package_name == "test-package-subfolder"
67
67
  assert config.version == "1.0.0"
68
68
  assert config.dependency_group is None
69
69
 
@@ -104,7 +104,7 @@ class TestSubfolderBuildConfig:
104
104
  content = pyproject_path.read_text()
105
105
 
106
106
  # Check package name and version are set
107
- assert 'name = "subfolder"' in content
107
+ assert 'name = "test-package-subfolder"' in content
108
108
  assert 'version = "2.0.0"' in content
109
109
 
110
110
  # Check dynamic versioning is removed
@@ -226,7 +226,7 @@ class TestSubfolderBuildConfig:
226
226
  ) as config:
227
227
  config.create_temp_pyproject()
228
228
  content = (test_project_with_pyproject / "pyproject.toml").read_text()
229
- assert 'name = "subfolder"' in content
229
+ assert 'name = "test-package-subfolder"' in content
230
230
 
231
231
  # Check restore happened automatically
232
232
  restored_content = (test_project_with_pyproject / "pyproject.toml").read_text()
@@ -262,7 +262,7 @@ class TestSubfolderBuildConfig:
262
262
  config.create_temp_pyproject()
263
263
 
264
264
  def test_package_name_derivation(self, test_project_with_pyproject: Path) -> None:
265
- """Test package name derivation from directory name."""
265
+ """Test package name derivation from root project name and directory name."""
266
266
  # Test with underscores
267
267
  subfolder = test_project_with_pyproject / "subfolder_to_build"
268
268
  subfolder.mkdir()
@@ -271,7 +271,7 @@ class TestSubfolderBuildConfig:
271
271
  src_dir=subfolder,
272
272
  version="1.0.0",
273
273
  )
274
- assert config.package_name == "subfolder-to-build"
274
+ assert config.package_name == "test-package-subfolder-to-build"
275
275
 
276
276
  # Test with spaces
277
277
  subfolder2 = test_project_with_pyproject / "subfolder with spaces"
@@ -281,7 +281,24 @@ class TestSubfolderBuildConfig:
281
281
  src_dir=subfolder2,
282
282
  version="1.0.0",
283
283
  )
284
- assert config2.package_name == "subfolder-with-spaces"
284
+ assert config2.package_name == "test-package-subfolder-with-spaces"
285
+
286
+ def test_package_name_derivation_no_root_project(self, tmp_path: Path) -> None:
287
+ """Test package name derivation when root project name is not found (fallback)."""
288
+ # Create a project without pyproject.toml
289
+ project_root = tmp_path / "test_project_no_pyproject"
290
+ project_root.mkdir()
291
+
292
+ subfolder = project_root / "subfolder"
293
+ subfolder.mkdir()
294
+
295
+ config = SubfolderBuildConfig(
296
+ project_root=project_root,
297
+ src_dir=subfolder,
298
+ version="1.0.0",
299
+ )
300
+ # Should fallback to just subfolder name when root project name not found
301
+ assert config.package_name == "subfolder"
285
302
 
286
303
 
287
304
  def test_readme_handling_with_existing_readme(test_project_with_pyproject: Path):
@@ -856,7 +873,7 @@ description = "Subfolder package"
856
873
  # Verify it was modified
857
874
  modified_content = (project_root / "pyproject.toml").read_text()
858
875
  assert modified_content != original_content
859
- assert 'name = "subfolder"' in modified_content
876
+ assert 'name = "test-package-subfolder"' in modified_content
860
877
 
861
878
  # Restore
862
879
  config.restore()