paper-draft 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paper_draft-0.1.0/PKG-INFO +468 -0
- paper_draft-0.1.0/README.md +453 -0
- paper_draft-0.1.0/paper/__init__.py +0 -0
- paper_draft-0.1.0/paper/draft/cli/__init__.py +0 -0
- paper_draft-0.1.0/paper/draft/cli/commands/__init__.py +2 -0
- paper_draft-0.1.0/paper/draft/cli/commands/init.py +204 -0
- paper_draft-0.1.0/paper/draft/cli/commands/new.py +204 -0
- paper_draft-0.1.0/paper/draft/cli/commands/run.py +101 -0
- paper_draft-0.1.0/paper/draft/cli/commands/validate.py +169 -0
- paper_draft-0.1.0/paper/draft/cli/main.py +23 -0
- paper_draft-0.1.0/paper_draft.egg-info/PKG-INFO +468 -0
- paper_draft-0.1.0/paper_draft.egg-info/SOURCES.txt +16 -0
- paper_draft-0.1.0/paper_draft.egg-info/dependency_links.txt +1 -0
- paper_draft-0.1.0/paper_draft.egg-info/entry_points.txt +2 -0
- paper_draft-0.1.0/paper_draft.egg-info/requires.txt +9 -0
- paper_draft-0.1.0/paper_draft.egg-info/top_level.txt +1 -0
- paper_draft-0.1.0/pyproject.toml +34 -0
- paper_draft-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paper-draft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Opinionated FastAPI toolkit with CLI-driven architecture
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: paper-core
|
|
8
|
+
Requires-Dist: typer
|
|
9
|
+
Requires-Dist: jinja2
|
|
10
|
+
Requires-Dist: rich
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
14
|
+
Requires-Dist: httpx; extra == "dev"
|
|
15
|
+
|
|
16
|
+
# PaperDraft
|
|
17
|
+
|
|
18
|
+
Opinionated Python REST API framework with CLI-driven architecture.
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
22
|
+
> **Status:** pre-release · v0.1 in progress · Paper Plane Consulting LLC
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Table of Contents
|
|
27
|
+
|
|
28
|
+
1. [Installation](#installation)
|
|
29
|
+
2. [Setup](#setup)
|
|
30
|
+
3. [CLI Reference](#cli-reference)
|
|
31
|
+
4. [Building with paper-draft](#building-with-paper-draft)
|
|
32
|
+
5. [Project Structure](#project-structure)
|
|
33
|
+
6. [Extending paper-draft](#extending-paper-draft)
|
|
34
|
+
7. [Architecture](#architecture)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Packages
|
|
39
|
+
|
|
40
|
+
PaperDraft is split into two packages:
|
|
41
|
+
|
|
42
|
+
| Package | Purpose |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `paper-core` | Runtime library — auth, DB, security, middleware, email, errors, audit |
|
|
45
|
+
| `paper-draft` | CLI tooling — project scaffolding and service generation |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
**Requirements:** Python 3.11+
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install paper-draft
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`paper-core` is installed automatically as a dependency.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Local Development Setup
|
|
62
|
+
|
|
63
|
+
**Prerequisites:** Python 3.11+
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 1. Clone the CLI repo
|
|
67
|
+
git clone https://flypaperplane@bitbucket.org/ppc-llc/paper-draft.git
|
|
68
|
+
cd paper-draft
|
|
69
|
+
|
|
70
|
+
# 2. Create and activate a virtual environment
|
|
71
|
+
python -m venv .venv
|
|
72
|
+
source .venv/bin/activate # macOS/Linux
|
|
73
|
+
.venv\Scripts\activate # Windows
|
|
74
|
+
|
|
75
|
+
# 3. Install paper-core
|
|
76
|
+
pip install paper-core
|
|
77
|
+
|
|
78
|
+
# 4. Install paper-draft CLI in editable mode with dev dependencies
|
|
79
|
+
pip install -e ".[dev]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Once installed, the `draft` CLI is available in your environment:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
draft --help
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Setup
|
|
91
|
+
|
|
92
|
+
### Environment Files
|
|
93
|
+
|
|
94
|
+
PaperDraft uses layered `.env` files. The base `.env` sets the active environment, and the environment-specific file is loaded on top.
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
.env ← sets ENV=dev (or staging, production)
|
|
98
|
+
.env.dev ← development values (auto-created by draft init)
|
|
99
|
+
.env.staging ← staging values (create manually)
|
|
100
|
+
.env.production ← production values (create manually)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**`.env`** (minimal — sets the active environment):
|
|
104
|
+
```env
|
|
105
|
+
ENV=dev
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**`.env.dev`** (created automatically by `draft init`):
|
|
109
|
+
```env
|
|
110
|
+
ENV=dev
|
|
111
|
+
APP_URL=http://localhost:8000
|
|
112
|
+
APP_PORT=8000
|
|
113
|
+
|
|
114
|
+
POSTGRES_CONN_STRING=postgresql+asyncpg://user:password@localhost:5432/dbname
|
|
115
|
+
POSTGRES_ADMIN_CONN_STRING=postgresql+asyncpg://user:password@localhost:5432/postgres
|
|
116
|
+
|
|
117
|
+
ENCRYPTION_PUBLIC_KEY=<base64-encoded PEM public key>
|
|
118
|
+
ENCRYPTION_PRIVATE_KEY=<base64-encoded PEM private key>
|
|
119
|
+
|
|
120
|
+
EMAIL_SMTP_HOST=smtp.example.com
|
|
121
|
+
EMAIL_SMTP_PORT=587
|
|
122
|
+
EMAIL_SMTP_USERNAME=your@email.com
|
|
123
|
+
EMAIL_SMTP_PASSWORD=your-smtp-password
|
|
124
|
+
EMAIL_SENDER_NAME=Your App
|
|
125
|
+
EMAIL_SENDER_ADDRESS=no-reply@example.com
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Generating Encryption Keys
|
|
129
|
+
|
|
130
|
+
PaperDraft uses RSA key pairs (RS256) for JWT signing and field-level encryption.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Generate a 2048-bit RSA private key
|
|
134
|
+
openssl genrsa -out private.pem 2048
|
|
135
|
+
|
|
136
|
+
# Derive the public key
|
|
137
|
+
openssl rsa -in private.pem -pubout -out public.pem
|
|
138
|
+
|
|
139
|
+
# Base64-encode each for .env storage
|
|
140
|
+
base64 -w 0 private.pem # → ENCRYPTION_PRIVATE_KEY
|
|
141
|
+
base64 -w 0 public.pem # → ENCRYPTION_PUBLIC_KEY
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## CLI Reference
|
|
147
|
+
|
|
148
|
+
### `draft init <name> --port <port>`
|
|
149
|
+
|
|
150
|
+
Creates a new project in a `<name>/` directory.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
draft init my-api --port 8000
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Creates:**
|
|
157
|
+
```
|
|
158
|
+
my-api/
|
|
159
|
+
├── main.py # FastAPI app entry point
|
|
160
|
+
├── dependencies.py # DB + auth dependency injection
|
|
161
|
+
├── config.py # Pydantic Settings configuration
|
|
162
|
+
├── requirements.txt # App dependencies
|
|
163
|
+
├── .gitignore
|
|
164
|
+
├── .env.dev # Pre-filled environment stub
|
|
165
|
+
├── services/ # Your services go here
|
|
166
|
+
└── .venv/ # Python virtual environment (auto-created)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Then:**
|
|
170
|
+
```bash
|
|
171
|
+
cd my-api
|
|
172
|
+
# Fill in .env.dev with your DB connection string and keys
|
|
173
|
+
# Activate the venv: source .venv/bin/activate (macOS/Linux) or .venv\Scripts\activate (Windows)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### `draft new service <name>`
|
|
179
|
+
|
|
180
|
+
Scaffolds a new service inside an existing project.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
draft new service users
|
|
184
|
+
draft new service users --prefix api/v1/users # custom URL prefix
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Creates:**
|
|
188
|
+
```
|
|
189
|
+
services/users/
|
|
190
|
+
├── __init__.py
|
|
191
|
+
├── router.py # FastAPI APIRouter — define your routes here
|
|
192
|
+
├── controller.py # UsersController — validate + orchestrate
|
|
193
|
+
└── service.py # UsersService — business logic
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Also patches `main.py` to import and register the router automatically.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### `draft validate <service>`
|
|
201
|
+
|
|
202
|
+
Validates a service's structure and `main.py` registration.
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
draft validate users
|
|
206
|
+
draft validate users --quiet # suppress output on success
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Checks:**
|
|
210
|
+
- `services/users/` directory exists
|
|
211
|
+
- `router.py`, `controller.py`, `service.py`, `__init__.py` all present
|
|
212
|
+
- `router.py` references `UsersController`
|
|
213
|
+
- `controller.py` defines `class UsersController`
|
|
214
|
+
- `service.py` defines `class UsersService`
|
|
215
|
+
- `main.py` imports and registers the router
|
|
216
|
+
|
|
217
|
+
Exits `0` on success, `1` on validation errors, `2` if not in a project root.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### `draft run`
|
|
222
|
+
|
|
223
|
+
Runs the app with uvicorn. Port is read from `APP_PORT` in your `.env.{ENV}` file.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
draft run # dev mode — auto-reload enabled
|
|
227
|
+
draft run --prod # production — 4 workers, no reload
|
|
228
|
+
draft run --host 0.0.0.0 --prod
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Building with PaperDraft
|
|
234
|
+
|
|
235
|
+
### Workflow
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# 1. Create a new project
|
|
239
|
+
draft init my-api --port 8000
|
|
240
|
+
cd my-api
|
|
241
|
+
|
|
242
|
+
# 2. Fill in .env.dev
|
|
243
|
+
|
|
244
|
+
# 3. Add a service
|
|
245
|
+
draft new service users
|
|
246
|
+
|
|
247
|
+
# 4. Define your routes in services/users/router.py
|
|
248
|
+
# 5. Implement controller logic in services/users/controller.py
|
|
249
|
+
# 6. Implement business logic in services/users/service.py
|
|
250
|
+
|
|
251
|
+
# 7. Validate
|
|
252
|
+
draft validate users
|
|
253
|
+
|
|
254
|
+
# 8. Run
|
|
255
|
+
draft run
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Layer Responsibilities
|
|
259
|
+
|
|
260
|
+
Each scaffolded service has three layers with strict responsibilities:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
router.py ← HTTP only. Receives request, injects dependencies, returns response.
|
|
264
|
+
No logic. No direct DB access.
|
|
265
|
+
|
|
266
|
+
controller.py ← Validates the request. Orchestrates service calls.
|
|
267
|
+
No business logic. No direct DB access.
|
|
268
|
+
|
|
269
|
+
service.py ← All business logic lives here.
|
|
270
|
+
Calls the DB via injected Postgres instance.
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Dependency Injection
|
|
274
|
+
|
|
275
|
+
`dependencies.py` wires up DB and config — inject them directly in route signatures:
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from dependencies import MasterDb, TenantDb, Configuration
|
|
279
|
+
|
|
280
|
+
@router.get("")
|
|
281
|
+
async def retrieve(
|
|
282
|
+
db: MasterDb,
|
|
283
|
+
config: Configuration,
|
|
284
|
+
claims: Dict[str, Any] = Depends(Authenticate())
|
|
285
|
+
):
|
|
286
|
+
return await UsersController.retrieve(db, config, claims)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Auth
|
|
290
|
+
|
|
291
|
+
Use `Authenticate` for any valid JWT, `Authorize` for role enforcement:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from paper.core.auth import Authenticate, Authorize
|
|
295
|
+
|
|
296
|
+
# Any authenticated user
|
|
297
|
+
claims = Depends(Authenticate())
|
|
298
|
+
|
|
299
|
+
# Role-restricted
|
|
300
|
+
claims = Depends(Authorize(["admin", "manager"]))
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Project Structure
|
|
306
|
+
|
|
307
|
+
A full PaperDraft project looks like this:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
my-api/
|
|
311
|
+
├── main.py # FastAPI app, middleware, router registration
|
|
312
|
+
├── dependencies.py # DB + auth dependency wiring
|
|
313
|
+
├── config.py # Pydantic Settings
|
|
314
|
+
├── requirements.txt
|
|
315
|
+
├── .env # Sets ENV=dev|staging|production
|
|
316
|
+
├── .env.dev # Dev config (never commit)
|
|
317
|
+
├── services/
|
|
318
|
+
│ ├── users/
|
|
319
|
+
│ │ ├── router.py
|
|
320
|
+
│ │ ├── controller.py
|
|
321
|
+
│ │ └── service.py
|
|
322
|
+
│ └── auth/
|
|
323
|
+
│ ├── router.py
|
|
324
|
+
│ ├── controller.py
|
|
325
|
+
│ └── service.py
|
|
326
|
+
└── .venv/
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Extending PaperDraft
|
|
332
|
+
|
|
333
|
+
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.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### Custom Database Engine
|
|
338
|
+
|
|
339
|
+
Extend `Repository` to add a new DB engine (MySQL, MongoDB, etc.).
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from paper.core.db.base import Repository, FilterType
|
|
343
|
+
from typing import Any, Dict, List, Optional
|
|
344
|
+
|
|
345
|
+
class MySQLRepository(Repository[T, M]):
|
|
346
|
+
|
|
347
|
+
def __init__(self, connection_string: str) -> None:
|
|
348
|
+
# set up your engine here
|
|
349
|
+
...
|
|
350
|
+
|
|
351
|
+
async def create(self, entity, model, data):
|
|
352
|
+
...
|
|
353
|
+
|
|
354
|
+
async def retrieve(self, entity, model, filter=None):
|
|
355
|
+
...
|
|
356
|
+
|
|
357
|
+
async def single(self, entity, model, id):
|
|
358
|
+
...
|
|
359
|
+
|
|
360
|
+
async def update(self, entity, model, id, data):
|
|
361
|
+
...
|
|
362
|
+
|
|
363
|
+
async def delete(self, entity, id):
|
|
364
|
+
...
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Inject it the same way as `Postgres` via `dependencies.py`.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Custom Encryption Algorithm
|
|
372
|
+
|
|
373
|
+
Extend `BaseCrypto` to add a new cipher (AES, ChaCha20, etc.).
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
from paper.core.security.base import BaseCrypto
|
|
377
|
+
|
|
378
|
+
class AESCrypto(BaseCrypto):
|
|
379
|
+
|
|
380
|
+
def __init__(self, key: str) -> None:
|
|
381
|
+
self._key = key
|
|
382
|
+
|
|
383
|
+
def encrypt(self, value: str) -> str:
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
def decrypt(self, cipher: str) -> str:
|
|
387
|
+
...
|
|
388
|
+
|
|
389
|
+
def encrypt_urlsafe(self, value: str) -> str:
|
|
390
|
+
...
|
|
391
|
+
|
|
392
|
+
def decrypt_urlsafe(self, cipher: str) -> str:
|
|
393
|
+
...
|
|
394
|
+
|
|
395
|
+
def encrypt_raw(self, value: str) -> bytes:
|
|
396
|
+
...
|
|
397
|
+
|
|
398
|
+
def decrypt_raw(self, cipher_bytes: bytes) -> str:
|
|
399
|
+
...
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Pass your implementation to `MultiTenantDbDependency` or anywhere `BaseCrypto` is accepted:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
crypto = AESCrypto(key=config.ENCRYPTION.KEY.SYMMETRIC)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
### Custom Email Provider
|
|
411
|
+
|
|
412
|
+
Extend `BaseEmailService` to add a new provider (SendGrid, SES, Postmark, etc.).
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
from paper.core.email.base import BaseEmailService
|
|
416
|
+
from typing import Dict
|
|
417
|
+
|
|
418
|
+
class SendGridEmailService(BaseEmailService):
|
|
419
|
+
|
|
420
|
+
def __init__(self, api_key: str, sender_name: str, sender_email: str) -> None:
|
|
421
|
+
self._api_key = api_key
|
|
422
|
+
self._sender_name = sender_name
|
|
423
|
+
self._sender_email = sender_email
|
|
424
|
+
|
|
425
|
+
def send(
|
|
426
|
+
self,
|
|
427
|
+
subject: str,
|
|
428
|
+
recipient_name: str,
|
|
429
|
+
recipient_email: str,
|
|
430
|
+
data: Dict[str, str],
|
|
431
|
+
) -> bool:
|
|
432
|
+
# implement SendGrid API call here
|
|
433
|
+
...
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Instantiate in `dependencies.py` and inject wherever email is needed.
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Architecture
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
HTTP Request
|
|
444
|
+
↓
|
|
445
|
+
[ Router ] receives request, injects dependencies, returns response
|
|
446
|
+
↓
|
|
447
|
+
[ Auth Middleware ] JWT validation + RBAC — always first, never skipped
|
|
448
|
+
↓
|
|
449
|
+
[ Custom Middleware ] CORS, HIPAA headers, rate limiting, audit logging
|
|
450
|
+
↓
|
|
451
|
+
[ Controller ] validates request, orchestrates service calls
|
|
452
|
+
↓
|
|
453
|
+
[ Service ] all business logic lives here
|
|
454
|
+
↓
|
|
455
|
+
[ Postgres / DB ] async SQLAlchemy — injected, never instantiated directly
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Core modules available via `paper.core`:**
|
|
459
|
+
|
|
460
|
+
| Module | Contents |
|
|
461
|
+
|--------|----------|
|
|
462
|
+
| `paper.core.auth` | `Authenticate`, `Authorize`, `LoginAttemptLimit`, `Password`, JWT utils |
|
|
463
|
+
| `paper.core.db` | `Postgres`, `Repository`, `FilterType`, `MultiTenantDbDependency` |
|
|
464
|
+
| `paper.core.security` | `RSACrypto`, `BaseCrypto`, `Crypto`, `Hasher`, `Pem` |
|
|
465
|
+
| `paper.core.email` | `SMTPEmailService`, `BaseEmailService`, `Subject`, `EmailTheme` |
|
|
466
|
+
| `paper.core.errors` | `ErrorHandler`, `ErrorMessage` |
|
|
467
|
+
| `paper.core.middleware` | `HipaaResponseHeaders`, `RequestLoggingMiddleware`, `RequestIdMiddleware` |
|
|
468
|
+
| `paper.core.audit` | `Audit` |
|