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.
@@ -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` |