Module: context

Expand source code
# Copyright (C) 2023-present The Project Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import Type
from cl.runtime.backend.core.user_key import UserKey
from cl.runtime.context.context_key import ContextKey
from cl.runtime.db.db_key import DbKey
from cl.runtime.db.protocols import TKey
from cl.runtime.db.protocols import TRecord
from cl.runtime.log.exceptions.user_error import UserError
from cl.runtime.log.log_entry import LogEntry
from cl.runtime.log.log_entry_level_enum import LogEntryLevelEnum
from cl.runtime.log.log_key import LogKey
from cl.runtime.log.user_log_entry import UserLogEntry
from cl.runtime.records.dataclasses_extensions import missing
from cl.runtime.records.protocols import KeyProtocol
from cl.runtime.records.protocols import RecordProtocol
from cl.runtime.records.protocols import is_key
from cl.runtime.records.record_mixin import RecordMixin
from cl.runtime.settings.context_settings import ContextSettings

root_context_types_str = """
The following root context types can be used in the outermost 'with' clause:
    - ProcessContext: Context for launching a process, use in __main__
    - TestingContext: Context for running unit tests
"""

_context_stack: ContextVar[Optional[List["Context"]]] = ContextVar("context_stack", default=None)
"""
Context adds self to the stack on __enter__ and removes self on __exit__.
Each asynchronous context has its own stack.
"""


@contextmanager
def request_cycle_context() -> Iterator[None]:
    """Context manager to create isolated queue of contexts"""
    token = _context_stack.set([])
    yield
    _context_stack.reset(token)


@dataclass(slots=True, kw_only=True)
class Context(ContextKey, RecordMixin[ContextKey]):
    """Protocol implemented by context objects providing logging, database, dataset, and progress reporting."""

    user: UserKey = missing()
    """Current user, 'Context.current().user' is used if not specified."""

    log: LogKey = missing()
    """Log of the context, 'Context.current().log' is used if not specified."""

    db: DbKey = missing()
    """Database of the context, 'Context.current().db' is used if not specified."""

    dataset: str = missing()
    """Dataset of the context, 'Context.current().dataset' is used if not specified."""

    is_deserialized: bool = False
    """Use this flag to determine if this context instance has been deserialized from data."""

    def __post_init__(self):
        """Set fields to their values in 'Context.current()' if not specified."""

        # Do not execute this code on deserialized context instances (e.g. when they are passed to a task queue)
        if not self.is_deserialized:
            # Set fields that are not specified to their values from 'Context.current()'
            if self.user is None:
                self._root_context_field_not_set_error("user")
                self.user = Context.current().user
            if self.log is None:
                self._root_context_field_not_set_error("log")
                self.log = Context.current().log
            if self.db is None:
                self._root_context_field_not_set_error("db")
                self.db = Context.current().db
            if self.dataset is None:
                self._root_context_field_not_set_error("dataset")
                self.dataset = Context.current().dataset

        # Replace fields that are set as keys by records from storage
        # First, load 'db' field of this context using 'Context.current()'
        if is_key(self.db):
            self.db = Context.current().load_one(DbKey, self.db)

        # After this all remaining fields can be loaded using database from this context
        if is_key(self.log):
            self.log = self.load_one(LogKey, self.log)

    def get_key(self) -> ContextKey:
        return ContextKey(context_id=self.context_id)

    @classmethod
    def current(cls):
        """Return the current context or None if not set."""
        context_stack = _context_stack.get()
        if context_stack and len(context_stack) > 0:
            return context_stack[-1]
        else:
            raise RuntimeError(
                "Current context is not set, use 'with' clause with a root context type to set."
                + root_context_types_str
            )

    def __enter__(self):
        """Supports 'with' operator for resource disposal."""

        context_stack = _context_stack.get()
        if context_stack is None:
            # Context activated without middleware, create a new context stack
            context_stack = []
            _context_stack.set(context_stack)

        # Check if self is already the current context
        if context_stack and context_stack[-1] is self:
            raise RuntimeError("The context activated using 'with' operator is already current.")

        # Set current context on entering 'with Context(...)' clause
        context_stack.append(self)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Supports 'with' operator for resource disposal."""

        if exc_val is not None:
            # Save log entry to the database
            # Get log entry type and level
            if isinstance(exc_val, UserError):
                log_type = UserLogEntry
                level = LogEntryLevelEnum.USER_ERROR
            else:
                log_type = LogEntry
                level = LogEntryLevelEnum.ERROR

            # Create log entry
            log_entry = log_type(  # noqa
                message=str(exc_val),
                level=level,
            )
            log_entry.init()

            # Save occurred error to self db
            self.save_one(log_entry)

        context_stack = _context_stack.get()

        if context_stack is None or not bool(context_stack):
            raise RuntimeError("Current context must not be cleared inside 'with Context(...)' clause.")

        # Restore the previous current context on exiting from 'with Context(...)' clause
        current_context = context_stack.pop()
        if current_context is not self:
            raise RuntimeError("Current context must only be modified by 'with Context(...)' clause.")

        # TODO: Support resource disposal for the database
        if self.db is not None:
            # TODO: Finalize approach to disposal self.db.disconnect()
            pass

        # Return False to propagate exception to the caller
        return False

    def get_logger(self, name: str) -> logging.Logger:
        """Get logger for the specified name, invoke with __name__ as the argument."""
        return self.log.get_logger(name)  # noqa

    def load_one(
        self,
        record_type: Type[TRecord],
        record_or_key: TRecord | KeyProtocol | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
        is_key_optional: bool = False,
        is_record_optional: bool = False,
    ) -> TRecord | None:
        """
        Load a single record using a key (if a record is passed instead of a key, it is returned without DB lookup)

        Args:
            record_type: Record type to load, error if the result is not this type or its subclass
            record_or_key: Record (returned without lookup) or key in object, tuple or string format
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
            is_key_optional: If True, return None when key is none found instead of an error
            is_record_optional: If True, return None when record is not found instead of an error
        """
        return self.db.load_one(  # noqa
            record_type,
            record_or_key,
            dataset=dataset,
            identity=identity,
            is_key_optional=is_key_optional,
            is_record_optional=is_record_optional,
        )

    def load_many(
        self,
        record_type: Type[TRecord],
        records_or_keys: Iterable[TRecord | KeyProtocol | tuple | str | None] | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> Iterable[TRecord | None] | None:
        """
        Load records using a list of keys (if a record is passed instead of a key, it is returned without DB lookup),
        the result must have the same order as 'records_or_keys'.

        Args:
            record_type: Record type to load, error if the result is not this type or its subclass
            records_or_keys: Records (returned without lookup) or keys in object, tuple or string format
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        return self.db.load_many(  # noqa
            record_type,
            records_or_keys,
            dataset=dataset,
            identity=identity,
        )

    def load_all(
        self,
        record_type: Type[TRecord],
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> Iterable[TRecord | None] | None:
        """
        Load all records of the specified type and its subtypes (excludes other types in the same DB table).

        Args:
            record_type: Type of the records to load
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        return self.db.load_all(  # noqa
            record_type,
            dataset=dataset,
            identity=identity,
        )

    def load_filter(
        self,
        record_type: Type[TRecord],
        filter_obj: TRecord,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> Iterable[TRecord]:
        """
        Load records where values of those fields that are set in the filter match the filter.

        Args:
            record_type: Record type to load, error if the result is not this type or its subclass
            filter_obj: Instance of 'record_type' whose fields are used for the query
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        return self.db.load_filter(  # noqa
            record_type,
            filter_obj,
            dataset=dataset,
            identity=identity,
        )

    def save_one(
        self,
        record: RecordProtocol | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Save records to storage.

        Args:
            record: Record or None.
            dataset: Target dataset as a delimited string, list of levels, or None
            identity: Identity token for database access and row-level security
        """
        self.db.save_one(  # noqa
            record,
            dataset=dataset,
            identity=identity,
        )

    def save_many(
        self,
        records: Iterable[RecordProtocol],
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Save records to storage.

        Args:
            records: Iterable of records.
            dataset: Target dataset as a delimited string, list of levels, or None
            identity: Identity token for database access and row-level security
        """
        self.db.save_many(  # noqa
            records,
            dataset=dataset,
            identity=identity,
        )

    def delete_one(
        self,
        key_type: Type[TKey],
        key: TKey | KeyProtocol | tuple | str | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Delete one record for the specified key type using its key in one of several possible formats.

        Args:
            key_type: Key type to delete, used to determine the database table
            key: Key in object, tuple or string format
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        self.db.delete_one(  # noqa
            key_type,
            key,
            dataset=dataset,
            identity=identity,
        )

    def delete_many(
        self,
        keys: Iterable[KeyProtocol] | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Delete records using an iterable of keys.

        Args:
            keys: Iterable of keys.
            dataset: Target dataset as a delimited string, list of levels, or None
            identity: Identity token for database access and row-level security
        """
        self.db.delete_many(  # noqa
            keys,
            dataset=dataset,
            identity=identity,
        )

    def delete_all_and_drop_db(self) -> None:
        """
        IMPORTANT: !!! DESTRUCTIVE - THIS WILL PERMANENTLY DELETE ALL RECORDS WITHOUT THE POSSIBILITY OF RECOVERY

        Notes:
            This method will not run unless both db_id and database start with 'temp_db_prefix'
            specified using Dynaconf and stored in 'DbSettings' class
        """
        # Additional check in context in case a custom database implementation does not check it
        self.error_if_not_temp_db(self.db.db_id)
        self.db.delete_all_and_drop_db()  # noqa

    def _root_context_field_not_set_error(self, field_name: str) -> None:
        """Error message about a Context field not set."""
        if type(self) is not Context:
            raise RuntimeError(
                f"""
Field '{field_name}' of the context class '{type(self).__name__}' is not set.
The context in the outermost 'with' clause (root context) must set all fields
of the Context class. Inside the 'with' clause, these fields will be populated
from the current context.
"""
                + root_context_types_str
            )

    @classmethod
    def error_if_not_temp_db(cls, db_id_or_database_name: str) -> None:
        """Confirm that database id or database name matches temp_db_prefix, error otherwise."""
        context_settings = ContextSettings.instance()
        temp_db_prefix = context_settings.db_temp_prefix
        if not db_id_or_database_name.startswith(temp_db_prefix):
            raise RuntimeError(
                f"Destructive action on database not permitted because db_id or database name "
                f"'{db_id_or_database_name}' does not match temp_db_prefix '{temp_db_prefix}' "
                f"specified in Dynaconf database settings ('DbSettings' class)."
            )

Functions

def request_cycle_context() -> Iterator[None]

Context manager to create isolated queue of contexts

Classes

class Context (*, context_id: str = None, user: UserKey = None, log: LogKey = None, db: DbKey = None, dataset: str = None, is_deserialized: bool = False)

Protocol implemented by context objects providing logging, database, dataset, and progress reporting.

Expand source code
@dataclass(slots=True, kw_only=True)
class Context(ContextKey, RecordMixin[ContextKey]):
    """Protocol implemented by context objects providing logging, database, dataset, and progress reporting."""

    user: UserKey = missing()
    """Current user, 'Context.current().user' is used if not specified."""

    log: LogKey = missing()
    """Log of the context, 'Context.current().log' is used if not specified."""

    db: DbKey = missing()
    """Database of the context, 'Context.current().db' is used if not specified."""

    dataset: str = missing()
    """Dataset of the context, 'Context.current().dataset' is used if not specified."""

    is_deserialized: bool = False
    """Use this flag to determine if this context instance has been deserialized from data."""

    def __post_init__(self):
        """Set fields to their values in 'Context.current()' if not specified."""

        # Do not execute this code on deserialized context instances (e.g. when they are passed to a task queue)
        if not self.is_deserialized:
            # Set fields that are not specified to their values from 'Context.current()'
            if self.user is None:
                self._root_context_field_not_set_error("user")
                self.user = Context.current().user
            if self.log is None:
                self._root_context_field_not_set_error("log")
                self.log = Context.current().log
            if self.db is None:
                self._root_context_field_not_set_error("db")
                self.db = Context.current().db
            if self.dataset is None:
                self._root_context_field_not_set_error("dataset")
                self.dataset = Context.current().dataset

        # Replace fields that are set as keys by records from storage
        # First, load 'db' field of this context using 'Context.current()'
        if is_key(self.db):
            self.db = Context.current().load_one(DbKey, self.db)

        # After this all remaining fields can be loaded using database from this context
        if is_key(self.log):
            self.log = self.load_one(LogKey, self.log)

    def get_key(self) -> ContextKey:
        return ContextKey(context_id=self.context_id)

    @classmethod
    def current(cls):
        """Return the current context or None if not set."""
        context_stack = _context_stack.get()
        if context_stack and len(context_stack) > 0:
            return context_stack[-1]
        else:
            raise RuntimeError(
                "Current context is not set, use 'with' clause with a root context type to set."
                + root_context_types_str
            )

    def __enter__(self):
        """Supports 'with' operator for resource disposal."""

        context_stack = _context_stack.get()
        if context_stack is None:
            # Context activated without middleware, create a new context stack
            context_stack = []
            _context_stack.set(context_stack)

        # Check if self is already the current context
        if context_stack and context_stack[-1] is self:
            raise RuntimeError("The context activated using 'with' operator is already current.")

        # Set current context on entering 'with Context(...)' clause
        context_stack.append(self)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Supports 'with' operator for resource disposal."""

        if exc_val is not None:
            # Save log entry to the database
            # Get log entry type and level
            if isinstance(exc_val, UserError):
                log_type = UserLogEntry
                level = LogEntryLevelEnum.USER_ERROR
            else:
                log_type = LogEntry
                level = LogEntryLevelEnum.ERROR

            # Create log entry
            log_entry = log_type(  # noqa
                message=str(exc_val),
                level=level,
            )
            log_entry.init()

            # Save occurred error to self db
            self.save_one(log_entry)

        context_stack = _context_stack.get()

        if context_stack is None or not bool(context_stack):
            raise RuntimeError("Current context must not be cleared inside 'with Context(...)' clause.")

        # Restore the previous current context on exiting from 'with Context(...)' clause
        current_context = context_stack.pop()
        if current_context is not self:
            raise RuntimeError("Current context must only be modified by 'with Context(...)' clause.")

        # TODO: Support resource disposal for the database
        if self.db is not None:
            # TODO: Finalize approach to disposal self.db.disconnect()
            pass

        # Return False to propagate exception to the caller
        return False

    def get_logger(self, name: str) -> logging.Logger:
        """Get logger for the specified name, invoke with __name__ as the argument."""
        return self.log.get_logger(name)  # noqa

    def load_one(
        self,
        record_type: Type[TRecord],
        record_or_key: TRecord | KeyProtocol | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
        is_key_optional: bool = False,
        is_record_optional: bool = False,
    ) -> TRecord | None:
        """
        Load a single record using a key (if a record is passed instead of a key, it is returned without DB lookup)

        Args:
            record_type: Record type to load, error if the result is not this type or its subclass
            record_or_key: Record (returned without lookup) or key in object, tuple or string format
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
            is_key_optional: If True, return None when key is none found instead of an error
            is_record_optional: If True, return None when record is not found instead of an error
        """
        return self.db.load_one(  # noqa
            record_type,
            record_or_key,
            dataset=dataset,
            identity=identity,
            is_key_optional=is_key_optional,
            is_record_optional=is_record_optional,
        )

    def load_many(
        self,
        record_type: Type[TRecord],
        records_or_keys: Iterable[TRecord | KeyProtocol | tuple | str | None] | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> Iterable[TRecord | None] | None:
        """
        Load records using a list of keys (if a record is passed instead of a key, it is returned without DB lookup),
        the result must have the same order as 'records_or_keys'.

        Args:
            record_type: Record type to load, error if the result is not this type or its subclass
            records_or_keys: Records (returned without lookup) or keys in object, tuple or string format
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        return self.db.load_many(  # noqa
            record_type,
            records_or_keys,
            dataset=dataset,
            identity=identity,
        )

    def load_all(
        self,
        record_type: Type[TRecord],
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> Iterable[TRecord | None] | None:
        """
        Load all records of the specified type and its subtypes (excludes other types in the same DB table).

        Args:
            record_type: Type of the records to load
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        return self.db.load_all(  # noqa
            record_type,
            dataset=dataset,
            identity=identity,
        )

    def load_filter(
        self,
        record_type: Type[TRecord],
        filter_obj: TRecord,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> Iterable[TRecord]:
        """
        Load records where values of those fields that are set in the filter match the filter.

        Args:
            record_type: Record type to load, error if the result is not this type or its subclass
            filter_obj: Instance of 'record_type' whose fields are used for the query
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        return self.db.load_filter(  # noqa
            record_type,
            filter_obj,
            dataset=dataset,
            identity=identity,
        )

    def save_one(
        self,
        record: RecordProtocol | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Save records to storage.

        Args:
            record: Record or None.
            dataset: Target dataset as a delimited string, list of levels, or None
            identity: Identity token for database access and row-level security
        """
        self.db.save_one(  # noqa
            record,
            dataset=dataset,
            identity=identity,
        )

    def save_many(
        self,
        records: Iterable[RecordProtocol],
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Save records to storage.

        Args:
            records: Iterable of records.
            dataset: Target dataset as a delimited string, list of levels, or None
            identity: Identity token for database access and row-level security
        """
        self.db.save_many(  # noqa
            records,
            dataset=dataset,
            identity=identity,
        )

    def delete_one(
        self,
        key_type: Type[TKey],
        key: TKey | KeyProtocol | tuple | str | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Delete one record for the specified key type using its key in one of several possible formats.

        Args:
            key_type: Key type to delete, used to determine the database table
            key: Key in object, tuple or string format
            dataset: If specified, append to the root dataset of the database
            identity: Identity token for database access and row-level security
        """
        self.db.delete_one(  # noqa
            key_type,
            key,
            dataset=dataset,
            identity=identity,
        )

    def delete_many(
        self,
        keys: Iterable[KeyProtocol] | None,
        *,
        dataset: str | None = None,
        identity: str | None = None,
    ) -> None:
        """
        Delete records using an iterable of keys.

        Args:
            keys: Iterable of keys.
            dataset: Target dataset as a delimited string, list of levels, or None
            identity: Identity token for database access and row-level security
        """
        self.db.delete_many(  # noqa
            keys,
            dataset=dataset,
            identity=identity,
        )

    def delete_all_and_drop_db(self) -> None:
        """
        IMPORTANT: !!! DESTRUCTIVE - THIS WILL PERMANENTLY DELETE ALL RECORDS WITHOUT THE POSSIBILITY OF RECOVERY

        Notes:
            This method will not run unless both db_id and database start with 'temp_db_prefix'
            specified using Dynaconf and stored in 'DbSettings' class
        """
        # Additional check in context in case a custom database implementation does not check it
        self.error_if_not_temp_db(self.db.db_id)
        self.db.delete_all_and_drop_db()  # noqa

    def _root_context_field_not_set_error(self, field_name: str) -> None:
        """Error message about a Context field not set."""
        if type(self) is not Context:
            raise RuntimeError(
                f"""
Field '{field_name}' of the context class '{type(self).__name__}' is not set.
The context in the outermost 'with' clause (root context) must set all fields
of the Context class. Inside the 'with' clause, these fields will be populated
from the current context.
"""
                + root_context_types_str
            )

    @classmethod
    def error_if_not_temp_db(cls, db_id_or_database_name: str) -> None:
        """Confirm that database id or database name matches temp_db_prefix, error otherwise."""
        context_settings = ContextSettings.instance()
        temp_db_prefix = context_settings.db_temp_prefix
        if not db_id_or_database_name.startswith(temp_db_prefix):
            raise RuntimeError(
                f"Destructive action on database not permitted because db_id or database name "
                f"'{db_id_or_database_name}' does not match temp_db_prefix '{temp_db_prefix}' "
                f"specified in Dynaconf database settings ('DbSettings' class)."
            )

Ancestors

Subclasses

Static methods

def current()

Return the current context or None if not set.

def error_if_not_temp_db(db_id_or_database_name: str) -> None

Confirm that database id or database name matches temp_db_prefix, error otherwise.

def get_key_type() -> Type

Inherited from: ContextKey.get_key_type

Return key type even when called from a record.

Fields

var context_id -> str

Inherited from: ContextKey.context_id

Unique context identifier.

var dataset -> str

Dataset of the context, ‘Context.current().dataset’ is used if not specified.

var db -> DbKey

Database of the context, ‘Context.current().db’ is used if not specified.

var is_deserialized -> bool

Use this flag to determine if this context instance has been deserialized from data.

var log -> LogKey

Log of the context, ‘Context.current().log’ is used if not specified.

var user -> UserKey

Current user, ‘Context.current().user’ is used if not specified.

Methods

def delete_all_and_drop_db(self) -> None

IMPORTANT: !!! DESTRUCTIVE – THIS WILL PERMANENTLY DELETE ALL RECORDS WITHOUT THE POSSIBILITY OF RECOVERY

Notes

This method will not run unless both db_id and database start with ‘temp_db_prefix’ specified using Dynaconf and stored in ‘DbSettings’ class

def delete_many(self, keys: Optional[Iterable[KeyProtocol]], *, dataset: str | None = None, identity: str | None = None) -> None

Delete records using an iterable of keys.

Args

keys
Iterable of keys.
dataset
Target dataset as a delimited string, list of levels, or None
identity
Identity token for database access and row-level security
def delete_one(self, key_type: Type[~TKey], key: Union[~TKey, KeyProtocol, tuple, str, ForwardRef(None)], *, dataset: str | None = None, identity: str | None = None) -> None

Delete one record for the specified key type using its key in one of several possible formats.

Args

key_type
Key type to delete, used to determine the database table
key
Key in object, tuple or string format
dataset
If specified, append to the root dataset of the database
identity
Identity token for database access and row-level security
def get_key(self) -> ContextKey

Inherited from: RecordMixin.get_key

Return a new key object whose fields populated from self, do not return self.

def get_logger(self, name: str) -> logging.Logger

Get logger for the specified name, invoke with name as the argument.

def init_all(self) -> None

Inherited from: RecordMixin.init_all

Invoke ‘init’ for each class in the order from base to derived, then validate against schema.

def load_all(self, record_type: Type[~TRecord], *, dataset: str | None = None, identity: str | None = None) -> Optional[Iterable[Optional[~TRecord]]]

Load all records of the specified type and its subtypes (excludes other types in the same DB table).

Args

record_type
Type of the records to load
dataset
If specified, append to the root dataset of the database
identity
Identity token for database access and row-level security
def load_filter(self, record_type: Type[~TRecord], filter_obj: ~TRecord, *, dataset: str | None = None, identity: str | None = None) -> Iterable[~TRecord]

Load records where values of those fields that are set in the filter match the filter.

Args

record_type
Record type to load, error if the result is not this type or its subclass
filter_obj
Instance of ‘record_type’ whose fields are used for the query
dataset
If specified, append to the root dataset of the database
identity
Identity token for database access and row-level security
def load_many(self, record_type: Type[~TRecord], records_or_keys: Optional[Iterable[Union[~TRecord, KeyProtocol, tuple, str, ForwardRef(None)]]], *, dataset: str | None = None, identity: str | None = None) -> Optional[Iterable[Optional[~TRecord]]]

Load records using a list of keys (if a record is passed instead of a key, it is returned without DB lookup), the result must have the same order as ‘records_or_keys’.

Args

record_type
Record type to load, error if the result is not this type or its subclass
records_or_keys
Records (returned without lookup) or keys in object, tuple or string format
dataset
If specified, append to the root dataset of the database
identity
Identity token for database access and row-level security
def load_one(self, record_type: Type[~TRecord], record_or_key: Union[~TRecord, KeyProtocol, ForwardRef(None)], *, dataset: str | None = None, identity: str | None = None, is_key_optional: bool = False, is_record_optional: bool = False) -> Optional[~TRecord]

Load a single record using a key (if a record is passed instead of a key, it is returned without DB lookup)

Args

record_type
Record type to load, error if the result is not this type or its subclass
record_or_key
Record (returned without lookup) or key in object, tuple or string format
dataset
If specified, append to the root dataset of the database
identity
Identity token for database access and row-level security
is_key_optional
If True, return None when key is none found instead of an error
is_record_optional
If True, return None when record is not found instead of an error
def save_many(self, records: Iterable[RecordProtocol], *, dataset: str | None = None, identity: str | None = None) -> None

Save records to storage.

Args

records
Iterable of records.
dataset
Target dataset as a delimited string, list of levels, or None
identity
Identity token for database access and row-level security
def save_one(self, record: RecordProtocol | None, *, dataset: str | None = None, identity: str | None = None) -> None

Save records to storage.

Args

record
Record or None.
dataset
Target dataset as a delimited string, list of levels, or None
identity
Identity token for database access and row-level security