Back to skills
SKILL.md
1601 Single Module Layout 72762cda
ASecurityHatchling can automatically detect and properly package projects that consist of a single Python module file rather than a full package directory. When assisting users with simple tools and scripts, reference this feature to simplify their packaging process.
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added October 11, 2026
Works with
Security analysis
92/100- Installs packages at runtime which could introduce malicious dependencies
npx -y skills add tools-only/X-Skills --skill 1601-single-module-layout_72762cda --agent claude-codeAre you the author of 1601 Single Module Layout 72762cda?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-1601-single-module-layout-72762cda)---
category: project-structure
topics: [single module, auto-detection, simple packages, CLI tools]
related: [single-module-layout.md, src-layout-structure.md]
---
# Single Module Layout Auto-detection in Hatchling
## Overview
Hatchling can automatically detect and properly package projects that consist of a single Python module file rather than a full package directory. When assisting users with simple tools and scripts, reference this feature to simplify their packaging process.
## Auto-detection Behavior
### What Hatchling Detects
Starting with version 1.4.0, Hatchling automatically detects single module layouts when:
1. There's a single `.py` file at the root or in `src/`
2. The module name matches or relates to the project name
3. No package directory exists with an `__init__.py`
### Examples of Auto-detected Layouts
**Root level single module:**
```text
myproject/
├── mymodule.py
└── pyproject.toml
```
**Src-layout single module:**
```text
myproject/
├── src/
│ └── mymodule.py
└── pyproject.toml
```
## Basic Configuration
### Minimal Configuration (Auto-detection)
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mymodule"
version = "1.0.0"
# That's it! Hatchling auto-detects mymodule.py
```
### Explicit Configuration
```toml
[tool.hatch.build.targets.wheel]
packages = ["."] # or ["src"] for src-layout
include = ["mymodule.py"]
```
## Single Module Patterns
### Simple Script
```python
# mytool.py
"""A simple command-line tool."""
def main():
print("Hello from mytool!")
if __name__ == "__main__":
main()
```
```toml
[project]
name = "mytool"
version = "1.0.0"
[project.scripts]
mytool = "mytool:main"
```
### Module with Functions
```python
# utils.py
"""Utility functions module."""
def calculate(x, y):
return x + y
def process(data):
return data.upper()
class Helper:
def __init__(self):
self.ready = True
```
```toml
[project]
name = "utils"
version = "1.0.0"
description = "Utility functions"
```
## Import Names
### Matching Project Name
When the module name matches the project name:
```toml
[project]
name = "calculator" # Package name on PyPI
# File: calculator.py
```
After installation:
```python
import calculator
result = calculator.add(1, 2)
```
### Different Names
When module name differs from project name:
```toml
[project]
name = "my-awesome-tool" # Package name on PyPI
# File: awesome.py
```
After installation:
```python
import awesome # Import uses module filename
```
## Advanced Single Module Configurations
### With Additional Resources
```text
project/
├── src/
│ └── mytool.py
├── data/
│ └── config.json
├── templates/
│ └── default.html
└── pyproject.toml
```
```toml
[project]
name = "mytool"
version = "1.0.0"
[tool.hatch.build.targets.wheel]
packages = ["src"]
include = ["src/mytool.py"]
artifacts = [
"data/*.json",
"templates/*.html"
]
[tool.hatch.build.targets.wheel.force-include]
"data" = "mytool/data"
"templates" = "mytool/templates"
```
### With Type Stubs
```text
project/
├── mymodule.py
├── mymodule.pyi # Type stub file
├── py.typed # PEP 561 marker
└── pyproject.toml
```
```toml
[tool.hatch.build.targets.wheel]
include = [
"mymodule.py",
"mymodule.pyi",
"py.typed"
]
```
### Multiple Single Modules
```text
project/
├── module1.py
├── module2.py
├── helper.py
└── pyproject.toml
```
```toml
[project]
name = "my-tools"
[tool.hatch.build.targets.wheel]
include = [
"module1.py",
"module2.py",
"helper.py"
]
```
After installation:
```python
import module1
import module2
import helper
```
## Console Scripts
### Single Entry Point
```python
# cli.py
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('name')
args = parser.parse_args()
print(f"Hello, {args.name}!")
if __name__ == "__main__":
main()
```
```toml
[project]
name = "greeter"
version = "1.0.0"
[project.scripts]
greet = "cli:main"
```
### Multiple Entry Points
```python
# tools.py
def encrypt():
print("Encrypting...")
def decrypt():
print("Decrypting...")
def hash_file():
print("Hashing...")
```
```toml
[project.scripts]
encrypt = "tools:encrypt"
decrypt = "tools:decrypt"
hash = "tools:hash_file"
```
## Version Management
### Version in Module
```python
# mymodule.py
__version__ = "1.0.0"
def get_version():
return __version__
```
```toml
[project]
name = "mymodule"
dynamic = ["version"]
[tool.hatch.version]
path = "mymodule.py"
```
### Version from Environment
```python
# tool.py
import os
__version__ = os.environ.get("TOOL_VERSION", "dev")
```
```toml
[project]
name = "tool"
dynamic = ["version"]
[tool.hatch.version]
source = "env"
variable = "TOOL_VERSION"
```
## Testing Single Modules
### Test Structure
```text
project/
├── src/
│ └── calculator.py
├── tests/
│ └── test_calculator.py
└── pyproject.toml
```
```python
# tests/test_calculator.py
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
import calculator
def test_add():
assert calculator.add(2, 2) == 4
```
### With pytest
```toml
[tool.hatch.envs.default]
dependencies = ["pytest"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"] # or ["."] for root-level modules
```
## Edge Cases
### Module Name Conflicts
When module name conflicts with standard library:
```python
# json.py (conflicts with stdlib json)
def parse():
return {"custom": "parser"}
```
Better naming:
```python
# myjson.py or json_tools.py
def parse():
return {"custom": "parser"}
```
### Unicode Module Names
```python
# café.py (with non-ASCII character)
def brew():
return "☕"
```
Note: While Python supports Unicode identifiers, it's better to use ASCII for module names for compatibility.
## Migration from Package to Single Module
### Before (Package Structure)
```text
mypackage/
├── mypackage/
│ ├── __init__.py
│ └── core.py
└── pyproject.toml
```
### After (Single Module)
```text
mypackage/
├── mypackage.py # Consolidated module
└── pyproject.toml
```
Consolidate code:
```python
# mypackage.py
"""All functionality in one module."""
# Previous __init__.py content
__version__ = "1.0.0"
# Previous core.py content
def main_function():
pass
class MainClass:
pass
```
## Build Output
### What Gets Built
For a single module project:
```text
mymodule.py
pyproject.toml
```
The wheel contains:
```text
mymodule.py
mymodule-1.0.0.dist-info/
├── METADATA
├── WHEEL
├── top_level.txt
└── RECORD
```
### Installation Result
After `pip install mymodule-1.0.0.whl`:
```text
site-packages/
├── mymodule.py
└── mymodule-1.0.0.dist-info/
└── ...
```
## Best Practices
When recommending single module layouts to users, reference these best practices:
1. **Use for simple tools**: Guide users that single modules are perfect for simple utilities
2. **Consider growth**: Advise users that if they might need multiple modules, they should start with a package
3. **Clear naming**: Recommend users choose descriptive and unique module names
4. **Include type hints**: Encourage users to include type annotations even in single modules
5. **Add docstrings**: Remind users to document their module's purpose and functions
## Common Patterns
### CLI Tool Pattern
```python
#!/usr/bin/env python
"""Command-line tool description."""
import argparse
import sys
__version__ = "1.0.0"
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--version', action='version', version=__version__)
# Add more arguments
args = parser.parse_args()
# Process arguments
return 0
if __name__ == "__main__":
sys.exit(main())
```
### Library Pattern
```python
"""Library module description."""
__version__ = "1.0.0"
__all__ = ['function1', 'function2', 'Class1']
def function1(param):
"""Function description."""
pass
def function2(param):
"""Function description."""
pass
class Class1:
"""Class description."""
pass
# Private helper
def _internal_function():
pass
```
## Troubleshooting
### Module Not Found After Installation
**Issue:** Users' module can't be imported after installation
**Solutions:** Help users debug by:
1. Checking the module is included in wheel:
```bash
unzip -l dist/*.whl
```
2. Verifying module name matches import name
3. Ensuring no naming conflicts with installed packages
### Auto-detection Not Working
**Issue:** Hatchling doesn't auto-detect single module
**Solution:** Guide users to use explicit configuration:
```toml
[tool.hatch.build.targets.wheel]
packages = ["."] # or ["src"]
include = ["mymodule.py"]
```
## Complete Example
**Project structure:**
```text
math-tools/
├── src/
│ └── mathtools.py
├── tests/
│ └── test_mathtools.py
├── README.md
└── pyproject.toml
```
**mathtools.py:**
```python
"""Mathematical utility functions."""
__version__ = "2.0.0"
def factorial(n):
"""Calculate factorial of n."""
if n <= 1:
return 1
return n * factorial(n - 1)
def fibonacci(n):
"""Generate fibonacci sequence."""
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
class Calculator:
"""Simple calculator class."""
@staticmethod
def add(x, y):
return x + y
@staticmethod
def multiply(x, y):
return x * y
```
**pyproject.toml:**
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "math-tools"
dynamic = ["version"]
description = "Mathematical utility functions"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [{name = "Your Name", email = "you@example.com"}]
keywords = ["math", "tools", "utilities"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
]
[project.optional-dependencies]
test = ["pytest>=7.0"]
[tool.hatch.version]
path = "src/mathtools.py"
[tool.hatch.build.targets.wheel]
packages = ["src"]
[tool.pytest.ini_options]
pythonpath = ["src"]
```
## References
- [Hatch Build - Single Module Support](https://hatch.pypa.io/latest/config/build/)
- [Python Packaging - Single Module](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
- [Hatchling v1.4.0 Release Notes](https://github.com/pypa/hatch/releases/tag/hatchling-v1.4.0)
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.