Skip to main content

Opinionated FastAPI toolkit with CLI-driven architecture

Reason this release was yanked:

still in test mode

Project description

PaperDraft

Opinionated Python REST API framework with CLI-driven architecture.

PaperDraft scaffolds projects and services with an enforced layered structure, and ships a production-ready core library covering auth, database, encryption, email, middleware, and more.

Status: pre-release · v0.1 in progress · Paper Plane Consulting LLC


Table of Contents

  1. Installation
  2. Setup
  3. CLI Reference
  4. Building with paper-draft
  5. Project Structure
  6. Extending paper-draft
  7. Architecture

Packages

PaperDraft is split into two packages:

Package Purpose
paper-core Runtime library — auth, DB, security, middleware, email, errors, audit
paper-draft CLI tooling — project scaffolding and service generation

Installation

Requirements: Python 3.11+

pip install paper-draft

paper-core is installed automatically as a dependency.


Local Development Setup

Prerequisites: Python 3.11+

# 1. Clone the CLI repo
git clone https://flypaperplane@bitbucket.org/ppc-llc/paper-draft.git
cd paper-draft

# 2. Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate        # macOS/Linux
.venv\Scripts\activate           # Windows

# 3. Install paper-core
pip install paper-core

# 4. Install paper-draft CLI in editable mode with dev dependencies
pip install -e ".[dev]"

Once installed, the draft CLI is available in your environment:

draft --help

Setup

Environment Files

PaperDraft uses layered .env files. The base .env sets the active environment, and the environment-specific file is loaded on top.

.env               ← sets ENV=dev (or staging, production)
.env.dev           ← development values (auto-created by draft init)
.env.staging       ← staging values (create manually)
.env.production    ← production values (create manually)

.env (minimal — sets the active environment):

ENV=dev

.env.dev (created automatically by draft init):

ENV=dev
APP_URL=http://localhost:8000
APP_PORT=8000

POSTGRES_CONN_STRING=postgresql+asyncpg://user:password@localhost:5432/dbname
POSTGRES_ADMIN_CONN_STRING=postgresql+asyncpg://user:password@localhost:5432/postgres

ENCRYPTION_PUBLIC_KEY=<base64-encoded PEM public key>
ENCRYPTION_PRIVATE_KEY=<base64-encoded PEM private key>

EMAIL_SMTP_HOST=smtp.example.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USERNAME=your@email.com
EMAIL_SMTP_PASSWORD=your-smtp-password
EMAIL_SENDER_NAME=Your App
EMAIL_SENDER_ADDRESS=no-reply@example.com

Generating Encryption Keys

PaperDraft uses RSA key pairs (RS256) for JWT signing and field-level encryption.

# Generate a 2048-bit RSA private key
openssl genrsa -out private.pem 2048

# Derive the public key
openssl rsa -in private.pem -pubout -out public.pem

# Base64-encode each for .env storage
base64 -w 0 private.pem   # → ENCRYPTION_PRIVATE_KEY
base64 -w 0 public.pem    # → ENCRYPTION_PUBLIC_KEY

CLI Reference

draft init <name> --port <port>

Creates a new project in a <name>/ directory.

draft init my-api --port 8000

Creates:

my-api/
├── main.py            # FastAPI app entry point
├── dependencies.py    # DB + auth dependency injection
├── config.py          # Pydantic Settings configuration
├── requirements.txt   # App dependencies
├── .gitignore
├── .env.dev           # Pre-filled environment stub
├── services/          # Your services go here
└── .venv/             # Python virtual environment (auto-created)

Then:

cd my-api
# Fill in .env.dev with your DB connection string and keys
# Activate the venv: source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows)

draft new service <name>

Scaffolds a new service inside an existing project.

draft new service users
draft new service users --prefix api/v1/users   # custom URL prefix

Creates:

services/users/
├── __init__.py
├── router.py       # FastAPI APIRouter — define your routes here
├── controller.py   # UsersController — validate + orchestrate
└── service.py      # UsersService — business logic

Also patches main.py to import and register the router automatically.


draft validate <service>

Validates a service's structure and main.py registration.

draft validate users
draft validate users --quiet   # suppress output on success

Checks:

  • services/users/ directory exists
  • router.py, controller.py, service.py, __init__.py all present
  • router.py references UsersController
  • controller.py defines class UsersController
  • service.py defines class UsersService
  • main.py imports and registers the router

Exits 0 on success, 1 on validation errors, 2 if not in a project root.


draft run

Runs the app with uvicorn. Port is read from APP_PORT in your .env.{ENV} file.

draft run               # dev mode — auto-reload enabled
draft run --prod        # production — 4 workers, no reload
draft run --host 0.0.0.0 --prod

Building with PaperDraft

Workflow

# 1. Create a new project
draft init my-api --port 8000
cd my-api

# 2. Fill in .env.dev

# 3. Add a service
draft new service users

# 4. Define your routes in services/users/router.py
# 5. Implement controller logic in services/users/controller.py
# 6. Implement business logic in services/users/service.py

# 7. Validate
draft validate users

# 8. Run
draft run

Layer Responsibilities

Each scaffolded service has three layers with strict responsibilities:

router.py       ← HTTP only. Receives request, injects dependencies, returns response.
                  No logic. No direct DB access.

controller.py   ← Validates the request. Orchestrates service calls.
                  No business logic. No direct DB access.

service.py      ← All business logic lives here.
                  Calls the DB via injected Postgres instance.

Dependency Injection

dependencies.py wires up DB and config — inject them directly in route signatures:

from dependencies import MasterDb, TenantDb, Configuration

@router.get("")
async def retrieve(
    db:     MasterDb,
    config: Configuration,
    claims: Dict[str, Any] = Depends(Authenticate())
):
    return await UsersController.retrieve(db, config, claims)

Auth

Use Authenticate for any valid JWT, Authorize for role enforcement:

from paper.core.auth import Authenticate, Authorize

# Any authenticated user
claims = Depends(Authenticate())

# Role-restricted
claims = Depends(Authorize(["admin", "manager"]))

Project Structure

A full PaperDraft project looks like this:

my-api/
├── main.py                   # FastAPI app, middleware, router registration
├── dependencies.py           # DB + auth dependency wiring
├── config.py                 # Pydantic Settings
├── requirements.txt
├── .env                      # Sets ENV=dev|staging|production
├── .env.dev                  # Dev config (never commit)
├── services/
│   ├── users/
│   │   ├── router.py
│   │   ├── controller.py
│   │   └── service.py
│   └── auth/
│       ├── router.py
│       ├── controller.py
│       └── service.py
└── .venv/

Extending PaperDraft

PaperDraft's core components are built on abstract base classes. Extend them to add new database engines, encryption algorithms, or email providers while keeping the same interface everywhere.


Custom Database Engine

Extend Repository to add a new DB engine (MySQL, MongoDB, etc.).

from paper.core.db.base import Repository, FilterType
from typing import Any, Dict, List, Optional

class MySQLRepository(Repository[T, M]):

    def __init__(self, connection_string: str) -> None:
        # set up your engine here
        ...

    async def create(self, entity, model, data):
        ...

    async def retrieve(self, entity, model, filter=None):
        ...

    async def single(self, entity, model, id):
        ...

    async def update(self, entity, model, id, data):
        ...

    async def delete(self, entity, id):
        ...

Inject it the same way as Postgres via dependencies.py.


Custom Encryption Algorithm

Extend BaseCrypto to add a new cipher (AES, ChaCha20, etc.).

from paper.core.security.base import BaseCrypto

class AESCrypto(BaseCrypto):

    def __init__(self, key: str) -> None:
        self._key = key

    def encrypt(self, value: str) -> str:
        ...

    def decrypt(self, cipher: str) -> str:
        ...

    def encrypt_urlsafe(self, value: str) -> str:
        ...

    def decrypt_urlsafe(self, cipher: str) -> str:
        ...

    def encrypt_raw(self, value: str) -> bytes:
        ...

    def decrypt_raw(self, cipher_bytes: bytes) -> str:
        ...

Pass your implementation to MultiTenantDbDependency or anywhere BaseCrypto is accepted:

crypto = AESCrypto(key=config.ENCRYPTION.KEY.SYMMETRIC)

Custom Email Provider

Extend BaseEmailService to add a new provider (SendGrid, SES, Postmark, etc.).

from paper.core.email.base import BaseEmailService
from typing import Dict

class SendGridEmailService(BaseEmailService):

    def __init__(self, api_key: str, sender_name: str, sender_email: str) -> None:
        self._api_key      = api_key
        self._sender_name  = sender_name
        self._sender_email = sender_email

    def send(
        self,
        subject:         str,
        recipient_name:  str,
        recipient_email: str,
        data:            Dict[str, str],
    ) -> bool:
        # implement SendGrid API call here
        ...

Instantiate in dependencies.py and inject wherever email is needed.


Architecture

HTTP Request
    ↓
[ Router ]            receives request, injects dependencies, returns response
    ↓
[ Auth Middleware ]    JWT validation + RBAC — always first, never skipped
    ↓
[ Custom Middleware ]  CORS, HIPAA headers, rate limiting, audit logging
    ↓
[ Controller ]        validates request, orchestrates service calls
    ↓
[ Service ]           all business logic lives here
    ↓
[ Postgres / DB ]     async SQLAlchemy — injected, never instantiated directly

Core modules available via paper.core:

Module Contents
paper.core.auth Authenticate, Authorize, LoginAttemptLimit, Password, JWT utils
paper.core.db Postgres, Repository, FilterType, MultiTenantDbDependency
paper.core.security RSACrypto, BaseCrypto, Crypto, Hasher, Pem
paper.core.email SMTPEmailService, BaseEmailService, Subject, EmailTheme
paper.core.errors ErrorHandler, ErrorMessage
paper.core.middleware HipaaResponseHeaders, RequestLoggingMiddleware, RequestIdMiddleware
paper.core.audit Audit

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paper_draft-0.1.0.tar.gz (16.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paper_draft-0.1.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file paper_draft-0.1.0.tar.gz.

File metadata

  • Download URL: paper_draft-0.1.0.tar.gz
  • Upload date:
  • Size: 16.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for paper_draft-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6ba4564d87e89033b7adee4d387dd0d3556f0361bfc8fd9788af84d75d434cfc
MD5 522db998ebdbd7acd0cfc7ba924a9f61
BLAKE2b-256 2ee6850cb87794b79660c0390765d9a970b5e46529d093b3b62c7da61e4d98d4

See more details on using hashes here.

File details

Details for the file paper_draft-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: paper_draft-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for paper_draft-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f573190c03f0509e7e2d6e275f373ab58903ac9afec394e7a8466ac1933bb60c
MD5 3742201261f04755ba13bfd5b3abee87
BLAKE2b-256 ddc51c54b94e1f4e8845e2b2d826097dc91de548791c8f29e08e4aa21f379ac5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page