sillyorm package

Submodules

sillyorm.registry module

class sillyorm.registry.Registry(create_engine_url: str, create_engine_kwargs: dict[str, Any] | None = None)

Bases: object

sillyORM Model Registry - keeps track of models and handles model inheritance

Variables:
  • engine – The SQLAlchemy database engine

  • metadata – The SQLAlchemy MetaData

Parameters:
  • create_engine_url (str) – The URL passed to sqlalchemy.create_engine

  • create_engine_kwargs (dict[str, Any]) – keyword arguments passed to sqlalchemy.create_engine

environment(autocommit: bool = False) Generator[Environment, None, None]

Context manager for environments, will close the environment for you when you are done.

Parameters:

autocommit (bool, optional) – Whether to automatically run commit after each database transaction that requires it (and rollback on error)

get_environment(autocommit: bool = False) Environment

Returns a new environment object (will also grab a new connection from the connection pool)

Parameters:

autocommit (bool, optional) – Whether to automatically run commit after each database transaction that requires it (and rollback on error)

Returns:

The new Environment object

Return type:

environment

get_schema_diffs() Any

Asks Alembic for the differences between the metadata in the registry and the current DB state are

init_db_tables(automigrate: Literal['ignore', 'none', 'safe'] = 'safe') None

Initializes database tables.

register_model(model: type[Model]) None

Registers a model class in the Registry (for later initialization)

The order in which this function is called on each model matters! It determines the exact inheritance!

Parameters:

model (type[Model]) – The Model to register

reset() None

Reset the registry object, minus the models that have been registered

reset_full() None

Fully Reset the registry object, including the models that have been registered

resolve_tables() None

Resolve model inheritance and build the models table in the registry

sillyorm.environment module

class sillyorm.environment.Environment(models: dict[str, type[Model]], connection: sqlalchemy.Connection, registry: Registry, autocommit: bool = False)

Bases: object

This class is meant for keeping track of models registered in it, some settings and the database connection (SQLAlchemy Connection object).

Once registered, models in the environment can be accessed using the index operator. When a model is accessed this way, it will return an empty recordset.

it is not meant to be initialized directly by the user

Variables:
  • connection (sqlalchemy.Connection) – The database Connection

  • registry (sillyorm.registry.Registry) – The registry this environment object was created from

  • autocommit (bool) – Whether to automatically run commit after each database transaction that requires it (and rollback on error)

Parameters:
  • models – The database cursor that will be passed to all models

  • connection (sqlalchemy.Connection) – The database connection

  • registry (sillyorm.registry.Registry) – The registry this environment object was created from

  • autocommit (bool, optional) – Whether to automatically run commit after each database transaction that requires it (and rollback on error)

__getitem__(key: str) Model
close() None

Close this environment object, close it’s connection If it has an active transaction that will be rolled back.

managed_transaction() Generator[None, None, None]

Context manager for transactions, this is mostly for internal use and will do not do _anything_ without autocommit being set!

transaction() Generator[None, None, None]

Context manager for transactions, this will start a transaction and roll it back on error

sillyorm.exceptions module

exception sillyorm.exceptions.SillyORMException

Bases: Exception

Generic sillyORM Exceptions

sillyorm.fields module

class sillyorm.fields.Boolean(required: bool = False, unique: bool = False)

Bases: Field

Boolean field. Can represent either True or False.

class ExampleModel(sillyorm.model.Model):
    _name = "example_bool"
    field = sillyorm.fields.Boolean()

env = reinit_env([ExampleModel])

record = env["example_bool"].create({"field": True})
print(record.field)
record.field = False
print(record.field)
record.field = None
print(record.field)
True
False
None
sql_type: TypeEngine = Boolean()
class sillyorm.fields.Date(required: bool = False, unique: bool = False)

Bases: Field

Date field. Represents a python date object.

import datetime

class ExampleModel(sillyorm.model.Model):
    _name = "example3"
    field = sillyorm.fields.Date()

env = reinit_env([ExampleModel])

record = env["example3"].create({"field": datetime.date(1970, 1, 1)})
print(record.field)
record.field += datetime.timedelta(days=1)
print(record.field)
record.field = None
print(record.field)
1970-01-01
1970-01-02
None
sql_type: TypeEngine = Date()
class sillyorm.fields.Datetime(tzinfo: tzinfo | None, required: bool = False, unique: bool = False)

Bases: Field

Datetime field. Represents a python datetime object.

A timezone (or the value None - which means it’s naive) must be provided because in the database this field may not store any timzeone-related information. Mixing timezones would be fatal so this field takes care of that for you.

Parameters:

tzinfo (datetime.tzinfo | None) – time zone of the date stored - None means it’s a naive datetime object

import datetime

class ExampleModel(sillyorm.model.Model):
    _name = "example_datetime"
    field = sillyorm.fields.Datetime(None)

env = reinit_env([ExampleModel])

record = env["example_datetime"].create({"field": datetime.datetime(1970, 1, 1, 1, 2, 3)})
print(record.field)
record.field += datetime.timedelta(days=1, hours=2, minutes=6)
print(record.field)
record.field = None
print(record.field)
1970-01-01 01:02:03
1970-01-02 03:08:03
None
sql_type: TypeEngine = DateTime()
class sillyorm.fields.Field(required: bool = False, unique: bool = False)

Bases: object

Base descriptor class for BaseModel fields

Variables:
  • sql_type (sqlalchemy.types.TypeEngine) – SQL type of the field

  • materialize (bool) – Whether the field actually exists as a column in the database table

  • constraints (list[sqlalchemy.schema.Constraint | tuple[str, Any]]) – SQL constraints of the field

  • name (str) – column name of the field

  • required (bool) – If the field must be set (checked via SQL constraints and runtime checks)

  • unique (bool) – If the field’s value should be unique in the column (checked via SQL constraints)

Parameters:
  • required (bool) – If the field must be set (checked via SQL constraints and runtime checks)

  • unique (bool) – If the field’s value should be unique in the column (checked via SQL constraints)

Default required:

False

Default unique:

False

materialize = True
name: str = None
sql_type: TypeEngine = None
class sillyorm.fields.Float(required: bool = False, unique: bool = False)

Bases: Field

Float field. Can represent floating point numbers from at least -1.2e-38 to 3.4e+38 (may be significantly more depending on the dbms used).

class ExampleModel(sillyorm.model.Model):
    _name = "example_float"
    field = sillyorm.fields.Float()

env = reinit_env([ExampleModel])

record = env["example_float"].create({"field": 32768.123321})
print(record.field)
record.field = -0.000000000000000000000000000000000000012
print(record.field)
record.field = 340000000000000000000000000000000000000.0
print(record.field)
record.field = None
print(record.field)
32768.123321
-1.2e-38
3.4e+38
None
sql_type: TypeEngine = Float()
class sillyorm.fields.Id(required: bool = False, unique: bool = False)

Bases: Integer

Special ID field used as PRIMARY KEY in model tables. It’s value cannot be changed.

class ExampleModel(sillyorm.model.Model):
    _name = "example1"
    # Each model automatically has an ID field

env = reinit_env([ExampleModel])

record = env["example1"].create({})
record2 = env["example1"].create({})
print(record.id)
print(record2.id)
1
2
class sillyorm.fields.Integer(required: bool = False, unique: bool = False)

Bases: Field

Integer field. Can represent numbers from at least -32768 to 32767 (may be significantly more depending on the dbms used).

class ExampleModel(sillyorm.model.Model):
    _name = "example0"
    field = sillyorm.fields.Integer()

env = reinit_env([ExampleModel])

record = env["example0"].create({"field": 5})
print(record.field)
record.field = -32768
print(record.field)
record.field = 32767
print(record.field)
record.field = None
print(record.field)
5
-32768
32767
None
sql_type: TypeEngine = Integer()
class sillyorm.fields.Many2one(foreign_model: str, required: bool = False, unique: bool = False)

Bases: Integer

Many to one relational field. Represents a single record of another model.

When read this field returns a recordset. When written it expects an integer (the ID of a foreign record).

class ExampleModel1(sillyorm.model.Model):
    _name = "example4"
    field = sillyorm.fields.String()

class ExampleModel2(sillyorm.model.Model):
    _name = "example5"
    many2one_field = sillyorm.fields.Many2one("example4")

env = reinit_env([ExampleModel1, ExampleModel2])

other_record = env["example4"].create({"field": "Hello world!"})
record = env["example5"].create({"many2one_field": other_record.id})

print(other_record.field)
print(record.many2one_field)
print(record.many2one_field.field)
record.many2one_field.field = "test"
print(other_record.field)
record.many2one_field = None
print(record.many2one_field)
Hello world!
example4[1]
Hello world!
test
None
Parameters:

foreign_model (str) – Foreign model name

class sillyorm.fields.One2many(foreign_model: str, foreign_field: str, required: bool = False, unique: bool = False)

Bases: Field

One to many relational field. It’s the inverse of a Many2one field. Represents multiple records of another model. This field does not exist in the database table.

When read this field returns a recordset. It cannot be written.

class ExampleModel1(sillyorm.model.Model):
    _name = "example6"
    field = sillyorm.fields.String()
    one2many_field = sillyorm.fields.One2many("example7", "many2one_field")

class ExampleModel2(sillyorm.model.Model):
    _name = "example7"
    many2one_field = sillyorm.fields.Many2one("example6")

env = reinit_env([ExampleModel1, ExampleModel2])

other_record = env["example6"].create({})
record = env["example7"].create({"many2one_field": other_record.id})
record2 = env["example7"].create({"many2one_field": other_record.id})

print(record.many2one_field)
print(record2.many2one_field)
print(other_record.one2many_field)
example6[1]
example6[1]
example7[1, 2]
Parameters:
  • foreign_model (str) – Foreign model name

  • foreign_field (str) – Foreign Many2one field name

materialize = False
class sillyorm.fields.Selection(options: list[str], length: int = 255, required: bool = False, unique: bool = False)

Bases: String

Selection field. Basically just a string field with a little logic around it that allows you to choose between multiple different predefined options.

class ExampleModel(sillyorm.model.Model):
    _name = "example_selection"
    field = sillyorm.fields.Selection(["option1", "option2"])

env = reinit_env([ExampleModel])

record = env["example_selection"].create({"field": "option1"})
print(record.field)
record.field = "option2"
print(record.field)
record.field = None
print(record.field)
option1
option2
None
Parameters:
  • options (list[str]) – List of possible selection options

  • length (int, optional) – Maximum selection length, defaults to 255

class sillyorm.fields.String(length: int = 255, required: bool = False, unique: bool = False)

Bases: Field

String field. Represents a string of at most length characters

class ExampleModel(sillyorm.model.Model):
    _name = "example2"
    field = sillyorm.fields.String()

env = reinit_env([ExampleModel])

record = env["example2"].create({"field": "hello"})
print(record.field)
record.field += " world!"
print(record.field)
record.field = None
print(record.field)
hello
hello world!
None
Parameters:

length (int, optional) – Maximum string length, defaults to 255

class sillyorm.fields.Text(required: bool = False, unique: bool = False)

Bases: Field

Text field. Represents a large string of text

class ExampleModel(sillyorm.model.Model):
    _name = "example_text"
    field = sillyorm.fields.Text()

env = reinit_env([ExampleModel])

record = env["example_text"].create({"field": "hello"})
print(record.field)
record.field += " world!"
print(record.field)

largestring = "0123456789" * 100000 # 1MB of data
record.field = largestring
print(record.field == largestring)
record.field = None
print(record.field)
hello
hello world!
True
None

sillyorm.model module

class sillyorm.model.AbstractModel(env: Environment, ids: list[int])

Bases: BaseModel

Use this as the base for any Abstract Models. Won’t create a database table. See sillyorm.model.BaseModel for docs

class sillyorm.model.BaseModel(env: Environment, ids: list[int])

Bases: object

Each model represents a single table in the database. A model can have fields which represent columns in the database table.

The _name attribute specifies the name of the database table the model represents and the name of the model in the environment.

An instance of the model class (or a subclass instance) represents a recordset.

Warning

You should never call the constructor of the model class yourself. Get an empty recordset via the environment and interact with the model from there.

class ExampleModel(sillyorm.model.Model):
    _name = "example0"
    field = sillyorm.fields.String()

registry.register_model(ExampleModel)
registry.resolve_tables()
registry.init_db_tables()
env = registry.get_environment()

record = env["example0"].create({"field": "Hello world!"})
print(record.field)
Hello world!
Variables:

env (sillyorm.environment.Environment) – The environment

Parameters:
  • env (list[int]) – The environment

  • ids – list of id’s the recordset should have

__getitem__(key: int) Self
__iter__() Iterator[Self]
browse(ids: list[int] | int) None | Self

Returns a recordset for the ids provided.

Warning

The order of the ids in the recordset returned may not be the same as the ids provided as input

Parameters:

ids – The ids or id

Returns:

A recordset with the ids provided. None if none of the ids could be found

Return type:

None | Self

create(vals: dict[str, Any]) Self

Creates a recordset with the values provided.

Parameters:

vals (dict[str, Any]) – The values to write into the new recordset. The keys represent the field names and the values the values for the fields

Returns:

The recordset that was created (containing one record)

Return type:

Self

delete() None

Deletes all records in the recordset

ensure_one() Self

Makes sure the recordset contains exactly one record. Raises an exception otherwise

Raises:

SillyORMException – If the recordset does not contain exactly one record

id

Special sillyorm.fields.Id field used as PRIMARY KEY

read(field_names: list[str]) list[dict[str, Any]]

Reads the specified fields of the recordset.

Parameters:

field_names (list[str]) – The fields to read

Returns:

The fields read as a list of dictionaries.

Return type:

list[dict[str, Any]]

search(domain: list[str | tuple[str, str, Any]], order_by: str | None = None, order_asc: bool = True, offset: int | None = None, limit: int | None = None) Self

Searches records.

Search domains are closely tied to the SQL WHERE statement.

[
    "(",
    ("test2", "=", "test2"),
    "&",
    ("test", "=", "hello world!"),
    ")",
    "|",
    ("test2", "=", "2 Hii!!"),
]

This search domain will result in SQL code that looks something like this:

SELECT "id"
FROM   "test_model"
WHERE  ( "test2" = 'test2'
         AND "test" = 'hello world!' )
        OR "test2" = '2 Hii!!';

Search operators:

  • = Equals to

  • != not equal

  • > greater than

  • >= greater than or equal

  • < less than

  • <= less than or equal

  • =ilike matches against the pattern provided (case-insentitive), _ in the pattern matches any single character and % matches any string of zero or more characters

  • ilike similar to =ilike but will wrap the pattern provided in %

Usage example:

class ExampleModel(sillyorm.model.Model):
    _name = "example1"
    field = sillyorm.fields.String()

registry.register_model(ExampleModel)
registry.resolve_tables()
registry.init_db_tables()
env = registry.get_environment()

record1 = env["example1"].create({"field": "test1"})
record2 = env["example1"].create({"field": "test2"})
record3 = env["example1"].create({"field": "test3"})
print(record1.id, record2.id, record3.id)

print(env["example1"].search([
    ("field", "=", "test1"),
    "|",
    ("field", "!=", "test2"),
]))
1 2 3
example1[1, 3]
Parameters:
  • domain (list[str | tuple[str, str, Any]]) – The search domain.

  • order_by (str | None) – The column to order by

  • order_asc (bool) – Wether the order is ascending or not

  • offset (int | None) – The row offset to use

  • limit (int | None) – The maximum amount of rows to return

Returns:

A recordset with the records found. An empty recordset if nothing could be found

Return type:

Self

search_count(domain: list[str | tuple[str, str, Any]]) int

Counts the total amount of records that match a domain.

The domain is the same format as for the search function.

Usage example:

class ExampleModel(sillyorm.model.Model):
    _name = "example_msc1"
    field = sillyorm.fields.String()

registry.register_model(ExampleModel)
registry.resolve_tables()
registry.init_db_tables()
env = registry.get_environment()

record1 = env["example_msc1"].create({"field": "test1"})
record2 = env["example_msc1"].create({"field": "test1"})
record3 = env["example_msc1"].create({"field": "test2"})

print(env["example_msc1"].search_count([
    ("field", "=", "test1"),
]))

print(env["example_msc1"].search_count([
    ("field", "=", "test2"),
]))
2
1
Parameters:

domain (list[str | tuple[str, str, Any]]) – The search domain.

Returns:

The amount of records that match the provided domain

Return type:

int

write(vals: dict[str, Any]) None

Writes the specified fields into all records contained in the recordset.

Parameters:

vals (dict[str, Any]) – The values to write. The keys represent the field names and the values the values for the fields

class sillyorm.model.Model(env: Environment, ids: list[int])

Bases: BaseModel

Use this as the base for any normal Models. Will create a database table. See sillyorm.model.BaseModel for docs

sillyorm.migration_helpers module

sillyorm.migration_helpers.helper_do_migrate(revision: str = 'head', downgrade: bool = False) None

Runs migration scripts up to revision

sillyorm.migration_helpers.helper_gen_migrations(message: str | Callable[[], str], revid: str | None = None, head: str = 'head') bool

Checks for difference of the Registry metadata to the DB autogenerates a migration script, returns True if a migration was generated, False otherwise

sillyorm.migration_helpers.helper_init(registry: Registry, migration_folder_path: str, script_template_path: str | None = None, version_table: str = 'alembic_version') None

Initialize the migration helper. This must be called before using any of the helper functions!

Will also initialize the migration folder in case it isn’t initialized yet

Module contents