python-package-folder 8.0.0__tar.gz → 8.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/PKG-INFO +1 -1
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/coverage.svg +2 -2
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/pyproject.toml +1 -1
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/finder.py +46 -29
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/manager.py +19 -3
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/subfolder_build.py +209 -1
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_subfolder_build.py +167 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.copier-answers.yml +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.cursor/plans/optional_version_+_semantic-release_efed88a6.plan.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.cursor/plans/replace_node.js_semantic-release_with_custom_python_implementation_64e05e1a.plan.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.cursor/rules/general.mdc +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.cursor/rules/python.mdc +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.github/workflows/ci.yml +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.github/workflows/publish.yml +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.gitignore +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/.vscode/settings.json +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/LICENSE +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/MANIFEST.in +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/Makefile +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/README.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/development.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/docs/DEVELOPMENT.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/docs/INSTALLATION.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/docs/PUBLISHING.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/docs/REFERENCE.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/docs/USAGE.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/docs/VERSION_RESOLUTION.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/installation.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/publishing.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/__init__.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/__main__.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/analyzer.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/publisher.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/py.typed +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/python_package_folder.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/types.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/utils.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/version.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/version_calculator.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/conftest.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/some_globals.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/subfolder_to_build/README.md +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/subfolder_to_build/__init__.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/subfolder_to_build/some_function.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/subfolder_to_build/some_globals.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/utility_folder/_SS/some_superseded_file.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/folder_structure/utility_folder/some_utility.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_build_with_external_deps.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_exclude_patterns.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_linting.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_preserve_directory_structure.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_publisher.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_shared_subdirectory_imports.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_spreadsheet_creation_imports.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_third_party_dependencies.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_utils.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_version_calculator.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_version_manager.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/tests.py +0 -0
- {python_package_folder-8.0.0 → python_package_folder-8.2.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
|
|
5
5
|
Project-URL: Repository, https://github.com/alelom/python-package-folder
|
|
6
6
|
Author-email: Alessio Lombardi <work@alelom.com>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
15
15
|
<text x="31.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
|
|
16
16
|
<text x="31.5" y="14">coverage</text>
|
|
17
|
-
<text x="81" y="15" fill="#010101" fill-opacity=".3">
|
|
18
|
-
<text x="81" y="14">
|
|
17
|
+
<text x="81" y="15" fill="#010101" fill-opacity=".3">67%</text>
|
|
18
|
+
<text x="81" y="14">67%</text>
|
|
19
19
|
</g>
|
|
20
20
|
</svg>
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/finder.py
RENAMED
|
@@ -29,18 +29,26 @@ class ExternalDependencyFinder:
|
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
32
|
-
self,
|
|
32
|
+
self,
|
|
33
|
+
project_root: Path,
|
|
34
|
+
src_dir: Path,
|
|
35
|
+
exclude_patterns: list[str] | None = None,
|
|
36
|
+
original_src_dir: Path | None = None,
|
|
33
37
|
) -> None:
|
|
34
38
|
"""
|
|
35
39
|
Initialize the dependency finder.
|
|
36
40
|
|
|
37
41
|
Args:
|
|
38
42
|
project_root: Root directory of the project
|
|
39
|
-
src_dir: Source directory to analyze
|
|
43
|
+
src_dir: Source directory to analyze (may be temp directory for subfolder builds)
|
|
40
44
|
exclude_patterns: Additional patterns to exclude (default: common sandbox patterns)
|
|
45
|
+
original_src_dir: Original source directory before any changes (e.g., before temp directory creation).
|
|
46
|
+
Used for relative path checks. If not provided, uses src_dir.
|
|
41
47
|
"""
|
|
42
48
|
self.project_root = project_root.resolve()
|
|
43
49
|
self.src_dir = src_dir.resolve()
|
|
50
|
+
# Store original src_dir for relative path checks (important for subfolder builds)
|
|
51
|
+
self.original_src_dir = (original_src_dir or src_dir).resolve()
|
|
44
52
|
self.analyzer = ImportAnalyzer(project_root)
|
|
45
53
|
# Patterns for directories/files to exclude (sandbox, skip, etc.)
|
|
46
54
|
default_patterns = [
|
|
@@ -90,34 +98,43 @@ class ExternalDependencyFinder:
|
|
|
90
98
|
if source_path.is_file():
|
|
91
99
|
parent_dir = source_path.parent
|
|
92
100
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
files_are_imported = True # Always true when processing an import
|
|
99
|
-
|
|
100
|
-
# Only copy immediate parent directory, not grandparent directories
|
|
101
|
-
# This prevents copying entire trees like models/Information_extraction
|
|
102
|
-
# when we only need models/Information_extraction/_shared_ie
|
|
103
|
-
should_copy_dir = (
|
|
104
|
-
not self._should_exclude_path(parent_dir)
|
|
105
|
-
and (
|
|
106
|
-
parent_is_package or files_are_imported
|
|
107
|
-
) # Package OR files imported
|
|
108
|
-
and not parent_dir.is_relative_to(self.src_dir)
|
|
109
|
-
and not self.src_dir.is_relative_to(parent_dir)
|
|
110
|
-
and parent_dir != self.project_root
|
|
111
|
-
and parent_dir != self.project_root.parent
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
if should_copy_dir:
|
|
115
|
-
# Copy the directory instead of just the file
|
|
116
|
-
track_path = parent_dir
|
|
117
|
-
source_path = parent_dir
|
|
118
|
-
else:
|
|
119
|
-
# Copy just the file
|
|
101
|
+
# Never copy the src/ directory itself - only copy individual files at its root
|
|
102
|
+
# This prevents copying the entire src/ directory (with all subdirectories)
|
|
103
|
+
# when only a file like _globals.py is needed
|
|
104
|
+
if parent_dir == self.project_root / "src":
|
|
105
|
+
# For files at root of src/, only copy the file, not the directory
|
|
120
106
|
track_path = source_path
|
|
107
|
+
else:
|
|
108
|
+
# Only copy parent directory if:
|
|
109
|
+
# 1. It's a package (has __init__.py), OR
|
|
110
|
+
# 2. Files from it are actually imported (which is the case here)
|
|
111
|
+
# But only copy the immediate parent, not entire directory trees
|
|
112
|
+
parent_is_package = (parent_dir / "__init__.py").exists()
|
|
113
|
+
files_are_imported = True # Always true when processing an import
|
|
114
|
+
|
|
115
|
+
# Only copy immediate parent directory, not grandparent directories
|
|
116
|
+
# This prevents copying entire trees like models/Information_extraction
|
|
117
|
+
# when we only need models/Information_extraction/_shared_ie
|
|
118
|
+
# Use original_src_dir for relative path checks to correctly handle
|
|
119
|
+
# subfolder builds where src_dir may point to temp directory
|
|
120
|
+
should_copy_dir = (
|
|
121
|
+
not self._should_exclude_path(parent_dir)
|
|
122
|
+
and (
|
|
123
|
+
parent_is_package or files_are_imported
|
|
124
|
+
) # Package OR files imported
|
|
125
|
+
and not parent_dir.is_relative_to(self.src_dir)
|
|
126
|
+
and not self.original_src_dir.is_relative_to(parent_dir)
|
|
127
|
+
and parent_dir != self.project_root
|
|
128
|
+
and parent_dir != self.project_root.parent
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if should_copy_dir:
|
|
132
|
+
# Copy the directory instead of just the file
|
|
133
|
+
track_path = parent_dir
|
|
134
|
+
source_path = parent_dir
|
|
135
|
+
else:
|
|
136
|
+
# Copy just the file
|
|
137
|
+
track_path = source_path
|
|
121
138
|
elif source_path.is_dir():
|
|
122
139
|
# Don't copy directories that contain src_dir
|
|
123
140
|
if self.src_dir.is_relative_to(source_path):
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/manager.py
RENAMED
|
@@ -71,6 +71,8 @@ class BuildManager:
|
|
|
71
71
|
src_dir = self.project_root / "src"
|
|
72
72
|
|
|
73
73
|
self.src_dir = Path(src_dir).resolve()
|
|
74
|
+
# Store original src_dir before any changes (e.g., when temp directory is created)
|
|
75
|
+
self.original_src_dir = self.src_dir
|
|
74
76
|
|
|
75
77
|
# Validate source directory
|
|
76
78
|
if not self.src_dir.exists():
|
|
@@ -280,10 +282,12 @@ class BuildManager:
|
|
|
280
282
|
# Update src_dir to point to temp package directory
|
|
281
283
|
self.src_dir = self.subfolder_config._temp_package_dir
|
|
282
284
|
# Recreate finder with updated src_dir so it calculates target paths correctly
|
|
285
|
+
# Pass original_src_dir for relative path checks to prevent copying entire src/ directory
|
|
283
286
|
self.finder = ExternalDependencyFinder(
|
|
284
287
|
self.project_root,
|
|
285
288
|
self.src_dir,
|
|
286
289
|
exclude_patterns=self.exclude_patterns,
|
|
290
|
+
original_src_dir=self.original_src_dir,
|
|
287
291
|
)
|
|
288
292
|
print(
|
|
289
293
|
f"Using temporary package directory for build: {self.src_dir}"
|
|
@@ -1153,12 +1157,24 @@ class BuildManager:
|
|
|
1153
1157
|
f"Temporary package directory does not exist: {temp_dir}. "
|
|
1154
1158
|
"This should have been created during prepare_build()."
|
|
1155
1159
|
)
|
|
1160
|
+
# Debug: List all files in temp directory
|
|
1161
|
+
all_files = list(temp_dir.rglob("*"))
|
|
1162
|
+
all_files = [f for f in all_files if f.is_file()]
|
|
1163
|
+
print(
|
|
1164
|
+
f"DEBUG: Temp package directory {temp_dir} contains {len(all_files)} files: "
|
|
1165
|
+
f"{[str(f.relative_to(temp_dir)) for f in all_files[:10]]}",
|
|
1166
|
+
file=sys.stderr,
|
|
1167
|
+
)
|
|
1156
1168
|
# Verify it contains Python files
|
|
1157
1169
|
py_files = list(temp_dir.glob("*.py"))
|
|
1158
1170
|
if not py_files:
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1171
|
+
# Also check recursively
|
|
1172
|
+
py_files = list(temp_dir.rglob("*.py"))
|
|
1173
|
+
if not py_files:
|
|
1174
|
+
raise RuntimeError(
|
|
1175
|
+
f"Temporary package directory exists but contains no Python files: {temp_dir}. "
|
|
1176
|
+
f"Total files found: {len(all_files)}"
|
|
1177
|
+
)
|
|
1162
1178
|
# Verify __init__.py exists
|
|
1163
1179
|
if not (temp_dir / "__init__.py").exists():
|
|
1164
1180
|
raise RuntimeError(
|
|
@@ -134,6 +134,7 @@ class SubfolderBuildConfig:
|
|
|
134
134
|
This way, hatchling will install it with the correct name without needing force-include.
|
|
135
135
|
"""
|
|
136
136
|
if not self.package_name:
|
|
137
|
+
print("DEBUG: No package_name provided, skipping temp package directory creation", file=sys.stderr)
|
|
137
138
|
return
|
|
138
139
|
|
|
139
140
|
# Convert package name (with hyphens) to import name (with underscores)
|
|
@@ -144,13 +145,21 @@ class SubfolderBuildConfig:
|
|
|
144
145
|
# This way, hatchling will install it with the correct name
|
|
145
146
|
import_name_dir = self.project_root / import_name
|
|
146
147
|
|
|
148
|
+
print(
|
|
149
|
+
f"DEBUG: Creating temporary package directory: {import_name_dir} "
|
|
150
|
+
f"(from src_dir: {self.src_dir}, import name: {import_name})",
|
|
151
|
+
file=sys.stderr,
|
|
152
|
+
)
|
|
153
|
+
|
|
147
154
|
# Check if the directory already exists and is the correct one
|
|
148
155
|
if import_name_dir.exists() and import_name_dir == self._temp_package_dir:
|
|
149
156
|
# Directory already exists and is the correct one, no need to recreate
|
|
157
|
+
print(f"DEBUG: Temporary package directory already exists: {import_name_dir}", file=sys.stderr)
|
|
150
158
|
return
|
|
151
159
|
|
|
152
160
|
# Remove if it already exists (from a previous build)
|
|
153
161
|
if import_name_dir.exists():
|
|
162
|
+
print(f"DEBUG: Removing existing temporary package directory: {import_name_dir}", file=sys.stderr)
|
|
154
163
|
shutil.rmtree(import_name_dir)
|
|
155
164
|
|
|
156
165
|
# Copy the entire source directory contents directly to the import name directory
|
|
@@ -171,9 +180,38 @@ class SubfolderBuildConfig:
|
|
|
171
180
|
self._temp_package_dir = None
|
|
172
181
|
return
|
|
173
182
|
|
|
183
|
+
# Get exclude patterns from parent pyproject.toml
|
|
184
|
+
exclude_patterns = []
|
|
185
|
+
original_pyproject = self.project_root / "pyproject.toml"
|
|
186
|
+
if original_pyproject.exists():
|
|
187
|
+
exclude_patterns = read_exclude_patterns(original_pyproject)
|
|
188
|
+
print(
|
|
189
|
+
f"DEBUG: Using exclude patterns for temp directory copy: {exclude_patterns}",
|
|
190
|
+
file=sys.stderr,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Check if src_dir has any files before copying
|
|
194
|
+
src_files = list(self.src_dir.rglob("*"))
|
|
195
|
+
src_files = [f for f in src_files if f.is_file()]
|
|
196
|
+
print(
|
|
197
|
+
f"DEBUG: Source directory {self.src_dir} contains {len(src_files)} files before copy",
|
|
198
|
+
file=sys.stderr,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Use a copy method that respects exclude patterns and handles missing directories
|
|
174
202
|
try:
|
|
175
|
-
|
|
203
|
+
print(f"DEBUG: Starting copy from {self.src_dir} to {import_name_dir}", file=sys.stderr)
|
|
204
|
+
self._copytree_excluding_patterns(self.src_dir, import_name_dir, exclude_patterns)
|
|
176
205
|
self._temp_package_dir = import_name_dir
|
|
206
|
+
|
|
207
|
+
# Verify files were copied
|
|
208
|
+
copied_files = list(import_name_dir.rglob("*"))
|
|
209
|
+
copied_files = [f for f in copied_files if f.is_file()]
|
|
210
|
+
print(
|
|
211
|
+
f"DEBUG: After copy, temp directory {import_name_dir} contains {len(copied_files)} files",
|
|
212
|
+
file=sys.stderr,
|
|
213
|
+
)
|
|
214
|
+
|
|
177
215
|
print(
|
|
178
216
|
f"Created temporary package directory: {import_name_dir} "
|
|
179
217
|
f"(import name: {import_name})"
|
|
@@ -183,8 +221,141 @@ class SubfolderBuildConfig:
|
|
|
183
221
|
f"Warning: Could not create temporary package directory: {e}",
|
|
184
222
|
file=sys.stderr,
|
|
185
223
|
)
|
|
224
|
+
import traceback
|
|
225
|
+
print(f"DEBUG: Traceback: {traceback.format_exc()}", file=sys.stderr)
|
|
186
226
|
# Fall back to using src_dir directly
|
|
187
227
|
self._temp_package_dir = None
|
|
228
|
+
|
|
229
|
+
def _copytree_excluding_patterns(self, src: Path, dst: Path, exclude_patterns: list[str]) -> None:
|
|
230
|
+
"""
|
|
231
|
+
Copy a directory tree, excluding certain patterns and handling missing directories gracefully.
|
|
232
|
+
|
|
233
|
+
This is similar to BuildManager._copytree_excluding but works without needing
|
|
234
|
+
the BuildManager instance. It respects exclude patterns and skips missing directories
|
|
235
|
+
(e.g., broken symlinks or already-excluded directories).
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
src: Source directory
|
|
239
|
+
dst: Destination directory
|
|
240
|
+
exclude_patterns: List of patterns to exclude (e.g., ['_SS', '__SS', '.*_test.*'])
|
|
241
|
+
"""
|
|
242
|
+
default_patterns = [
|
|
243
|
+
"_SS",
|
|
244
|
+
"__SS",
|
|
245
|
+
"_sandbox",
|
|
246
|
+
"__sandbox",
|
|
247
|
+
"_skip",
|
|
248
|
+
"__skip",
|
|
249
|
+
"_test",
|
|
250
|
+
"__test__",
|
|
251
|
+
]
|
|
252
|
+
all_exclude_patterns = default_patterns + exclude_patterns
|
|
253
|
+
|
|
254
|
+
def should_exclude(path: Path) -> bool:
|
|
255
|
+
"""Check if a path should be excluded."""
|
|
256
|
+
import re
|
|
257
|
+
# Only check parts of the path relative to src_dir, not the entire absolute path
|
|
258
|
+
# This prevents matching test directory names or other parts outside the source
|
|
259
|
+
try:
|
|
260
|
+
rel_path = path.relative_to(src)
|
|
261
|
+
# Check each component of the relative path
|
|
262
|
+
for part in rel_path.parts:
|
|
263
|
+
# Check if any part matches an exclusion pattern
|
|
264
|
+
for pattern in all_exclude_patterns:
|
|
265
|
+
# Determine if pattern is a regex (contains regex special characters)
|
|
266
|
+
is_regex = any(c in pattern for c in ['.', '*', '+', '?', '^', '$', '[', ']', '(', ')', '{', '}', '|', '\\'])
|
|
267
|
+
|
|
268
|
+
if is_regex:
|
|
269
|
+
# Use regex matching for patterns like '.*_test.*'
|
|
270
|
+
try:
|
|
271
|
+
if re.search(pattern, part):
|
|
272
|
+
print(f"DEBUG: Excluding {path} (part '{part}' matches regex pattern '{pattern}')", file=sys.stderr)
|
|
273
|
+
return True
|
|
274
|
+
except re.error:
|
|
275
|
+
# Invalid regex, fall back to simple string matching
|
|
276
|
+
if part == pattern or part.startswith(pattern):
|
|
277
|
+
print(f"DEBUG: Excluding {path} (part '{part}' matches pattern '{pattern}')", file=sys.stderr)
|
|
278
|
+
return True
|
|
279
|
+
else:
|
|
280
|
+
# Simple string matching for patterns like '_SS'
|
|
281
|
+
if part == pattern or part.startswith(pattern):
|
|
282
|
+
print(f"DEBUG: Excluding {path} (part '{part}' matches pattern '{pattern}')", file=sys.stderr)
|
|
283
|
+
return True
|
|
284
|
+
except ValueError:
|
|
285
|
+
# Path is not relative to src, check the name only
|
|
286
|
+
for pattern in all_exclude_patterns:
|
|
287
|
+
is_regex = any(c in pattern for c in ['.', '*', '+', '?', '^', '$', '[', ']', '(', ')', '{', '}', '|', '\\'])
|
|
288
|
+
if is_regex:
|
|
289
|
+
try:
|
|
290
|
+
if re.search(pattern, path.name):
|
|
291
|
+
return True
|
|
292
|
+
except re.error:
|
|
293
|
+
if path.name == pattern or path.name.startswith(pattern):
|
|
294
|
+
return True
|
|
295
|
+
else:
|
|
296
|
+
if path.name == pattern or path.name.startswith(pattern):
|
|
297
|
+
return True
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
# Create destination directory
|
|
301
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
|
|
303
|
+
# Copy files and subdirectories, excluding patterns
|
|
304
|
+
copied_count = 0
|
|
305
|
+
excluded_count = 0
|
|
306
|
+
skipped_count = 0
|
|
307
|
+
try:
|
|
308
|
+
items = list(src.iterdir())
|
|
309
|
+
print(f"DEBUG: Copying from {src} to {dst}, found {len(items)} items", file=sys.stderr)
|
|
310
|
+
for item in items:
|
|
311
|
+
if should_exclude(item):
|
|
312
|
+
print(f"DEBUG: Excluding {item} from temp package directory copy", file=sys.stderr)
|
|
313
|
+
excluded_count += 1
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
src_item = src / item.name
|
|
317
|
+
dst_item = dst / item.name
|
|
318
|
+
|
|
319
|
+
# Skip if source doesn't exist (broken symlink, already deleted, etc.)
|
|
320
|
+
if not src_item.exists():
|
|
321
|
+
print(
|
|
322
|
+
f"DEBUG: Skipping non-existent item: {src_item}",
|
|
323
|
+
file=sys.stderr,
|
|
324
|
+
)
|
|
325
|
+
skipped_count += 1
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
if src_item.is_file():
|
|
329
|
+
try:
|
|
330
|
+
shutil.copy2(src_item, dst_item)
|
|
331
|
+
copied_count += 1
|
|
332
|
+
print(f"DEBUG: Copied file {src_item} -> {dst_item}", file=sys.stderr)
|
|
333
|
+
except (OSError, IOError) as e:
|
|
334
|
+
print(
|
|
335
|
+
f"DEBUG: Could not copy file {src_item}: {e}, skipping",
|
|
336
|
+
file=sys.stderr,
|
|
337
|
+
)
|
|
338
|
+
skipped_count += 1
|
|
339
|
+
continue
|
|
340
|
+
elif src_item.is_dir():
|
|
341
|
+
try:
|
|
342
|
+
self._copytree_excluding_patterns(src_item, dst_item, exclude_patterns)
|
|
343
|
+
copied_count += 1
|
|
344
|
+
print(f"DEBUG: Copied directory {src_item} -> {dst_item}", file=sys.stderr)
|
|
345
|
+
except (OSError, IOError) as e:
|
|
346
|
+
print(
|
|
347
|
+
f"DEBUG: Could not copy directory {src_item}: {e}, skipping",
|
|
348
|
+
file=sys.stderr,
|
|
349
|
+
)
|
|
350
|
+
skipped_count += 1
|
|
351
|
+
continue
|
|
352
|
+
print(
|
|
353
|
+
f"DEBUG: Copy summary: {copied_count} items copied, {excluded_count} excluded, {skipped_count} skipped",
|
|
354
|
+
file=sys.stderr,
|
|
355
|
+
)
|
|
356
|
+
except (OSError, IOError) as e:
|
|
357
|
+
# If we can't even iterate the source directory, that's a problem
|
|
358
|
+
raise RuntimeError(f"Cannot iterate source directory {src}: {e}") from e
|
|
188
359
|
|
|
189
360
|
def _get_package_structure(self) -> tuple[str, list[str]]:
|
|
190
361
|
"""
|
|
@@ -198,6 +369,13 @@ class SubfolderBuildConfig:
|
|
|
198
369
|
# Use temporary package directory if it exists, otherwise use src_dir
|
|
199
370
|
package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
|
|
200
371
|
|
|
372
|
+
print(
|
|
373
|
+
f"DEBUG: _get_package_structure: temp_package_dir={self._temp_package_dir}, "
|
|
374
|
+
f"exists={self._temp_package_dir.exists() if self._temp_package_dir else False}, "
|
|
375
|
+
f"using package_dir={package_dir}",
|
|
376
|
+
file=sys.stderr,
|
|
377
|
+
)
|
|
378
|
+
|
|
201
379
|
# Check if package_dir itself is a package (has __init__.py)
|
|
202
380
|
has_init = (package_dir / "__init__.py").exists()
|
|
203
381
|
|
|
@@ -211,6 +389,16 @@ class SubfolderBuildConfig:
|
|
|
211
389
|
packages_path = str(rel_path).replace("\\", "/")
|
|
212
390
|
except ValueError:
|
|
213
391
|
packages_path = None
|
|
392
|
+
print(
|
|
393
|
+
f"DEBUG: Could not calculate relative path from {self.project_root} to {package_dir}",
|
|
394
|
+
file=sys.stderr,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
print(
|
|
398
|
+
f"DEBUG: _get_package_structure returning: packages_path={packages_path}, "
|
|
399
|
+
f"has_init={has_init}, has_py_files={has_py_files}",
|
|
400
|
+
file=sys.stderr,
|
|
401
|
+
)
|
|
214
402
|
|
|
215
403
|
# If package_dir has Python files but no __init__.py, we need to make it a package
|
|
216
404
|
# or include it as a module directory
|
|
@@ -518,8 +706,28 @@ class SubfolderBuildConfig:
|
|
|
518
706
|
# This will copy the __init__.py we just created (if any)
|
|
519
707
|
self._create_temp_package_directory()
|
|
520
708
|
|
|
709
|
+
# Log the result of temp directory creation
|
|
710
|
+
if self._temp_package_dir and self._temp_package_dir.exists():
|
|
711
|
+
py_files = list(self._temp_package_dir.glob("*.py"))
|
|
712
|
+
print(
|
|
713
|
+
f"DEBUG: Temp package directory created successfully: {self._temp_package_dir}, "
|
|
714
|
+
f"contains {len(py_files)} Python files",
|
|
715
|
+
file=sys.stderr,
|
|
716
|
+
)
|
|
717
|
+
else:
|
|
718
|
+
print(
|
|
719
|
+
f"WARNING: Temp package directory was NOT created. "
|
|
720
|
+
f"Will fall back to using src_dir: {self.src_dir}",
|
|
721
|
+
file=sys.stderr,
|
|
722
|
+
)
|
|
723
|
+
|
|
521
724
|
# Determine which directory to use (temp package dir or src_dir)
|
|
522
725
|
package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
|
|
726
|
+
print(
|
|
727
|
+
f"DEBUG: Using package_dir for build: {package_dir} "
|
|
728
|
+
f"(temp_dir={self._temp_package_dir}, src_dir={self.src_dir})",
|
|
729
|
+
file=sys.stderr,
|
|
730
|
+
)
|
|
523
731
|
|
|
524
732
|
# Read the original pyproject.toml
|
|
525
733
|
original_pyproject = self.project_root / "pyproject.toml"
|
|
@@ -1130,6 +1130,173 @@ class TestTemporaryPackageDirectory:
|
|
|
1130
1130
|
|
|
1131
1131
|
config.restore()
|
|
1132
1132
|
|
|
1133
|
+
def test_temp_package_directory_respects_exclude_patterns_without_matching_test_dirs(
|
|
1134
|
+
self, test_project_with_pyproject: Path
|
|
1135
|
+
) -> None:
|
|
1136
|
+
"""
|
|
1137
|
+
Test that exclude patterns don't incorrectly match pytest temp directory names.
|
|
1138
|
+
|
|
1139
|
+
This test ensures that exclude patterns like '.*test_.*' only match files/directories
|
|
1140
|
+
within the source directory, not the pytest temp directory name (e.g.,
|
|
1141
|
+
'test_real_world_ml_drawing_assistant_data_scenario').
|
|
1142
|
+
|
|
1143
|
+
This is a regression test for the bug where all files were excluded because the
|
|
1144
|
+
exclude pattern matching checked the entire absolute path instead of just the
|
|
1145
|
+
relative path within src_dir.
|
|
1146
|
+
"""
|
|
1147
|
+
project_root = test_project_with_pyproject
|
|
1148
|
+
subfolder = project_root / "subfolder"
|
|
1149
|
+
|
|
1150
|
+
# Create files that should NOT be excluded
|
|
1151
|
+
(subfolder / "__init__.py").write_text("# Package init")
|
|
1152
|
+
(subfolder / "module.py").write_text("def func(): pass")
|
|
1153
|
+
(subfolder / "utils.py").write_text("def util(): pass")
|
|
1154
|
+
|
|
1155
|
+
# Create a file that SHOULD be excluded (matches pattern)
|
|
1156
|
+
(subfolder / "test_helper.py").write_text("def test(): pass")
|
|
1157
|
+
(subfolder / "_SS").mkdir()
|
|
1158
|
+
(subfolder / "_SS" / "excluded.py").write_text("# Should be excluded")
|
|
1159
|
+
|
|
1160
|
+
# Update pyproject.toml with exclude patterns that could match test directory names
|
|
1161
|
+
pyproject_path = project_root / "pyproject.toml"
|
|
1162
|
+
pyproject_content = pyproject_path.read_text()
|
|
1163
|
+
# Add exclude patterns including ones that could match pytest temp dirs
|
|
1164
|
+
if "[tool.python-package-folder]" not in pyproject_content:
|
|
1165
|
+
pyproject_content += "\n[tool.python-package-folder]\n"
|
|
1166
|
+
pyproject_content += 'exclude-patterns = ["_SS", "__SS", ".*_test.*", ".*test_.*", "sandbox"]\n'
|
|
1167
|
+
pyproject_path.write_text(pyproject_content)
|
|
1168
|
+
|
|
1169
|
+
config = SubfolderBuildConfig(
|
|
1170
|
+
project_root=project_root,
|
|
1171
|
+
src_dir=subfolder,
|
|
1172
|
+
version="1.0.0",
|
|
1173
|
+
package_name="my-package",
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
# Create temp pyproject (which creates temp package directory with exclude patterns)
|
|
1177
|
+
config.create_temp_pyproject()
|
|
1178
|
+
|
|
1179
|
+
temp_package_dir = config._temp_package_dir
|
|
1180
|
+
assert temp_package_dir is not None
|
|
1181
|
+
assert temp_package_dir.exists()
|
|
1182
|
+
|
|
1183
|
+
# Files that should NOT be excluded should be copied
|
|
1184
|
+
assert (temp_package_dir / "__init__.py").exists(), (
|
|
1185
|
+
"__init__.py should be copied (doesn't match exclude patterns)"
|
|
1186
|
+
)
|
|
1187
|
+
assert (temp_package_dir / "module.py").exists(), (
|
|
1188
|
+
"module.py should be copied (doesn't match exclude patterns)"
|
|
1189
|
+
)
|
|
1190
|
+
assert (temp_package_dir / "utils.py").exists(), (
|
|
1191
|
+
"utils.py should be copied (doesn't match exclude patterns)"
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
# Files that SHOULD be excluded should NOT be copied
|
|
1195
|
+
assert not (temp_package_dir / "test_helper.py").exists(), (
|
|
1196
|
+
"test_helper.py should be excluded (matches '.*test_.*' pattern)"
|
|
1197
|
+
)
|
|
1198
|
+
assert not (temp_package_dir / "_SS").exists(), (
|
|
1199
|
+
"_SS directory should be excluded (matches '_SS' pattern)"
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
# Verify at least some files were copied (this would fail if all files were excluded)
|
|
1203
|
+
all_files = list(temp_package_dir.rglob("*"))
|
|
1204
|
+
all_files = [f for f in all_files if f.is_file()]
|
|
1205
|
+
assert len(all_files) >= 3, (
|
|
1206
|
+
f"Expected at least 3 files to be copied, but only found {len(all_files)}. "
|
|
1207
|
+
f"This suggests exclude patterns are incorrectly matching test directory names."
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
config.restore()
|
|
1211
|
+
|
|
1212
|
+
def test_only_globals_file_copied_not_entire_src_directory(
|
|
1213
|
+
self, test_project_with_pyproject: Path
|
|
1214
|
+
) -> None:
|
|
1215
|
+
"""
|
|
1216
|
+
Test that when a subfolder imports a file from src/ root (like _globals.py),
|
|
1217
|
+
only that file is copied, not the entire src/ directory.
|
|
1218
|
+
|
|
1219
|
+
This is a regression test for the bug where the entire src/ directory
|
|
1220
|
+
(including features/, integration/, docs/, infrastructure/) was being
|
|
1221
|
+
copied when only _globals.py was needed.
|
|
1222
|
+
"""
|
|
1223
|
+
project_root = test_project_with_pyproject
|
|
1224
|
+
subfolder = project_root / "subfolder"
|
|
1225
|
+
|
|
1226
|
+
# Create a file in subfolder that imports _globals
|
|
1227
|
+
(subfolder / "__init__.py").write_text("# Package init")
|
|
1228
|
+
(subfolder / "module.py").write_text(
|
|
1229
|
+
"from _globals import IS_TESTING\n\ndef func(): return IS_TESTING"
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
# Create _globals.py at root of src/ (outside subfolder)
|
|
1233
|
+
src_dir = project_root / "src"
|
|
1234
|
+
src_dir.mkdir(exist_ok=True)
|
|
1235
|
+
(src_dir / "_globals.py").write_text("IS_TESTING = False")
|
|
1236
|
+
|
|
1237
|
+
# Create other directories in src/ that should NOT be copied
|
|
1238
|
+
(src_dir / "features").mkdir()
|
|
1239
|
+
(src_dir / "features" / "__init__.py").write_text("# Features")
|
|
1240
|
+
(src_dir / "features" / "feature.py").write_text("def feature(): pass")
|
|
1241
|
+
|
|
1242
|
+
(src_dir / "integration").mkdir()
|
|
1243
|
+
(src_dir / "integration" / "__init__.py").write_text("# Integration")
|
|
1244
|
+
(src_dir / "integration" / "integration.py").write_text("def integration(): pass")
|
|
1245
|
+
|
|
1246
|
+
(src_dir / "docs").mkdir()
|
|
1247
|
+
(src_dir / "docs" / "readme.md").write_text("# Docs")
|
|
1248
|
+
|
|
1249
|
+
(src_dir / "infrastructure").mkdir()
|
|
1250
|
+
(src_dir / "infrastructure" / "__init__.py").write_text("# Infrastructure")
|
|
1251
|
+
|
|
1252
|
+
# Build the subfolder
|
|
1253
|
+
manager = BuildManager(project_root=project_root, src_dir=subfolder)
|
|
1254
|
+
|
|
1255
|
+
try:
|
|
1256
|
+
external_deps = manager.prepare_build(version="1.0.0", package_name="my-package")
|
|
1257
|
+
|
|
1258
|
+
# Verify _globals.py was found as an external dependency
|
|
1259
|
+
globals_deps = [d for d in external_deps if d.source_path.name == "_globals.py"]
|
|
1260
|
+
assert len(globals_deps) > 0, "_globals.py should be found as an external dependency"
|
|
1261
|
+
|
|
1262
|
+
# Verify the temp package directory exists
|
|
1263
|
+
assert manager.subfolder_config is not None
|
|
1264
|
+
temp_dir = manager.subfolder_config._temp_package_dir
|
|
1265
|
+
assert temp_dir is not None and temp_dir.exists()
|
|
1266
|
+
|
|
1267
|
+
# Verify _globals.py was copied to temp directory
|
|
1268
|
+
assert (temp_dir / "_globals.py").exists(), "_globals.py should be copied to temp directory"
|
|
1269
|
+
|
|
1270
|
+
# Verify other directories from src/ were NOT copied
|
|
1271
|
+
assert not (temp_dir / "features").exists(), (
|
|
1272
|
+
"features/ directory should NOT be copied (not imported)"
|
|
1273
|
+
)
|
|
1274
|
+
assert not (temp_dir / "integration").exists(), (
|
|
1275
|
+
"integration/ directory should NOT be copied (not imported)"
|
|
1276
|
+
)
|
|
1277
|
+
assert not (temp_dir / "docs").exists(), (
|
|
1278
|
+
"docs/ directory should NOT be copied (not imported)"
|
|
1279
|
+
)
|
|
1280
|
+
assert not (temp_dir / "infrastructure").exists(), (
|
|
1281
|
+
"infrastructure/ directory should NOT be copied (not imported)"
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
# Verify only _globals.py and subfolder contents are in temp directory
|
|
1285
|
+
all_items = list(temp_dir.iterdir())
|
|
1286
|
+
item_names = [item.name for item in all_items]
|
|
1287
|
+
|
|
1288
|
+
# Should have _globals.py, __init__.py, module.py, and possibly pyproject.toml
|
|
1289
|
+
# But NOT features/, integration/, docs/, infrastructure/
|
|
1290
|
+
unexpected_dirs = {"features", "integration", "docs", "infrastructure"}
|
|
1291
|
+
found_unexpected = unexpected_dirs.intersection(set(item_names))
|
|
1292
|
+
assert len(found_unexpected) == 0, (
|
|
1293
|
+
f"Found unexpected directories in temp package: {found_unexpected}. "
|
|
1294
|
+
f"Only _globals.py should be copied, not the entire src/ directory."
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
finally:
|
|
1298
|
+
manager.cleanup()
|
|
1299
|
+
|
|
1133
1300
|
|
|
1134
1301
|
class TestWheelPackaging:
|
|
1135
1302
|
"""Tests to verify that wheels are correctly packaged with the right directory structure."""
|
|
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-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/__init__.py
RENAMED
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/__main__.py
RENAMED
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/analyzer.py
RENAMED
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/publisher.py
RENAMED
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/types.py
RENAMED
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/utils.py
RENAMED
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/src/python_package_folder/version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.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
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/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-8.0.0 → python_package_folder-8.2.0}/tests/test_third_party_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_package_folder-8.0.0 → python_package_folder-8.2.0}/tests/test_version_calculator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|