@@ -130,8 +130,8 @@ def _create_temp_package_directory(self) -> None:
130130 The package name (with hyphens) is converted to the import name (with underscores).
131131 For example: 'ml-drawing-assistant-data' -> 'ml_drawing_assistant_data'
132132
133- The temporary directory is created in the project root and contains a copy
134- of the source directory contents .
133+ The temporary directory is created in the project root with the import name directly.
134+ This way, hatchling will install it with the correct name without needing force-include .
135135 """
136136 if not self .package_name :
137137 return
@@ -140,20 +140,42 @@ def _create_temp_package_directory(self) -> None:
140140 # PyPI package names use hyphens, but Python import names use underscores
141141 import_name = self .package_name .replace ("-" , "_" )
142142
143- # Create temporary directory name
144- temp_dir_name = f".temp_package_ { import_name } "
145- temp_package_dir = self .project_root / temp_dir_name
143+ # Create temporary directory with the import name directly
144+ # This way, hatchling will install it with the correct name
145+ import_name_dir = self .project_root / import_name
146146
147- # Remove if it already exists (from a previous failed build)
148- if temp_package_dir .exists ():
149- shutil .rmtree (temp_package_dir )
147+ # Check if the directory already exists and is the correct one
148+ if import_name_dir .exists () and import_name_dir == self ._temp_package_dir :
149+ # Directory already exists and is the correct one, no need to recreate
150+ return
151+
152+ # Remove if it already exists (from a previous build)
153+ if import_name_dir .exists ():
154+ shutil .rmtree (import_name_dir )
155+
156+ # Copy the entire source directory contents directly to the import name directory
157+ # Check if src_dir exists and is a directory before copying
158+ if not self .src_dir .exists ():
159+ print (
160+ f"Warning: Source directory does not exist: { self .src_dir } " ,
161+ file = sys .stderr ,
162+ )
163+ self ._temp_package_dir = None
164+ return
165+
166+ if not self .src_dir .is_dir ():
167+ print (
168+ f"Warning: Source path is not a directory: { self .src_dir } " ,
169+ file = sys .stderr ,
170+ )
171+ self ._temp_package_dir = None
172+ return
150173
151- # Copy the entire source directory contents to the temporary directory
152174 try :
153- shutil .copytree (self .src_dir , temp_package_dir )
154- self ._temp_package_dir = temp_package_dir
175+ shutil .copytree (self .src_dir , import_name_dir )
176+ self ._temp_package_dir = import_name_dir
155177 print (
156- f"Created temporary package directory: { temp_package_dir } "
178+ f"Created temporary package directory: { import_name_dir } "
157179 f"(import name: { import_name } )"
158180 )
159181 except Exception as e :
@@ -344,6 +366,116 @@ def create_temp_pyproject(self) -> Path | None:
344366 if not self .version :
345367 raise ValueError ("Version is required for subfolder builds" )
346368
369+ # Check if pyproject.toml exists in subfolder FIRST
370+ # This allows us to handle subfolder pyproject.toml even when parent doesn't exist
371+ # But first ensure src_dir exists
372+ if not self .src_dir .exists () or not self .src_dir .is_dir ():
373+ # If src_dir doesn't exist, we can't proceed
374+ print (
375+ f"Warning: Source directory does not exist or is not a directory: { self .src_dir } " ,
376+ file = sys .stderr ,
377+ )
378+ return None
379+
380+ subfolder_pyproject = self .src_dir / "pyproject.toml"
381+ if subfolder_pyproject .exists () and subfolder_pyproject .is_file ():
382+ # Read the subfolder pyproject.toml content IMMEDIATELY after checking it exists
383+ # This prevents any issues if the file is affected by subsequent operations
384+ try :
385+ subfolder_content = subfolder_pyproject .read_text (encoding = "utf-8" )
386+ except (FileNotFoundError , OSError ) as e :
387+ # File was deleted or inaccessible between check and read
388+ print (
389+ f"Warning: Could not read subfolder pyproject.toml at { subfolder_pyproject } : { e } . "
390+ "Falling back to creating from parent." ,
391+ file = sys .stderr ,
392+ )
393+ subfolder_content = None
394+
395+ if subfolder_content is not None :
396+ # Ensure src_dir is a package (has __init__.py) before creating temp directory
397+ # This way the __init__.py will be copied to the temp directory
398+ init_file = self .src_dir / "__init__.py"
399+ if not init_file .exists ():
400+ # Create a temporary __init__.py to make it a package
401+ init_file .write_text ("# Temporary __init__.py for build\n " , encoding = "utf-8" )
402+ self ._temp_init_created = True
403+ else :
404+ self ._temp_init_created = False
405+
406+ # Create temporary package directory with correct import name
407+ # This will copy the __init__.py we just created (if any)
408+ self ._create_temp_package_directory ()
409+
410+ # Determine which directory to use (temp package dir or src_dir)
411+ package_dir = self ._temp_package_dir if self ._temp_package_dir and self ._temp_package_dir .exists () else self .src_dir
412+ # Use the subfolder's pyproject.toml
413+ print (f"Using existing pyproject.toml from subfolder: { subfolder_pyproject } " )
414+ self ._used_subfolder_pyproject = True
415+
416+ # Store reference to original project root pyproject.toml
417+ original_pyproject = self .project_root / "pyproject.toml"
418+ self .original_pyproject_path = original_pyproject
419+
420+ # Create temporary pyproject.toml file
421+ temp_pyproject_path = self .project_root / "pyproject.toml.temp"
422+
423+ # Adjust packages path to be relative to project root
424+ adjusted_content = self ._adjust_subfolder_pyproject_packages_path (subfolder_content )
425+
426+ # Read exclude patterns from root pyproject.toml and inject them (if it exists)
427+ exclude_patterns = []
428+ if original_pyproject .exists ():
429+ exclude_patterns = read_exclude_patterns (original_pyproject )
430+ print (
431+ f"INFO: Read exclude patterns from { original_pyproject } : { exclude_patterns } " ,
432+ file = sys .stderr ,
433+ )
434+ else :
435+ print (
436+ f"INFO: No parent pyproject.toml found at { original_pyproject } , skipping exclude patterns" ,
437+ file = sys .stderr ,
438+ )
439+ if exclude_patterns :
440+ adjusted_content = self ._inject_exclude_patterns (adjusted_content , exclude_patterns )
441+
442+ # Write adjusted content to temporary file
443+ temp_pyproject_path .write_text (adjusted_content , encoding = "utf-8" )
444+ self .temp_pyproject = temp_pyproject_path
445+
446+ # Print the temporary pyproject.toml content for debugging
447+ print ("\n " + "=" * 80 )
448+ print ("Temporary pyproject.toml content (from subfolder pyproject.toml):" )
449+ print ("=" * 80 )
450+ print (adjusted_content )
451+ print ("=" * 80 + "\n " )
452+
453+ # If original pyproject.toml exists, temporarily move it
454+ if original_pyproject .exists ():
455+ backup_path = self .project_root / "pyproject.toml.original"
456+ # Remove backup if it already exists (from previous failed test or run)
457+ if backup_path .exists ():
458+ backup_path .unlink ()
459+ original_pyproject .rename (backup_path )
460+ self .original_pyproject_backup = backup_path
461+
462+ # Move temp file to pyproject.toml for the build
463+ temp_pyproject_path .rename (original_pyproject )
464+ self .temp_pyproject = original_pyproject
465+
466+ # Handle README file
467+ self ._handle_readme ()
468+
469+ # Exclude files matching exclude patterns
470+ if exclude_patterns :
471+ self ._exclude_files_by_patterns (exclude_patterns )
472+
473+ return original_pyproject
474+
475+ # No pyproject.toml in subfolder, create one from parent
476+ self ._used_subfolder_pyproject = False
477+ print ("No pyproject.toml found in subfolder, creating temporary one from parent" )
478+
347479 # Ensure src_dir is a package (has __init__.py) before creating temp directory
348480 # This way the __init__.py will be copied to the temp directory
349481 init_file = self .src_dir / "__init__.py"
@@ -361,71 +493,6 @@ def create_temp_pyproject(self) -> Path | None:
361493 # Determine which directory to use (temp package dir or src_dir)
362494 package_dir = self ._temp_package_dir if self ._temp_package_dir and self ._temp_package_dir .exists () else self .src_dir
363495
364- # Check if pyproject.toml exists in subfolder
365- subfolder_pyproject = self .src_dir / "pyproject.toml"
366- if subfolder_pyproject .exists ():
367- # Use the subfolder's pyproject.toml
368- print (f"Using existing pyproject.toml from subfolder: { subfolder_pyproject } " )
369- self ._used_subfolder_pyproject = True
370-
371- # Store reference to original project root pyproject.toml
372- original_pyproject = self .project_root / "pyproject.toml"
373- self .original_pyproject_path = original_pyproject
374-
375- # Create temporary pyproject.toml file
376- temp_pyproject_path = self .project_root / "pyproject.toml.temp"
377-
378- # Read and adjust the subfolder pyproject.toml
379- subfolder_content = subfolder_pyproject .read_text (encoding = "utf-8" )
380- # Adjust packages path to be relative to project root
381- adjusted_content = self ._adjust_subfolder_pyproject_packages_path (subfolder_content )
382-
383- # Read exclude patterns from root pyproject.toml and inject them
384- exclude_patterns = read_exclude_patterns (original_pyproject )
385- print (
386- f"INFO: Read exclude patterns from { original_pyproject } : { exclude_patterns } " ,
387- file = sys .stderr ,
388- )
389- if exclude_patterns :
390- adjusted_content = self ._inject_exclude_patterns (adjusted_content , exclude_patterns )
391-
392- # Write adjusted content to temporary file
393- temp_pyproject_path .write_text (adjusted_content , encoding = "utf-8" )
394- self .temp_pyproject = temp_pyproject_path
395-
396- # Print the temporary pyproject.toml content for debugging
397- print ("\n " + "=" * 80 )
398- print ("Temporary pyproject.toml content (from subfolder pyproject.toml):" )
399- print ("=" * 80 )
400- print (adjusted_content )
401- print ("=" * 80 + "\n " )
402-
403- # If original pyproject.toml exists, temporarily move it
404- if original_pyproject .exists ():
405- backup_path = self .project_root / "pyproject.toml.original"
406- # Remove backup if it already exists (from previous failed test or run)
407- if backup_path .exists ():
408- backup_path .unlink ()
409- original_pyproject .rename (backup_path )
410- self .original_pyproject_backup = backup_path
411-
412- # Move temp file to pyproject.toml for the build
413- temp_pyproject_path .rename (original_pyproject )
414- self .temp_pyproject = original_pyproject
415-
416- # Handle README file
417- self ._handle_readme ()
418-
419- # Exclude files matching exclude patterns
420- if exclude_patterns :
421- self ._exclude_files_by_patterns (exclude_patterns )
422-
423- return original_pyproject
424-
425- # No pyproject.toml in subfolder, create one from parent
426- self ._used_subfolder_pyproject = False
427- print ("No pyproject.toml found in subfolder, creating temporary one from parent" )
428-
429496 # Read the original pyproject.toml
430497 original_pyproject = self .project_root / "pyproject.toml"
431498 if not original_pyproject .exists ():
0 commit comments