Metadata-Version: 2.4
Name: flask-appkit
Version: 0.2.0
Summary: Reusable building blocks for Flask apps: JWT auth, response helpers, pagination, and owner-scoped CRUD.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: Flask>=3.0.0
Requires-Dist: Flask-SQLAlchemy>=3.1.1
Requires-Dist: PyJWT>=2.8.0
Requires-Dist: bcrypt>=4.1.2
Requires-Dist: Flask-Limiter>=3.5.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"

# flask-appkit

Reusable building blocks for **Flask** applications. Extracted from the
workout-notes project, these are the proven patterns for JWT auth, JSON
response helpers, pagination, and owner-scoped CRUD.

## Install

```bash
pip install flask-appkit
# or, for local development:
pip install -e ".[dev]"
```

Compatible with Python 3.10+, Flask, and Flask-SQLAlchemy.

## Modules

- **`flask_appkit.auth`** — bcrypt password hashing and JWT token creation/validation.
- **`flask_appkit.decorators`** — `token_required` view guard that resolves the user from the token.
- **`flask_appkit.responses`** — `api_ok` / `api_error` JSON helpers.
- **`flask_appkit.pagination`** — paginate a SQLAlchemy query into a JSON-safe dict.
- **`flask_appkit.crud`** — `OwnerScopedCRUD` base class for owner-scoped CRUD services.
- **`flask_appkit.base`** — `BaseModel` mixin: `id` + `created_at`/`updated_at` columns and default `to_dict()`.
- **`flask_appkit.rate_limit`** — Flask-Limiter setup helper.
- **`flask_appkit.health`** — liveness endpoint factory (optional DB check).

## Quick start

Configure auth in your app factory:

```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.update(
    SECRET_KEY="a-very-long-secret-key---at-least-32-bytes",
    ACCESS_TOKEN_EXPIRE_MINUTES=30,
)
db = SQLAlchemy(app)
```

Define an owner-scoped CRUD service for a model:

```python
from flask_appkit.crud import OwnerScopedCRUD

class Note(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, nullable=False, index=True)
    title = db.Column(db.String(200), nullable=False)

    def to_dict(self):
        return {"id": self.id, "user_id": self.user_id, "title": self.title}

class NoteService(OwnerScopedCRUD):
    model = Note


def resolve_user(user_id):
    return db.session.get(User, int(user_id))  # swap in your User model
```

Protect a route with JWT and return consistent responses:

```python
from flask import request, jsonify
from flask_appkit.auth import create_access_token
from flask_appkit.crud import OwnerScopedCRUD
from flask_appkit.responses import api_ok, api_error
from flask_appkit.decorators import token_required
from flask_appkit.pagination import pagination_meta

service = NoteService()

@app.route("/login", methods=["POST"])
def login():
    # ...verify credentials, then:
    token = create_access_token(user.id, email=user.email)
    return api_ok({"token": token}, "Logged in.")

@app.route("/notes")
@token_required(resolve_user)
def list_notes(current_user):
    result = service.list(current_user.id, page=request.args.get("page", 1, type=int))
    return jsonify({
        "data": [service.to_dict(n) for n in result["items"]],
        "pagination": pagination_meta(result),
    })
```

## Development

Run the test suite (requires `.[dev]`):

```bash
pytest
```

Coverage is enforced at 80% via `--cov-fail-under`.

## Releasing

Releases are driven by the bundled playbook (requires dev deps + `build` + `twine`):

```bash
pip install -e ".[dev]" build twine

cd flask-appkit
# PYPI_TOKEN is a PyPI API token with upload scope for this project
PYPI_TOKEN=<token> ./scripts/release.sh minor
```

`<part>` is `patch`, `minor`, or `major`. The script runs the tests, bumps
`__version__`, prompts you to update the CHANGELOG, builds, twine-checks,
uploads to PyPI, and commits + tags `vX.Y.Z`. Publishing is an explicit,
reviewed act — the script confirms the CHANGELOG before uploading.
