python-package-folder 5.4.0__tar.gz → 6.0.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.4.0 → python_package_folder-6.0.0}/PKG-INFO +1 -1
  2. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/pyproject.toml +1 -1
  3. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/analyzer.py +22 -45
  4. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/manager.py +21 -1
  5. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/subfolder_build.py +74 -8
  6. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_build_with_external_deps.py +14 -6
  7. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_preserve_directory_structure.py +4 -2
  8. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_spreadsheet_creation_imports.py +3 -1
  9. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_subfolder_build.py +174 -4
  10. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.copier-answers.yml +0 -0
  11. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
  12. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
  13. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.cursor/rules/general.mdc +0 -0
  14. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.cursor/rules/python.mdc +0 -0
  15. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.github/workflows/ci.yml +0 -0
  16. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.github/workflows/publish.yml +0 -0
  17. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.gitignore +0 -0
  18. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/.vscode/settings.json +0 -0
  19. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/LICENSE +0 -0
  20. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/MANIFEST.in +0 -0
  21. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/Makefile +0 -0
  22. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/README.md +0 -0
  23. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/coverage.svg +0 -0
  24. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/development.md +0 -0
  25. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/docs/DEVELOPMENT.md +0 -0
  26. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/docs/INSTALLATION.md +0 -0
  27. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/docs/PUBLISHING.md +0 -0
  28. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/docs/REFERENCE.md +0 -0
  29. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/docs/USAGE.md +0 -0
  30. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/docs/VERSION_RESOLUTION.md +0 -0
  31. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/installation.md +0 -0
  32. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/publishing.md +0 -0
  33. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/__init__.py +0 -0
  34. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/__main__.py +0 -0
  35. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/finder.py +0 -0
  36. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/publisher.py +0 -0
  37. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/py.typed +0 -0
  38. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/python_package_folder.py +0 -0
  39. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/types.py +0 -0
  40. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/utils.py +0 -0
  41. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/version.py +0 -0
  42. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/src/python_package_folder/version_calculator.py +0 -0
  43. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/conftest.py +0 -0
  44. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/some_globals.py +0 -0
  45. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
  46. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
  47. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
  48. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
  49. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
  50. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
  51. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_exclude_patterns.py +0 -0
  52. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_linting.py +0 -0
  53. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_publisher.py +0 -0
  54. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_shared_subdirectory_imports.py +0 -0
  55. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_third_party_dependencies.py +0 -0
  56. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_utils.py +0 -0
  57. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_version_calculator.py +0 -0
  58. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/test_version_manager.py +0 -0
  59. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/tests/tests.py +0 -0
  60. {python_package_folder-5.4.0 → python_package_folder-6.0.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 5.4.0
3
+ Version: 6.0.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 = "5.4.0"
46
+ version = "6.0.0"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pytest>=8.3.5",
@@ -330,56 +330,33 @@ class ImportAnalyzer:
330
330
 
331
331
  # Check all subdirectories in parent (not just common ones)
332
332
  # This handles cases like src/data/spreadsheet_creation/spreadsheet_formatting_dataclasses.py
333
- if parent.is_dir():
333
+ # Use recursive search to find modules in nested directories
334
+ if parent.is_dir() and parent.is_relative_to(self.project_root):
335
+ # Recursively search for the module file in subdirectories
336
+ # Limit search to project_root and its subdirectories to avoid searching too broadly
337
+ module_basename = module_name.split(".")[-1]
334
338
  try:
335
- for subdir in parent.iterdir():
336
- if not subdir.is_dir():
339
+ # Search recursively for the module file
340
+ for potential_file in parent.rglob(f"{module_basename}.py"):
341
+ # Only search within project_root to avoid going too far
342
+ if not potential_file.is_relative_to(self.project_root):
337
343
  continue
338
- # Skip common excluded patterns
339
- if subdir.name.startswith("_SS") or subdir.name.startswith("__SS"):
340
- continue
341
- # Check if module file exists directly in subdirectory
342
- potential_subdir_file = subdir / f"{module_name.split('.')[-1]}.py"
343
- if potential_subdir_file.exists():
344
- return potential_subdir_file
345
- # Check if module directory exists in subdirectory
346
- potential_subdir_module = subdir / module_name.replace(".", "/")
347
- if (
348
- potential_subdir_module.is_dir()
349
- and (potential_subdir_module / "__init__.py").exists()
344
+ # Skip excluded patterns
345
+ if any(
346
+ part.startswith("_SS")
347
+ or part.startswith("__SS")
348
+ or part.startswith("_sandbox")
349
+ or part.startswith("__sandbox")
350
+ for part in potential_file.parts
350
351
  ):
351
- return potential_subdir_module / "__init__.py"
352
- if potential_subdir_module.with_suffix(".py").is_file():
353
- return potential_subdir_module.with_suffix(".py")
354
- # Check nested subdirectories (e.g., data/spreadsheet_creation)
355
- # Recursively check subdirectories up to 2 levels deep
356
- try:
357
- for nested_subdir in subdir.iterdir():
358
- if not nested_subdir.is_dir():
359
- continue
360
- # Check if module file exists in nested subdirectory
361
- potential_nested_file = (
362
- nested_subdir / f"{module_name.split('.')[-1]}.py"
363
- )
364
- if potential_nested_file.exists():
365
- return potential_nested_file
366
- # Check if module directory exists in nested subdirectory
367
- potential_nested_module = nested_subdir / module_name.replace(
368
- ".", "/"
369
- )
370
- if (
371
- potential_nested_module.is_dir()
372
- and (potential_nested_module / "__init__.py").exists()
373
- ):
374
- return potential_nested_module / "__init__.py"
375
- if potential_nested_module.with_suffix(".py").is_file():
376
- return potential_nested_module.with_suffix(".py")
377
- except (OSError, PermissionError):
378
- # Skip nested directories we can't read
379
352
  continue
353
+ # Skip if it's in the src_dir (we're looking for external dependencies)
354
+ if potential_file.is_relative_to(src_dir):
355
+ continue
356
+ return potential_file
380
357
  except (OSError, PermissionError):
381
- # Skip directories we can't read
382
- continue
358
+ # Skip if we can't read the directory
359
+ pass
383
360
 
384
361
  # Check common subdirectories in parent (e.g., _shared, shared, common)
385
362
  # This handles cases like src/_shared/better_enum.py
@@ -268,10 +268,30 @@ class BuildManager:
268
268
  # This is acceptable for tests or dependency-only operations
269
269
  if temp_pyproject is None:
270
270
  self.subfolder_config = None
271
+ else:
272
+ # If temporary package directory was created, use it for all operations
273
+ # This ensures dependencies are copied to the correct location and
274
+ # imports are fixed in the files that will actually be packaged
275
+ if (
276
+ self.subfolder_config
277
+ and self.subfolder_config._temp_package_dir
278
+ and self.subfolder_config._temp_package_dir.exists()
279
+ ):
280
+ # Update src_dir to point to temp package directory
281
+ self.src_dir = self.subfolder_config._temp_package_dir
282
+ # Recreate finder with updated src_dir so it calculates target paths correctly
283
+ self.finder = ExternalDependencyFinder(
284
+ self.project_root,
285
+ self.src_dir,
286
+ exclude_patterns=self.exclude_patterns,
287
+ )
288
+ print(
289
+ f"Using temporary package directory for build: {self.src_dir}"
290
+ )
271
291
 
272
292
  analyzer = ImportAnalyzer(self.project_root)
273
293
 
274
- # Find all Python files in src/
294
+ # Find all Python files in src/ (which may now be the temp package directory)
275
295
  python_files = analyzer.find_all_python_files(self.src_dir)
276
296
 
277
297
  # Find external dependencies using the configured finder
@@ -78,6 +78,7 @@ class SubfolderBuildConfig:
78
78
  self._used_subfolder_pyproject = False
79
79
  self._excluded_files: list[tuple[Path, Path]] = [] # List of (original_path, temp_path) tuples
80
80
  self._exclude_temp_dir: Path | None = None
81
+ self._temp_package_dir: Path | None = None
81
82
 
82
83
  def _derive_package_name(self) -> str:
83
84
  """
@@ -121,6 +122,48 @@ class SubfolderBuildConfig:
121
122
  # Fallback to just subfolder name
122
123
  return subfolder_name
123
124
 
125
+ def _create_temp_package_directory(self) -> None:
126
+ """
127
+ Create a temporary package directory with the correct import name.
128
+
129
+ This ensures the installed package has the correct directory structure.
130
+ The package name (with hyphens) is converted to the import name (with underscores).
131
+ For example: 'ml-drawing-assistant-data' -> 'ml_drawing_assistant_data'
132
+
133
+ The temporary directory is created in the project root and contains a copy
134
+ of the source directory contents.
135
+ """
136
+ if not self.package_name:
137
+ return
138
+
139
+ # Convert package name (with hyphens) to import name (with underscores)
140
+ # PyPI package names use hyphens, but Python import names use underscores
141
+ import_name = self.package_name.replace("-", "_")
142
+
143
+ # Create temporary directory name
144
+ temp_dir_name = f".temp_package_{import_name}"
145
+ temp_package_dir = self.project_root / temp_dir_name
146
+
147
+ # Remove if it already exists (from a previous failed build)
148
+ if temp_package_dir.exists():
149
+ shutil.rmtree(temp_package_dir)
150
+
151
+ # Copy the entire source directory contents to the temporary directory
152
+ try:
153
+ shutil.copytree(self.src_dir, temp_package_dir)
154
+ self._temp_package_dir = temp_package_dir
155
+ print(
156
+ f"Created temporary package directory: {temp_package_dir} "
157
+ f"(import name: {import_name})"
158
+ )
159
+ except Exception as e:
160
+ print(
161
+ f"Warning: Could not create temporary package directory: {e}",
162
+ file=sys.stderr,
163
+ )
164
+ # Fall back to using src_dir directly
165
+ self._temp_package_dir = None
166
+
124
167
  def _get_package_structure(self) -> tuple[str, list[str]]:
125
168
  """
126
169
  Determine the package structure for hatchling.
@@ -130,21 +173,24 @@ class SubfolderBuildConfig:
130
173
  - packages_path: The path to the directory containing packages
131
174
  - package_dirs: List of package directories to include
132
175
  """
133
- # Check if src_dir itself is a package (has __init__.py)
134
- has_init = (self.src_dir / "__init__.py").exists()
176
+ # Use temporary package directory if it exists, otherwise use src_dir
177
+ package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
178
+
179
+ # Check if package_dir itself is a package (has __init__.py)
180
+ has_init = (package_dir / "__init__.py").exists()
135
181
 
136
- # Check for Python files directly in src_dir
137
- py_files = list(self.src_dir.glob("*.py"))
182
+ # Check for Python files directly in package_dir
183
+ py_files = list(package_dir.glob("*.py"))
138
184
  has_py_files = bool(py_files)
139
185
 
140
- # Calculate relative path
186
+ # Calculate relative path from project root
141
187
  try:
142
- rel_path = self.src_dir.relative_to(self.project_root)
188
+ rel_path = package_dir.relative_to(self.project_root)
143
189
  packages_path = str(rel_path).replace("\\", "/")
144
190
  except ValueError:
145
191
  packages_path = None
146
192
 
147
- # If src_dir has Python files but no __init__.py, we need to make it a package
193
+ # If package_dir has Python files but no __init__.py, we need to make it a package
148
194
  # or include it as a module directory
149
195
  if has_py_files and not has_init:
150
196
  # For flat structures, we include the directory itself
@@ -298,7 +344,8 @@ class SubfolderBuildConfig:
298
344
  if not self.version:
299
345
  raise ValueError("Version is required for subfolder builds")
300
346
 
301
- # Ensure src_dir is a package (has __init__.py) for hatchling
347
+ # Ensure src_dir is a package (has __init__.py) before creating temp directory
348
+ # This way the __init__.py will be copied to the temp directory
302
349
  init_file = self.src_dir / "__init__.py"
303
350
  if not init_file.exists():
304
351
  # Create a temporary __init__.py to make it a package
@@ -307,6 +354,13 @@ class SubfolderBuildConfig:
307
354
  else:
308
355
  self._temp_init_created = False
309
356
 
357
+ # Create temporary package directory with correct import name
358
+ # This will copy the __init__.py we just created (if any)
359
+ self._create_temp_package_directory()
360
+
361
+ # Determine which directory to use (temp package dir or src_dir)
362
+ package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
363
+
310
364
  # Check if pyproject.toml exists in subfolder
311
365
  subfolder_pyproject = self.src_dir / "pyproject.toml"
312
366
  if subfolder_pyproject.exists():
@@ -1206,6 +1260,18 @@ class SubfolderBuildConfig:
1206
1260
  self.original_pyproject_path = None
1207
1261
  self._used_subfolder_pyproject = False
1208
1262
 
1263
+ # Remove temporary package directory if it exists
1264
+ if self._temp_package_dir and self._temp_package_dir.exists():
1265
+ try:
1266
+ shutil.rmtree(self._temp_package_dir)
1267
+ print(f"Removed temporary package directory: {self._temp_package_dir}")
1268
+ except Exception as e:
1269
+ print(
1270
+ f"Warning: Could not remove temporary package directory {self._temp_package_dir}: {e}",
1271
+ file=sys.stderr,
1272
+ )
1273
+ self._temp_package_dir = None
1274
+
1209
1275
  def __enter__(self) -> Self:
1210
1276
  """Context manager entry."""
1211
1277
  return self
@@ -440,11 +440,15 @@ def shared_function():
440
440
 
441
441
  # Verify dependencies were copied
442
442
  assert len(external_deps) >= 2
443
- assert (subfolder / "_shared").exists()
444
- assert (subfolder / "_globals.py").exists()
443
+ # Dependencies are copied to the temp package directory (if it exists) or original subfolder
444
+ package_dir = manager.src_dir # This will be temp package dir if it was created
445
+ assert (package_dir / "_shared").exists()
446
+ assert (package_dir / "_globals.py").exists()
445
447
 
446
448
  # Verify imports were converted to relative
447
- modified_content = test_file.read_text()
449
+ # Read from package_dir which may be temp package directory
450
+ test_file_in_package = package_dir / test_file.name
451
+ modified_content = test_file_in_package.read_text()
448
452
  assert "from ._shared.image_utils import save_PIL_image" in modified_content
449
453
  assert "from ._shared.file_utils import get_filepaths_config" in modified_content
450
454
  assert "from ._globals import is_testing" in modified_content
@@ -455,12 +459,15 @@ def shared_function():
455
459
  assert "from pathlib import Path" in modified_content
456
460
 
457
461
  # Verify import statement conversion
458
- modified_content2 = test_file2.read_text()
462
+ test_file2_in_package = package_dir / test_file2.name
463
+ modified_content2 = test_file2_in_package.read_text()
459
464
  assert "from . import _globals" in modified_content2
460
465
 
461
466
  # Verify relative imports in copied files were fixed
462
- if (subfolder / "_shared" / "shared_utils.py").exists():
463
- shared_utils_content = (subfolder / "_shared" / "shared_utils.py").read_text()
467
+ # Check in the package directory (which may be temp package dir)
468
+ shared_utils_path = package_dir / "_shared" / "shared_utils.py"
469
+ if shared_utils_path.exists():
470
+ shared_utils_content = shared_utils_path.read_text()
464
471
  # The relative import should be converted to absolute
465
472
  assert (
466
473
  "from .file_utils import" not in shared_utils_content
@@ -470,6 +477,7 @@ def shared_function():
470
477
  # Cleanup should restore original imports
471
478
  manager.cleanup()
472
479
 
480
+ # After cleanup, check original files (not temp package dir)
473
481
  restored_content = test_file.read_text()
474
482
  assert restored_content == original_content
475
483
 
@@ -161,14 +161,16 @@ packages = ["src/test_package"]
161
161
  assert len(data_deps) > 0, "data dependencies should be found"
162
162
 
163
163
  # Verify models structure was copied with full path
164
- models_path = src_dir / "models" / "Information_extraction" / "_shared_ie" / "ie_enums.py"
164
+ # Use manager.src_dir which may point to temp package directory
165
+ package_dir = manager.src_dir
166
+ models_path = package_dir / "models" / "Information_extraction" / "_shared_ie" / "ie_enums.py"
165
167
  assert models_path.exists(), (
166
168
  "models/Information_extraction/_shared_ie/ie_enums.py should be copied with full structure"
167
169
  )
168
170
 
169
171
  # Verify data structure was copied with full path
170
172
  data_path = (
171
- src_dir / "data" / "spreadsheet_creation" / "spreadsheet_formatting_dataclasses.py"
173
+ package_dir / "data" / "spreadsheet_creation" / "spreadsheet_formatting_dataclasses.py"
172
174
  )
173
175
  assert data_path.exists(), (
174
176
  "data/spreadsheet_creation/spreadsheet_formatting_dataclasses.py should be copied with full structure"
@@ -139,7 +139,9 @@ packages = ["src/test_package"]
139
139
 
140
140
  # Verify spreadsheet_creation directory was copied with full structure preserved
141
141
  # Import is "data.spreadsheet_creation.spreadsheet_formatting_dataclasses", so structure should be preserved
142
- copied_dir = src_dir / "data" / "spreadsheet_creation"
142
+ # Use manager.src_dir which may point to temp package directory
143
+ package_dir = manager.src_dir
144
+ copied_dir = package_dir / "data" / "spreadsheet_creation"
143
145
  assert copied_dir.exists(), (
144
146
  f"spreadsheet_creation directory should be copied with structure at {copied_dir}"
145
147
  )
@@ -683,8 +683,8 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
683
683
  assert "[tool.hatch.version]" not in content
684
684
  assert "[tool.uv-dynamic-versioning]" not in content
685
685
 
686
- # Verify packages path is set correctly
687
- assert 'packages = ["subfolder"]' in content or '"subfolder"' in content
686
+ # Verify packages path is set correctly (should use temp package directory)
687
+ assert '.temp_package_my_custom_package' in content
688
688
 
689
689
  # Verify backup was created
690
690
  assert (project_root / "pyproject.toml.original").exists()
@@ -734,8 +734,8 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
734
734
  # Verify only-include is present
735
735
  assert "only-include = [" in content
736
736
 
737
- # Verify the subfolder is included
738
- assert '"subfolder"' in content
737
+ # Verify the temp package directory is included (not the original subfolder)
738
+ assert '.temp_package_test_package' in content
739
739
 
740
740
  # Verify necessary files are included
741
741
  assert '"pyproject.toml"' in content
@@ -950,3 +950,173 @@ description = "Subfolder package"
950
950
  assert 'build-backend = "hatchling.build"' in content
951
951
 
952
952
  config.restore()
953
+
954
+
955
+ class TestTemporaryPackageDirectory:
956
+ """Tests for temporary package directory creation and cleanup."""
957
+
958
+ def test_temp_package_directory_created_with_correct_name(
959
+ self, test_project_with_pyproject: Path
960
+ ) -> None:
961
+ """Test that temporary package directory is created with correct import name."""
962
+ project_root = test_project_with_pyproject
963
+ subfolder = project_root / "subfolder"
964
+ (subfolder / "module.py").write_text("def func(): pass")
965
+
966
+ config = SubfolderBuildConfig(
967
+ project_root=project_root,
968
+ src_dir=subfolder,
969
+ version="1.0.0",
970
+ )
971
+
972
+ # Package name should be "test-package-subfolder" (with hyphens)
973
+ assert config.package_name == "test-package-subfolder"
974
+
975
+ # Create temp pyproject (which creates temp package directory)
976
+ config.create_temp_pyproject()
977
+
978
+ # Temp package directory should exist with import name (underscores)
979
+ temp_package_dir = project_root / ".temp_package_test_package_subfolder"
980
+ assert temp_package_dir.exists()
981
+ assert config._temp_package_dir == temp_package_dir
982
+
983
+ # Temp package directory should contain the subfolder contents
984
+ assert (temp_package_dir / "module.py").exists()
985
+
986
+ # Cleanup
987
+ config.restore()
988
+
989
+ def test_temp_package_directory_uses_import_name(
990
+ self, test_project_with_pyproject: Path
991
+ ) -> None:
992
+ """Test that temp package directory name converts hyphens to underscores."""
993
+ project_root = test_project_with_pyproject
994
+ subfolder = project_root / "subfolder"
995
+ (subfolder / "module.py").write_text("def func(): pass")
996
+
997
+ config = SubfolderBuildConfig(
998
+ project_root=project_root,
999
+ src_dir=subfolder,
1000
+ version="1.0.0",
1001
+ package_name="my-custom-package", # Package name with hyphens
1002
+ )
1003
+
1004
+ config.create_temp_pyproject()
1005
+
1006
+ # Temp directory should use underscores (import name)
1007
+ temp_package_dir = project_root / ".temp_package_my_custom_package"
1008
+ assert temp_package_dir.exists()
1009
+ assert config._temp_package_dir == temp_package_dir
1010
+
1011
+ config.restore()
1012
+
1013
+ def test_temp_package_directory_cleaned_up(self, test_project_with_pyproject: Path) -> None:
1014
+ """Test that temporary package directory is cleaned up on restore."""
1015
+ project_root = test_project_with_pyproject
1016
+ subfolder = project_root / "subfolder"
1017
+ (subfolder / "module.py").write_text("def func(): pass")
1018
+
1019
+ config = SubfolderBuildConfig(
1020
+ project_root=project_root,
1021
+ src_dir=subfolder,
1022
+ version="1.0.0",
1023
+ )
1024
+
1025
+ config.create_temp_pyproject()
1026
+
1027
+ # Verify temp directory exists
1028
+ temp_package_dir = config._temp_package_dir
1029
+ assert temp_package_dir is not None
1030
+ assert temp_package_dir.exists()
1031
+
1032
+ # Restore should clean it up
1033
+ config.restore()
1034
+
1035
+ # Temp directory should be removed
1036
+ assert not temp_package_dir.exists()
1037
+ assert config._temp_package_dir is None
1038
+
1039
+ def test_packages_configuration_uses_temp_directory(
1040
+ self, test_project_with_pyproject: Path
1041
+ ) -> None:
1042
+ """Test that packages configuration uses temp directory path."""
1043
+ project_root = test_project_with_pyproject
1044
+ subfolder = project_root / "subfolder"
1045
+ (subfolder / "module.py").write_text("def func(): pass")
1046
+
1047
+ config = SubfolderBuildConfig(
1048
+ project_root=project_root,
1049
+ src_dir=subfolder,
1050
+ version="1.0.0",
1051
+ )
1052
+
1053
+ pyproject_path = config.create_temp_pyproject()
1054
+ assert pyproject_path is not None
1055
+
1056
+ content = pyproject_path.read_text()
1057
+
1058
+ # Packages configuration should use temp directory path
1059
+ # Temp directory name is ".temp_package_test_package_subfolder"
1060
+ assert ".temp_package_test_package_subfolder" in content
1061
+
1062
+ config.restore()
1063
+
1064
+ def test_temp_package_directory_preserves_structure(
1065
+ self, test_project_with_pyproject: Path
1066
+ ) -> None:
1067
+ """Test that temp package directory preserves the original directory structure."""
1068
+ project_root = test_project_with_pyproject
1069
+ subfolder = project_root / "subfolder"
1070
+ (subfolder / "module.py").write_text("def func(): pass")
1071
+ (subfolder / "submodule").mkdir()
1072
+ (subfolder / "submodule" / "__init__.py").write_text("")
1073
+ (subfolder / "submodule" / "helper.py").write_text("def helper(): pass")
1074
+
1075
+ config = SubfolderBuildConfig(
1076
+ project_root=project_root,
1077
+ src_dir=subfolder,
1078
+ version="1.0.0",
1079
+ )
1080
+
1081
+ config.create_temp_pyproject()
1082
+
1083
+ temp_package_dir = config._temp_package_dir
1084
+ assert temp_package_dir is not None
1085
+
1086
+ # Verify structure is preserved
1087
+ assert (temp_package_dir / "module.py").exists()
1088
+ assert (temp_package_dir / "submodule" / "__init__.py").exists()
1089
+ assert (temp_package_dir / "submodule" / "helper.py").exists()
1090
+
1091
+ config.restore()
1092
+
1093
+ def test_temp_package_directory_handles_existing_directory(
1094
+ self, test_project_with_pyproject: Path
1095
+ ) -> None:
1096
+ """Test that temp package directory creation handles existing directory."""
1097
+ project_root = test_project_with_pyproject
1098
+ subfolder = project_root / "subfolder"
1099
+ (subfolder / "module.py").write_text("def func(): pass")
1100
+
1101
+ # Create a directory that would conflict
1102
+ existing_temp_dir = project_root / ".temp_package_test_package_subfolder"
1103
+ existing_temp_dir.mkdir()
1104
+ (existing_temp_dir / "old_file.py").write_text("# Old file")
1105
+
1106
+ config = SubfolderBuildConfig(
1107
+ project_root=project_root,
1108
+ src_dir=subfolder,
1109
+ version="1.0.0",
1110
+ )
1111
+
1112
+ # Should remove existing directory and create new one
1113
+ config.create_temp_pyproject()
1114
+
1115
+ temp_package_dir = config._temp_package_dir
1116
+ assert temp_package_dir is not None
1117
+ assert temp_package_dir.exists()
1118
+ # Should have new file, not old file
1119
+ assert (temp_package_dir / "module.py").exists()
1120
+ assert not (temp_package_dir / "old_file.py").exists()
1121
+
1122
+ config.restore()