python-package-folder 1.2.0__py3-none-any.whl → 1.2.1__py3-none-any.whl
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/analyzer.py +43 -1
- python_package_folder/python_package_folder.py +2 -1
- python_package_folder/subfolder_build.py +124 -6
- python_package_folder/utils.py +7 -5
- {python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/METADATA +6 -4
- {python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/RECORD +9 -9
- {python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/WHEEL +0 -0
- {python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/entry_points.txt +0 -0
- {python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -45,13 +45,55 @@ class ImportAnalyzer:
|
|
|
45
45
|
"""
|
|
46
46
|
Recursively find all Python files in a directory.
|
|
47
47
|
|
|
48
|
+
Excludes common directories like .venv, venv, __pycache__, etc.
|
|
49
|
+
|
|
48
50
|
Args:
|
|
49
51
|
directory: Directory to search for Python files
|
|
50
52
|
|
|
51
53
|
Returns:
|
|
52
54
|
List of paths to all .py files found in the directory tree
|
|
53
55
|
"""
|
|
54
|
-
|
|
56
|
+
exclude_patterns = {
|
|
57
|
+
".venv",
|
|
58
|
+
"venv",
|
|
59
|
+
"__pycache__",
|
|
60
|
+
".git",
|
|
61
|
+
".pytest_cache",
|
|
62
|
+
".mypy_cache",
|
|
63
|
+
"node_modules",
|
|
64
|
+
".tox",
|
|
65
|
+
"dist",
|
|
66
|
+
"build",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
python_files = []
|
|
70
|
+
for path in directory.rglob("*.py"):
|
|
71
|
+
if not path.is_file():
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Check if any part of the path matches exclusion patterns
|
|
75
|
+
should_exclude = False
|
|
76
|
+
for part in path.parts:
|
|
77
|
+
# Check exact matches
|
|
78
|
+
if part in exclude_patterns:
|
|
79
|
+
should_exclude = True
|
|
80
|
+
break
|
|
81
|
+
# Check if part starts with excluded pattern or contains .egg-info
|
|
82
|
+
for pattern in exclude_patterns:
|
|
83
|
+
if part.startswith(pattern):
|
|
84
|
+
should_exclude = True
|
|
85
|
+
break
|
|
86
|
+
# Also exclude .egg-info directories
|
|
87
|
+
if ".egg-info" in part:
|
|
88
|
+
should_exclude = True
|
|
89
|
+
break
|
|
90
|
+
if should_exclude:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
if not should_exclude:
|
|
94
|
+
python_files.append(path)
|
|
95
|
+
|
|
96
|
+
return python_files
|
|
55
97
|
|
|
56
98
|
def extract_imports(self, file_path: Path) -> list[ImportInfo]:
|
|
57
99
|
"""
|
|
@@ -122,7 +122,8 @@ def main() -> int:
|
|
|
122
122
|
src_dir = Path(args.src_dir).resolve()
|
|
123
123
|
else:
|
|
124
124
|
# Auto-detect: use current directory if it has Python files, otherwise use project_root/src
|
|
125
|
-
|
|
125
|
+
current_dir = Path.cwd()
|
|
126
|
+
src_dir = find_source_directory(project_root, current_dir=current_dir)
|
|
126
127
|
if src_dir:
|
|
127
128
|
print(f"Auto-detected source directory: {src_dir}")
|
|
128
129
|
else:
|
|
@@ -30,8 +30,11 @@ class SubfolderBuildConfig:
|
|
|
30
30
|
Manages temporary build configuration for subfolder builds.
|
|
31
31
|
|
|
32
32
|
When building a subfolder as a separate package, this class:
|
|
33
|
-
- Uses the subfolder's pyproject.toml if it exists
|
|
33
|
+
- Uses the subfolder's pyproject.toml if it exists (adjusts package paths and ensures
|
|
34
|
+
[build-system] uses hatchling)
|
|
34
35
|
- Otherwise creates a temporary pyproject.toml with the appropriate package name and version
|
|
36
|
+
- Always ensures [build-system] section uses hatchling (replaces any existing build-system
|
|
37
|
+
configuration from parent or subfolder)
|
|
35
38
|
- Handles README files similarly (uses subfolder README if present)
|
|
36
39
|
"""
|
|
37
40
|
|
|
@@ -111,13 +114,96 @@ class SubfolderBuildConfig:
|
|
|
111
114
|
# If it's a package or has subpackages, return the path
|
|
112
115
|
return packages_path, [packages_path] if packages_path else []
|
|
113
116
|
|
|
117
|
+
def _adjust_subfolder_pyproject_packages_path(self, content: str) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Adjust packages path in subfolder pyproject.toml to be relative to project root.
|
|
120
|
+
|
|
121
|
+
When a subfolder's pyproject.toml is copied to project root, the packages path
|
|
122
|
+
needs to be adjusted to point to the subfolder relative to the project root.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
content: Content of the subfolder's pyproject.toml
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Adjusted content with correct packages path
|
|
129
|
+
"""
|
|
130
|
+
# Get the correct packages path relative to project root
|
|
131
|
+
_, package_dirs = self._get_package_structure()
|
|
132
|
+
if not package_dirs:
|
|
133
|
+
# No adjustment needed if we can't determine the path
|
|
134
|
+
return content
|
|
135
|
+
|
|
136
|
+
correct_packages_path = package_dirs[0]
|
|
137
|
+
lines = content.split("\n")
|
|
138
|
+
result = []
|
|
139
|
+
in_hatch_build = False
|
|
140
|
+
packages_set = False
|
|
141
|
+
|
|
142
|
+
for line in lines:
|
|
143
|
+
# Detect hatch build section
|
|
144
|
+
if line.strip().startswith("[tool.hatch.build.targets.wheel]"):
|
|
145
|
+
in_hatch_build = True
|
|
146
|
+
result.append(line)
|
|
147
|
+
continue
|
|
148
|
+
elif line.strip().startswith("[") and in_hatch_build:
|
|
149
|
+
# End of hatch build section, add packages if not set
|
|
150
|
+
if not packages_set and correct_packages_path:
|
|
151
|
+
packages_str = f'"{correct_packages_path}"'
|
|
152
|
+
result.append(f"packages = [{packages_str}]")
|
|
153
|
+
in_hatch_build = False
|
|
154
|
+
result.append(line)
|
|
155
|
+
elif in_hatch_build:
|
|
156
|
+
# Modify packages path if found
|
|
157
|
+
if re.match(r"^\s*packages\s*=", line):
|
|
158
|
+
packages_str = f'"{correct_packages_path}"'
|
|
159
|
+
result.append(f"packages = [{packages_str}]")
|
|
160
|
+
packages_set = True
|
|
161
|
+
continue
|
|
162
|
+
# Keep other lines in hatch build section
|
|
163
|
+
result.append(line)
|
|
164
|
+
else:
|
|
165
|
+
result.append(line)
|
|
166
|
+
|
|
167
|
+
# Add packages if we're still in hatch build section and haven't set it
|
|
168
|
+
if in_hatch_build and not packages_set and correct_packages_path:
|
|
169
|
+
packages_str = f'"{correct_packages_path}"'
|
|
170
|
+
result.append(f"packages = [{packages_str}]")
|
|
171
|
+
|
|
172
|
+
# Ensure build-system section exists (required for hatchling)
|
|
173
|
+
# Check if build-system section exists in the result
|
|
174
|
+
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
175
|
+
if not has_build_system:
|
|
176
|
+
# Insert build-system at the very beginning of the file
|
|
177
|
+
build_system_lines = [
|
|
178
|
+
"[build-system]",
|
|
179
|
+
'requires = ["hatchling"]',
|
|
180
|
+
'build-backend = "hatchling.build"',
|
|
181
|
+
"",
|
|
182
|
+
]
|
|
183
|
+
result = build_system_lines + result
|
|
184
|
+
|
|
185
|
+
# Ensure hatch build section exists if packages path is needed
|
|
186
|
+
if not packages_set and correct_packages_path:
|
|
187
|
+
# Check if we need to add the section
|
|
188
|
+
if "[tool.hatch.build.targets.wheel]" not in content:
|
|
189
|
+
result.append("")
|
|
190
|
+
result.append("[tool.hatch.build.targets.wheel]")
|
|
191
|
+
packages_str = f'"{correct_packages_path}"'
|
|
192
|
+
result.append(f"packages = [{packages_str}]")
|
|
193
|
+
|
|
194
|
+
return "\n".join(result)
|
|
195
|
+
|
|
114
196
|
def create_temp_pyproject(self) -> Path | None:
|
|
115
197
|
"""
|
|
116
198
|
Create a temporary pyproject.toml for the subfolder build.
|
|
117
199
|
|
|
118
|
-
If a pyproject.toml exists in the subfolder, it will be used
|
|
119
|
-
|
|
120
|
-
|
|
200
|
+
If a pyproject.toml exists in the subfolder, it will be used (copied to project root
|
|
201
|
+
with adjusted package paths and ensuring [build-system] uses hatchling). Otherwise,
|
|
202
|
+
creates a pyproject.toml in the project root based on the parent pyproject.toml with
|
|
203
|
+
the appropriate package name and version.
|
|
204
|
+
|
|
205
|
+
The [build-system] section is always set to use hatchling, even if the parent or
|
|
206
|
+
subfolder pyproject.toml uses a different build backend (e.g., setuptools).
|
|
121
207
|
|
|
122
208
|
Returns:
|
|
123
209
|
Path to the pyproject.toml file (either from subfolder or created temporary),
|
|
@@ -149,8 +235,13 @@ class SubfolderBuildConfig:
|
|
|
149
235
|
shutil.copy2(original_pyproject, backup_path)
|
|
150
236
|
self.original_pyproject_backup = backup_path
|
|
151
237
|
|
|
152
|
-
#
|
|
153
|
-
|
|
238
|
+
# Read and adjust the subfolder pyproject.toml
|
|
239
|
+
subfolder_content = subfolder_pyproject.read_text(encoding="utf-8")
|
|
240
|
+
# Adjust packages path to be relative to project root
|
|
241
|
+
adjusted_content = self._adjust_subfolder_pyproject_packages_path(subfolder_content)
|
|
242
|
+
|
|
243
|
+
# Write adjusted content to project root
|
|
244
|
+
original_pyproject.write_text(adjusted_content, encoding="utf-8")
|
|
154
245
|
self.temp_pyproject = original_pyproject
|
|
155
246
|
|
|
156
247
|
# Handle README file
|
|
@@ -259,6 +350,7 @@ class SubfolderBuildConfig:
|
|
|
259
350
|
skip_uv_dynamic = False
|
|
260
351
|
in_hatch_build = False
|
|
261
352
|
packages_set = False
|
|
353
|
+
build_system_set = False
|
|
262
354
|
|
|
263
355
|
# Get package structure
|
|
264
356
|
packages_path, package_dirs = self._get_package_structure()
|
|
@@ -266,6 +358,19 @@ class SubfolderBuildConfig:
|
|
|
266
358
|
package_dirs = []
|
|
267
359
|
|
|
268
360
|
for _i, line in enumerate(lines):
|
|
361
|
+
# Skip build-system section - we'll add our own for subfolder builds
|
|
362
|
+
if line.strip().startswith("[build-system]"):
|
|
363
|
+
build_system_set = True
|
|
364
|
+
continue # Skip the [build-system] line
|
|
365
|
+
elif build_system_set and line.strip().startswith("["):
|
|
366
|
+
# End of build-system section
|
|
367
|
+
build_system_set = False
|
|
368
|
+
result.append(line)
|
|
369
|
+
continue
|
|
370
|
+
elif build_system_set:
|
|
371
|
+
# Skip build-system content - we'll add our own
|
|
372
|
+
continue
|
|
373
|
+
|
|
269
374
|
# Skip hatch versioning and uv-dynamic-versioning sections
|
|
270
375
|
if line.strip().startswith("[tool.hatch.version]"):
|
|
271
376
|
skip_hatch_version = True
|
|
@@ -361,6 +466,19 @@ class SubfolderBuildConfig:
|
|
|
361
466
|
packages_str = ", ".join(f'"{p}"' for p in package_dirs)
|
|
362
467
|
result.append(f"packages = [{packages_str}]")
|
|
363
468
|
|
|
469
|
+
# Ensure build-system section exists (required for hatchling)
|
|
470
|
+
# Check if build-system section exists in the result
|
|
471
|
+
has_build_system = any(line.strip().startswith("[build-system]") for line in result)
|
|
472
|
+
if not has_build_system:
|
|
473
|
+
# Insert build-system at the very beginning of the file
|
|
474
|
+
build_system_lines = [
|
|
475
|
+
"[build-system]",
|
|
476
|
+
'requires = ["hatchling"]',
|
|
477
|
+
'build-backend = "hatchling.build"',
|
|
478
|
+
"",
|
|
479
|
+
]
|
|
480
|
+
result = build_system_lines + result
|
|
481
|
+
|
|
364
482
|
# Ensure packages is always set for subfolder builds
|
|
365
483
|
if not packages_set and package_dirs:
|
|
366
484
|
# Add the section if it doesn't exist
|
python_package_folder/utils.py
CHANGED
|
@@ -63,7 +63,8 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
|
|
|
63
63
|
project_root = project_root.resolve()
|
|
64
64
|
|
|
65
65
|
# Check if current directory is a subdirectory with Python files
|
|
66
|
-
if
|
|
66
|
+
# Prioritize current directory if it's within the project and has Python files
|
|
67
|
+
if current_dir.is_relative_to(project_root) and current_dir != project_root:
|
|
67
68
|
python_files = list(current_dir.glob("*.py"))
|
|
68
69
|
if python_files:
|
|
69
70
|
# Current directory has Python files, use it as source
|
|
@@ -74,10 +75,11 @@ def find_source_directory(project_root: Path, current_dir: Path | None = None) -
|
|
|
74
75
|
if src_dir.exists() and src_dir.is_dir():
|
|
75
76
|
return src_dir
|
|
76
77
|
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
# Only check project_root if current_dir is the project_root
|
|
79
|
+
if current_dir == project_root:
|
|
80
|
+
python_files = list(project_root.glob("*.py"))
|
|
81
|
+
if python_files:
|
|
82
|
+
return project_root
|
|
81
83
|
|
|
82
84
|
return None
|
|
83
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-package-folder
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
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>
|
|
@@ -247,6 +247,7 @@ python-package-folder --project-root /path/to/project --src-dir /path/to/src --b
|
|
|
247
247
|
- If found, copies the subfolder README to project root (backing up the original parent README)
|
|
248
248
|
- If not found, creates a minimal README with just the folder name
|
|
249
249
|
5. **Configuration Creation**: Creates temporary `pyproject.toml` with:
|
|
250
|
+
- `[build-system]` section using hatchling (replaces any existing build-system configuration)
|
|
250
251
|
- Subfolder-specific package name (derived or custom)
|
|
251
252
|
- Specified version
|
|
252
253
|
- Correct package path for hatchling
|
|
@@ -278,8 +279,9 @@ The tool automatically:
|
|
|
278
279
|
- Uses the current directory as the source directory if it contains Python files
|
|
279
280
|
- Falls back to `project_root/src` if the current directory isn't suitable
|
|
280
281
|
- **For subfolder builds**: Handles `pyproject.toml` configuration:
|
|
281
|
-
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily)
|
|
282
|
+
- **If `pyproject.toml` exists in subfolder**: Uses that file (copies it to project root temporarily, adjusting package paths and ensuring `[build-system]` uses hatchling)
|
|
282
283
|
- **If no `pyproject.toml` in subfolder**: Creates a temporary `pyproject.toml` with:
|
|
284
|
+
- `[build-system]` section using hatchling (always uses hatchling, even if parent uses setuptools)
|
|
283
285
|
- Package name derived from the subfolder name (e.g., `empty_drawing_detection` → `empty-drawing-detection`)
|
|
284
286
|
- Version from `--version` argument (defaults to `0.0.0` with a warning if not provided)
|
|
285
287
|
- Proper package path configuration for hatchling
|
|
@@ -811,11 +813,11 @@ config.restore()
|
|
|
811
813
|
```
|
|
812
814
|
|
|
813
815
|
**Methods:**
|
|
814
|
-
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists, otherwise create temporary `pyproject.toml` with subfolder-specific configuration
|
|
816
|
+
- `create_temp_pyproject() -> Path`: Use subfolder's `pyproject.toml` if it exists (adjusting package paths and ensuring `[build-system]` uses hatchling), otherwise create temporary `pyproject.toml` with subfolder-specific configuration including `[build-system]` section using hatchling
|
|
815
817
|
- `restore() -> None`: Restore original `pyproject.toml` and clean up temporary files
|
|
816
818
|
|
|
817
819
|
**Note**: This class automatically:
|
|
818
|
-
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily). Otherwise, creates a temporary one from the parent configuration.
|
|
820
|
+
- **pyproject.toml handling**: If a `pyproject.toml` exists in the subfolder, it will be used (copied to project root temporarily with adjusted package paths). Otherwise, creates a temporary one from the parent configuration. In both cases, the `[build-system]` section is always set to use hatchling, replacing any existing build-system configuration.
|
|
819
821
|
- **README handling**: If a README exists in the subfolder, it will be used instead of the parent README. If no README exists in the subfolder, a minimal README with just the folder name will be created. The original parent README is backed up and restored after the build completes.
|
|
820
822
|
- **Package initialization**: Creates `__init__.py` files if needed to make subfolders valid Python packages.
|
|
821
823
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
python_package_folder/__init__.py,sha256=DQt-uldOEKfh0MUqCvKdeNKOnpuOvpb7blYvXMyO9Wc,719
|
|
2
2
|
python_package_folder/__main__.py,sha256=a-__-VLhYw-J7S7CsHdhtEvQr3RiAZxiYDvKhKTgMX4,291
|
|
3
|
-
python_package_folder/analyzer.py,sha256=
|
|
3
|
+
python_package_folder/analyzer.py,sha256=w7hc2oyOoPK7tvlwcJDXnB3eiJsuGZc4BkOpTfZP7Vo,12257
|
|
4
4
|
python_package_folder/finder.py,sha256=_LvJ9xBVKv41UK5sbwbNyKmuYjAOqUbzvZhK7NCYQF8,9130
|
|
5
5
|
python_package_folder/manager.py,sha256=AlzEqI7q0Q2mVmc_HjIEBmomlT053qqLBMZZ52X7IDQ,26360
|
|
6
6
|
python_package_folder/publisher.py,sha256=1xa6PuduOXNVTTp4IrJcx4qOskugX2fTeJ9QsLDLuUM,11535
|
|
7
7
|
python_package_folder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
python_package_folder/python_package_folder.py,sha256=
|
|
9
|
-
python_package_folder/subfolder_build.py,sha256=
|
|
8
|
+
python_package_folder/python_package_folder.py,sha256=RPsqRcIy_LjzzTHdp4qdtFJ4-4xhtR_0YLIC0RlUxFo,8841
|
|
9
|
+
python_package_folder/subfolder_build.py,sha256=C_Hksc7JqeNfYvP1HUqXnxlaFFYde3ACXCAE-8SYh9M,27712
|
|
10
10
|
python_package_folder/types.py,sha256=3yeSRR5p_3PDKEAaehW_RJ7NwJHexOIeA08bGaT1iSY,2368
|
|
11
|
-
python_package_folder/utils.py,sha256=
|
|
11
|
+
python_package_folder/utils.py,sha256=lIkWsFKeAYAJ9TDUM99T4pUBHJVbUvCdUgkWQN-LUho,3111
|
|
12
12
|
python_package_folder/version.py,sha256=kIDP6S9trEfs9gj7lBYGxrWm4RPssRla24UtlO9Jkh4,9111
|
|
13
|
-
python_package_folder-1.2.
|
|
14
|
-
python_package_folder-1.2.
|
|
15
|
-
python_package_folder-1.2.
|
|
16
|
-
python_package_folder-1.2.
|
|
17
|
-
python_package_folder-1.2.
|
|
13
|
+
python_package_folder-1.2.1.dist-info/METADATA,sha256=rkIOYmrjFZxN0AMgT6LshC4qxRbQW1s0CzBgWNsalaQ,32711
|
|
14
|
+
python_package_folder-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
python_package_folder-1.2.1.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
|
|
16
|
+
python_package_folder-1.2.1.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
|
|
17
|
+
python_package_folder-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
{python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_package_folder-1.2.0.dist-info → python_package_folder-1.2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|