Welcome to Datalite’s documentation!

Datalite is a simple to use Python library that can bind a dataclass to an sqlite3 database.

Documentation

Getting Started

Welcome to the documentation of datalite. Datalite provides a simple, intuitive way to bind dataclasses to sqlite3 databases. In its current version, it provides implicit support for conversion between int, float, str, bytes classes and their sqlite3 counterparts, default values, basic schema migration and fetching functions.

Installation

Simply write:

pip install datalite

In the shell. And then, whenever you want to use it in Python, you can use:

import datalite

Basic Decorator Operations

Creating a datalite class

A datalite class is a special dataclass. It is created by using a decorator @datalite.datalite, members of this class are, from Python’s perspective, just normal classes. However, they have additional methods and attributes. @datalite decorator needs a database path to be provided. This database is the database the table for the dataclass will be created.

from datalite import datalite
@datalite(db_path='db.db')
@dataclass
class Student:
    student_id: int = 1
    student_name: str = "Kurt Gödel"
    student_gpa: float = 3.9

Here, datalite will create a table called student in the database file db.db, this file will include all the fields of the dataclass as columns. Default value of these columns are same as the default value of the dataclass.

Special Methods

Each object initialised from a dataclass decorated with the @dataclass decorator automatically gains access to three special methods. It should be noted, due to the nature of the library, extensions such as mypy and IDEs such as PyCharm will not be able to see these methods and may raise exceptions.

With this in mind, let us create a new object and run the methods over this objects.

new_student = Student(0, "Albert Einstein", 4.0)

Creating an Entry

First special method is .create_entry() when called on an object of a class decorated with the @datalite decorator, this method creates an entry in the table of the bound database of the class, in this case, table named student in the db.db. Therefore, to create the entry of Albert Einstein in the table:

new_student.create_entry()

This also modifies the object in an intresting way, it adds a new attribute obj_id, this is a unique, autoincremented value in the database. It can be accessed by new_student.obj_id.

Updating an Entry

Second special method is .update_entry(). If an object’s attribute is changed, to update its record in the database, this method must be called.

new_student.student_gpa = 5.0  # He is Einstein, after all.
new_student.update_entry()

Deleting an Entry

To delete an entry from the record, the third and last special method, .remove_entry() should be used.

new_student.remove_entry()

Warning

It should be noted that, if the new_student.obj_id attribute is modified, .update_entry() and .remove_entry() may have unexpected results.

Constraints

One of the most useful features provided by SQLite is the concept of constraints. Constraints signal the SQL engine that the values hold in a specific column MUST abide by specific constraints, these might be

  • Values of this column cannot be NULL. (NOT NULL)

  • Values of this column cannot be repeated. (UNIQUE)

  • Values of this column must fulfill a condition. (CHECK)

  • Values of this column can be used to identify a record. (PRIMARY)

  • Values of this column has a default value. (DEFAULT)

Some of these constraints are already implemented in datalite. With all of the set, is planned to be implemented in the future.

Default Values

Columns can be given default values. This is done the same way you would give a datafield a default value.

@datalite("db.db")
@dataclass
class Student:
    id_: int
    name: str = "Albert Einstein"

Therefore, from now on, any Student object, whose name is not specified, will have the default name "Albert Einstein" and if .create_entry() method is called on them, the newly inserted record will, by default, have this value in its corresponding column.

Unique Values

Declaring a field unique is done by a special TypeVar called Unique this uniqueness check is done in the database level, this introduces has some pros, but also some cons.

Pushing the uniqueness check to the database level introduces a better ability to handle concurrency for applications with large traffic, however, this check only runs when an item is registered, which means no problem will raise in the object creation even if another object of the same type with the same value hold in the unique field exists, no error will raise. However, if another record with the same value in the unique field is recorded in the bound database, upon the invocation of the .create_entry() will raise the ConstraintFailedError exception.

Uniqueness constraint is declared thusly:

from datalite.constraints import Unique

@datalite("db.db")
@dataclass
class Student:
    id_: Unique[int]
    name: str = "Albert Einstein"

Hinting a field with the Unique[T] type variable will introduce two rules:

  1. The values in the column of the field in the table that represents the dataclass Student in the bound database db.db cannot be NULL, thus the corresponding field cannot be assigned None.

  2. These same values must be unique for each and every record.

Failure of any of these two rules will result in a ConstraintFailedError exception.

Fetching Functions

A database is hardly useful if data does not persist between program runs. In datalite one can use datalite.fetch module to fetch data back from the database.

There are different sorts of fetching. One can fetch all the objects of a class using fetch_all(class_), or an object with a specific object id using fetch_from(class_, obj_id). There are more functions for plural conditional fetching (fetch_if, fetch_where) where all objects fitting a condition will be returned, as well as singular conditional fetching that returns the first object that fits a condition (fetch_equals).

Pagination

Pagination is a feature that allows a portion of the results to be returned. Since datalite is built to work with databases that may include large amounts of data, many systems using large portions of data also make use of pagination. By building pagination inside the system, we hope to allow easier usage.

  • fetch_where

  • fetch_if

  • fetch_all

Supports pagination, in general, pagination is controlled via two arguments page and element_count, page argument specifies which page to be returned and element_count specifies how many elements each page has. When page is set to 0, all results are returned irregardless of the value of the element_count.

Important

More information regarding the datalite.fetch functions can be found in the API reference.

Schema Migrations

Datalite provides a module, datalite.migrations that handles schema migrations. When a class definition is modified, datalite.migrations.basic_migration can be called to automatically transfer records to a table fitting the new definitions.

Let us say we have made changes to the fields of a dataclass called Student and now, we want these changes to be made to the database. More specifically, we had a field called studentt_id and realised this was a typo, we want it to be named into student_id, and we want the values that was previously hold in this column to be persistent despite the name change. We can achieve this easily by:

datalite.basic_migration(Student, {'studentt_id': 'student_id'})

This will make all the changes, if we had not provided the second argument, the values would be lost.

API Reference

datalite Module

@datalite.datalite(db_path: str, type_overload: Optional[Dict[Optional[type], str]] = None) → Callable

Bind a dataclass to a sqlite3 database. This adds new methods to the class, such as create_entry(), remove_entry() and update_entry().

Parameters
  • db_path – Path of the database to be binded.

  • type_overload – Type overload dictionary.

Returns

The new dataclass.

datalite.constraints module

datalite.constraints module introduces constraint

types that can be used to hint field variables, that can be used to signal datalite decorator constraints in the database.

exception datalite.constraints.ConstraintFailedError

Bases: Exception

This exception is raised when a Constraint fails.

datalite.fetch module

datalite.fetch.fetch_all(class_: type, page: int = 0, element_count: int = 10) → tuple

Fetchall the records in the bound database.

Parameters
  • class – Class of the records.

  • page – Which page to retrieve, default all. (0 means closed).

  • element_count – Element count in each page.

Returns

All the records of type class_ in the bound database as a tuple.

datalite.fetch.fetch_equals(class_: type, field: str, value: Any) → Any

Fetch a class_ type variable from its bound db.

Parameters
  • class – Class to fetch.

  • field – Field to check for, by default, object id.

  • value – Value of the field to check for.

Returns

The object whose data is taken from the database.

datalite.fetch.fetch_from(class_: type, obj_id: int) → Any

Fetch a class_ type variable from its bound dv.

Parameters
  • class – Class to fetch from.

  • obj_id – Unique object id of the object.

Returns

The fetched object.

datalite.fetch.fetch_if(class_: type, condition: str, page: int = 0, element_count: int = 10) → tuple

Fetch all class_ type variables from the bound db, provided they fit the given condition

Parameters
  • class – Class type to fetch.

  • condition – Condition to check for.

  • page – Which page to retrieve, default all. (0 means closed).

  • element_count – Element count in each page.

Returns

A tuple of records that fit the given condition of given type class_.

datalite.fetch.fetch_range(class_: type, range_: range) → tuple

Fetch the records in a given range of object ids.

Parameters
  • class – Class of the records.

  • range – Range of the object ids.

Returns

A tuple of class_ type objects whose values come from the class_’ bound database.

datalite.fetch.fetch_where(class_: type, field: str, value: Any, page: int = 0, element_count: int = 10) → tuple

Fetch all class_ type variables from the bound db, provided that the field of the records fit the given value.

Parameters
  • class – Class of the records.

  • field – Field to check.

  • value – Value to check for.

  • page – Which page to retrieve, default all. (0 means closed).

  • element_count – Element count in each page.

Returns

A tuple of the records.

datalite.fetch.is_fetchable(class_: type, obj_id: int) → bool

Check if a record is fetchable given its obj_id and class_ type.

Parameters
  • class – Class type of the object.

  • obj_id – Unique obj_id of the object.

Returns

If the object is fetchable.

datalite.mass_actions module

This module includes functions to insert multiple records to a bound database at one time, with one time open and closing of the database file.

exception datalite.mass_actions.HeterogeneousCollectionError

Bases: Exception

:raiseif the passed collection is not homogeneous.

ie: If a List or Tuple has elements of multiple types.

datalite.mass_actions.copy_many(objects: Union[List[T], Tuple[T]], db_name: str, protect_memory: bool = True) → None

Copy many records to another database, from their original database to new database, do not delete old records.

Parameters
  • objects – Objects to copy.

  • db_name – Name of the new database.

  • protect_memory – Wheter to protect memory during operation, Setting this to False will quicken the operation, but if the operation is cut short, database file will corrupt.

Returns

None

datalite.mass_actions.create_many(objects: Union[List[T], Tuple[T]], protect_memory: bool = True) → None

Insert many records corresponding to objects in a tuple or a list.

Parameters
  • protect_memory – If False, memory protections are turned off, makes it faster.

  • objects – A tuple or a list of objects decorated with datalite.

Returns

None.

datalite.migrations module

Migrations module deals with migrating data when the object definitions change. This functions deal with Schema Migrations.

datalite.migrations.basic_migrate(class_: type, column_transfer: dict = None) → None

Given a class, compare its previous table, delete the fields that no longer exist, create new columns for new fields. If the column_flow parameter is given, migrate elements from previous column to the new ones. It should be noted that, the obj_ids do not persist.

Parameters
  • class – Datalite class to migrate.

  • column_transfer – A dictionary showing which columns will be copied to new ones.

Returns

None.

Indices and tables