Skip to main content

Qivo

Qivo is a small Flask layer for dependency injection, authorization, and response serialization. Each app owns its container, serializer, guards, and view-extension pipeline; it does not choose or depend on a database.

CLI

Initialize a suggested app layout in the current directory, or choose a target directory and Python package name:

uv run qivo init
uv run qivo init --name "Mi lista" --path ./service --package service

qivo init always asks for a required application name unless --name is passed. The scaffold contains a minimal Flask todo list with a Task model, the four CRUD endpoints, qivo.toml, and one HTML template at app/template/app.html, loaded with Flask's normal render_template mechanism. Existing files are left untouched. Edit the TOML settings to configure the database URL, model modules, model base, and migrations directory.

uv run qivo migrate --message "initial schema"
uv run qivo migrate:apply
uv run qivo migrate:revert
uv run qivo migrate:revert base

Migration commands accept --config, --database-url, --model-base, repeated --models, and --migrations-dir overrides. migrate also supports --empty; migrate:apply defaults to head, and migrate:revert defaults to -1.

from flask import Flask

from qivo import Qivo

app = Flask(__name__)
qivo = Qivo(app)


class Greeting:
	def __init__(self):
		self.message = "Hello"


qivo.container.register(Greeting)


@app.route("/greeting", methods=["GET"])
@qivo.view()
def greeting(service: Greeting):
	return {"message": service.message}

Authorization

Implement Authenticator against the identity source used by your application, then add it to the app's guards mapping. Apply authentication or policies per route with auth, guard, and policies:

from qivo.guards import Authenticator, WithAll

qivo.guards["web"] = MyAuthenticator()


@app.route("/admin", methods=["GET"])
@qivo.view(auth=True, policies=[WithAll(["admin:read"])])
def admin_view():
	return {"ok": True}

Extending Views

Add a ViewExtension to customize view handling without changing Qivo or the built-in features. Extensions are applied outside-in in registration order. Pass extension-specific route settings directly to qivo.view(...):

from functools import wraps


class AuditExtension:
	def wrap_view(self, app, view_func, *, options):
		@wraps(view_func)
		def wrapper(*args, **kwargs):
			result = view_func(*args, **kwargs)
			app.app.logger.info("audit category=%s", options.get("audit_category"))
			return result

		return wrapper


qivo.register_view_extension(AuditExtension())


@app.route("/records", methods=["GET"])
@qivo.view(audit_category="records")
def records():
	return []

An extension receives the Qivo instance, the next view callable, and an immutable mapping of Qivo route options. Register extensions before declaring routes so they wrap those routes. Place @qivo.view(...) directly below either @app.route(...) or @blueprint.route(...); Flask handles registration and lifecycle as usual.

SQLAlchemy

Configure the database declaratively in Flask, then attach the SQL extension. SQLEngine reads the URL, engine options, and session options from app.config and configures the model base automatically. Each terminal query opens and closes its own session:

from flask import Flask
from sqlalchemy.orm import Mapped, mapped_column

from qivo import Qivo
from qivo.db.sql import Model
from qivo.db.sql.extensions import SQLEngine

app = Flask(__name__)
app.config.from_mapping(
	SQLALCHEMY_DATABASE_URI="sqlite:///app.db",
	SQLALCHEMY_ENGINE_OPTIONS={},
	SQLALCHEMY_SESSION_OPTIONS={},
)
qivo = Qivo(app)
db = SQLEngine(app)


class User(Model):
	__tablename__ = "users"

	id: Mapped[int] = mapped_column(primary_key=True)
	name: Mapped[str]
	is_active: Mapped[bool]


user = User(name="admin", is_active=True)
user.q.save()

user = User.q.get(user.id)
user.name = "root"
user.q.save()

user.q.delete()  # Returns False if the row no longer exists.

admin = User.q.filter(name="admin").first()
active_admins = User.q.where(User.name == "admin").where(
	User.is_active == True
).all()

Use eager loading for relationships that must be accessed after a query returns, because its session is closed when the operation completes. SQLEngine does not create tables automatically; use the migration commands for schema changes.

Migrations

AlembicMigrations uses Model.metadata and creates a standard migrations/ directory on the first revision. Import all model modules before autogenerating so Alembic can see their tables:

from qivo.db.sql import AlembicMigrations

migrations = AlembicMigrations(engine)
migrations.revision("initial schema")
migrations.upgrade()

The directory and autogeneration options can be customized:

from qivo.db.sql import AlembicMigrations, MigrationConfig

migrations = AlembicMigrations(
	engine,
	config=MigrationConfig(directory="db/migrations", compare_type=True),
)

Use migrations.downgrade() to revert one revision or pass a target such as "base"; use migrations.stamp() to mark a database without applying scripts.

Release files for qivo 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qivo 0.1.1
File Size Uploaded
qivo-0.1.1.tar.gz 12.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qivo 0.1.1
File Interpreter ABI Platform
qivo-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 30.3 kB

Release files / qivo-0.1.1.tar.gz

Download URL qivo-0.1.1.tar.gz
Size 12.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d1dae127b10a6a8d5a9542196fd40e4ba8cd74e7d7714841d6aaddf350563d15
BLAKE2b-256 checksum
How to use checksums
1b4db1c8640d68838486352b543d7e4306380ebeaf90298aa85b94f8b0f15801
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / qivo-0.1.1-py3-none-any.whl

Download URL qivo-0.1.1-py3-none-any.whl
Size 17.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a52c58ea35083fe791a5597744337a340d62be792dad0926c0b289f6ea57ffb6
BLAKE2b-256 checksum
How to use checksums
a4a8d32a5f57139a77fe8fdb6d86c5f0b669335cfbe044324d3ebc8a82e2c91b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page