Tortoise ORM

repository·develop·Indexed 26 days ago

https://github.com/tortoise/tortoise-orm

A lightweight, async-native Object-Relational Mapper (ORM) for Python inspired by the Django ORM. It provides high performance and a familiar API for asynchronous Python code, featuring a CLI for migrations, support for multiple database connections, and specialized contributions for FastAPI, aiohttp, BlackSheep, MySQL, and PostgreSQL.

Tokens
36K
Snippets
88
Records
198
Agent score
90%

What's inside tortoise-orm

  1. Supported Databases and Drivers

    develop

    Tortoise ORM supports the following databases. You must ensure the corresponding asyncio driver is installed in your environment:

    • SQLite: uses aiosqlite
    • PostgreSQL (>= 9.4): uses asyncpg or psycopg
    • MySQL/MariaDB: uses asyncmy or aiomysql
    • Microsoft SQL Server: uses asyncodbc
  2. Integrate Tortoise-ORM with FastAPI using RegisterTortoise

    develop
    Use the RegisterTortoise class from tortoise.contrib.fastapi to manage the Tortoise-ORM lifecycle within a FastAPI application. This utility handles the initialization (setup) and cleanup of the database connection within the FastAPI lifespan context, ensuring connections are properly established when the server starts and closed when it shuts down.
  3. Integrate Tortoise-ORM with aiohttp using register_tortoise

    develop
    Use the tortoise.contrib.aiohttp utility to simplify the lifecycle management of Tortoise-ORM within an aiohttp application. The register_tortoise function handles both the initialization of the ORM on application startup and the necessary cleanup during application teardown.
  4. Define indexes in Tortoise ORM

    develop
    By default, Tortoise ORM uses BTree indexes when you set db_index=True on a field or define indexes within a model's Meta class. For specialized index types like FullTextIndex (MySQL) or GinIndex (Postgres), use tortoise.indexes.Index and its subclasses within the Meta.indexes list.
  5. Close Tortoise ORM connections

    develop

    Because Tortoise ORM is asyncio-based, you must properly close database connections to prevent the Python interpreter from waiting for them to complete. Call Tortoise.close_connections() during your application's cleanup phase. Alternatively, use the tortoise.run_async() helper function, which handles connection closing automatically when the application terminates.

    await Tortoise.close_connections()
  6. Manage transactions with in_transaction() and atomic()

    develop

    Tortoise ORM provides two primary ways to manage database transactions: the in_transaction() context manager and the atomic() decorator.

    Both methods support nesting. When nested, inner blocks create transaction savepoints. If an exception is raised within a nested block and caught outside of it, the transaction rolls back to the state before that specific nested block was entered (the savepoint). The outermost block is responsible for the final commit.

    Database Support for Savepoints:

    • Postgres
    • MySQL
    • MSSQL
    • SQLite

    Note: For databases not listed above, it is recommended to propagate exceptions to the outermost block to ensure a full rollback.

    # this block will commit changes on exit
    async with in_transaction():
        await MyModel.create(name='foo')
        try:
            # this block will create a savepoint and rollback to it if an exception is raised
            async with in_transaction():
                await MyModel.create(name='bar')
                # this will rollback to the savepoint, meaning that
                # the 'bar' record will not be created, however,
                # the 'foo' record will be created
                raise Exception()
        except Exception:
            pass
  7. Install Tortoise ORM acceleration dependencies

    develop

    To improve performance, you can install optional dependencies like orjson (for JSON SerDes), uvloop (as an alternative to asyncio), and ciso8601 (for faster date parsing). Use the [accel] extra to install them all at once:

    pip install "tortoise-orm[accel]"
    pip install "tortoise-orm[accel]"
  8. Use non-default database schemas in models

    develop

    For PostgreSQL and MSSQL, you can place tables in specific database schemas by setting the schema attribute within a model's Meta class.

    On MySQL, setting schema maps the table to a separate database name using the `db`.`table` syntax. Cross-schema foreign keys are supported automatically, and the migration framework handles schema-qualified tables and schema creation.

    from tortoise import models, fields
    
    class Product(models.Model):
        name = fields.CharField(max_length=200)
    
        class Meta:
            schema = "catalog"
    
    class Inventory(models.Model):
        product = fields.ForeignKeyField("models.Product")
        quantity = fields.IntField()
    
        class Meta:
            schema = "warehouse"
  9. Configure Database via DB_URL

    develop

    You can specify database configuration using a URL string following this format:

    {DB_TYPE}://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DB_NAME}?{PARAM1}=value&{PARAM2}=value

    Important: Handling Special Characters in Passwords If your password contains special characters (like %), it must be URL encoded using urllib.parse.quote_plus.

    Alternatively, to avoid URL parsing issues entirely, use the dict-based configuration format (see 'Configure Database via Dictionary' below).

  10. Configure a Database Router in Tortoise ORM

    develop

    You can apply a router by adding its import path to the routers list in your Tortoise configuration dictionary, or by passing it directly to Tortoise.init. The router's returned connection identifiers must match the keys defined in the connections dictionary.

    CONFIG = {
        "connections": {
            "master": "sqlite:///tmp/m.db",
            "slave": "sqlite:///tmp/s.db",
        },
        "apps": {
            "app": {
                "models": ["__main__"],
                "default_connection": "master",
            }
        },
        "routers": ["path.Router"],
        "use_tz": False,
        "timezone": "UTC",
    }
    await Tortoise.init(config=CONFIG)
  11. Add syntax coloration to SQL logs using Pygments

    develop

    You can enhance your debug logs by using pygments to add syntax highlighting to the SQL queries emitted by tortoise.db_client. This requires implementing a custom logging.Formatter that uses a lexer (like PostgresLexer) and a terminal formatter to highlight the message attribute when the logger name is tortoise.db_client and the level is DEBUG.

    import logging
    from pygments import highlight
    from pygments.formatters.terminal import TerminalFormatter
    from pygments.lexers.sql import PostgresLexer
    
    postgres = PostgresLexer()
    terminal_formatter = TerminalFormatter()
    
    class PygmentsFormatter(logging.Formatter):
        def __init__(
            self,
            fmt="{asctime} - {name}:{lineno} - {levelname} - {message}",
            datefmt="%H:%M:%S",
        ):
            self.datefmt = datefmt
            self.fmt = fmt
            logging.Formatter.__init__(self, None, datefmt)
    
        def format(self, record: logging.LogRecord):
            """Format the logging record with slq's syntax coloration."""
            own_records = {
                attr: val
                for attr, val in record.__dict__.items()
                if not attr.startswith("_")
            }
            message = record.getMessage()
            name = record.name
            asctime = self.formatTime(record, self.datefmt)
    
            if name == "tortoise.db_client":
                if (
                    record.levelname == "DEBUG"
                    and not message.startswith("Created connection pool")
                    and not message.startswith("Closed connection pool")
                ):
                    message = highlight(message, postgres, terminal_formatter).rstrip()
    
            own_records.update(
                {
                    "message": message,
                    "name": name,
                    "asctime": asctime,
                }
            )
    
            return self.fmt.format(**own_records)
    
    # Usage example:
    fmt = PygmentsFormatter(
        fmt="{asctime} - {name}:{lineno} - {levelname} - {message}",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
  12. Migrate from legacy Tortoise unittest classes

    develop

    If you are upgrading from legacy Tortoise test classes, use the following mapping to transition to pytest and the db fixture:

    Legacy (Removed)Modern Replacement
    test.TestCasepytest + db fixture
    test.IsolatedTestCasepytest + db fixture (isolation is default)
    test.TruncationTestCasepytest + db fixture + truncate_all_models()
    test.SimpleTestCasepytest + db fixture
    initializer()tortoise_test_context()
    finalizer()(automatic with context manager)
    self.assertEqual(a, b)assert a == b
    self.assertIn(a, b)assert a in b
    self.assertRaises(Exc)pytest.raises(Exc)