Claude
Skills
Sign in
Back

python-lib-setup

Included with Lifetime
$97 forever

Set up Python project for PyPI publishing or private GitHub distribution with pyproject.toml, src layout, and optional build scripts. Use when creating a new Python package, setting up for PyPI distribution, initializing a Python library project, or setting up a private library distributed via GitHub.

General

What this skill does


# Python PyPI Project Setup Pattern

This skill helps you set up a Python project for PyPI publishing following modern best practices with pyproject.toml, src layout, and standardized build/publish scripts.

## When to Use This Skill

Use this skill when:
- Starting a new Python package for PyPI distribution
- Setting up a private Python library distributed via GitHub
- You want to use modern pyproject.toml-based configuration
- You need a standardized src/ layout with explicit package discovery
- You want automated build and publish scripts (PyPI path)

## What This Skill Creates

1. **`pyproject.toml`** - Modern Python project configuration
2. **`src/{package_name}/`** - Source layout with package structure
3. **`.gitignore`** - Comprehensive Python gitignore
4. **`dev-requirements.txt`** - Development dependencies (includes build/twine for PyPI only)
5. **`build-publish.sh`** - Automated build and publish script (PyPI only)
6. **`LICENSE`** - License file (Proprietary, MIT, or O'Saasy)
7. **`README.md`** - Basic project documentation

## Step 1: Gather Project Information

**IMPORTANT**: Before creating files, ask the user these questions:

1. **"What is your project name?"** (e.g., "pg-podcast-toolkit", "mypackage")
   - Use this to derive:
     - PyPI package name: `{project-name}` (with hyphens, e.g., `pg-podcast-toolkit`)
     - Python package name: `{package_name}` (with underscores, e.g., `pg_podcast_toolkit`)
     - Module directory: `src/{package_name}/`

2. **"What is the project description?"** (brief one-line description for PyPI)

3. **"What is your name?"** (for author field)

4. **"What is your email?"** (for author field)

5. **"What is your GitHub username?"** (for project URLs)

6. **"Will this package be published to PyPI or installed from GitHub?"**
   - **PyPI** — Public package registry. Includes build/twine tooling and a publish script.
   - **GitHub** — Private library installed via `git+https://` URL. Skips PyPI-specific artifacts (build, twine, build-publish.sh).

7. **"What license do you want to use?"** (options: Proprietary, MIT, O'Saasy)
   - **Proprietary**: All rights reserved, no open source distribution
   - **MIT**: Permissive open source, allows commercial use
   - **O'Saasy**: Modified MIT that reserves commercial SaaS rights for the copyright holder (see https://osaasy.dev/)

8. **"What Python version should be the minimum requirement?"** (default: 3.8)

9. **"What are your initial dependencies?"** (optional - comma-separated list, can be empty)

10. **"What keywords describe your project?"** (optional - for PyPI searchability)

## Step 2: Create Directory Structure

Create these directories if they don't exist:
```
{project_root}/
├── src/
│   └── {package_name}/
└── (other files at root)
```

## Step 3: Create pyproject.toml

Create `pyproject.toml` with the following structure, **substituting project-specific values**:

```toml
[project]
name = "{project-name}"
version = "0.0.1"
authors = [
  { name="{author_name}", email="{author_email}" },
]
description = "{project_description}"
keywords = [{keywords_list}]
readme = "README.md"
requires-python = ">={python_version}"
license = {text = "{license_name} License"}
classifiers = [
    "Programming Language :: Python :: 3",
    "{license_classifier}",
    "Operating System :: OS Independent",
]
dependencies = [
  {dependencies_list}
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/{package_name}"]

[project.urls]
Homepage = "https://github.com/{github_username}/{project-name}"
Issues = "https://github.com/{github_username}/{project-name}/issues"
```

**CRITICAL Substitutions**:
- `{project-name}` → project name with hyphens (e.g., `pg-podcast-toolkit`)
- `{package_name}` → package name with underscores (e.g., `pg_podcast_toolkit`)
- `{author_name}` → author's name
- `{author_email}` → author's email
- `{project_description}` → one-line description
- `{keywords_list}` → comma-separated quoted keywords (e.g., `"podcasting", "rss", "parser"`) or empty
- `{python_version}` → minimum Python version (e.g., `3.8`)
- `{license_name}` → license name (e.g., `MIT`, `O'Saasy`, `Proprietary - All Rights Reserved`)
- `{license_classifier}` → Full classifier string:
  - MIT: `License :: OSI Approved :: MIT License`
  - O'Saasy: `License :: Other/Proprietary License`
  - Proprietary: `License :: Other/Proprietary License`
- `{dependencies_list}` → comma-separated quoted dependencies (e.g., `'requests', 'beautifulsoup4'`) or empty
- `{github_username}` → GitHub username

**License Classifiers Mapping**:
- Proprietary → `Other/Proprietary License`
- MIT → `MIT License`
- O'Saasy → `Other/Proprietary License` (modified MIT with SaaS restrictions)

**License Text Handling**:
- **Proprietary**: Use `license = {text = "Proprietary - All Rights Reserved"}`
- **MIT**: Use `license = {text = "MIT License"}`
- **O'Saasy**: Use `license = {text = "O'Saasy License"}` and create LICENSE file from https://osaasy.dev/

## Step 4: Create Comprehensive .gitignore

Create `.gitignore` with comprehensive Python patterns:

```gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
Pipfile.lock

# PEP 582
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
bin/
include/
pyvenv.cfg

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
```

## Step 5: Create dev-requirements.txt

Create `dev-requirements.txt` with development dependencies.

**If distribution is PyPI:**
```
build
twine
pytest
black
mypy
```

**If distribution is GitHub:**
```
pytest
black
mypy
```

These are the tools needed to develop (and for PyPI, build/publish) the package. Add other dev tools as needed (isort, pytest-cov, etc.).

## Step 6: Create build-publish.sh (PyPI only)

**If distribution is GitHub, skip this step entirely.**

**If distribution is PyPI**, create `build-publish.sh` with venv activation and build/publish commands:

```bash
#!/bin/bash
# Build and publish package to PyPI
# Activates virtual environment before running

# Activate virtual environment
source bin/activate

# Clean previous builds
rm -rf dist/*

# Build package
python -m build

# Upload to PyPI
python -m twine upload dist/*
```

**Note**: This script follows the convention that the virtual environment is in `bin/` at the project root.

## Step 7: Create Package Structure

Create the basic package structure:

1. **`src/{package_name}/__init__.py`** - Package initialization file:
   ```python
   """
   {project_description}
   """

   __version__ = "0.0.1"
   ```

2. **If this is a library package**, you can add:
   ```python
   # Export main classes/functions here for easier imports
   # from .module import ClassName, function_name
   # __all__ = ['ClassName', 'function_name']
   ```

## Step 8: Create LICENSE File

Create the appropriate LICENSE file based on the user's license choice:

### For MIT

Related in General