python-package-folder 5.1.1__tar.gz → 5.1.2__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.1.1 → python_package_folder-5.1.2}/PKG-INFO +1 -1
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/pyproject.toml +1 -3
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/subfolder_build.py +153 -166
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_subfolder_build.py +3 -3
- python_package_folder-5.1.1/src/python_package_folder/_hatch_build.py +0 -173
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.copier-answers.yml +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.github/workflows/ci.yml +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.github/workflows/publish.yml +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.gitignore +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/.vscode/settings.json +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/LICENSE +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/MANIFEST.in +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/Makefile +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/README.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/coverage.svg +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/development.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/docs/INSTALLATION.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/docs/PUBLISHING.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/docs/REFERENCE.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/docs/USAGE.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/installation.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/publishing.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/finder.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/manager.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/types.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/version.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/conftest.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_linting.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_publisher.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_utils.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_version_calculator.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_version_manager.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/tests.py +0 -0
- {python_package_folder-5.1.1 → python_package_folder-5.1.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.2
|
|
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.1.
|
|
46
|
+
version = "5.1.2"
|
|
47
47
|
[dependency-groups]
|
|
48
48
|
dev = [
|
|
49
49
|
"pytest>=8.3.5",
|
|
@@ -154,7 +154,5 @@ testpaths = [
|
|
|
154
154
|
"src",
|
|
155
155
|
"tests",
|
|
156
156
|
]
|
|
157
|
-
# Ignore build hooks - they're not test files and require hatchling which isn't in test deps
|
|
158
|
-
ignore = ["_hatch_build.py"]
|
|
159
157
|
norecursedirs = []
|
|
160
158
|
filterwarnings = []
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import re
|
|
11
11
|
import shutil
|
|
12
12
|
import sys
|
|
13
|
+
import tempfile
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import TYPE_CHECKING
|
|
15
16
|
|
|
@@ -75,6 +76,8 @@ class SubfolderBuildConfig:
|
|
|
75
76
|
self.temp_readme: Path | None = None
|
|
76
77
|
self.original_readme_backup: Path | None = None
|
|
77
78
|
self._used_subfolder_pyproject = False
|
|
79
|
+
self._excluded_files: list[tuple[Path, Path]] = [] # List of (original_path, temp_path) tuples
|
|
80
|
+
self._exclude_temp_dir: Path | None = None
|
|
78
81
|
|
|
79
82
|
def _derive_package_name(self) -> str:
|
|
80
83
|
"""Derive package name from source directory name."""
|
|
@@ -205,80 +208,13 @@ class SubfolderBuildConfig:
|
|
|
205
208
|
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
206
209
|
if not has_build_system:
|
|
207
210
|
# Insert build-system at the very beginning of the file
|
|
208
|
-
# Include python-package-folder in requires so the build hook is available
|
|
209
211
|
build_system_lines = [
|
|
210
212
|
"[build-system]",
|
|
211
|
-
'requires = ["hatchling"
|
|
213
|
+
'requires = ["hatchling"]',
|
|
212
214
|
'build-backend = "hatchling.build"',
|
|
213
215
|
"",
|
|
214
216
|
]
|
|
215
217
|
result = build_system_lines + result
|
|
216
|
-
else:
|
|
217
|
-
# Ensure python-package-folder is in requires for build hook availability
|
|
218
|
-
in_build_system = False
|
|
219
|
-
for i, line in enumerate(result):
|
|
220
|
-
if line.strip().startswith("[build-system]"):
|
|
221
|
-
in_build_system = True
|
|
222
|
-
elif in_build_system and line.strip().startswith("requires"):
|
|
223
|
-
# Check if python-package-folder is already in requires
|
|
224
|
-
if "python-package-folder" not in line:
|
|
225
|
-
# Add python-package-folder to requires
|
|
226
|
-
if "]" in line:
|
|
227
|
-
# Single-line requires
|
|
228
|
-
result[i] = line.rstrip().rstrip("]") + ', "python-package-folder"]'
|
|
229
|
-
else:
|
|
230
|
-
# Multi-line requires, find the closing bracket
|
|
231
|
-
for j in range(i + 1, len(result)):
|
|
232
|
-
if "]" in result[j]:
|
|
233
|
-
# Insert before closing
|
|
234
|
-
# Determine indentation from the previous line (last item in list)
|
|
235
|
-
if j > i + 1:
|
|
236
|
-
# Look at the previous line to get indentation
|
|
237
|
-
prev_line = result[j - 1]
|
|
238
|
-
# Extract leading whitespace from previous line
|
|
239
|
-
indent = len(prev_line) - len(prev_line.lstrip())
|
|
240
|
-
indent_str = " " * indent
|
|
241
|
-
else:
|
|
242
|
-
# Fallback: use standard 4-space indent
|
|
243
|
-
indent_str = " "
|
|
244
|
-
result.insert(j, f'{indent_str}"python-package-folder",')
|
|
245
|
-
break
|
|
246
|
-
break
|
|
247
|
-
elif in_build_system and line.strip().startswith("[") and not line.strip().startswith("[build-system"):
|
|
248
|
-
# End of build-system section
|
|
249
|
-
break
|
|
250
|
-
|
|
251
|
-
# Register the build hook to enable exclude patterns
|
|
252
|
-
build_hook_registered = any(
|
|
253
|
-
"[tool.hatch.build.hooks.custom]" in line for line in result
|
|
254
|
-
)
|
|
255
|
-
if not build_hook_registered:
|
|
256
|
-
# Add build hook registration after [tool.hatch.build.targets.wheel] section
|
|
257
|
-
hook_insert_index = len(result)
|
|
258
|
-
for i, line in enumerate(result):
|
|
259
|
-
if line.strip().startswith("[tool.hatch.build.targets.wheel]"):
|
|
260
|
-
# Find the end of this section
|
|
261
|
-
for j in range(i + 1, len(result)):
|
|
262
|
-
if result[j].strip().startswith("[") and not result[j].strip().startswith(
|
|
263
|
-
"[tool.hatch.build.targets"
|
|
264
|
-
):
|
|
265
|
-
hook_insert_index = j
|
|
266
|
-
break
|
|
267
|
-
if hook_insert_index == len(result):
|
|
268
|
-
# Insert after packages line if section continues
|
|
269
|
-
for j in range(i + 1, len(result)):
|
|
270
|
-
if result[j].strip().startswith("["):
|
|
271
|
-
hook_insert_index = j
|
|
272
|
-
break
|
|
273
|
-
break
|
|
274
|
-
|
|
275
|
-
# Insert build hook registration
|
|
276
|
-
hook_lines = [
|
|
277
|
-
"",
|
|
278
|
-
"[tool.hatch.build.hooks.custom]",
|
|
279
|
-
'path = "python_package_folder._hatch_build:CustomBuildHook"',
|
|
280
|
-
]
|
|
281
|
-
result[hook_insert_index:hook_insert_index] = hook_lines
|
|
282
218
|
|
|
283
219
|
# Ensure hatch build section exists if packages path is needed
|
|
284
220
|
if not packages_set and correct_packages_path:
|
|
@@ -390,6 +326,10 @@ class SubfolderBuildConfig:
|
|
|
390
326
|
# Handle README file
|
|
391
327
|
self._handle_readme()
|
|
392
328
|
|
|
329
|
+
# Exclude files matching exclude patterns
|
|
330
|
+
if exclude_patterns:
|
|
331
|
+
self._exclude_files_by_patterns(exclude_patterns)
|
|
332
|
+
|
|
393
333
|
return original_pyproject
|
|
394
334
|
|
|
395
335
|
# No pyproject.toml in subfolder, create one from parent
|
|
@@ -497,6 +437,10 @@ class SubfolderBuildConfig:
|
|
|
497
437
|
# Handle README file
|
|
498
438
|
self._handle_readme()
|
|
499
439
|
|
|
440
|
+
# Exclude files matching exclude patterns
|
|
441
|
+
if exclude_patterns:
|
|
442
|
+
self._exclude_files_by_patterns(exclude_patterns)
|
|
443
|
+
|
|
500
444
|
return original_pyproject
|
|
501
445
|
|
|
502
446
|
def _modify_pyproject_string(
|
|
@@ -637,54 +581,13 @@ class SubfolderBuildConfig:
|
|
|
637
581
|
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
638
582
|
if not has_build_system:
|
|
639
583
|
# Insert build-system at the very beginning of the file
|
|
640
|
-
# Include python-package-folder in requires so the build hook is available
|
|
641
584
|
build_system_lines = [
|
|
642
585
|
"[build-system]",
|
|
643
|
-
'requires = ["hatchling"
|
|
586
|
+
'requires = ["hatchling"]',
|
|
644
587
|
'build-backend = "hatchling.build"',
|
|
645
588
|
"",
|
|
646
589
|
]
|
|
647
590
|
result = build_system_lines + result
|
|
648
|
-
else:
|
|
649
|
-
# Ensure python-package-folder is in requires for build hook availability
|
|
650
|
-
in_build_system = False
|
|
651
|
-
requires_modified = False
|
|
652
|
-
for i, line in enumerate(result):
|
|
653
|
-
if line.strip().startswith("[build-system]"):
|
|
654
|
-
in_build_system = True
|
|
655
|
-
elif in_build_system and line.strip().startswith("requires"):
|
|
656
|
-
# Check if python-package-folder is already in requires
|
|
657
|
-
if "python-package-folder" not in line:
|
|
658
|
-
# Add python-package-folder to requires
|
|
659
|
-
if "]" in line:
|
|
660
|
-
# Single-line requires (may have closing bracket on same line)
|
|
661
|
-
if line.strip().endswith("]"):
|
|
662
|
-
result[i] = line.rstrip().rstrip("]") + ', "python-package-folder"]'
|
|
663
|
-
else:
|
|
664
|
-
# Closing bracket might be on same line but not at end
|
|
665
|
-
result[i] = line.rstrip().rstrip("]") + ', "python-package-folder"]'
|
|
666
|
-
else:
|
|
667
|
-
# Multi-line requires, find the closing bracket
|
|
668
|
-
for j in range(i + 1, len(result)):
|
|
669
|
-
if "]" in result[j]:
|
|
670
|
-
# Insert before closing
|
|
671
|
-
# Determine indentation from the previous line (last item in list)
|
|
672
|
-
if j > i + 1:
|
|
673
|
-
# Look at the previous line to get indentation
|
|
674
|
-
prev_line = result[j - 1]
|
|
675
|
-
# Extract leading whitespace from previous line
|
|
676
|
-
indent = len(prev_line) - len(prev_line.lstrip())
|
|
677
|
-
indent_str = " " * indent
|
|
678
|
-
else:
|
|
679
|
-
# Fallback: use standard 4-space indent
|
|
680
|
-
indent_str = " "
|
|
681
|
-
result.insert(j, f'{indent_str}"python-package-folder",')
|
|
682
|
-
break
|
|
683
|
-
requires_modified = True
|
|
684
|
-
break
|
|
685
|
-
elif in_build_system and line.strip().startswith("[") and not line.strip().startswith("[build-system"):
|
|
686
|
-
# End of build-system section
|
|
687
|
-
break
|
|
688
591
|
|
|
689
592
|
# Ensure packages is always set for subfolder builds
|
|
690
593
|
if not packages_set and package_dirs:
|
|
@@ -695,65 +598,42 @@ class SubfolderBuildConfig:
|
|
|
695
598
|
packages_str = ", ".join(f'"{p}"' for p in package_dirs)
|
|
696
599
|
result.append(f"packages = [{packages_str}]")
|
|
697
600
|
|
|
698
|
-
# Register the build hook to enable exclude patterns
|
|
699
|
-
# Always register the build hook if exclude patterns are present, or if we want to support them
|
|
700
|
-
# Check if build hook is already registered
|
|
701
|
-
build_hook_registered = any(
|
|
702
|
-
"[tool.hatch.build.hooks.custom]" in line for line in result
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
if not build_hook_registered:
|
|
706
|
-
# Add build hook registration after [tool.hatch.build.targets.wheel] section
|
|
707
|
-
hook_insert_index = len(result)
|
|
708
|
-
wheel_section_found = False
|
|
709
|
-
for i, line in enumerate(result):
|
|
710
|
-
if line.strip().startswith("[tool.hatch.build.targets.wheel]"):
|
|
711
|
-
wheel_section_found = True
|
|
712
|
-
# Find the end of this section
|
|
713
|
-
for j in range(i + 1, len(result)):
|
|
714
|
-
if result[j].strip().startswith("[") and not result[j].strip().startswith(
|
|
715
|
-
"[tool.hatch.build.targets"
|
|
716
|
-
):
|
|
717
|
-
hook_insert_index = j
|
|
718
|
-
break
|
|
719
|
-
if hook_insert_index == len(result):
|
|
720
|
-
# Insert after packages line if section continues
|
|
721
|
-
for j in range(i + 1, len(result)):
|
|
722
|
-
if result[j].strip().startswith("["):
|
|
723
|
-
hook_insert_index = j
|
|
724
|
-
break
|
|
725
|
-
break
|
|
726
|
-
|
|
727
|
-
# If wheel section not found, insert before sdist section or at end
|
|
728
|
-
if not wheel_section_found:
|
|
729
|
-
for i, line in enumerate(result):
|
|
730
|
-
if line.strip().startswith("[tool.hatch.build.targets.sdist]"):
|
|
731
|
-
hook_insert_index = i
|
|
732
|
-
break
|
|
733
|
-
|
|
734
|
-
# Insert build hook registration
|
|
735
|
-
hook_lines = [
|
|
736
|
-
"",
|
|
737
|
-
"[tool.hatch.build.hooks.custom]",
|
|
738
|
-
'path = "python_package_folder._hatch_build:CustomBuildHook"',
|
|
739
|
-
]
|
|
740
|
-
result[hook_insert_index:hook_insert_index] = hook_lines
|
|
741
|
-
|
|
742
601
|
# Use only-include for source distributions to ensure only the subfolder is included
|
|
743
602
|
# This prevents including files from the project root
|
|
744
603
|
if package_dirs:
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
604
|
+
# Check if sdist section already exists
|
|
605
|
+
sdist_section_exists = any(
|
|
606
|
+
line.strip().startswith("[tool.hatch.build.targets.sdist]")
|
|
607
|
+
for line in result
|
|
608
|
+
)
|
|
609
|
+
# Check if only-include is already set in the sdist section
|
|
610
|
+
only_include_set = False
|
|
611
|
+
if sdist_section_exists:
|
|
612
|
+
in_sdist_section = False
|
|
613
|
+
for line in result:
|
|
614
|
+
if line.strip().startswith("[tool.hatch.build.targets.sdist]"):
|
|
615
|
+
in_sdist_section = True
|
|
616
|
+
elif line.strip().startswith("[") and in_sdist_section:
|
|
617
|
+
in_sdist_section = False
|
|
618
|
+
elif in_sdist_section and re.match(r"^\s*only-include\s*=", line):
|
|
619
|
+
only_include_set = True
|
|
620
|
+
break
|
|
621
|
+
|
|
622
|
+
if not sdist_section_exists or not only_include_set:
|
|
623
|
+
if not sdist_section_exists:
|
|
624
|
+
result.append("")
|
|
625
|
+
result.append("[tool.hatch.build.targets.sdist]")
|
|
626
|
+
# Include only the subfolder directory and necessary files
|
|
627
|
+
only_include_paths = [package_dirs[0]]
|
|
628
|
+
# Also include pyproject.toml and README if they exist
|
|
629
|
+
only_include_paths.append("pyproject.toml")
|
|
630
|
+
only_include_paths.append("README.md")
|
|
631
|
+
only_include_paths.append("README.rst")
|
|
632
|
+
only_include_paths.append("README.txt")
|
|
633
|
+
only_include_paths.append("README")
|
|
634
|
+
only_include_str = ", ".join(f'"{p}"' for p in only_include_paths)
|
|
635
|
+
if not only_include_set:
|
|
636
|
+
result.append(f"only-include = [{only_include_str}]")
|
|
757
637
|
|
|
758
638
|
# Add dependency group if specified
|
|
759
639
|
if dependency_group:
|
|
@@ -1085,6 +965,110 @@ class SubfolderBuildConfig:
|
|
|
1085
965
|
target_readme.write_text(readme_content, encoding="utf-8")
|
|
1086
966
|
self.temp_readme = target_readme
|
|
1087
967
|
|
|
968
|
+
def _exclude_files_by_patterns(self, exclude_patterns: list[str]) -> None:
|
|
969
|
+
"""
|
|
970
|
+
Temporarily move files matching exclude patterns out of the source directory.
|
|
971
|
+
|
|
972
|
+
Files are moved to a temporary directory and will be restored in restore().
|
|
973
|
+
This ensures excluded files are not included in the build.
|
|
974
|
+
|
|
975
|
+
Args:
|
|
976
|
+
exclude_patterns: List of regex patterns to match against path components
|
|
977
|
+
"""
|
|
978
|
+
if not exclude_patterns:
|
|
979
|
+
return
|
|
980
|
+
|
|
981
|
+
# Compile regex patterns for efficiency
|
|
982
|
+
compiled_patterns = [re.compile(pattern) for pattern in exclude_patterns]
|
|
983
|
+
|
|
984
|
+
def should_exclude(path: Path) -> bool:
|
|
985
|
+
"""Check if a path should be excluded based on patterns."""
|
|
986
|
+
# Check each component of the path
|
|
987
|
+
for part in path.parts:
|
|
988
|
+
# Check if any part matches any pattern
|
|
989
|
+
for pattern in compiled_patterns:
|
|
990
|
+
if pattern.search(part):
|
|
991
|
+
return True
|
|
992
|
+
return False
|
|
993
|
+
|
|
994
|
+
# Create temporary directory for excluded files
|
|
995
|
+
if self._exclude_temp_dir is None:
|
|
996
|
+
self._exclude_temp_dir = Path(tempfile.mkdtemp(prefix="python-package-folder-excluded-"))
|
|
997
|
+
|
|
998
|
+
# Find all files and directories in src_dir that match exclude patterns
|
|
999
|
+
excluded_items: list[Path] = []
|
|
1000
|
+
|
|
1001
|
+
# Walk through src_dir and find matching items
|
|
1002
|
+
# Sort by depth (shallow first) so we can skip children of excluded directories
|
|
1003
|
+
all_items = sorted(self.src_dir.rglob("*"), key=lambda p: len(p.parts))
|
|
1004
|
+
|
|
1005
|
+
for item in all_items:
|
|
1006
|
+
# Skip if already excluded (parent was excluded)
|
|
1007
|
+
if any(
|
|
1008
|
+
excluded.is_dir() and (item == excluded or item.is_relative_to(excluded))
|
|
1009
|
+
for excluded in excluded_items
|
|
1010
|
+
):
|
|
1011
|
+
continue
|
|
1012
|
+
|
|
1013
|
+
# Check if this item should be excluded
|
|
1014
|
+
try:
|
|
1015
|
+
rel_path = item.relative_to(self.src_dir)
|
|
1016
|
+
if should_exclude(rel_path):
|
|
1017
|
+
excluded_items.append(item)
|
|
1018
|
+
except ValueError:
|
|
1019
|
+
# Path is not relative to src_dir, skip
|
|
1020
|
+
continue
|
|
1021
|
+
|
|
1022
|
+
# Move excluded items to temporary directory
|
|
1023
|
+
for item in excluded_items:
|
|
1024
|
+
try:
|
|
1025
|
+
# Calculate relative path from src_dir
|
|
1026
|
+
rel_path = item.relative_to(self.src_dir)
|
|
1027
|
+
# Create corresponding path in temp directory
|
|
1028
|
+
temp_path = self._exclude_temp_dir / rel_path
|
|
1029
|
+
# Create parent directories if needed
|
|
1030
|
+
temp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1031
|
+
|
|
1032
|
+
# Move the item
|
|
1033
|
+
if item.exists():
|
|
1034
|
+
shutil.move(str(item), str(temp_path))
|
|
1035
|
+
self._excluded_files.append((item, temp_path))
|
|
1036
|
+
print(f"Excluded {rel_path} from build", file=sys.stderr)
|
|
1037
|
+
except Exception as e:
|
|
1038
|
+
print(
|
|
1039
|
+
f"Warning: Could not exclude {item}: {e}",
|
|
1040
|
+
file=sys.stderr,
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
def _restore_excluded_files(self) -> None:
|
|
1044
|
+
"""Restore files that were excluded by _exclude_files_by_patterns."""
|
|
1045
|
+
# Restore files in reverse order (to handle nested directories correctly)
|
|
1046
|
+
for original_path, temp_path in reversed(self._excluded_files):
|
|
1047
|
+
try:
|
|
1048
|
+
if temp_path.exists():
|
|
1049
|
+
# Ensure parent directory exists
|
|
1050
|
+
original_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1051
|
+
# Move back
|
|
1052
|
+
shutil.move(str(temp_path), str(original_path))
|
|
1053
|
+
except Exception as e:
|
|
1054
|
+
print(
|
|
1055
|
+
f"Warning: Could not restore excluded file {original_path}: {e}",
|
|
1056
|
+
file=sys.stderr,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
# Clean up temporary directory
|
|
1060
|
+
if self._exclude_temp_dir and self._exclude_temp_dir.exists():
|
|
1061
|
+
try:
|
|
1062
|
+
shutil.rmtree(self._exclude_temp_dir)
|
|
1063
|
+
except Exception as e:
|
|
1064
|
+
print(
|
|
1065
|
+
f"Warning: Could not remove temporary exclude directory {self._exclude_temp_dir}: {e}",
|
|
1066
|
+
file=sys.stderr,
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
self._excluded_files.clear()
|
|
1070
|
+
self._exclude_temp_dir = None
|
|
1071
|
+
|
|
1088
1072
|
def restore(self) -> None:
|
|
1089
1073
|
"""
|
|
1090
1074
|
Restore the original pyproject.toml and remove temporary __init__.py if created.
|
|
@@ -1094,6 +1078,9 @@ class SubfolderBuildConfig:
|
|
|
1094
1078
|
This method removes the temporary pyproject.toml and restores the original from
|
|
1095
1079
|
the backup location, ensuring the original file is never modified.
|
|
1096
1080
|
"""
|
|
1081
|
+
# Restore excluded files first
|
|
1082
|
+
self._restore_excluded_files()
|
|
1083
|
+
|
|
1097
1084
|
# Remove temporary __init__.py if we created it
|
|
1098
1085
|
if self._temp_init_created:
|
|
1099
1086
|
init_file = self.src_dir / "__init__.py"
|
|
@@ -661,7 +661,7 @@ class TestSubfolderBuildTemporaryPyprojectCreation:
|
|
|
661
661
|
|
|
662
662
|
# Verify build-system section is added (required for hatchling)
|
|
663
663
|
assert "[build-system]" in content
|
|
664
|
-
assert 'requires = ["hatchling"
|
|
664
|
+
assert 'requires = ["hatchling"]' in content
|
|
665
665
|
assert 'build-backend = "hatchling.build"' in content
|
|
666
666
|
assert "[tool.hatch.version]" not in content
|
|
667
667
|
assert "[tool.uv-dynamic-versioning]" not in content
|
|
@@ -894,7 +894,7 @@ description = "Subfolder package"
|
|
|
894
894
|
|
|
895
895
|
# Verify build-system section uses hatchling, not setuptools
|
|
896
896
|
assert "[build-system]" in content
|
|
897
|
-
assert 'requires = ["hatchling"
|
|
897
|
+
assert 'requires = ["hatchling"]' in content
|
|
898
898
|
assert 'build-backend = "hatchling.build"' in content
|
|
899
899
|
assert "setuptools" not in content or 'build-backend = "setuptools' not in content
|
|
900
900
|
|
|
@@ -929,7 +929,7 @@ description = "Subfolder package"
|
|
|
929
929
|
|
|
930
930
|
# Verify build-system section is added
|
|
931
931
|
assert "[build-system]" in content
|
|
932
|
-
assert 'requires = ["hatchling"
|
|
932
|
+
assert 'requires = ["hatchling"]' in content
|
|
933
933
|
assert 'build-backend = "hatchling.build"' in content
|
|
934
934
|
|
|
935
935
|
config.restore()
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Hatch build hook to automatically include all files from the scripts directory.
|
|
3
|
-
|
|
4
|
-
This hook ensures all non-Python files in the scripts directory are included
|
|
5
|
-
in the wheel without creating duplicates, and automatically includes any new
|
|
6
|
-
files added to the directory without requiring manual configuration updates.
|
|
7
|
-
|
|
8
|
-
Also filters files based on exclude patterns from pyproject.toml.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import re
|
|
12
|
-
import sys
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
17
|
-
|
|
18
|
-
from .utils import read_exclude_patterns
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class CustomBuildHook(BuildHookInterface):
|
|
22
|
-
"""Build hook to include all files from the scripts directory and filter exclude patterns."""
|
|
23
|
-
|
|
24
|
-
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
25
|
-
"""Initialize the build hook and add scripts directory files."""
|
|
26
|
-
# Debug: Print to stderr so it shows in build output
|
|
27
|
-
print(f"[DEBUG] Build hook called. Root: {self.root}", file=sys.stderr)
|
|
28
|
-
|
|
29
|
-
# Read exclude patterns from pyproject.toml
|
|
30
|
-
pyproject_path = Path(self.root) / "pyproject.toml"
|
|
31
|
-
exclude_patterns = read_exclude_patterns(pyproject_path)
|
|
32
|
-
|
|
33
|
-
if exclude_patterns:
|
|
34
|
-
print(f"[DEBUG] Found {len(exclude_patterns)} exclude pattern(s): {exclude_patterns}", file=sys.stderr)
|
|
35
|
-
# Filter build_data entries based on exclude patterns
|
|
36
|
-
self._filter_build_data(build_data, exclude_patterns)
|
|
37
|
-
|
|
38
|
-
# Try multiple possible locations for the scripts directory
|
|
39
|
-
# 1. Source layout: src/python_package_folder/scripts
|
|
40
|
-
# 2. Sdist layout: python_package_folder/scripts (after extraction)
|
|
41
|
-
# 3. Alternative sdist layout: scripts/ (if extracted differently)
|
|
42
|
-
possible_scripts_dirs = [
|
|
43
|
-
Path(self.root) / "src" / "python_package_folder" / "scripts",
|
|
44
|
-
Path(self.root) / "python_package_folder" / "scripts",
|
|
45
|
-
Path(self.root) / "scripts",
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
scripts_dir = None
|
|
49
|
-
for possible_dir in possible_scripts_dirs:
|
|
50
|
-
if possible_dir.exists() and possible_dir.is_dir():
|
|
51
|
-
scripts_dir = possible_dir
|
|
52
|
-
print(f"[DEBUG] Found scripts dir at: {scripts_dir}", file=sys.stderr)
|
|
53
|
-
break
|
|
54
|
-
|
|
55
|
-
if scripts_dir is None:
|
|
56
|
-
print(f"[DEBUG] Scripts directory not found. Tried: {[str(d) for d in possible_scripts_dirs]}", file=sys.stderr)
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
# If scripts directory exists, include all files from it
|
|
60
|
-
if scripts_dir.exists() and scripts_dir.is_dir():
|
|
61
|
-
# Add all files from scripts directory to force-include
|
|
62
|
-
# This ensures they're included in the wheel at the correct location
|
|
63
|
-
for script_file in scripts_dir.iterdir():
|
|
64
|
-
if script_file.is_file():
|
|
65
|
-
# Calculate relative paths from project root
|
|
66
|
-
try:
|
|
67
|
-
source_path = script_file.relative_to(self.root)
|
|
68
|
-
except ValueError:
|
|
69
|
-
# If relative_to fails, try to construct path manually
|
|
70
|
-
# This can happen with sdist layouts
|
|
71
|
-
if "python_package_folder" in str(script_file):
|
|
72
|
-
# Extract the part after python_package_folder
|
|
73
|
-
parts = script_file.parts
|
|
74
|
-
try:
|
|
75
|
-
idx = parts.index("python_package_folder")
|
|
76
|
-
source_path = Path(*parts[idx:])
|
|
77
|
-
except (ValueError, IndexError):
|
|
78
|
-
# Fallback: use the filename
|
|
79
|
-
source_path = Path("python_package_folder") / "scripts" / script_file.name
|
|
80
|
-
else:
|
|
81
|
-
source_path = Path("python_package_folder") / "scripts" / script_file.name
|
|
82
|
-
|
|
83
|
-
# Target path inside the wheel package (always the same)
|
|
84
|
-
target_path = f"python_package_folder/scripts/{script_file.name}"
|
|
85
|
-
|
|
86
|
-
print(f"[DEBUG] Adding {source_path} -> {target_path}", file=sys.stderr)
|
|
87
|
-
|
|
88
|
-
# Add to force-include (hatchling will handle this)
|
|
89
|
-
# We need to add it to build_data['force_include']
|
|
90
|
-
if "force_include" not in build_data:
|
|
91
|
-
build_data["force_include"] = {}
|
|
92
|
-
build_data["force_include"][str(source_path)] = target_path
|
|
93
|
-
|
|
94
|
-
print(f"[DEBUG] force_include now has {len(build_data.get('force_include', {}))} entries", file=sys.stderr)
|
|
95
|
-
|
|
96
|
-
def _filter_build_data(self, build_data: dict[str, Any], exclude_patterns: list[str]) -> None:
|
|
97
|
-
"""
|
|
98
|
-
Filter build_data entries based on exclude patterns.
|
|
99
|
-
|
|
100
|
-
Removes files/directories that match any of the exclude patterns from
|
|
101
|
-
build_data. Patterns are matched against any path component using regex.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
build_data: Hatchling build data dictionary
|
|
105
|
-
exclude_patterns: List of regex patterns to match against paths
|
|
106
|
-
"""
|
|
107
|
-
# Compile regex patterns for efficiency
|
|
108
|
-
compiled_patterns = [re.compile(pattern) for pattern in exclude_patterns]
|
|
109
|
-
|
|
110
|
-
def should_exclude(path_str: str) -> bool:
|
|
111
|
-
"""Check if a path should be excluded based on patterns."""
|
|
112
|
-
# Check each component of the path
|
|
113
|
-
path = Path(path_str)
|
|
114
|
-
for part in path.parts:
|
|
115
|
-
# Check if any part matches any pattern
|
|
116
|
-
for pattern in compiled_patterns:
|
|
117
|
-
if pattern.search(part):
|
|
118
|
-
return True
|
|
119
|
-
return False
|
|
120
|
-
|
|
121
|
-
# Filter force_include entries
|
|
122
|
-
if "force_include" in build_data and isinstance(build_data["force_include"], dict):
|
|
123
|
-
original_count = len(build_data["force_include"])
|
|
124
|
-
filtered = {
|
|
125
|
-
source: target
|
|
126
|
-
for source, target in build_data["force_include"].items()
|
|
127
|
-
if not should_exclude(source) and not should_exclude(target)
|
|
128
|
-
}
|
|
129
|
-
build_data["force_include"] = filtered
|
|
130
|
-
excluded_count = original_count - len(filtered)
|
|
131
|
-
if excluded_count > 0:
|
|
132
|
-
print(
|
|
133
|
-
f"[DEBUG] Excluded {excluded_count} file(s) from force_include based on exclude patterns",
|
|
134
|
-
file=sys.stderr,
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
# Filter other file collections that might exist
|
|
138
|
-
# Hatchling may store files in different keys depending on the build target
|
|
139
|
-
for key in ["shared_data", "artifacts"]:
|
|
140
|
-
if key in build_data and isinstance(build_data[key], dict):
|
|
141
|
-
original_count = len(build_data[key])
|
|
142
|
-
filtered = {
|
|
143
|
-
source: target
|
|
144
|
-
for source, target in build_data[key].items()
|
|
145
|
-
if not should_exclude(source) and not should_exclude(target)
|
|
146
|
-
}
|
|
147
|
-
build_data[key] = filtered
|
|
148
|
-
excluded_count = original_count - len(filtered)
|
|
149
|
-
if excluded_count > 0:
|
|
150
|
-
print(
|
|
151
|
-
f"[DEBUG] Excluded {excluded_count} file(s) from {key} based on exclude patterns",
|
|
152
|
-
file=sys.stderr,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
# Filter files list if it exists (for sdist)
|
|
156
|
-
if "files" in build_data and isinstance(build_data["files"], list):
|
|
157
|
-
original_count = len(build_data["files"])
|
|
158
|
-
filtered = [
|
|
159
|
-
file_entry
|
|
160
|
-
for file_entry in build_data["files"]
|
|
161
|
-
if not should_exclude(str(file_entry))
|
|
162
|
-
]
|
|
163
|
-
build_data["files"] = filtered
|
|
164
|
-
excluded_count = original_count - len(filtered)
|
|
165
|
-
if excluded_count > 0:
|
|
166
|
-
print(
|
|
167
|
-
f"[DEBUG] Excluded {excluded_count} file(s) from files list based on exclude patterns",
|
|
168
|
-
file=sys.stderr,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
# Export the hook class (hatchling might need this)
|
|
173
|
-
__all__ = ["CustomBuildHook"]
|
|
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.1.1 → python_package_folder-5.1.2}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/analyzer.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/finder.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/manager.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/publisher.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/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
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_build_with_external_deps.py
RENAMED
|
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.1.1 → python_package_folder-5.1.2}/tests/test_third_party_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-5.1.1 → python_package_folder-5.1.2}/tests/test_version_calculator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|