python-package-folder 5.4.0__tar.gz → 7.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.
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/PKG-INFO +1 -1
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/pyproject.toml +1 -1
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/analyzer.py +37 -49
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/manager.py +21 -1
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/subfolder_build.py +202 -69
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_build_with_external_deps.py +14 -6
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_preserve_directory_structure.py +4 -2
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_spreadsheet_creation_imports.py +3 -1
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_subfolder_build.py +297 -5
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.copier-answers.yml +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.github/workflows/ci.yml +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.github/workflows/publish.yml +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.gitignore +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/.vscode/settings.json +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/LICENSE +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/MANIFEST.in +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/Makefile +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/README.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/coverage.svg +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/development.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/docs/INSTALLATION.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/docs/PUBLISHING.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/docs/REFERENCE.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/docs/USAGE.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/installation.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/publishing.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/conftest.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_linting.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_publisher.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_utils.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_version_calculator.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/tests.py +0 -0
- {python_package_folder-5.4.0 → python_package_folder-7.0.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.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>
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/analyzer.py
RENAMED
|
@@ -227,9 +227,22 @@ class ImportAnalyzer:
|
|
|
227
227
|
import_info.classification = "stdlib"
|
|
228
228
|
return
|
|
229
229
|
|
|
230
|
+
# Check if it's a third-party package (in site-packages) FIRST
|
|
231
|
+
# This must be checked before resolve_local_import to avoid incorrectly
|
|
232
|
+
# classifying site-packages modules as "external" when they're found
|
|
233
|
+
# by the recursive search
|
|
234
|
+
if self.is_third_party(module_name):
|
|
235
|
+
import_info.classification = "third_party"
|
|
236
|
+
return
|
|
237
|
+
|
|
230
238
|
# Try to resolve as a local import
|
|
231
239
|
resolved = self.resolve_local_import(import_info, src_dir)
|
|
232
240
|
if resolved is not None:
|
|
241
|
+
# Double-check: if resolved path is in site-packages, it's actually third-party
|
|
242
|
+
# (this can happen if the recursive search finds it before importlib does)
|
|
243
|
+
if "site-packages" in str(resolved) or "dist-packages" in str(resolved):
|
|
244
|
+
import_info.classification = "third_party"
|
|
245
|
+
return
|
|
233
246
|
if resolved.is_relative_to(src_dir):
|
|
234
247
|
import_info.classification = "local"
|
|
235
248
|
else:
|
|
@@ -237,11 +250,6 @@ class ImportAnalyzer:
|
|
|
237
250
|
import_info.resolved_path = resolved
|
|
238
251
|
return
|
|
239
252
|
|
|
240
|
-
# Check if it's a third-party package (in site-packages)
|
|
241
|
-
if self.is_third_party(module_name):
|
|
242
|
-
import_info.classification = "third_party"
|
|
243
|
-
return
|
|
244
|
-
|
|
245
253
|
# Mark as ambiguous if we can't determine
|
|
246
254
|
import_info.classification = "ambiguous"
|
|
247
255
|
|
|
@@ -330,56 +338,36 @@ class ImportAnalyzer:
|
|
|
330
338
|
|
|
331
339
|
# Check all subdirectories in parent (not just common ones)
|
|
332
340
|
# This handles cases like src/data/spreadsheet_creation/spreadsheet_formatting_dataclasses.py
|
|
333
|
-
|
|
341
|
+
# Use recursive search to find modules in nested directories
|
|
342
|
+
if parent.is_dir() and parent.is_relative_to(self.project_root):
|
|
343
|
+
# Recursively search for the module file in subdirectories
|
|
344
|
+
# Limit search to project_root and its subdirectories to avoid searching too broadly
|
|
345
|
+
module_basename = module_name.split(".")[-1]
|
|
334
346
|
try:
|
|
335
|
-
for
|
|
336
|
-
|
|
347
|
+
# Search recursively for the module file
|
|
348
|
+
for potential_file in parent.rglob(f"{module_basename}.py"):
|
|
349
|
+
# Only search within project_root to avoid going too far
|
|
350
|
+
if not potential_file.is_relative_to(self.project_root):
|
|
337
351
|
continue
|
|
338
|
-
# Skip
|
|
339
|
-
if
|
|
352
|
+
# Skip site-packages and dist-packages (these are third-party, not external)
|
|
353
|
+
if "site-packages" in str(potential_file) or "dist-packages" in str(potential_file):
|
|
340
354
|
continue
|
|
341
|
-
#
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
potential_subdir_module.is_dir()
|
|
349
|
-
and (potential_subdir_module / "__init__.py").exists()
|
|
355
|
+
# Skip excluded patterns
|
|
356
|
+
if any(
|
|
357
|
+
part.startswith("_SS")
|
|
358
|
+
or part.startswith("__SS")
|
|
359
|
+
or part.startswith("_sandbox")
|
|
360
|
+
or part.startswith("__sandbox")
|
|
361
|
+
for part in potential_file.parts
|
|
350
362
|
):
|
|
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
363
|
continue
|
|
364
|
+
# Skip if it's in the src_dir (we're looking for external dependencies)
|
|
365
|
+
if potential_file.is_relative_to(src_dir):
|
|
366
|
+
continue
|
|
367
|
+
return potential_file
|
|
380
368
|
except (OSError, PermissionError):
|
|
381
|
-
# Skip
|
|
382
|
-
|
|
369
|
+
# Skip if we can't read the directory
|
|
370
|
+
pass
|
|
383
371
|
|
|
384
372
|
# Check common subdirectories in parent (e.g., _shared, shared, common)
|
|
385
373
|
# This handles cases like src/_shared/better_enum.py
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/manager.py
RENAMED
|
@@ -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,70 @@ 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 with the import name directly.
|
|
134
|
+
This way, hatchling will install it with the correct name without needing force-include.
|
|
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 with the import name directly
|
|
144
|
+
# This way, hatchling will install it with the correct name
|
|
145
|
+
import_name_dir = self.project_root / import_name
|
|
146
|
+
|
|
147
|
+
# Check if the directory already exists and is the correct one
|
|
148
|
+
if import_name_dir.exists() and import_name_dir == self._temp_package_dir:
|
|
149
|
+
# Directory already exists and is the correct one, no need to recreate
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
# Remove if it already exists (from a previous build)
|
|
153
|
+
if import_name_dir.exists():
|
|
154
|
+
shutil.rmtree(import_name_dir)
|
|
155
|
+
|
|
156
|
+
# Copy the entire source directory contents directly to the import name directory
|
|
157
|
+
# Check if src_dir exists and is a directory before copying
|
|
158
|
+
if not self.src_dir.exists():
|
|
159
|
+
print(
|
|
160
|
+
f"Warning: Source directory does not exist: {self.src_dir}",
|
|
161
|
+
file=sys.stderr,
|
|
162
|
+
)
|
|
163
|
+
self._temp_package_dir = None
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
if not self.src_dir.is_dir():
|
|
167
|
+
print(
|
|
168
|
+
f"Warning: Source path is not a directory: {self.src_dir}",
|
|
169
|
+
file=sys.stderr,
|
|
170
|
+
)
|
|
171
|
+
self._temp_package_dir = None
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
shutil.copytree(self.src_dir, import_name_dir)
|
|
176
|
+
self._temp_package_dir = import_name_dir
|
|
177
|
+
print(
|
|
178
|
+
f"Created temporary package directory: {import_name_dir} "
|
|
179
|
+
f"(import name: {import_name})"
|
|
180
|
+
)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(
|
|
183
|
+
f"Warning: Could not create temporary package directory: {e}",
|
|
184
|
+
file=sys.stderr,
|
|
185
|
+
)
|
|
186
|
+
# Fall back to using src_dir directly
|
|
187
|
+
self._temp_package_dir = None
|
|
188
|
+
|
|
124
189
|
def _get_package_structure(self) -> tuple[str, list[str]]:
|
|
125
190
|
"""
|
|
126
191
|
Determine the package structure for hatchling.
|
|
@@ -130,21 +195,24 @@ class SubfolderBuildConfig:
|
|
|
130
195
|
- packages_path: The path to the directory containing packages
|
|
131
196
|
- package_dirs: List of package directories to include
|
|
132
197
|
"""
|
|
133
|
-
#
|
|
134
|
-
|
|
198
|
+
# Use temporary package directory if it exists, otherwise use src_dir
|
|
199
|
+
package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
|
|
200
|
+
|
|
201
|
+
# Check if package_dir itself is a package (has __init__.py)
|
|
202
|
+
has_init = (package_dir / "__init__.py").exists()
|
|
135
203
|
|
|
136
|
-
# Check for Python files directly in
|
|
137
|
-
py_files = list(
|
|
204
|
+
# Check for Python files directly in package_dir
|
|
205
|
+
py_files = list(package_dir.glob("*.py"))
|
|
138
206
|
has_py_files = bool(py_files)
|
|
139
207
|
|
|
140
|
-
# Calculate relative path
|
|
208
|
+
# Calculate relative path from project root
|
|
141
209
|
try:
|
|
142
|
-
rel_path =
|
|
210
|
+
rel_path = package_dir.relative_to(self.project_root)
|
|
143
211
|
packages_path = str(rel_path).replace("\\", "/")
|
|
144
212
|
except ValueError:
|
|
145
213
|
packages_path = None
|
|
146
214
|
|
|
147
|
-
# If
|
|
215
|
+
# If package_dir has Python files but no __init__.py, we need to make it a package
|
|
148
216
|
# or include it as a module directory
|
|
149
217
|
if has_py_files and not has_init:
|
|
150
218
|
# For flat structures, we include the directory itself
|
|
@@ -298,80 +366,133 @@ class SubfolderBuildConfig:
|
|
|
298
366
|
if not self.version:
|
|
299
367
|
raise ValueError("Version is required for subfolder builds")
|
|
300
368
|
|
|
301
|
-
#
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
self._temp_init_created = True
|
|
307
|
-
else:
|
|
308
|
-
self._temp_init_created = False
|
|
309
|
-
|
|
310
|
-
# Check if pyproject.toml exists in subfolder
|
|
311
|
-
subfolder_pyproject = self.src_dir / "pyproject.toml"
|
|
312
|
-
if subfolder_pyproject.exists():
|
|
313
|
-
# Use the subfolder's pyproject.toml
|
|
314
|
-
print(f"Using existing pyproject.toml from subfolder: {subfolder_pyproject}")
|
|
315
|
-
self._used_subfolder_pyproject = True
|
|
316
|
-
|
|
317
|
-
# Store reference to original project root pyproject.toml
|
|
318
|
-
original_pyproject = self.project_root / "pyproject.toml"
|
|
319
|
-
self.original_pyproject_path = original_pyproject
|
|
320
|
-
|
|
321
|
-
# Create temporary pyproject.toml file
|
|
322
|
-
temp_pyproject_path = self.project_root / "pyproject.toml.temp"
|
|
323
|
-
|
|
324
|
-
# Read and adjust the subfolder pyproject.toml
|
|
325
|
-
subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
|
|
326
|
-
# Adjust packages path to be relative to project root
|
|
327
|
-
adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
|
|
328
|
-
|
|
329
|
-
# Read exclude patterns from root pyproject.toml and inject them
|
|
330
|
-
exclude_patterns = read_exclude_patterns(original_pyproject)
|
|
369
|
+
# Check if pyproject.toml exists in subfolder FIRST
|
|
370
|
+
# This allows us to handle subfolder pyproject.toml even when parent doesn't exist
|
|
371
|
+
# But first ensure src_dir exists
|
|
372
|
+
if not self.src_dir.exists() or not self.src_dir.is_dir():
|
|
373
|
+
# If src_dir doesn't exist, we can't proceed
|
|
331
374
|
print(
|
|
332
|
-
f"
|
|
375
|
+
f"Warning: Source directory does not exist or is not a directory: {self.src_dir}",
|
|
333
376
|
file=sys.stderr,
|
|
334
377
|
)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
subfolder_pyproject = self.src_dir / "pyproject.toml"
|
|
381
|
+
if subfolder_pyproject.exists() and subfolder_pyproject.is_file():
|
|
382
|
+
# Read the subfolder pyproject.toml content IMMEDIATELY after checking it exists
|
|
383
|
+
# This prevents any issues if the file is affected by subsequent operations
|
|
384
|
+
try:
|
|
385
|
+
subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
|
|
386
|
+
except (FileNotFoundError, OSError) as e:
|
|
387
|
+
# File was deleted or inaccessible between check and read
|
|
388
|
+
print(
|
|
389
|
+
f"Warning: Could not read subfolder pyproject.toml at {subfolder_pyproject}: {e}. "
|
|
390
|
+
"Falling back to creating from parent.",
|
|
391
|
+
file=sys.stderr,
|
|
392
|
+
)
|
|
393
|
+
subfolder_content = None
|
|
394
|
+
|
|
395
|
+
if subfolder_content is not None:
|
|
396
|
+
# Ensure src_dir is a package (has __init__.py) before creating temp directory
|
|
397
|
+
# This way the __init__.py will be copied to the temp directory
|
|
398
|
+
init_file = self.src_dir / "__init__.py"
|
|
399
|
+
if not init_file.exists():
|
|
400
|
+
# Create a temporary __init__.py to make it a package
|
|
401
|
+
init_file.write_text("# Temporary __init__.py for build\n", encoding="utf-8")
|
|
402
|
+
self._temp_init_created = True
|
|
403
|
+
else:
|
|
404
|
+
self._temp_init_created = False
|
|
341
405
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
406
|
+
# Create temporary package directory with correct import name
|
|
407
|
+
# This will copy the __init__.py we just created (if any)
|
|
408
|
+
self._create_temp_package_directory()
|
|
409
|
+
|
|
410
|
+
# Determine which directory to use (temp package dir or src_dir)
|
|
411
|
+
package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
|
|
412
|
+
# Use the subfolder's pyproject.toml
|
|
413
|
+
print(f"Using existing pyproject.toml from subfolder: {subfolder_pyproject}")
|
|
414
|
+
self._used_subfolder_pyproject = True
|
|
348
415
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
# Remove backup if it already exists (from previous failed test or run)
|
|
353
|
-
if backup_path.exists():
|
|
354
|
-
backup_path.unlink()
|
|
355
|
-
original_pyproject.rename(backup_path)
|
|
356
|
-
self.original_pyproject_backup = backup_path
|
|
357
|
-
|
|
358
|
-
# Move temp file to pyproject.toml for the build
|
|
359
|
-
temp_pyproject_path.rename(original_pyproject)
|
|
360
|
-
self.temp_pyproject = original_pyproject
|
|
361
|
-
|
|
362
|
-
# Handle README file
|
|
363
|
-
self._handle_readme()
|
|
416
|
+
# Store reference to original project root pyproject.toml
|
|
417
|
+
original_pyproject = self.project_root / "pyproject.toml"
|
|
418
|
+
self.original_pyproject_path = original_pyproject
|
|
364
419
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
self._exclude_files_by_patterns(exclude_patterns)
|
|
420
|
+
# Create temporary pyproject.toml file
|
|
421
|
+
temp_pyproject_path = self.project_root / "pyproject.toml.temp"
|
|
368
422
|
|
|
369
|
-
|
|
423
|
+
# Adjust packages path to be relative to project root
|
|
424
|
+
adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
|
|
425
|
+
|
|
426
|
+
# Read exclude patterns from root pyproject.toml and inject them (if it exists)
|
|
427
|
+
exclude_patterns = []
|
|
428
|
+
if original_pyproject.exists():
|
|
429
|
+
exclude_patterns = read_exclude_patterns(original_pyproject)
|
|
430
|
+
print(
|
|
431
|
+
f"INFO: Read exclude patterns from {original_pyproject}: {exclude_patterns}",
|
|
432
|
+
file=sys.stderr,
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
print(
|
|
436
|
+
f"INFO: No parent pyproject.toml found at {original_pyproject}, skipping exclude patterns",
|
|
437
|
+
file=sys.stderr,
|
|
438
|
+
)
|
|
439
|
+
if exclude_patterns:
|
|
440
|
+
adjusted_content = self._inject_exclude_patterns(adjusted_content, exclude_patterns)
|
|
441
|
+
|
|
442
|
+
# Write adjusted content to temporary file
|
|
443
|
+
temp_pyproject_path.write_text(adjusted_content, encoding="utf-8")
|
|
444
|
+
self.temp_pyproject = temp_pyproject_path
|
|
445
|
+
|
|
446
|
+
# Print the temporary pyproject.toml content for debugging
|
|
447
|
+
print("\n" + "=" * 80)
|
|
448
|
+
print("Temporary pyproject.toml content (from subfolder pyproject.toml):")
|
|
449
|
+
print("=" * 80)
|
|
450
|
+
print(adjusted_content)
|
|
451
|
+
print("=" * 80 + "\n")
|
|
452
|
+
|
|
453
|
+
# If original pyproject.toml exists, temporarily move it
|
|
454
|
+
if original_pyproject.exists():
|
|
455
|
+
backup_path = self.project_root / "pyproject.toml.original"
|
|
456
|
+
# Remove backup if it already exists (from previous failed test or run)
|
|
457
|
+
if backup_path.exists():
|
|
458
|
+
backup_path.unlink()
|
|
459
|
+
original_pyproject.rename(backup_path)
|
|
460
|
+
self.original_pyproject_backup = backup_path
|
|
461
|
+
|
|
462
|
+
# Move temp file to pyproject.toml for the build
|
|
463
|
+
temp_pyproject_path.rename(original_pyproject)
|
|
464
|
+
self.temp_pyproject = original_pyproject
|
|
465
|
+
|
|
466
|
+
# Handle README file
|
|
467
|
+
self._handle_readme()
|
|
468
|
+
|
|
469
|
+
# Exclude files matching exclude patterns
|
|
470
|
+
if exclude_patterns:
|
|
471
|
+
self._exclude_files_by_patterns(exclude_patterns)
|
|
472
|
+
|
|
473
|
+
return original_pyproject
|
|
370
474
|
|
|
371
475
|
# No pyproject.toml in subfolder, create one from parent
|
|
372
476
|
self._used_subfolder_pyproject = False
|
|
373
477
|
print("No pyproject.toml found in subfolder, creating temporary one from parent")
|
|
374
478
|
|
|
479
|
+
# Ensure src_dir is a package (has __init__.py) before creating temp directory
|
|
480
|
+
# This way the __init__.py will be copied to the temp directory
|
|
481
|
+
init_file = self.src_dir / "__init__.py"
|
|
482
|
+
if not init_file.exists():
|
|
483
|
+
# Create a temporary __init__.py to make it a package
|
|
484
|
+
init_file.write_text("# Temporary __init__.py for build\n", encoding="utf-8")
|
|
485
|
+
self._temp_init_created = True
|
|
486
|
+
else:
|
|
487
|
+
self._temp_init_created = False
|
|
488
|
+
|
|
489
|
+
# Create temporary package directory with correct import name
|
|
490
|
+
# This will copy the __init__.py we just created (if any)
|
|
491
|
+
self._create_temp_package_directory()
|
|
492
|
+
|
|
493
|
+
# Determine which directory to use (temp package dir or src_dir)
|
|
494
|
+
package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
|
|
495
|
+
|
|
375
496
|
# Read the original pyproject.toml
|
|
376
497
|
original_pyproject = self.project_root / "pyproject.toml"
|
|
377
498
|
if not original_pyproject.exists():
|
|
@@ -1206,6 +1327,18 @@ class SubfolderBuildConfig:
|
|
|
1206
1327
|
self.original_pyproject_path = None
|
|
1207
1328
|
self._used_subfolder_pyproject = False
|
|
1208
1329
|
|
|
1330
|
+
# Remove temporary package directory if it exists
|
|
1331
|
+
if self._temp_package_dir and self._temp_package_dir.exists():
|
|
1332
|
+
try:
|
|
1333
|
+
shutil.rmtree(self._temp_package_dir)
|
|
1334
|
+
print(f"Removed temporary package directory: {self._temp_package_dir}")
|
|
1335
|
+
except Exception as e:
|
|
1336
|
+
print(
|
|
1337
|
+
f"Warning: Could not remove temporary package directory {self._temp_package_dir}: {e}",
|
|
1338
|
+
file=sys.stderr,
|
|
1339
|
+
)
|
|
1340
|
+
self._temp_package_dir = None
|
|
1341
|
+
|
|
1209
1342
|
def __enter__(self) -> Self:
|
|
1210
1343
|
"""Context manager entry."""
|
|
1211
1344
|
return self
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_build_with_external_deps.py
RENAMED
|
@@ -440,11 +440,15 @@ def shared_function():
|
|
|
440
440
|
|
|
441
441
|
# Verify dependencies were copied
|
|
442
442
|
assert len(external_deps) >= 2
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
)
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import subprocess
|
|
6
|
+
import zipfile
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
import pytest
|
|
8
10
|
|
|
9
|
-
from python_package_folder import SubfolderBuildConfig
|
|
11
|
+
from python_package_folder import BuildManager, SubfolderBuildConfig
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@pytest.fixture
|
|
@@ -683,8 +685,8 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
|
|
|
683
685
|
assert "[tool.hatch.version]" not in content
|
|
684
686
|
assert "[tool.uv-dynamic-versioning]" not in content
|
|
685
687
|
|
|
686
|
-
# Verify packages path is set correctly
|
|
687
|
-
assert '
|
|
688
|
+
# Verify packages path is set correctly (should use import name, not temp directory name)
|
|
689
|
+
assert '"my_custom_package"' in content or "'my_custom_package'" in content
|
|
688
690
|
|
|
689
691
|
# Verify backup was created
|
|
690
692
|
assert (project_root / "pyproject.toml.original").exists()
|
|
@@ -734,8 +736,9 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
|
|
|
734
736
|
# Verify only-include is present
|
|
735
737
|
assert "only-include = [" in content
|
|
736
738
|
|
|
737
|
-
# Verify the
|
|
738
|
-
|
|
739
|
+
# Verify the import name is used in packages configuration (not the original subfolder)
|
|
740
|
+
# The temp directory is renamed to the import name, so packages should use that
|
|
741
|
+
assert '"test_package"' in content or "'test_package'" in content
|
|
739
742
|
|
|
740
743
|
# Verify necessary files are included
|
|
741
744
|
assert '"pyproject.toml"' in content
|
|
@@ -950,3 +953,292 @@ description = "Subfolder package"
|
|
|
950
953
|
assert 'build-backend = "hatchling.build"' in content
|
|
951
954
|
|
|
952
955
|
config.restore()
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
class TestTemporaryPackageDirectory:
|
|
959
|
+
"""Tests for temporary package directory creation and cleanup."""
|
|
960
|
+
|
|
961
|
+
def test_temp_package_directory_created_with_correct_name(
|
|
962
|
+
self, test_project_with_pyproject: Path
|
|
963
|
+
) -> None:
|
|
964
|
+
"""Test that temporary package directory is created with correct import name."""
|
|
965
|
+
project_root = test_project_with_pyproject
|
|
966
|
+
subfolder = project_root / "subfolder"
|
|
967
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
968
|
+
|
|
969
|
+
config = SubfolderBuildConfig(
|
|
970
|
+
project_root=project_root,
|
|
971
|
+
src_dir=subfolder,
|
|
972
|
+
version="1.0.0",
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
# Package name should be "test-package-subfolder" (with hyphens)
|
|
976
|
+
assert config.package_name == "test-package-subfolder"
|
|
977
|
+
|
|
978
|
+
# Create temp pyproject (which creates temp package directory)
|
|
979
|
+
config.create_temp_pyproject()
|
|
980
|
+
|
|
981
|
+
# Temp package directory should exist with import name (underscores, no temp prefix)
|
|
982
|
+
import_name = "test_package_subfolder" # Import name from "test-package-subfolder"
|
|
983
|
+
temp_package_dir = project_root / import_name
|
|
984
|
+
assert temp_package_dir.exists()
|
|
985
|
+
assert config._temp_package_dir == temp_package_dir
|
|
986
|
+
|
|
987
|
+
# Temp package directory should contain the subfolder contents
|
|
988
|
+
assert (temp_package_dir / "module.py").exists()
|
|
989
|
+
|
|
990
|
+
# Cleanup
|
|
991
|
+
config.restore()
|
|
992
|
+
|
|
993
|
+
def test_temp_package_directory_uses_import_name(
|
|
994
|
+
self, test_project_with_pyproject: Path
|
|
995
|
+
) -> None:
|
|
996
|
+
"""Test that temp package directory name converts hyphens to underscores."""
|
|
997
|
+
project_root = test_project_with_pyproject
|
|
998
|
+
subfolder = project_root / "subfolder"
|
|
999
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
1000
|
+
|
|
1001
|
+
config = SubfolderBuildConfig(
|
|
1002
|
+
project_root=project_root,
|
|
1003
|
+
src_dir=subfolder,
|
|
1004
|
+
version="1.0.0",
|
|
1005
|
+
package_name="my-custom-package", # Package name with hyphens
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
config.create_temp_pyproject()
|
|
1009
|
+
|
|
1010
|
+
# Temp directory should use underscores (import name, no temp prefix)
|
|
1011
|
+
import_name = "my_custom_package" # Import name from "my-custom-package"
|
|
1012
|
+
temp_package_dir = project_root / import_name
|
|
1013
|
+
assert temp_package_dir.exists()
|
|
1014
|
+
assert config._temp_package_dir == temp_package_dir
|
|
1015
|
+
|
|
1016
|
+
config.restore()
|
|
1017
|
+
|
|
1018
|
+
def test_temp_package_directory_cleaned_up(self, test_project_with_pyproject: Path) -> None:
|
|
1019
|
+
"""Test that temporary package directory is cleaned up on restore."""
|
|
1020
|
+
project_root = test_project_with_pyproject
|
|
1021
|
+
subfolder = project_root / "subfolder"
|
|
1022
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
1023
|
+
|
|
1024
|
+
config = SubfolderBuildConfig(
|
|
1025
|
+
project_root=project_root,
|
|
1026
|
+
src_dir=subfolder,
|
|
1027
|
+
version="1.0.0",
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
config.create_temp_pyproject()
|
|
1031
|
+
|
|
1032
|
+
# Verify temp directory exists
|
|
1033
|
+
temp_package_dir = config._temp_package_dir
|
|
1034
|
+
assert temp_package_dir is not None
|
|
1035
|
+
assert temp_package_dir.exists()
|
|
1036
|
+
|
|
1037
|
+
# Restore should clean it up
|
|
1038
|
+
config.restore()
|
|
1039
|
+
|
|
1040
|
+
# Temp directory should be removed
|
|
1041
|
+
assert not temp_package_dir.exists()
|
|
1042
|
+
assert config._temp_package_dir is None
|
|
1043
|
+
|
|
1044
|
+
def test_packages_configuration_uses_temp_directory(
|
|
1045
|
+
self, test_project_with_pyproject: Path
|
|
1046
|
+
) -> None:
|
|
1047
|
+
"""Test that packages configuration uses temp directory path."""
|
|
1048
|
+
project_root = test_project_with_pyproject
|
|
1049
|
+
subfolder = project_root / "subfolder"
|
|
1050
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
1051
|
+
|
|
1052
|
+
config = SubfolderBuildConfig(
|
|
1053
|
+
project_root=project_root,
|
|
1054
|
+
src_dir=subfolder,
|
|
1055
|
+
version="1.0.0",
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
pyproject_path = config.create_temp_pyproject()
|
|
1059
|
+
assert pyproject_path is not None
|
|
1060
|
+
|
|
1061
|
+
content = pyproject_path.read_text()
|
|
1062
|
+
|
|
1063
|
+
# Packages configuration should use import name (temp directory is renamed to import name)
|
|
1064
|
+
# Import name is "test_package_subfolder" (from "test-package-subfolder")
|
|
1065
|
+
assert '"test_package_subfolder"' in content or "'test_package_subfolder'" in content
|
|
1066
|
+
|
|
1067
|
+
config.restore()
|
|
1068
|
+
|
|
1069
|
+
def test_temp_package_directory_preserves_structure(
|
|
1070
|
+
self, test_project_with_pyproject: Path
|
|
1071
|
+
) -> None:
|
|
1072
|
+
"""Test that temp package directory preserves the original directory structure."""
|
|
1073
|
+
project_root = test_project_with_pyproject
|
|
1074
|
+
subfolder = project_root / "subfolder"
|
|
1075
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
1076
|
+
(subfolder / "submodule").mkdir()
|
|
1077
|
+
(subfolder / "submodule" / "__init__.py").write_text("")
|
|
1078
|
+
(subfolder / "submodule" / "helper.py").write_text("def helper(): pass")
|
|
1079
|
+
|
|
1080
|
+
config = SubfolderBuildConfig(
|
|
1081
|
+
project_root=project_root,
|
|
1082
|
+
src_dir=subfolder,
|
|
1083
|
+
version="1.0.0",
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
config.create_temp_pyproject()
|
|
1087
|
+
|
|
1088
|
+
temp_package_dir = config._temp_package_dir
|
|
1089
|
+
assert temp_package_dir is not None
|
|
1090
|
+
|
|
1091
|
+
# Verify structure is preserved
|
|
1092
|
+
assert (temp_package_dir / "module.py").exists()
|
|
1093
|
+
assert (temp_package_dir / "submodule" / "__init__.py").exists()
|
|
1094
|
+
assert (temp_package_dir / "submodule" / "helper.py").exists()
|
|
1095
|
+
|
|
1096
|
+
config.restore()
|
|
1097
|
+
|
|
1098
|
+
def test_temp_package_directory_handles_existing_directory(
|
|
1099
|
+
self, test_project_with_pyproject: Path
|
|
1100
|
+
) -> None:
|
|
1101
|
+
"""Test that temp package directory creation handles existing directory."""
|
|
1102
|
+
project_root = test_project_with_pyproject
|
|
1103
|
+
subfolder = project_root / "subfolder"
|
|
1104
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
1105
|
+
|
|
1106
|
+
# Create a directory that would conflict (using import name directly)
|
|
1107
|
+
import_name = "test_package_subfolder" # Import name from "test-package-subfolder"
|
|
1108
|
+
existing_temp_dir = project_root / import_name
|
|
1109
|
+
existing_temp_dir.mkdir()
|
|
1110
|
+
(existing_temp_dir / "old_file.py").write_text("# Old file")
|
|
1111
|
+
|
|
1112
|
+
config = SubfolderBuildConfig(
|
|
1113
|
+
project_root=project_root,
|
|
1114
|
+
src_dir=subfolder,
|
|
1115
|
+
version="1.0.0",
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
# Should remove existing directory and create new one
|
|
1119
|
+
config.create_temp_pyproject()
|
|
1120
|
+
|
|
1121
|
+
temp_package_dir = config._temp_package_dir
|
|
1122
|
+
assert temp_package_dir is not None
|
|
1123
|
+
assert temp_package_dir.exists()
|
|
1124
|
+
# Should have new file, not old file
|
|
1125
|
+
assert (temp_package_dir / "module.py").exists()
|
|
1126
|
+
assert not (temp_package_dir / "old_file.py").exists()
|
|
1127
|
+
|
|
1128
|
+
config.restore()
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
class TestWheelPackaging:
|
|
1132
|
+
"""Tests to verify that wheels are correctly packaged with the right directory structure."""
|
|
1133
|
+
|
|
1134
|
+
def test_wheel_contains_package_directory_with_correct_name(self, tmp_path: Path) -> None:
|
|
1135
|
+
"""Test that a built wheel contains the package directory with the correct import name."""
|
|
1136
|
+
project_root = tmp_path / "test_project"
|
|
1137
|
+
project_root.mkdir()
|
|
1138
|
+
|
|
1139
|
+
# Create pyproject.toml
|
|
1140
|
+
pyproject_content = """[project]
|
|
1141
|
+
name = "test-package"
|
|
1142
|
+
version = "0.1.0"
|
|
1143
|
+
|
|
1144
|
+
[build-system]
|
|
1145
|
+
requires = ["hatchling"]
|
|
1146
|
+
build-backend = "hatchling.build"
|
|
1147
|
+
"""
|
|
1148
|
+
(project_root / "pyproject.toml").write_text(pyproject_content)
|
|
1149
|
+
|
|
1150
|
+
# Create subfolder with package name that has hyphens
|
|
1151
|
+
subfolder = project_root / "src" / "data"
|
|
1152
|
+
subfolder.mkdir(parents=True)
|
|
1153
|
+
|
|
1154
|
+
# Create some Python files
|
|
1155
|
+
(subfolder / "__init__.py").write_text("# Package init")
|
|
1156
|
+
(subfolder / "module.py").write_text("def hello(): return 'world'")
|
|
1157
|
+
(subfolder / "utils.py").write_text("def util(): return 'helper'")
|
|
1158
|
+
|
|
1159
|
+
# Package name with hyphens (like ml-drawing-assistant-data)
|
|
1160
|
+
package_name = "ml-drawing-assistant-data"
|
|
1161
|
+
import_name = "ml_drawing_assistant_data" # Expected import name
|
|
1162
|
+
version = "1.0.0"
|
|
1163
|
+
|
|
1164
|
+
# Build the wheel
|
|
1165
|
+
manager = BuildManager(project_root=project_root, src_dir=subfolder)
|
|
1166
|
+
|
|
1167
|
+
def build_wheel() -> None:
|
|
1168
|
+
"""Build the wheel using uv build."""
|
|
1169
|
+
subprocess.run(
|
|
1170
|
+
["uv", "build", "--wheel"],
|
|
1171
|
+
cwd=project_root,
|
|
1172
|
+
check=True,
|
|
1173
|
+
capture_output=True,
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
try:
|
|
1177
|
+
# run_build will call prepare_build internally, so we don't need to call it explicitly
|
|
1178
|
+
manager.run_build(build_wheel, version=version, package_name=package_name)
|
|
1179
|
+
finally:
|
|
1180
|
+
manager.cleanup()
|
|
1181
|
+
|
|
1182
|
+
# Find the built wheel
|
|
1183
|
+
dist_dir = project_root / "dist"
|
|
1184
|
+
assert dist_dir.exists(), "dist directory should exist after build"
|
|
1185
|
+
|
|
1186
|
+
wheel_files = list(dist_dir.glob("*.whl"))
|
|
1187
|
+
assert len(wheel_files) > 0, "At least one wheel should be built"
|
|
1188
|
+
|
|
1189
|
+
wheel_file = wheel_files[0]
|
|
1190
|
+
|
|
1191
|
+
# Extract and inspect the wheel
|
|
1192
|
+
with zipfile.ZipFile(wheel_file, "r") as wheel:
|
|
1193
|
+
# Get all file names in the wheel
|
|
1194
|
+
file_names = wheel.namelist()
|
|
1195
|
+
|
|
1196
|
+
# Debug: Print all files to understand what's in the wheel
|
|
1197
|
+
print(f"\nWheel contents ({len(file_names)} files):")
|
|
1198
|
+
for f in sorted(file_names)[:20]:
|
|
1199
|
+
print(f" {f}")
|
|
1200
|
+
if len(file_names) > 20:
|
|
1201
|
+
print(f" ... and {len(file_names) - 20} more files")
|
|
1202
|
+
|
|
1203
|
+
# Verify the package directory exists with the correct import name
|
|
1204
|
+
# The package should be installed as ml_drawing_assistant_data/, not .temp_package_ml_drawing_assistant_data/
|
|
1205
|
+
package_dir_prefix = f"{import_name}/"
|
|
1206
|
+
package_files = [f for f in file_names if f.startswith(package_dir_prefix)]
|
|
1207
|
+
|
|
1208
|
+
# Also check for temp directory name (should NOT be present)
|
|
1209
|
+
temp_dir_prefix = ".temp_package_"
|
|
1210
|
+
temp_dir_files = [f for f in file_names if temp_dir_prefix in f and ".dist-info" not in f]
|
|
1211
|
+
|
|
1212
|
+
assert len(package_files) > 0, (
|
|
1213
|
+
f"Wheel should contain files in {import_name}/ directory. "
|
|
1214
|
+
f"Found {len(file_names)} total files. "
|
|
1215
|
+
f"Files with '/' in name: {[f for f in file_names if '/' in f and '.dist-info' not in f][:10]}"
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
# Verify the expected files are present
|
|
1219
|
+
assert f"{import_name}/__init__.py" in file_names, (
|
|
1220
|
+
f"Wheel should contain {import_name}/__init__.py"
|
|
1221
|
+
)
|
|
1222
|
+
assert f"{import_name}/module.py" in file_names, (
|
|
1223
|
+
f"Wheel should contain {import_name}/module.py"
|
|
1224
|
+
)
|
|
1225
|
+
assert f"{import_name}/utils.py" in file_names, (
|
|
1226
|
+
f"Wheel should contain {import_name}/utils.py"
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
# Verify the .dist-info folder exists
|
|
1230
|
+
dist_info_files = [f for f in file_names if ".dist-info" in f]
|
|
1231
|
+
assert len(dist_info_files) > 0, "Wheel should contain .dist-info files"
|
|
1232
|
+
|
|
1233
|
+
# Verify the temp directory name is NOT in the wheel
|
|
1234
|
+
assert len(temp_dir_files) == 0, (
|
|
1235
|
+
f"Wheel should not contain temp directory files. Found: {temp_dir_files[:5]}"
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
# Verify the original subfolder name is NOT in the wheel (if different from import name)
|
|
1239
|
+
if "data/" in file_names and import_name != "data":
|
|
1240
|
+
# Only check if data/ would be different from the import name
|
|
1241
|
+
data_files = [f for f in file_names if f.startswith("data/") and ".dist-info" not in f]
|
|
1242
|
+
assert len(data_files) == 0, (
|
|
1243
|
+
f"Wheel should not contain 'data/' directory, should use '{import_name}/' instead"
|
|
1244
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/finder.py
RENAMED
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/publisher.py
RENAMED
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/folder_structure/some_globals.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_third_party_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.4.0 → python_package_folder-7.0.0}/tests/test_version_calculator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|