Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

saython-0.1.2.tar.gz (21.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

saython-0.1.2-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file saython-0.1.2.tar.gz.

File metadata

  • Download URL: saython-0.1.2.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

Hashes for saython-0.1.2.tar.gz
Algorithm Hash digest
SHA256 613a8771c3eaf743a2c56001461a36be56bb0809a8c2d4e5325c3f7ca61d466f
MD5 76d095dee4af0b3346878b9f017419f7
BLAKE2b-256 f0fa1ba66496a30239e4e551242ae908d814a78176e20a00d2b0b712d1500ea4

See more details on using hashes here.

File details

Details for the file saython-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: saython-0.1.2-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

Hashes for saython-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a30cb333eaf31cae1d99cf40ba939e6aa8fe300bfac9d57ad9f5e0066dbaa058
MD5 d839c725b4fefee1f40b0cf6fa1e5923
BLAKE2b-256 4e2a9c7b27810f53b8c176defde121fc55b5cfdd4dba3373107704dff34694cb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page