setuptools — Cursor rule: what it does, how to install
Guides Cursor to configure Python packages using declarative pyproject.toml files and the clean src/ layout.
.cursor/rules/setuptools.mdcWhat setuptools does
The rule forces Cursor to abandon legacy setup.py scripts in favor of standard-compliant pyproject.toml configurations following PEP 517 and PEP 518. It mandates the src/ directory layout to keep importable library code isolated from root test suites and project manifests.
It provides explicit patterns for Git-based dynamic versioning with setuptools-scm, strict separation of runtime and optional dependencies like dev or docs, and declaring CLI entry points inside [project.scripts].
Added entry points or plugins remain unregistered in your local virtual environment until you re-run pip install -e ..
What it helps with
- configure a Python package using the src/ directory structure
- define package metadata declaratively in pyproject.toml
- automate version extraction from Git tags with setuptools-scm
- declare CLI entry points and optional test dependencies
Example prompts for the agent
- Create a pyproject.toml with setuptools-scm versioning and CLI entry points
- Restructure this Python project into a src/ layout with dev dependencies
- Add package data files and configure an editable install command
How to install setuptools
Manually: put the files in place
.cursor/rules/setuptools.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/setuptools.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/setuptools.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/setuptools.mdManually: put the files in place
.clinerules/setuptools.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about setuptools
How do I install setuptools in Cursor?
Save the rule file inside your repository at .cursor/rules/setuptools.mdc.
Why does this rule enforce the src/ layout?
The src/ directory prevents the local source tree from being accidentally imported over the installed package during test execution.
How are package versions managed with this rule?
The package derives versions directly from Git tags by setting dynamic = ["version"] and adding setuptools-scm to the build-system table.
Source text
setuptools.mdc · CC0-1.0
---
description: This guide defines best practices for configuring Python packages using setuptools, emphasizing modern, declarative `pyproject.toml` workflows for robust and maintainable projects.
globs: **/*.py
---
# setuptools Best Practices
`setuptools` is the de-facto standard for Python packaging. This guide outlines the definitive, modern approach to configuring your Python projects, prioritizing declarative `pyproject.toml` files, automated versioning, and clear dependency management. Adhere to these rules for consistent, maintainable, and standards-compliant package distributions.
## 1. Code Organization and Structure
Always use the `src/` layout for your package source code. This clearly separates your package's importable code from project-level files (like `pyproject.toml`, `README.md`, `tests/`).
❌ BAD: Flat layout
```
my_package/
├── __init__.py
├── module.py
├── pyproject.toml
└── tests/
```
✅ GOOD: `src/` layout
```
my_package/
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── module.py
├── pyproject.toml
├── README.md
└── tests/
```
## 2. Declarative Configuration in `pyproject.toml`
Configure all project metadata and build settings declaratively in `pyproject.toml`. This aligns with PEP 517/518, enables isolated builds, and makes your package metadata machine-readable without executing code. Avoid `setup.py` for metadata.
**Always include `[build-system]` and `[project]` tables.**
```toml title="pyproject.toml"
[build-system]
requires = ["setuptools>=61", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "my-awesome-package"
dynamic = ["version"] # Managed by setuptools-scm
description = "A short description of my awesome package."
readme = "README.md"
requires-python = ">=3.8"
license = { file = "LICENSE" }
authors = [
{ name = "Your Name", email = "[email protected]" },
]
keywords = ["python", "utility", "example"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
Homepage = "https://github.com/yourusername/my-awesome-package"
Documentation = "https://my-awesome-package.readthedocs.io/"
Repository = "https://github.com/yourusername/my-awesome-package"
[tool.setuptools]
package-dir = {"" = "src"} # Essential for src/ layout
[tool.setuptools_scm]
write_to = "src/my_package/_version.py" # Optional: writes version to a file
```
## 3. Version Management with `setuptools-scm`
Derive your package version directly from VCS (Git) tags using `setuptools-scm`. This eliminates manual version bumps and ensures your package version always reflects your repository state.
1. **Declare `setuptools-scm`** in `build-system.requires`.
2. **Set `dynamic = ["version"]`** in `[project]`.
3. **Configure `[tool.setuptools_scm]`** (optional, but recommended for `write_to`).
❌ BAD: Manual version in `pyproject.toml` or `__init__.py`
```toml
# pyproject.toml
[project]
version = "0.1.0" # ❌ BAD: Requires manual updates
```
✅ GOOD: Dynamic versioning with `setuptools-scm`
```toml title="pyproject.toml"
[build-system]
requires = ["setuptools>=61", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
dynamic = ["version"] # ✅ GOOD: Version derived from Git tags
[tool.setuptools_scm]
# Optional: write the version to a file for runtime access
write_to = "src/my_package/_version.py"
```
## 4. Dependency Declaration
Manage all dependencies declaratively in `pyproject.toml`.
* **`dependencies`**: For runtime dependencies.
* **`optional-dependencies`**: For optional dependencies (e.g., `dev`, `test`, `docs`).
❌ BAD: `dependency_links` or `requirements.txt` for runtime dependencies
```toml
# pyproject.toml
[project]
# ❌ BAD: dependency_links is deprecated.
# ❌ BAD: requirements.txt is for application environments, not libraries.
```
✅ GOOD: `dependencies` and `optional-dependencies`
```toml title="pyproject.toml"
[project]
dependencies = [ # ✅ GOOD: Runtime dependencies
"requests~=2.30",
"rich>=13.0.0",
]
[project.optional-dependencies] # ✅ GOOD: Optional dependencies
dev = [
"pytest>=7.0",
"pytest-cov",
"black",
"isort",
"mypy",
"setuptools-scm[toml]>=8", # Include setuptools-scm for dev installs
]
docs = [
"sphinx",
"furo",
]
```
## 5. Entry Points for Scripts and Plugins
Expose console scripts, GUI entry points, or plugin APIs using the `[project.scripts]` and `[project.entry-points]` tables. This is the standard way to create discoverable extensions and command-line tools.
```toml title="pyproject.toml"
[project.scripts] # ✅ GOOD: Console scripts
my-cli = "my_package.cli:main"
[project.gui-scripts] # ✅ GOOD: GUI scripts
my-gui = "my_package.gui:start_app"
[project.entry-points."my_package.plugins"] # ✅ GOOD: Custom plugin entry points
plugin_a = "my_package.plugins:PluginA"
plugin_b = "my_package.plugins:PluginB"
```
## 6. Resource Files and Namespace Packages
Bundle non-code assets (e.g., templates, data files) cleanly using `package_data`. For larger projects, use native namespace package support (PEP 420).
```toml title="pyproject.toml"
[tool.setuptools.package-data]
"my_package" = ["data/*.json", "templates/*.html"] # ✅ GOOD: Include specific data files
```
For namespace packages, simply omit `__init__.py` from the parent directory and ensure `setuptools` is configured to find them (e.g., via `package-dir` for `src/` layout).
```
src/
└── my_namespace/
├── my_package_a/
│ └── __init__.py
└── my_package_b/
└── __init__.py
```
## 7. Development Workflow
Always use a virtual environment. For local development, install your package in "editable" mode to reflect source changes instantly.
```bash
# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # On Unix/macOS
# .venv\Scripts\activate # On Windows
# Install your package in editable mode with dev dependencies
pip install -e ".[dev]" # ✅ GOOD: Editable install for live code changes
```
**Pitfall**: Entry points (scripts, plugins) are only discoverable *after* the package is installed (even in editable mode). If you add a new entry point, you might need to re-run `pip install -e .` for it to register.
## 8. Building and Publishing
Generate standard distribution packages (wheels and source distributions) using `build`, and upload them securely with `twine`.
```bash
# Ensure build and twine are installed in your dev environment
pip install build twine
# Clean any previous builds
rm -rf dist/ build/ *.egg-info/
# Build distributions
python -m build # ✅ GOOD: Creates .whl and .tar.gz in dist/
# Upload to TestPyPI (always test here first!)
twine upload --repository testpypi dist/* # ✅ GOOD: Secure upload
# Upload to PyPI
twine upload dist/*
```
## 9. Type Hints
While not `setuptools`-specific, consistently apply type hints throughout your Python codebase. This improves code readability, maintainability, and enables static analysis tools to catch errors early. `setuptools` itself does not dictate type hint usage, but a well-packaged project is a well-typed project.
```python
# ✅ GOOD: Use type hints for clarity and static analysis
def calculate_sum(a: int, b: int) -> int:
return a + b
```
## 10. Custom Build Logic (When Absolutely Necessary)
If you *must* perform custom build steps (e.g., compiling C extensions, generating code), `setup.py` is still the place for imperative logic. However, keep it minimal and ensure it only handles build-time operations, not metadata.
```python title="setup.py"
# ❌ BAD: Defining metadata here. Use pyproject.toml instead.
# from setuptools import setup
# setup(name='my_package', version='0.1.0', ...)
# ✅ GOOD: Minimal setup.py for custom build logic (e.g., C extensions)
from setuptools import setup, Extension
my_extension = Extension(
'my_package.my_c_module',
sources=['src/my_package/my_c_module.c'],
)
setup(
ext_modules=[my_extension],
)
```
**Note**: Even for extensions, much can be configured in `pyproject.toml` directly with `setuptools`'s `[tool.setuptools.extensions]` table. Only resort to `setup.py` if truly complex, imperative logic is required.
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc