Core runtime library for the PaperDraft framework
Project description
paper-core
Runtime library for the PaperDraft framework. Provides auth, database, encryption, email, middleware, error handling, and audit logging — all importable under the paper.core namespace.
Status: pre-release · v0.1 in progress · Paper Plane Consulting LLC
Table of Contents
Installation
pip install paper-core
Requirements: Python 3.11+
Module Reference
paper.core.auth
JWT signing, password hashing, and FastAPI authentication dependencies.
from paper.core.auth import (
Auth, # signs JWT access + refresh token pairs
Password, # Argon2 hash and verify
Authenticate, # FastAPI dep — validates JWT from header or cookie
Authorize, # FastAPI dep — validates JWT + enforces RBAC roles
LoginAttemptLimit, # FastAPI dep — rate limits login attempts per IP
set_auth_params, # call once at startup
set_login_attempt_params,
Claims, Key, # token decode utilities
Credentials, Signature, # request/response models
Algorithm, TokenType, ClaimsKey, AuthErrorMessage,
)
Startup wiring (in dependencies.py):
from paper.core.auth import set_auth_params, set_login_attempt_params
from paper.core.auth.enums import Algorithm
from datetime import timedelta
set_auth_params({
"public_key": config.ENCRYPTION.PUBLIC_KEY,
"excluded_paths": ["/auth/login", "/auth/refresh", "/health"],
"alg": Algorithm.RS256,
})
set_login_attempt_params(max_attempts=5, lockout_duration=timedelta(minutes=15))
Route usage:
from paper.core.auth import Authenticate, Authorize
@router.get("/me")
async def me(claims = Depends(Authenticate())):
return claims
@router.delete("/{id}")
async def delete(claims = Depends(Authorize(["admin"]))):
...
paper.core.db
Async SQLAlchemy PostgreSQL repository with optional multi-tenant pool management.
from paper.core.db import (
Postgres, # async SQLAlchemy repository
ConnectionPoolConfig, # pool settings
Repository, # abstract base — extend to add engines
FilterType, # EQUAL, LIKE, IN, NOT_IN, etc.
MultiTenantPoolManager, # one pool per tenant DSN
MultiTenantDbDependency, # FastAPI dep — resolves tenant DB from JWT
)
Single-tenant setup:
from paper.core.db import Postgres, ConnectionPoolConfig
db = Postgres(
connection_string = config.POSTGRES_CONN_STRING,
config = ConnectionPoolConfig(
future=True, size=10, max_overflow=5,
recycle_after=3600, timeout=30, pre_ping=True,
),
)
Filtering:
results = await db.retrieve(
UserEntity, UserModel,
filter={FilterType.EQUAL: {"is_active": True}},
)
paper.core.security
RSA-OAEP encryption and random code generation.
from paper.core.security import Crypto, RSACrypto, Hasher, Pem, Encoding
# Static utility (key passed per call)
cipher = Crypto.encrypt_urlsafe(dsn, public_key_b64)
dsn = Crypto.decrypt_urlsafe(cipher, private_key_b64)
# Instance-based (keys injected once)
crypto = RSACrypto(public_key_b64, private_key_b64)
cipher = crypto.encrypt_urlsafe(dsn)
# Random codes (invites, resets)
code = Hasher.generate(8) # e.g. "aB3xZ9mK"
Encoding PEM keys for .env storage:
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
Or with Pem:
from paper.core.security import Pem
print(Pem.to_base64("private.pem"))
paper.core.middleware
from paper.core.middleware import (
HipaaResponseHeaders, # X-Frame-Options, CSP, HSTS, etc.
RequestIdMiddleware, # X-Request-ID on every request
RequestLoggingMiddleware, # structured request/response logging
)
Registration order in main.py:
app.add_middleware(CORSMiddleware, ...)
app.add_middleware(HipaaResponseHeaders)
app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(RequestIdMiddleware) # executes first
Access the request ID downstream:
request.state.request_id
paper.core.errors
from paper.core.errors import ErrorHandler, ErrorMessage
ErrorHandler.handle(404, f"{ErrorMessage.NOT_FOUND.value} user {id}")
ErrorHandler.handle(401, "Unauthorized", {"WWW-Authenticate": "Bearer"})
All service layers raise through ErrorHandler — never construct HTTPException directly.
paper.core.audit
Generic audit logger. Accepts any DB, entity, and model — no framework-level schema dependency.
from paper.core.audit import Audit, AuditAction, AuditOutcome
audit = Audit(db=db, entity=AuditLogEntity, model=AuditLogModel)
await audit.log(
event_type = LoginEvent.SUCCESS,
action = AuditAction.ATTEMPT,
outcome = AuditOutcome.SUCCESS,
email = credentials.email,
ip_address = request.client.host,
user_agent = request.headers.get("user-agent"),
)
Audit failures are swallowed silently and logged — they never block the calling operation.
paper.core.email
SMTP email with framework lifecycle templates and themeable HTML.
from paper.core.email import (
SMTPEmailService,
Body, Subject, EmailTheme, EmailBodyParam,
)
Setup:
email = SMTPEmailService(
smtp_host = config.SMTP_HOST,
smtp_port = config.SMTP_PORT,
smtp_username = config.SMTP_USERNAME,
smtp_password = config.SMTP_PASSWORD,
sender_name = "My App",
sender_email = "noreply@myapp.com",
)
Sending with a built-in template:
body = Body(
subject = Subject.RESET_PASSWORD,
data = {
EmailBodyParam.REDIRECT_URL.value: reset_url,
EmailBodyParam.RESET_CODE.value: code,
},
)
email.send(
subject = Subject.RESET_PASSWORD.value,
recipient_name = user.name,
recipient_email = user.email,
text = body.text,
html = body.html,
)
Sending with a custom template:
email.send(
subject = "Your invoice is ready",
recipient_name = user.name,
recipient_email = user.email,
text = "Hi, your invoice is attached.",
html = "<p>Hi, your invoice is attached.</p>",
)
Theming the built-in templates:
Pass an EmailTheme to Body, or set environment variables:
theme = EmailTheme(
primary_color = "#c0392b",
button_color = "#c0392b",
logo_url = "https://cdn.myapp.com/logo.png",
company_name = "My App",
)
body = Body(Subject.ACCOUNT_CREATED, data, theme)
EMAIL_THEME_PRIMARY_COLOR=#0057a8
EMAIL_THEME_BUTTON_COLOR=#0057a8
EMAIL_THEME_LOGO_URL=https://cdn.myapp.com/logo.png
EMAIL_THEME_COMPANY_NAME=My App
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file paper_core-0.1.15.tar.gz.
File metadata
- Download URL: paper_core-0.1.15.tar.gz
- Upload date:
- Size: 35.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
818e026cefae4e0de10f140d711589ab3c1eee0c7675fbdaf490a977287b77a6
|
|
| MD5 |
99e41f46b74bc76cfc0625b985a19f07
|
|
| BLAKE2b-256 |
7e2eb0c1d9e36611193ec49d5a1dd30a8abf4ba0cb9687c31b0080c88481fa5a
|
File details
Details for the file paper_core-0.1.15-py3-none-any.whl.
File metadata
- Download URL: paper_core-0.1.15-py3-none-any.whl
- Upload date:
- Size: 35.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ff0d9660242ffe84e69b3883ee949c7d185e84f8158ba567b064c76c0dc5a86
|
|
| MD5 |
b2418f77c26b4c3c15df3c3cecd7ceff
|
|
| BLAKE2b-256 |
2a11e4e98d5cdd74761f818721c9c7ddd66169970da47b30893377cece431fff
|