Package Discovery and Heuristics
When assisting users with Python package inclusion in wheels, reference this guide to explain automatic package discovery, common project layouts, and how to handle edge cases.
Automatic Package Discovery
When users don't explicitly define file selection with packages, only-include, or include/exclude patterns, Hatchling uses automatic heuristics to discover what to include. Reference this sequence when explaining discovery:
The wheel builder checks for packages in this order, using the project name from [project] name:
- Standard layout -
<NAME>/__init__.pyat project root - Src-layout -
src/<NAME>/__init__.py - Single module -
<NAME>.py(single-file module) - Namespace package -
<NAMESPACE>/<NAME>/__init__.pyfor namespace packages
The first match found is used. If none match and bypass-selection is false, an error occurs prompting the user to define file selection.
Common Project Layouts
Help users understand their project layout by explaining these patterns:
Standard Layout
Used when the package directory sits at project root:
myproject/
├── pyproject.toml
├── README.md
└── mypackage/
├── __init__.py
└── module.py
Configuration (can be implicit):
[tool.hatch.build.targets.wheel]
packages = ["mypackage"]
When the project name normalizes to "mypackage", Hatchling finds this automatically.
Src-Layout
Used when packages are in a src subdirectory (recommended for modern projects):
myproject/
├── pyproject.toml
├── README.md
└── src/
└── mypackage/
├── __init__.py
└── module.py
Configuration (can be implicit):
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
When using packages, Hatchling automatically applies only-include and sets sources = ["src"] for path rewriting.
Single Module Layout
For projects that are a single Python file:
myproject/
├── pyproject.toml
├── README.md
└── mymodule.py
Configuration (implicit with matching project name):
[project]
name = "mymodule"
Hatchling auto-detects single modules when the file <PROJECT_NAME>.py exists at root.
Namespace Packages
For namespace packages (PEP 420 style):
myproject/
├── pyproject.toml
├── namespace/
│ ├── subpackage/
│ │ ├── __init__.py
│ │ └── module.py
│ └── py.typed
Configuration:
[tool.hatch.build.targets.wheel]
packages = ["namespace"]
Namespace packages lack an __init__.py at the namespace level but contain subpackages with proper __init__.py files.
Package Discovery Heuristics
When helping users understand discovery, explain:
- Normalization - Project name "My-Package" becomes "my_package" or "my-package" for matching
- Directory matching - Hatchling looks for directories matching the normalized name
- Single module fallback - If no package directory exists, a single
.pyfile matching the name is checked - Namespace traversal - For namespace packages, top-level directories without
__init__.pyare traversed
Case-Insensitive File Systems
When users report issues on Windows or macOS:
Explain that Hatchling v1.19.0+ properly handles case-insensitive file systems where the project metadata name doesn't match the directory name on disk. For example, a project named "MyPackage" can find a directory named "mypackage".
Explicit Package Declaration
When automatic discovery doesn't work, guide users to use explicit declaration:
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage", "src/another_package"]
The packages option:
- Automatically applies
only-includewith those paths - Automatically sets
sources = ["src"]for path rewriting - Prevents directory traversal beyond specified packages
When Discovery Fails
If Hatchling cannot find packages, it raises an error with guidance. When helping users fix this:
- Verify the package directory exists at the expected location
- Confirm the directory contains
__init__.py(except for namespace packages) - Check that the project name matches (or explicitly use
packages) - For single modules, verify the
.pyfilename matches the project name - Use
packagesoronly-includeto explicitly define what to include
Single Module Auto-Detection
Explain to users that Hatchling v1.4.0+ automatically detects single-module projects:
# Single module detection example
# If only mymodule.py exists at root and project name is "mymodule"
# The wheel automatically includes it
```toml
This eliminates the need for explicit configuration in simple, single-file projects.
## Interaction with File Selection
When users combine discovery with explicit file selection:
- `packages` overrides discovery and applies `only-include` + `sources`
- `only-include` overrides discovery and targets specific paths
- `include`/`exclude` patterns work with discovery to refine what's included
- `artifacts` adds files from build hooks (complementary to discovery)
Reference the file selection guide for more details on these options.