A beginner-friendly Python web framework for CRUD apps with auth and ORM
Project description
SAYTHON
A beginner-friendly Python web framework focused on CRUD apps — with built-in ORM and authentication.
Zero hard dependencies. Pure Python standard library + optional extras.
Install
pip install saython
# or from source:
git clone <repo>
pip install -e .
Optional extras for better performance:
pip install PyJWT bcrypt # faster JWT + stronger password hashing
pip install psycopg2-binary # PostgreSQL support
pip install gunicorn # production WSGI server
Quickstart
from saython import Saython, Model, Field, auth_required, get_auth_router, Router, Response
app = Saython(__name__)
class Book(Model):
title = Field(str, required=True)
author = Field(str, required=True)
year = Field(int, default=2024)
# Mount auth routes
app.include("/auth", get_auth_router())
# Write your own routes
book_router = Router()
@book_router.get("/")
def list_books(req):
return Book.all_as_dicts()
@book_router.post("/")
@auth_required
def create_book(req):
book = Book.create(**req.json)
return Response(book.to_dict(), 201)
@book_router.get("/<id>")
def get_book(req):
return Book.get(int(req.params["id"])).to_dict()
@book_router.delete("/<id>")
@auth_required
def delete_book(req):
Book.get(int(req.params["id"])).delete()
return {"message": "deleted"}
app.include("/books", book_router)
@app.get("/")
def index(req):
return {"message": "Hello from SAYTHON!"}
app.run(debug=True)
| Method | Path | Description |
|---|---|---|
| POST | /auth/register | Register user |
| POST | /auth/login | Login → get token |
| GET | /auth/me | Current user (token) |
| GET | /books | List all books |
| POST | /books | Create (auth) |
| GET | /books/<id> | Get one book |
| DELETE | /books/<id> | Delete (auth) |
| GET | / | Custom endpoint |
CLI
saython new my-blog # scaffold a new project
cd my-blog
python main.py # run it
saython run # same, from CLI
saython routes # print all registered routes
ORM
from saython import Model, Field
class Post(Model):
title = Field(str, required=True)
content = Field(str, required=True)
views = Field(int, default=0)
published = Field(bool, default=False)
# Every Model gets: id, created_at, updated_at automatically.
CRUD operations
# Create
post = Post.create(title="Hello", content="World")
# Read
post = Post.get(1) # by id (NotFound if missing)
posts = Post.all() # list all
posts = Post.filter(published=True) # equality filter
page = Post.paginate(page=1, limit=10) # pagination dict
post = Post.first(title="Hello") # first match or None
count = Post.count()
exists = Post.exists(title="Hello")
# Update
post.title = "Updated"
post.save()
# Delete
post.delete()
# Serialise
post.to_dict() # all fields as dict
post.to_dict(exclude=["content"]) # exclude fields
Switch database
# SQLite (default):
app.configure(DATABASE_URL="myapp.db")
# PostgreSQL:
app.configure(DATABASE_URL="postgresql://user:pass@localhost/dbname")
# MySQL:
app.configure(DATABASE_URL="mysql://user:pass@localhost/dbname")
Authentication
Register & Login
POST /auth/register
{ "username": "alice", "email": "alice@example.com", "password": "secret123" }
POST /auth/login
{ "username": "alice", "password": "secret123" }
→ { "token": "eyJ...", "user": { "id": 1, "username": "alice", ... } }
Protect endpoints
from saython import auth_required, admin_required
@app.get("/dashboard")
@auth_required
def dashboard(req):
return {"hello": req.user.username}
@app.delete("/admin/wipe")
@admin_required # is_admin=True required
def wipe(req):
return {"done": True}
Send the token in requests:
Authorization: Bearer eyJ...
User model
from saython.auth import User
user = User.register(username="bob", email="bob@x.com", password="pass123")
user = User.authenticate("bob", "pass123")
token = user.generate_token()
user.to_dict() # password field automatically excluded
Custom Routing
@app.get("/items")
def list_items(req):
page = int(req.query.get("page", 1))
limit = int(req.query.get("limit", 20))
return Item.paginate(page=page, limit=limit)
@app.post("/items")
def create_item(req):
data = req.json # parsed JSON body
item = Item.create(**data)
return item.to_dict(), 201
@app.get("/items/<id>")
def get_item(req):
return Item.get(int(req.params["id"])).to_dict()
@app.delete("/items/<id>")
@auth_required
def delete_item(req):
Item.get(int(req.params["id"])).delete()
return {"deleted": True}
Sub-Routers
from saython import Router
book_router = Router()
@book_router.get("/")
def list_books(req): ...
@book_router.post("/")
def create_book(req): ...
app.include("/books", book_router)
Configuration
app.configure(
SECRET_KEY="very-secret", # JWT signing key
DATABASE_URL="myapp.db", # DB connection
DEBUG=True, # show tracebacks
CORS=True, # enable CORS headers
CORS_ORIGINS=["https://myapp.com"],
TOKEN_EXPIRY_HOURS=48, # JWT lifetime
HOST="0.0.0.0",
PORT=8080,
)
Middleware & Hooks
@app.before_request
def log_request(req):
print(f"→ {req.method} {req.path}")
@app.after_request
def add_header(req, resp):
resp.headers["X-Powered-By"] = "SAYTHON"
@app.error_handler(404)
def not_found(req, err):
return {"error": "Page not found", "path": req.path}, 404
Production
pip install gunicorn
gunicorn main:app --bind 0.0.0.0:8000 --workers 4
Project Structure (generated by saython new)
my-project/
├── main.py ← app + routes + model registration
├── models.py ← extra model definitions
├── .env ← secret key, DB URL
├── requirements.txt
└── README.md
License
MIT
Project details
Release history Release notifications | RSS feed
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 saython-0.1.1.tar.gz.
File metadata
- Download URL: saython-0.1.1.tar.gz
- Upload date:
- Size: 21.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9e0640e2894b2de61d4d40bce3a9879097e7942471934526a1d05bf9b3cdf45
|
|
| MD5 |
ea7f1fa17527372114a901c69f6b1269
|
|
| BLAKE2b-256 |
bade3fccd766877e277668c713a3a40a0915496ef28b4e4e9065471dd31d47ac
|
File details
Details for the file saython-0.1.1-py3-none-any.whl.
File metadata
- Download URL: saython-0.1.1-py3-none-any.whl
- Upload date:
- Size: 23.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b0d59558bf52c8dc3867d96d8415c38f8c755f4a31619faaae73c5c351ff8ff
|
|
| MD5 |
2350928f7f081ec3dade2b16378b307e
|
|
| BLAKE2b-256 |
802be60012770fc42db22213949497a24f5554ac78ec2fc6190d2157358fff08
|