Skip to content
Back to skills

663 Wheel File Selection B9d8422a

ASecurity

When assisting users with wheel builds in Hatchling, reference this guide to help them understand and resolve file selection errors. The wheel build target in Hatchling requires explicit or implicit file selection configuration. This document covers file selection errors, heuristics, and resolution strategies.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
code-qualitypythongodebuggingdocumentation

Works with

  • cli

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 663-wheel-file-selection_b9d8422a --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 663 Wheel File Selection B9d8422a?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 663 Wheel File Selection B9d8422a
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-663-wheel-file-selection-b9d8422a/badge)](https://www.skillsdirectory.com/skills/tools-only-663-wheel-file-selection-b9d8422a)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
category: Error Handling
topics: [file-selection, wheel-target, heuristics, bypass-selection, package-discovery]
related: [./path-validation.md, ./heuristic-failures.md, ./build-validation.md]
---

# Wheel Target File Selection Errors in Hatchling

## Overview

When assisting users with wheel builds in Hatchling, reference this guide to help them understand and resolve file selection errors. The wheel build target in Hatchling requires explicit or implicit file selection configuration. This document covers file selection errors, heuristics, and resolution strategies.

## Main Error: No File Selection Options

### Error Message (v1.19.0+)

```text
ValueError: At least one file selection option must be defined, see:
https://hatch.pypa.io/latest/config/build/
```

### Root Cause

The wheel builder cannot determine what files to include in the distribution when:

- No explicit file selection options are configured
- Default heuristics fail to identify package structure
- Project structure doesn't match common patterns

## File Selection Options

### 1. Packages Option (Recommended)

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]  # Explicit package directory
```

**Directory Structures:**

```text
# Standard layout
project/
├── pyproject.toml
├── src/
│   └── mypackage/
│       ├── __init__.py
│       └── module.py

# Flat layout
project/
├── pyproject.toml
├── mypackage/
│   ├── __init__.py
│   └── module.py
```

### 2. Include Option

```toml
[tool.hatch.build.targets.wheel]
include = [
    "mypackage",
    "data/*.json",
    "configs/*.yaml"
]
```

### 3. Only-Include Option (v1.4.0+)

```toml
[tool.hatch.build.targets.wheel]
only-include = ["src/mypackage"]
# More targeted than packages option
```

### 4. Force-Include Option

```toml
[tool.hatch.build.targets.wheel]
force-include = {
    "external/lib.so" = "mypackage/lib.so",
    "../shared/data.json" = "mypackage/data.json"
}
```

## Default Heuristics and When They Fail

### Working Default Heuristics

Hatchling attempts to auto-detect in this order:

1. **Single top-level package:**

   ```text
   project/
   ├── pyproject.toml
   ├── mypackage/
   │   └── __init__.py
   ```

2. **Src-layout:**

   ```text
   project/
   ├── pyproject.toml
   ├── src/
   │   └── mypackage/
   │       └── __init__.py
   ```

3. **Single module:**
   ```text
   project/
   ├── pyproject.toml
   ├── mymodule.py
   ```

### When Heuristics Fail

1. **Multiple packages without clear selection:**

   ```text
   project/
   ├── pyproject.toml
   ├── package1/
   │   └── __init__.py
   ├── package2/
   │   └── __init__.py
   ```

   **Solution:**

   ```toml
   [tool.hatch.build.targets.wheel]
   packages = ["package1", "package2"]
   ```

2. **Non-standard directory names:**

   ```text
   project/
   ├── pyproject.toml
   ├── core_lib/  # Not matching project name
   │   └── __init__.py
   ```

   **Solution:**

   ```toml
   [tool.hatch.build.targets.wheel]
   packages = ["core_lib"]
   ```

3. **Mixed content types:**

   ```text
   project/
   ├── pyproject.toml
   ├── lib.py
   ├── scripts/
   ├── data/
   ```

   **Solution:**

   ```toml
   [tool.hatch.build.targets.wheel]
   include = [
       "lib.py",
       "scripts/*.py",
       "data/*.json"
   ]
   ```

## Advanced Selection Scenarios

### Namespace Packages

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/company", "src/company/project"]
# Include all namespace levels
```

### Excluding Test Files

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
exclude = [
    "**/tests/**",
    "**/*_test.py",
    "**/test_*.py"
]
```

### Including Non-Python Files

```toml
[tool.hatch.build.targets.wheel]
include = [
    "mypackage",
    "mypackage/**/*.json",
    "mypackage/**/*.yaml",
    "mypackage/**/*.txt"
]
```

## Build-Time File Generation

### Error: Files Generated by Build Hooks

**Problem:** Build hooks generate files after selection validation

**Solution 1: Use artifacts in build hook**

```python
# hatch_build.py
class CustomBuildHook(BuildHookInterface):
    def initialize(self, version, build_data):
        # Generate file
        generated_file = Path("src/mypackage/version.py")
        generated_file.write_text(f'__version__ = "{version}"')

        # Add as artifact
        build_data['artifacts'] = ['src/mypackage/version.py']
```

**Solution 2: Force-include with build hook**

```toml
[tool.hatch.build.targets.wheel]
force-include = {"generated/file.py" = "mypackage/file.py"}

[tool.hatch.build.hooks.custom]
# Hook generates the file before force-include runs
```

## Bypass Selection (Metadata-Only Wheels)

### Creating Empty Wheels (v1.20.0+)

```toml
[tool.hatch.build.targets.wheel]
bypass-selection = true
# Creates wheel with only metadata, no files
```

**Use Cases:**

- Metadata-only packages
- Virtual packages
- Namespace package roots

## Debugging File Selection

### 1. List Files That Will Be Included

```python
#!/usr/bin/env python3
"""Debug file selection for wheel building."""

from pathlib import Path
from hatchling.builders.wheel import WheelBuilder
from hatchling.metadata.core import ProjectMetadata

# Load metadata
metadata = ProjectMetadata(Path.cwd() / "pyproject.toml")

# Create wheel builder
builder = WheelBuilder(str(Path.cwd()), metadata=metadata)

# List files that will be included
for included_file in builder.recurse_included_files():
    print(f"Include: {included_file.path}")
```

### 2. Validate Selection Configuration

```python
#!/usr/bin/env python3
"""Validate wheel file selection configuration."""

import sys
import tomllib
from pathlib import Path

def validate_wheel_config():
    with open("pyproject.toml", "rb") as f:
        config = tomllib.load(f)

    wheel_config = (
        config.get("tool", {})
        .get("hatch", {})
        .get("build", {})
        .get("targets", {})
        .get("wheel", {})
    )

    # Check if any selection option is defined
    selection_options = [
        "packages",
        "include",
        "only-include",
        "force-include",
        "bypass-selection"
    ]

    has_selection = any(
        wheel_config.get(opt) for opt in selection_options
    )

    if not has_selection:
        print("ERROR: No file selection options defined!")
        print("Add one of:", ", ".join(selection_options))
        sys.exit(1)

    print("File selection configuration valid ✓")

if __name__ == "__main__":
    validate_wheel_config()
```

## Common Solutions by Project Type

### Library with Source Layout

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/mylibrary"]
```

### Application with Multiple Packages

```toml
[tool.hatch.build.targets.wheel]
packages = [
    "app",
    "app_cli",
    "app_web"
]
```

### Data Science Project

```toml
[tool.hatch.build.targets.wheel]
include = [
    "notebooks/*.ipynb",
    "src/**/*.py",
    "data/*.csv",
    "models/*.pkl"
]
exclude = [
    "data/raw/**",
    "notebooks/scratch/**"
]
```

### Plugin System

```toml
[tool.hatch.build.targets.wheel]
only-include = ["plugins"]
force-include = {
    "plugin.yaml" = "plugins/plugin.yaml"
}
```

## Migration from v1.18 to v1.19+

### Old Behavior (v1.18)

- Silent fallback to empty wheel
- Implicit file inclusion
- No validation errors

### New Behavior (v1.19+)

- Explicit error when no files selected
- Force-include paths must exist
- Clear error messages with documentation links

### Migration Checklist

1. ✓ Add explicit `packages` configuration
2. ✓ Verify all force-included paths exist
3. ✓ Test build with `hatch build -t wheel`
4. ✓ Validate wheel contents: `unzip -l dist/*.whl`

## Error Resolution Flowchart

```text
Error: "At least one file selection option must be defined"
│
├─> Do you have a standard layout?
│   ├─> Yes: Add packages = ["src/packagename"]
│   └─> No: Continue
│
├─> Do you need specific files only?
│   ├─> Yes: Use only-include = [...]
│   └─> No: Continue
│
├─> Do you need pattern matching?
│   ├─> Yes: Use include = ["pattern/**/*.py"]
│   └─> No: Continue
│
├─> Do you need external files?
│   ├─> Yes: Use force-include = {...}
│   └─> No: Continue
│
└─> Metadata-only package?
    └─> Yes: Set bypass-selection = true
```

## Best Practices

1. **Always use explicit selection** for reproducible builds
2. **Prefer `packages`** for standard Python packages
3. **Use `only-include`** for precise control
4. **Validate configuration** before publishing
5. **Test wheel contents** after building

## Version History

- **v1.4.0**: Added `only-include` option
- **v1.19.0**: Added file selection validation error
- **v1.19.1**: Improved error messages
- **v1.20.0**: Added `bypass-selection` option

## Related Documentation

- [Path Validation](./path-validation.md)
- [Build Validation](./build-validation.md)
- [Heuristic Failures](./heuristic-failures.md)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.