CursorBackend

The cursor backend connects your models to a MySQL or MariaDB database.

  1. Overview
  2. cursor_dependency_name
  3. table_prefix
  4. can_create
  5. can_update
  6. can_delete
  7. can_query

Overview

Installing Dependencies

clearskies uses PyMySQL to manage the database connection and make queries. This is not installed by default, but is a named extra that you can install when needed via:

pip install clear-skies[mysql]

Connecting to your server

The backend fetches its cursor from the dependency injection container under the name cursor (configurable via the cursor_dependency_name argument). The value must be a clearskies cursor, i.e. an instance of clearskies.cursors.Cursor, and not a raw DB-API cursor or connection. By default, cursor is clearskies.cursors.from_environment.Mysql(), which reads its connection details from the environment (environment variables or a .env file):

NameDefaultValue
DATABASE_HOST The hostname where the database can be found
DATABASE_USERNAME The username to connect as
DATABASE_PASSWORD The password to connect with
DATABASE_NAME The name of the database to use
DATABASE_PORT3306The network port to connect to
DATABASE_CERT_PATH Path to a certificate to use: enables SSL over the connection
DATABASE_AUTOCOMMIT Enables autocommit when set to a non-empty value
DATABASE_CONNECT_TIMEOUT2Connection timeout in seconds

The names of these environment variables can be changed via the *_environment_key arguments of clearskies.cursors.from_environment.Mysql. clearskies.cursors.from_environment.Postgresql (same variables) and clearskies.cursors.from_environment.Sqlite (DATABASE_NAME only) are also available.

To use a different cursor, bind one of the classes from clearskies.cursors to cursor. For example, to connect to a PostgreSQL server (requires psycopg to be installed) with explicit settings:

import clearskies

wsgi = clearskies.contexts.WsgiRef(
    some_application,
    bindings={
        "cursor": clearskies.cursors.Postgresql(
            hostname="db.example.com",
            username="example-user",
            password="<YOUR_PASSWORD>",
            database="appdb",
        ),
    },
)

clearskies.cursors.Mysql and clearskies.cursors.Sqlite work the same way. The MySQL and PostgreSQL cursors also accept a port_forwarding argument (see clearskies.cursors.port_forwarding). If different models need different databases, bind each cursor under its own name and point the backend at it:

class Report(clearskies.Model):
    backend = clearskies.backends.CursorBackend(cursor_dependency_name="reporting_cursor")


cli = clearskies.contexts.Cli(
    some_application,
    bindings={"reporting_cursor": clearskies.cursors.Sqlite(database="reports.db")},
)

The cursors don’t read from a secret manager directly. To pull credentials from one, provide the cursor from an AdditionalConfig class, which can have the secrets dependency injected:

import clearskies


class DatabaseConfig(clearskies.di.AdditionalConfig):
    def provide_cursor(self, secrets):
        return clearskies.cursors.Mysql(
            hostname="db.example.com",
            username="example-user",
            password=secrets.get("/path/to/database/password"),
            database="appdb",
        )


wsgi = clearskies.contexts.WsgiRef(
    some_application,
    additional_configs=[DatabaseConfig()],
    bindings={
        "secrets": clearskies.secrets.Akeyless(access_id="p-abc123", access_type="aws_iam"),
    },
)

Connecting models to tables

The table name for your model comes from calling the destination_name class method of the model class. By default, this takes the class name, converts it to snake case, and then pluralizes it. So, if you have a model class named UserPreference then the cursor backend will look for a table called user_preferences. If this isn’t what you want, then you can simply override destination_name to return whatever table you want:

class UserPreference(clearskies.Model):
    @classmethod
    def destination_name(cls):
        return "some_other_table_name"

Additionally, the cursor backend accepts an argument called table_prefix which, if provided, will be prefixed to your table name. Finally, you can declare a dependency called global_table_prefix which will automatically be added to every table name. In the following example, the table name will be user_configuration_preferences due to:

  1. The destination_name method sets the table name to preferences
  2. The table_prefix argument to the CursorBackend constructor adds a prefix of configuration_
  3. The global_table_prefix binding sets a prefix of user_, wihch goes before everything else.
import clearskies


class UserPreference(clearskies.Model):
    id_column_name = "id"
    backend = clearskies.backends.CursorBackend(table_prefix="configuration_")
    id = clearskies.columns.Uuid()

    @classmethod
    def destination_name(cls):
        return "preferences"


cli = clearskies.contexts.Cli(
    clearskies.endpoints.Callable(
        lambda user_preferences: user_preferences.create(no_data=True).id,
    ),
    classes=[UserPreference],
    bindings={
        "global_table_prefix": "user_",
    },
)

cursor_dependency_name

Optional

table_prefix

Optional

can_create

Optional

Whether creating new records is allowed for this backend.

When set to False, any attempt to create a record will raise a ValueError. This can be set as a class attribute or passed to the constructor.

can_update

Optional

Whether updating existing records is allowed for this backend.

When set to False, any attempt to update a record will raise a ValueError. This can be set as a class attribute or passed to the constructor.

can_delete

Optional

Whether deleting records is allowed for this backend.

When set to False, any attempt to delete a record will raise a ValueError. This can be set as a class attribute or passed to the constructor.

can_query

Optional

Whether querying/reading records is allowed for this backend.

When set to False, any attempt to query records (via iteration, count, find, etc.) will raise a ValueError. This can be set as a class attribute or passed to the constructor.