GINO Documentation

repository·master·Indexed 25 days ago

https://github.com/python-gino/gino

GINO (GINO Is Not ORM) is a lightweight asynchronous ORM for Python's asyncio, built on top of SQLAlchemy core. It provides asynchronous CRUD capabilities and connection management while utilizing SQLAlchemy's query grammar. Version 1.1.0-rc.1 supports Python 3.6 through 3.9 and integrates with asyncpg for PostgreSQL.

Tokens
29.7K
Snippets
84
Records
151
Agent score
83%

What's inside gino

  1. Overview of GINO features

    master

    GINO (GINO Is Not ORM) is a lightweight asynchronous ORM for Python. Key features include:

    • SQLAlchemy Integration: Uses SQLAlchemy core for query building and supports the SQLAlchemy ecosystem (e.g., Alembic for migrations).
    • Asynchronous Engine: Provides an asynchronous SQLAlchemy-alike engine, connection, and dialect API.
    • Model Support: Asynchronous-friendly CRUD objective models.
    • Database Support: Robust PostgreSQL JSONB support. Supports PostgreSQL with asyncpg and MySQL with aiomysql.
    • Management: Well-considered contextual connection and transaction management.
    • Framework Compatibility: Community support for Starlette/FastAPI, aiohttp, Sanic, Tornado, and Quart.
  2. Overview of GINO usage modes

    master

    GINO supports three levels of integration depending on your needs:

    1. Minimalist: Use GINO only for asynchronous execution while following SQLAlchemy Core principles.
    2. Table-only: Define tables without using object mapping (avoids 'objects' entirely).
    3. Full-power: Use the non-typical asynchronous ORM features for maximum convenience.
  3. Understand the GINO Engine and Connection architecture

    master

    The gino.engine.GinoEngine is the core component of GINO. It manages a pool of connections and is associated with a specific dialect (e.g., asyncpg).

    Key architectural behaviors:

    • Connection Pooling: The engine uses the dialect to create a database connection pool.
    • Connection Wrapper: The engine produces GinoConnection instances, which are many-to-one wrappers around raw connections to support reuse and lazy features.
    • Execution: SQLAlchemy queries can be executed directly on the engine or a connection. When executed on the engine, it acquires a connection, executes the query, and immediately releases the connection once the result is returned. This differs from vanilla SQLAlchemy, where connections might be held until results are exhausted.
    • Implicit Execution: GINO supports implicit execution by binding an engine to a db instance (a gino.api.Gino instance). Models can also perform implicit execution if their associated db instance has a bind.
  4. Set up Alembic migrations with GINO

    master

    Alembic can be used with GINO to manage database migrations. Follow these steps to integrate them:

    1. Install Alembic:

      pip install --user alembic
    2. Initialize Alembic: Run this command from your project's root directory (where your application code resides):

      alembic init alembic
    3. Configure Database URL: Open alembic.ini and update the sqlalchemy.url property with your database credentials:

      sqlalchemy.url = postgres://{{username}}:{{password}}@{{address}}/{{db_name}}
    4. Link GINO Models to Alembic: Open alembic/env.py and perform the following:

      • Import your Gino() instance (the db object) from your models module.
      • Set target_metadata to your db object.

    Example alembic/env.py configuration:

    from main_app.models import db
    
    # ... other code ...
    
    target_metadata = db

    Note: All alembic commands must be executed from the directory containing the alembic.ini file.

    alembic init alembic
  5. Implement One-to-Many relationships with distinct loaders

    master

    To build one-to-many relationships, use the distinct() method on a model loader. This allows GINO to combine multiple rows into a single parent instance by using a setter or method to collect children.

    When using Parent.distinct(Parent.id).load(add_child=Child), GINO reuses the same parent instance for rows with the same ID and calls the add_child setter/method for each associated child found in the result set.

    query = Child.outerjoin(Parent).select()
    parents = await query.gino.load(
        Parent.distinct(Parent.id).load(add_child=Child)
    ).all()
  6. Implement Self-Referencing relationships

    master

    To load a tree-like structure (where a model references itself), you must use an alias for the parent model to avoid name collisions in the SQL join. This is currently an experimental feature.

    Use Model.alias() to create the alias and .on() to define the self-referencing join condition.

    class Category(db.Model):
        __tablename__ = 'categories'
        id = db.Column(db.Integer, primary_key=True)
        parent_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    
    # Load leaf categories with their parents
    Parent = Category.alias()
    query = Category.load(parent=Parent.on(
        Category.parent_id == Parent.id
    )).where(
        ~Category.id.in_(db.select([Category.alias().parent_id]))
    )
    
    async for c in query.gino.iterate():
        print(f'Leaf: {c.id}, Parent: {c.parent.id}')
  7. Integrate GINO with Starlette

    master

    You can integrate GINO with a Starlette application using two patterns: direct initialization or an application factory pattern. The gino-starlette extension provides middleware that handles database setup and cleanup based on the provided configuration.

    from starlette.applications import Starlette
    from gino.ext.starlette import Gino
    
    # Pattern 1: Direct initialization
    app = Starlette()
    db = Gino(app, **kwargs)
    
    # Pattern 2: Application factory pattern
    app = Starlette()
    db = Gino(**kwargs)
    db.init_app(app)
  8. Set session-wide isolation level for asyncpg

    master

    Due to limitations in how asyncpg handles implicit transactions and a known bug where Connection.transaction(isolation="read_committed") may emit a standard BEGIN without the explicit isolation level, you may need to set the isolation level session-wise.

    You can achieve this by listening to the connect event on the engine's sync engine and using the PGDialect.set_isolation_level method to ensure the level is applied to the session.

    import sqlalchemy as sa
    from sqlalchemy import event
    from sqlalchemy.dialects.postgresql.base import PGDialect
    from sqlalchemy.ext.asyncio import create_async_engine
    
    async def main():
        e = create_async_engine(
            "postgresql+asyncpg:///",
            execution_options={"isolation_level": "AUTOCOMMIT"},
        )
    
        def set_isolation_level(dbapi_conn, record):
            PGDialect.set_isolation_level(
                e.sync_engine.dialect,
                dbapi_conn,
                "SERIALIZABLE",
            )
    
        event.listen(e.sync_engine, "connect", set_isolation_level)
    
        async with e.connect() as conn:
            print(await conn.scalar(sa.text("SHOW TRANSACTION ISOLATION LEVEL")))
            # Outputs: serializable
  9. Create a basic FastAPI server structure

    master

    Organize your project using a src layout. Create a main.py to define the application factory and an asgi.py to instantiate the app for the ASGI server.

    # src/gino_fastapi_demo/main.py
    from fastapi import FastAPI
    
    def get_app():
        app = FastAPI(title="GINO FastAPI Demo")
        return app
    
    # src/gino_fastapi_demo/asgi.py
    from .main import get_app
    
    app = get_app()