python-package-folder 5.4.0__py3-none-any.whl → 6.0.0__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.
@@ -330,56 +330,33 @@ class ImportAnalyzer:
330
330
 
331
331
  # Check all subdirectories in parent (not just common ones)
332
332
  # This handles cases like src/data/spreadsheet_creation/spreadsheet_formatting_dataclasses.py
333
- if parent.is_dir():
333
+ # Use recursive search to find modules in nested directories
334
+ if parent.is_dir() and parent.is_relative_to(self.project_root):
335
+ # Recursively search for the module file in subdirectories
336
+ # Limit search to project_root and its subdirectories to avoid searching too broadly
337
+ module_basename = module_name.split(".")[-1]
334
338
  try:
335
- for subdir in parent.iterdir():
336
- if not subdir.is_dir():
339
+ # Search recursively for the module file
340
+ for potential_file in parent.rglob(f"{module_basename}.py"):
341
+ # Only search within project_root to avoid going too far
342
+ if not potential_file.is_relative_to(self.project_root):
337
343
  continue
338
- # Skip common excluded patterns
339
- if subdir.name.startswith("_SS") or subdir.name.startswith("__SS"):
340
- continue
341
- # Check if module file exists directly in subdirectory
342
- potential_subdir_file = subdir / f"{module_name.split('.')[-1]}.py"
343
- if potential_subdir_file.exists():
344
- return potential_subdir_file
345
- # Check if module directory exists in subdirectory
346
- potential_subdir_module = subdir / module_name.replace(".", "/")
347
- if (
348
- potential_subdir_module.is_dir()
349
- and (potential_subdir_module / "__init__.py").exists()
344
+ # Skip excluded patterns
345
+ if any(
346
+ part.startswith("_SS")
347
+ or part.startswith("__SS")
348
+ or part.startswith("_sandbox")
349
+ or part.startswith("__sandbox")
350
+ for part in potential_file.parts
350
351
  ):
351
- return potential_subdir_module / "__init__.py"
352
- if potential_subdir_module.with_suffix(".py").is_file():
353
- return potential_subdir_module.with_suffix(".py")
354
- # Check nested subdirectories (e.g., data/spreadsheet_creation)
355
- # Recursively check subdirectories up to 2 levels deep
356
- try:
357
- for nested_subdir in subdir.iterdir():
358
- if not nested_subdir.is_dir():
359
- continue
360
- # Check if module file exists in nested subdirectory
361
- potential_nested_file = (
362
- nested_subdir / f"{module_name.split('.')[-1]}.py"
363
- )
364
- if potential_nested_file.exists():
365
- return potential_nested_file
366
- # Check if module directory exists in nested subdirectory
367
- potential_nested_module = nested_subdir / module_name.replace(
368
- ".", "/"
369
- )
370
- if (
371
- potential_nested_module.is_dir()
372
- and (potential_nested_module / "__init__.py").exists()
373
- ):
374
- return potential_nested_module / "__init__.py"
375
- if potential_nested_module.with_suffix(".py").is_file():
376
- return potential_nested_module.with_suffix(".py")
377
- except (OSError, PermissionError):
378
- # Skip nested directories we can't read
379
352
  continue
353
+ # Skip if it's in the src_dir (we're looking for external dependencies)
354
+ if potential_file.is_relative_to(src_dir):
355
+ continue
356
+ return potential_file
380
357
  except (OSError, PermissionError):
381
- # Skip directories we can't read
382
- continue
358
+ # Skip if we can't read the directory
359
+ pass
383
360
 
384
361
  # Check common subdirectories in parent (e.g., _shared, shared, common)
385
362
  # This handles cases like src/_shared/better_enum.py
@@ -268,10 +268,30 @@ class BuildManager:
268
268
  # This is acceptable for tests or dependency-only operations
269
269
  if temp_pyproject is None:
270
270
  self.subfolder_config = None
271
+ else:
272
+ # If temporary package directory was created, use it for all operations
273
+ # This ensures dependencies are copied to the correct location and
274
+ # imports are fixed in the files that will actually be packaged
275
+ if (
276
+ self.subfolder_config
277
+ and self.subfolder_config._temp_package_dir
278
+ and self.subfolder_config._temp_package_dir.exists()
279
+ ):
280
+ # Update src_dir to point to temp package directory
281
+ self.src_dir = self.subfolder_config._temp_package_dir
282
+ # Recreate finder with updated src_dir so it calculates target paths correctly
283
+ self.finder = ExternalDependencyFinder(
284
+ self.project_root,
285
+ self.src_dir,
286
+ exclude_patterns=self.exclude_patterns,
287
+ )
288
+ print(
289
+ f"Using temporary package directory for build: {self.src_dir}"
290
+ )
271
291
 
272
292
  analyzer = ImportAnalyzer(self.project_root)
273
293
 
274
- # Find all Python files in src/
294
+ # Find all Python files in src/ (which may now be the temp package directory)
275
295
  python_files = analyzer.find_all_python_files(self.src_dir)
276
296
 
277
297
  # Find external dependencies using the configured finder
@@ -78,6 +78,7 @@ class SubfolderBuildConfig:
78
78
  self._used_subfolder_pyproject = False
79
79
  self._excluded_files: list[tuple[Path, Path]] = [] # List of (original_path, temp_path) tuples
80
80
  self._exclude_temp_dir: Path | None = None
81
+ self._temp_package_dir: Path | None = None
81
82
 
82
83
  def _derive_package_name(self) -> str:
83
84
  """
@@ -121,6 +122,48 @@ class SubfolderBuildConfig:
121
122
  # Fallback to just subfolder name
122
123
  return subfolder_name
123
124
 
125
+ def _create_temp_package_directory(self) -> None:
126
+ """
127
+ Create a temporary package directory with the correct import name.
128
+
129
+ This ensures the installed package has the correct directory structure.
130
+ The package name (with hyphens) is converted to the import name (with underscores).
131
+ For example: 'ml-drawing-assistant-data' -> 'ml_drawing_assistant_data'
132
+
133
+ The temporary directory is created in the project root and contains a copy
134
+ of the source directory contents.
135
+ """
136
+ if not self.package_name:
137
+ return
138
+
139
+ # Convert package name (with hyphens) to import name (with underscores)
140
+ # PyPI package names use hyphens, but Python import names use underscores
141
+ import_name = self.package_name.replace("-", "_")
142
+
143
+ # Create temporary directory name
144
+ temp_dir_name = f".temp_package_{import_name}"
145
+ temp_package_dir = self.project_root / temp_dir_name
146
+
147
+ # Remove if it already exists (from a previous failed build)
148
+ if temp_package_dir.exists():
149
+ shutil.rmtree(temp_package_dir)
150
+
151
+ # Copy the entire source directory contents to the temporary directory
152
+ try:
153
+ shutil.copytree(self.src_dir, temp_package_dir)
154
+ self._temp_package_dir = temp_package_dir
155
+ print(
156
+ f"Created temporary package directory: {temp_package_dir} "
157
+ f"(import name: {import_name})"
158
+ )
159
+ except Exception as e:
160
+ print(
161
+ f"Warning: Could not create temporary package directory: {e}",
162
+ file=sys.stderr,
163
+ )
164
+ # Fall back to using src_dir directly
165
+ self._temp_package_dir = None
166
+
124
167
  def _get_package_structure(self) -> tuple[str, list[str]]:
125
168
  """
126
169
  Determine the package structure for hatchling.
@@ -130,21 +173,24 @@ class SubfolderBuildConfig:
130
173
  - packages_path: The path to the directory containing packages
131
174
  - package_dirs: List of package directories to include
132
175
  """
133
- # Check if src_dir itself is a package (has __init__.py)
134
- has_init = (self.src_dir / "__init__.py").exists()
176
+ # Use temporary package directory if it exists, otherwise use src_dir
177
+ package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
178
+
179
+ # Check if package_dir itself is a package (has __init__.py)
180
+ has_init = (package_dir / "__init__.py").exists()
135
181
 
136
- # Check for Python files directly in src_dir
137
- py_files = list(self.src_dir.glob("*.py"))
182
+ # Check for Python files directly in package_dir
183
+ py_files = list(package_dir.glob("*.py"))
138
184
  has_py_files = bool(py_files)
139
185
 
140
- # Calculate relative path
186
+ # Calculate relative path from project root
141
187
  try:
142
- rel_path = self.src_dir.relative_to(self.project_root)
188
+ rel_path = package_dir.relative_to(self.project_root)
143
189
  packages_path = str(rel_path).replace("\\", "/")
144
190
  except ValueError:
145
191
  packages_path = None
146
192
 
147
- # If src_dir has Python files but no __init__.py, we need to make it a package
193
+ # If package_dir has Python files but no __init__.py, we need to make it a package
148
194
  # or include it as a module directory
149
195
  if has_py_files and not has_init:
150
196
  # For flat structures, we include the directory itself
@@ -298,7 +344,8 @@ class SubfolderBuildConfig:
298
344
  if not self.version:
299
345
  raise ValueError("Version is required for subfolder builds")
300
346
 
301
- # Ensure src_dir is a package (has __init__.py) for hatchling
347
+ # Ensure src_dir is a package (has __init__.py) before creating temp directory
348
+ # This way the __init__.py will be copied to the temp directory
302
349
  init_file = self.src_dir / "__init__.py"
303
350
  if not init_file.exists():
304
351
  # Create a temporary __init__.py to make it a package
@@ -307,6 +354,13 @@ class SubfolderBuildConfig:
307
354
  else:
308
355
  self._temp_init_created = False
309
356
 
357
+ # Create temporary package directory with correct import name
358
+ # This will copy the __init__.py we just created (if any)
359
+ self._create_temp_package_directory()
360
+
361
+ # Determine which directory to use (temp package dir or src_dir)
362
+ package_dir = self._temp_package_dir if self._temp_package_dir and self._temp_package_dir.exists() else self.src_dir
363
+
310
364
  # Check if pyproject.toml exists in subfolder
311
365
  subfolder_pyproject = self.src_dir / "pyproject.toml"
312
366
  if subfolder_pyproject.exists():
@@ -1206,6 +1260,18 @@ class SubfolderBuildConfig:
1206
1260
  self.original_pyproject_path = None
1207
1261
  self._used_subfolder_pyproject = False
1208
1262
 
1263
+ # Remove temporary package directory if it exists
1264
+ if self._temp_package_dir and self._temp_package_dir.exists():
1265
+ try:
1266
+ shutil.rmtree(self._temp_package_dir)
1267
+ print(f"Removed temporary package directory: {self._temp_package_dir}")
1268
+ except Exception as e:
1269
+ print(
1270
+ f"Warning: Could not remove temporary package directory {self._temp_package_dir}: {e}",
1271
+ file=sys.stderr,
1272
+ )
1273
+ self._temp_package_dir = None
1274
+
1209
1275
  def __enter__(self) -> Self:
1210
1276
  """Context manager entry."""
1211
1277
  return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-package-folder
3
- Version: 5.4.0
3
+ Version: 6.0.0
4
4
  Summary: Python package to automatically package and build a folder, fetching all relevant dependencies.
5
5
  Project-URL: Repository, https://github.com/alelom/python-package-folder
6
6
  Author-email: Alessio Lombardi <work@alelom.com>
@@ -1,18 +1,18 @@
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=cmTNUDCWBIh3XZ_mShlQVG1P9NN_oe3FUBTirVtYfTQ,16709
3
+ python_package_folder/analyzer.py,sha256=7AfJCN29aY7dSYB5H6t1xnHbT-hCZsLtYN7i27pAgMU,15191
4
4
  python_package_folder/finder.py,sha256=RPidZ7LKCFuQ_KgCFIZdHWPXsZIDor3M4C0hKeYW7EI,11799
5
- python_package_folder/manager.py,sha256=SFHpQMhrn_kgJFcPIUcAF_hrCTQPussQZNxqcoXEEQs,58280
5
+ python_package_folder/manager.py,sha256=erX8uPu3KA593wsSH9o15O8MNc6mwIZgGGqUJf7Z5i0,59423
6
6
  python_package_folder/publisher.py,sha256=fmf3l0zMY9CD49gurxlXyvm9mOP0FzDjmiSt0yDqt1M,18813
7
7
  python_package_folder/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  python_package_folder/python_package_folder.py,sha256=QZ-vdOZ40wF-eGbp39JotDhMwIhhT3Z_sC5obQj3i4s,17024
9
- python_package_folder/subfolder_build.py,sha256=6ht9vURMHm99AbM99NCBOKHrVeAOwzaVzhf_sG_zK4A,52764
9
+ python_package_folder/subfolder_build.py,sha256=4H6XDb8nxpjLPKlTixXy-PtX0JbW8jiMZvxn7Hgbqs0,55883
10
10
  python_package_folder/types.py,sha256=3yeSRR5p_3PDKEAaehW_RJ7NwJHexOIeA08bGaT1iSY,2368
11
11
  python_package_folder/utils.py,sha256=b6Ukcc0fctXdxS5zhGLS86kqn0vz1yOEK7XjCY9fjfY,5621
12
12
  python_package_folder/version.py,sha256=kIDP6S9trEfs9gj7lBYGxrWm4RPssRla24UtlO9Jkh4,9111
13
13
  python_package_folder/version_calculator.py,sha256=_gcc8IbMjt27UxePcc7RZlFTMCG3AGnRI_-Mp_4qRG0,39568
14
- python_package_folder-5.4.0.dist-info/METADATA,sha256=iLkfFZoVJF3IVE-gR6LOmHfb95JpkMNe7bkShze-k20,7838
15
- python_package_folder-5.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
16
- python_package_folder-5.4.0.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
17
- python_package_folder-5.4.0.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
18
- python_package_folder-5.4.0.dist-info/RECORD,,
14
+ python_package_folder-6.0.0.dist-info/METADATA,sha256=UeEwTJf4IxlZpjf-ekl7lX4tyxf-MgqCANxi16aDENc,7838
15
+ python_package_folder-6.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
16
+ python_package_folder-6.0.0.dist-info/entry_points.txt,sha256=ttu4wAhoYSHGhWQNercLz9IVTTpXxhVlRA9vSTvaLe0,91
17
+ python_package_folder-6.0.0.dist-info/licenses/LICENSE,sha256=vNgRJh8YiecqZoZld7TtwPI5I72HIymKD9g32fiJjCE,1073
18
+ python_package_folder-6.0.0.dist-info/RECORD,,