Metadata-Version: 2.4
Name: paper-draft
Version: 0.2.0
Summary: Opinionated FastAPI toolkit with CLI-driven architecture
Author: Paper Plane Consulting LLC
License: MIT License
        
        Copyright (c) 2024 Paper Plane Consulting LLC
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Framework :: FastAPI
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: paper-core>=0.1.14
Requires-Dist: typer
Requires-Dist: jinja2
Requires-Dist: rich
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: httpx; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# 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. [Models & Entities](#models--entities)
2. [Getting Started](#getting-started)
3. [Setup](#setup)
4. [CLI Reference](#cli-reference)
   - [`draft init`](#draft-init)
   - [`draft service new`](#draft-service-new-name)
   - [`draft service validate`](#draft-service-validate-service)
   - [`draft model init`](#draft-model-init)
   - [`draft model new`](#draft-model-new-name)
   - [`draft model entity`](#draft-model-entity-name)
   - [`draft model schema`](#draft-model-schema-name)
   - [`draft run`](#draft-run)
5. [Building with PaperDraft](#building-with-paperdraft)
6. [Project Structure](#project-structure)
7. [Architecture](#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 |

---

## Models & Entities

> **Where should SQLAlchemy entities and Pydantic models live?**

### Recommended: a dedicated models package (separate repo)

The cleanest architecture puts all entities and models in their own repository,
installed as a package into every service that needs them. No code is duplicated.
Your backend services share a single source of truth. Your TypeScript frontend
generates its types from the same OpenAPI schema those models produce.

**Why a separate repo?**
- A single entity change is made once and versioned once.
- Services can pin to a specific version and upgrade independently.
- Models are never coupled to any one service's dependencies.
- The package can be installed directly from a private Bitbucket (or GitHub/GitLab)
  repository — no PyPI account or private registry required.

**Install from a private Bitbucket repo (no publishing needed):**

```bash
# Using HTTPS + app password
pip install "git+https://your-user:your-app-password@bitbucket.org/your-org/myapp-models.git@main"

# Using SSH (recommended for CI/CD — add the deploy key to Bitbucket)
pip install "git+ssh://git@bitbucket.org/your-org/myapp-models.git@main"

# Pin to a tag for reproducible installs
pip install "git+ssh://git@bitbucket.org/your-org/myapp-models.git@v1.2.0"
```

**In `requirements.txt`:**

```txt
# Pinned to a release tag — change the tag when you want to upgrade
myapp-models @ git+ssh://git@bitbucket.org/your-org/myapp-models.git@v1.2.0
```

**Minimal models package layout:**

```
myapp-models/
├── pyproject.toml
└── myapp_models/
    ├── __init__.py
    ├── entities/
    │   ├── base.py      # SQLAlchemy DeclarativeBase + Base (id, timestamps)
    │   ├── user.py      # User entity
    │   └── account.py   # Account entity
    └── schemas/
        ├── user.py      # UserModel (Pydantic)
        └── account.py   # AccountModel (Pydantic)
```

**Then in any service:**

```python
from myapp_models.user import UserEntity, UserModel
```

---

### Alternative: `models/` package inside this project

If a separate repo isn't practical yet (early stage, solo project, strict monorepo),
put a `models/` package at the project root. Services import from it directly.
The structure mirrors the separate-repo layout exactly, so extracting it later
is a rename (`from models.x` → `from myapp_models.x`) and nothing else changes.

```
my-api/
├── models/
│   ├── __init__.py
│   ├── entities/
│   │   ├── base.py      # SQLAlchemy DeclarativeBase + Base (id, timestamps)
│   │   ├── user.py      # User entity
│   │   └── account.py   # Account entity
│   └── schemas/
│       ├── user.py      # UserModel (Pydantic)
│       └── account.py   # AccountModel (Pydantic)
├── services/
│   └── users/
│       └── service.py   # from models.entities.user import User
│                        # from models.schemas.user  import UserModel
```

**Sharing across repos without a dedicated models package is not clean.**
The practical options — installing the whole app as a package (brings all its
dependencies), copying files, or running a private PyPI registry — all have
meaningful downsides. At that point, extracting `models/` into its own repo is
the right call.

---

## Getting Started

**Requirements:** Python 3.11+

### 1. Open your workspace in VS Code

Open VS Code, select your workspace folder, and open an integrated terminal (`Terminal → New Terminal`).

### 2. Create a folder for your API service

```bash
mkdir my-api
cd my-api
```

### 3. Create a virtual environment

**macOS / Linux:**
```bash
python -m venv .venv
```

**Windows:**
```powershell
python -m venv .venv
```

### 4. Activate the virtual environment

**macOS / Linux:**
```bash
source .venv/bin/activate
```
**Windows:**
```bash
source .venv/Scripts/activate
```

**Windows:**
```powershell
.venv\Scripts\activate
```

Your terminal prompt will show `(.venv)` once active.

### 5. Install PaperDraft

```bash
pip install paper-draft
```

`paper-core` is installed automatically as a dependency.

### 6. Verify the install

```bash
draft --help
```

You should see the full list of available `draft` commands. You're ready to build.

---

## 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
ENV=dev
```

**`.env.dev`** (created automatically by `draft init`):
```env
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.

```bash
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
base64 -w 0 private.pem   # → ENCRYPTION_PRIVATE_KEY
base64 -w 0 public.pem    # → ENCRYPTION_PUBLIC_KEY
```

---

## CLI Reference

### `draft init`

Initialises a PaperDraft project in the current directory.

```bash
draft init
draft init --port 9000
draft init --port 9000 --description "My API"
```

**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 (RSA keys auto-generated)
├── services/          # Your services live here
└── .venv/             # Python virtual environment (auto-created)
```

**Then:**
```bash
# Fill in .env.dev with your DB connection string
# Activate: source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows)
```

---

### `draft service new <name>`

Scaffolds a new service and registers it in `main.py`.

```bash
draft service new users                              # prefix defaults to /users
draft service new authenticate --prefix /auth        # explicit prefix
draft service new order -p /v1/orders
draft service new users --model account              # wire Account entity + AccountModel
draft service new users -m AccountSubscription       # case-insensitive
```

**Creates:**
```
services/users/
├── __init__.py
├── router.py       # FastAPI APIRouter — HTTP layer only, routes + auth
├── controller.py   # UsersController — input validation + orchestration
└── service.py      # UsersService — DB access and business logic
```

Patches `main.py` with the import and `app.include_router()` call automatically.

**`--model NAME`** — when provided, all three files use the named model instead of `Dict[str, Any]` stubs:
- Imports `Account` from `models/entities/account.py` and `AccountModel` from `models/schemas/account.py`
- Route signatures and return types use `AccountModel`
- Service DB calls (`create`, `retrieve`, `single`, `update`, `delete`, `filter`) are wired and active — no `raise NotImplementedError`

Accepts any casing — `account`, `Account`, `account_subscription`, `AccountSubscription` all work.

---

### `draft model init`

Initialises the `models/` package inside an existing project.

```bash
draft model init
```

**Creates:**
```
models/
├── __init__.py          # package root
├── entities/
│   ├── base.py          # SQLAlchemy Base + full column/relationship reference
│   └── __init__.py      # entity exports accumulate here
└── schemas/
    ├── base.py          # BaseSchema (from_attributes=True) + Pydantic field reference
    └── __init__.py      # schema exports accumulate here
```

Run once per project. Then use `draft model entity` and `draft model schema` to add files.

---

### `draft model new <name>`

Scaffolds both an entity and a schema in one step. Equivalent to running `draft model entity` and `draft model schema` with the same arguments.

```bash
draft model new account
draft model new account_subscription --section "Master DB — Billing"
```

**Creates:**
- `models/entities/account.py` — `class Account(Base)`
- `models/schemas/account.py` — `class AccountModel(BaseSchema)`
- Registers both in their respective `__init__.py` under the same section

---

### `draft model entity <name>`

Scaffolds a SQLAlchemy entity in `models/entities/`.

```bash
draft model entity account
draft model entity account_subscription --section "Master DB — Billing"
```

**Creates** `models/entities/account.py` with:
- `class Account(Base)` inheriting `id`, `created_at`, `updated_at`
- Inline commented examples for every column category (strings, numerics, booleans, timestamps, FKs, relationships, constraints)
- Full reference documentation lives in `models/entities/base.py` — read once, applies to every entity

Adds `from models.entities.account import Account` to `models/entities/__init__.py`.

**`--section`** sets the comment header label (e.g. `"Master DB — Core"`, `"Tenant DB — Billing"`).
Use the same label for the matching schema so paired files are visually obvious.

---

### `draft model schema <name>`

Scaffolds a Pydantic schema in `models/schemas/`.

```bash
draft model schema account
draft model schema account_subscription --section "Master DB — Billing"
```

**Creates** `models/schemas/account.py` with:
- `class AccountModel(BaseSchema)` — inherits `from_attributes=True`, no repeated config
- Inline commented examples for every field category (required, optional, FK, nested, sensitive)
- Full reference lives in `models/schemas/base.py` — `Field()`, validators, split patterns, `model_config` options

Adds `from models.schemas.account import AccountModel` to `models/schemas/__init__.py`.

---

### `draft service validate <service>`

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

```bash
draft service validate users
draft service 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.

```bash
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

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

# 2. Fill in .env.dev with your DB connection string

# 3. Initialise the models package
draft model init

# 4. Add an entity and its matching schema (both at once)
draft model new account --section "Master DB — Core"
# Define columns in models/entities/account.py
# Mirror them as fields in models/schemas/account.py

# 5. Add a service
draft service new users

# 6. Define routes in services/users/router.py
# 7. Validate + orchestrate in services/users/controller.py
# 8. Implement DB logic in services/users/service.py
#    Import from models: from models.entities.account import Account
#                        from models.schemas.account  import AccountModel

# 9. Validate the service
draft service validate users

# 10. 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:

```python
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:

```python
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)
├── models/                   # Entities + Pydantic schemas (see Models & Entities above)
│   ├── __init__.py           # or install myapp-models as a package instead
│   ├── entities/             # SQLAlchemy ORM classes
│   │   ├── base.py           # DeclarativeBase + Base (id, created_at, updated_at)
│   │   └── account.py        # Account entity
│   └── schemas/              # Pydantic schemas
│       ├── base.py           # BaseSchema + Pydantic field/validator reference
│       └── account.py        # AccountModel schema
├── services/
│   ├── users/
│   │   ├── __init__.py
│   │   ├── router.py
│   │   ├── controller.py
│   │   └── service.py
│   └── auth/
│       ├── __init__.py
│       ├── router.py
│       ├── controller.py
│       └── service.py
└── .venv/
```

---

## 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` |
