sillyorm package¶
Subpackages¶
Submodules¶
sillyorm.environment module¶
- class sillyorm.environment.Environment(cursor: Cursor, do_commit: bool = True, update_tables: bool = True)¶
Bases:
object
This class is meant for keeping track of
models
registered in it, some settings and the database cursor.A model can be registered in the environment using the
register_model
function.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.
>>> import tempfile >>> import sillyorm >>> class TestModel(sillyorm.model.Model): ... _name = "testmodel" >>> env = sillyorm.Environment( ... sillyorm.dbms.sqlite.SQLiteConnection( ... tempfile.NamedTemporaryFile().name ... ).cursor() ... ) >>> env.register_model(TestModel) >>> env.init_tables() >>> env["testmodel"] testmodel[]
- Variables:
cr (
sillyorm.sql.Cursor
) – The database cursordo_commit (bool) – Whether to run commit after each database transaction that requires it
update_tables (bool) – Whether to update database tables.
- Parameters:
cursor (
sillyorm.sql.Cursor
) – The database cursor that will be passed to all modelsdo_commit (bool, optional) – Whether to run commit after each database transaction that requires it
update_tables –
Whether to automagically update database tables. This can be dangerous in some cases, e.g. when a field is renamed or field parameters are changed, all data in the field may be lost!
When this is False and the tables don’t match an error will be thrown upon calling
Environment.init_tables
- init_tables() None ¶
Initializes database tables of all models registered in the environment and takes care of model inheritance
- managed_transaction() Generator[None, None, None] ¶
Context manager for transactions, only actually does anything when do_commit is set on the environment object
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.register_model(ExampleModel) env.init_tables() 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
- 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.register_model(ExampleModel) env.init_tables() 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
- 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 none at all - which means it’s naive) must be provided because in the database this field does 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.register_model(ExampleModel) env.init_tables() 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
- class sillyorm.fields.Field(required: bool = False, unique: bool = False)¶
Bases:
object
Base descriptor class for
Model
fields- Variables:
sql_type (
sillyorm.sql.SqlType
) – SQL type of the fieldmaterialize (bool) – Whether the field actually exists as a column in the database table
constraints (list[
sillyorm.sql.SqlConstraint
]) – SQL constraints of the fieldname (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¶
- 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
to3.4e+38
(may be significantly more depending on the dbms used).class ExampleModel(sillyorm.model.Model): _name = "example_float" field = sillyorm.fields.Float() env.register_model(ExampleModel) env.init_tables() 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
- 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.register_model(ExampleModel) env.init_tables() 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
to32767
(may be significantly more depending on the dbms used).class ExampleModel(sillyorm.model.Model): _name = "example0" field = sillyorm.fields.Integer() env.register_model(ExampleModel) env.init_tables() 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
- 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.register_model(ExampleModel1) env.register_model(ExampleModel2) env.init_tables() 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.register_model(ExampleModel1) env.register_model(ExampleModel2) env.init_tables() 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.register_model(ExampleModel) env.init_tables() 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
charactersclass ExampleModel(sillyorm.model.Model): _name = "example2" field = sillyorm.fields.String() env.register_model(ExampleModel) env.init_tables() 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.register_model(ExampleModel) env.init_tables() 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.Model(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.
When a model is registered the ORM ensures the table with all required fields is created. If any columns/fields exist in the database but are not specified in the model they will be removed in the database.
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() env.register_model(ExampleModel) env.init_tables() 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
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 the following SQL:
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() env.register_model(ExampleModel) env.init_tables() 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() env.register_model(ExampleModel) env.init_tables() 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
sillyorm.sql module¶
- class sillyorm.sql.ColumnInfo(name: str, type: SqlType, constraints: list[SqlConstraint])¶
Bases:
NamedTuple
NamedTuple for describing SQL table columns
- constraints: list[SqlConstraint]¶
Alias for field number 2
- name: str¶
Alias for field number 0
- class sillyorm.sql.Connection¶
Bases:
object
For managing database connections
- close() None ¶
Closes the connection
- class sillyorm.sql.Cursor¶
Bases:
object
Abstraction over standard python database cursors with extra features
- case_insensitive_like() str ¶
Returns the operator for case-insensitive like in the current DBMS
- Returns:
the case-insensitive LIKE operator
- Return type:
str
- commit() None ¶
Commits the current transaction
- ensure_table(name: str, columns: list[ColumnInfo], no_update: bool) None ¶
Makes sure a table with the specified name and columns exists. If any extra columns exist or their type does not match they will be removed. If any columns don’t exist they will be created.
- Parameters:
name (str) – The name of the table
columns (list[
sillyorm.sql.ColumnInfo
]) – The columns of the tableno_update (bool) – If True, do not update anything, just check and if something needs to be updated, throw an exception
- execute(sqlcode: SQL) Self ¶
Executes SQL code
- Parameters:
sqlcode (
sillyorm.sql.SQL
) – The SQL code- Returns:
Returns the Cursor
- Return type:
- fetchall() list[tuple[Any, ...]] ¶
Fetches all remaining rows of the query.
- Returns:
All remaining rows of the query. Empty list if nothing is available
- Return type:
list[tuple[Any, …]]
- fetchone() tuple[Any, ...] ¶
Fetches the next row of the query.
- Returns:
The next row of the query. None if nothing is available
- Return type:
tuple[Any, …]
- get_table_column_info(name: str) list[ColumnInfo] ¶
Returns the column info of a table
- Parameters:
name (str) – The name of the table
- Returns:
The column info of the specified table
- Return type:
list[
sillyorm.sql.ColumnInfo
]
- rollback() None ¶
Rolls back the current transaction
- table_exists(name: str) bool ¶
Checks if a table exists
- Parameters:
name (str) – The name of the table
- Returns:
whether or not the table exists
- Return type:
bool
- class sillyorm.sql.SQL(code: str, **kwargs: Self | str | int | float)¶
Bases:
object
Class for properly constructing and escaping SQL code
- Parameters:
code (str) – SQL format string
**kwargs – arguments for the format string
Warning
The
code
parameter may ABSOLUTELY not contain ANY user-provided input as that would likely cause SQL injectionExample:
>>> from sillyorm.sql import SQL >>> where = SQL( ... "WHERE {id} IN {ids};", ... id=SQL.identifier("id"), ... ids=SQL.set([1, 2, 3]), ... ) >>> print(where.code()) WHERE "id" IN (1, 2, 3); >>> sql = SQL( ... "SELECT * FROM {table} {where}", ... table=SQL.identifier("table"), ... where=where, ... ) >>> print(sql.code()) SELECT * FROM "table" WHERE "id" IN (1, 2, 3);
- __add__(sql: Self) Self ¶
- code() str ¶
Generates the raw SQL code as a string
>>> from sillyorm.sql import SQL >>> sql = SQL( ... "SELECT * FROM {table};", ... table=SQL.identifier("something"), ... ) >>> print(sql.code()) SELECT * FROM "something";
- Returns:
The resulting code
- Return type:
str
- classmethod commaseperated(values: list[Any] | tuple[Any, ...]) Self ¶
Creates an SQL comma seperated list
>>> from sillyorm.sql import SQL >>> print(SQL.commaseperated( ... [SQL.identifier("someid"), 123, 1.2, 'hello world'] ... ).code()) "someid", 123, 1.2, 'hello world'
- Parameters:
values (list[Any] | tuple[Any, ...]) – The values in the list
- Returns:
Returns an instance of the
SQL
class with the list in it- Return type:
- classmethod escape(value: str | int | float) Self ¶
Escapes values so they can be safely used in SQL
>>> from sillyorm.sql import SQL >>> print(SQL.escape("hello ' \" world").code()) 'hello '' " world' >>> print(SQL.escape(123).code()) 123
- Parameters:
value (str | int | float) – Value to escape
- Returns:
Returns an instance of the
SQL
class with the escaped SQL in it- Return type:
- classmethod identifier(name: str) Self ¶
Creates an SQL identifier (surrounded in double quotes) and ensures it does not contain any invalid characters
>>> from sillyorm.sql import SQL >>> SQL.identifier("hello\"world") Traceback (most recent call last): ... sillyorm.exceptions.SillyORMException: invalid SQL identifier >>> print(SQL.identifier("some_identifier").code()) "some_identifier"
- Parameters:
name (str) – The identifier string
- Returns:
Returns an instance of the
SQL
class with the identifier in it- Return type:
- classmethod set(values: list[Any] | tuple[Any, ...]) Self ¶
Creates an SQL set
>>> from sillyorm.sql import SQL >>> print(SQL.set( ... [SQL.identifier("someid"), 123, 1.2, 'hello world'] ... ).code()) ("someid", 123, 1.2, 'hello world')
- Parameters:
values (list[Any] | tuple[Any, ...]) – The values in the list
- Returns:
Returns an instance of the
SQL
class with the set in it- Return type:
- classmethod type(t: SqlType) Self ¶
Creates an SQL type
>>> from sillyorm.sql import SQL, SqlType >>> print(SQL.type(SqlType.varchar(123)).code()) VARCHAR(123)
- Parameters:
t (
sillyorm.sql.SqlType
) – The type- Returns:
Returns an instance of the
SQL
class with the type in it- Return type:
- class sillyorm.sql.SqlConstraint(kind: str, **kwargs: Any)¶
Bases:
object
Class for SQL constraints
- Variables:
kind (str) – SQL constraint kind as string
args (dict) – Extra arguments (like foreign_table for
foreign_key
)
- Parameters:
kind – SQL constraint kind as string
**kwargs – The kwargs dict is saved in
args
Warning
You should not call the constructor of this class directly.
- static foreign_key(foreign_table: str, foreign_column: str) SqlConstraint ¶
FOREIGN KEY SQL constraint
- Parameters:
foreign_table (str) – Foreign table
foreign_column (str) – Foreign column
- static not_null() SqlConstraint ¶
NOT NULL SQL constraint
- static primary_key() SqlConstraint ¶
PRIMARY KEY SQL constraint
- static unique() SqlConstraint ¶
UNIQUE SQL constraint
- class sillyorm.sql.SqlType(value: str)¶
Bases:
object
Class for SQL data types
- Variables:
value (str) – SQL type as string
- Parameters:
value (str) – SQL type as string
Warning
You should not call the constructor of this class directly.
- class sillyorm.sql.TableManager(table_name: str)¶
Bases:
object
Class for managing an SQL table
- Parameters:
table_name (str) – SQL table name
- delete_records(cr: Cursor, extra_sql: SQL) None ¶
Deletes records
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to useextra_sql (
sillyorm.sql.SQL
) – Some extra SQL to use for selecting which records to delete. Would typically be some WHERE.
- insert_record(cr: Cursor, vals: dict[str, Any]) None ¶
Creates a record
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to usevals (dict[str, Any]) – The values for the columns
- read_records(cr: Cursor, columns: list[str], extra_sql: SQL) list[dict[str, Any]] ¶
Reads records
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to usecolumns (list[str]) – The names of the columns to return
extra_sql (
sillyorm.sql.SQL
) – Some extra SQL to use for selecting which records to update. Would typically be some WHERE.
- Returns:
A list of dictionaries where the key is the column name and the value is the value read from the specified column
- Return type:
list[dict[str, Any]]
- search_count_records(cr: Cursor, domain: list[str | tuple[str, str, Any]]) int ¶
Counts the amount of records that would be found by a search_records with the specified domain
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to usedomain (list[str | tuple[str, str, Any]]) – The search domain
- Returns:
The amount of records found
- Return type:
int
- search_records(cr: Cursor, columns: list[str], domain: list[str | tuple[str, str, Any]], order_by: str | None = None, order_asc: bool = True, offset: int | None = None, limit: int | None = None) list[Any] ¶
Searches for records
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to usecolumns (list[str]) – The names of the columns to return
domain (list[str | tuple[str, str, Any]]) – The search domain
order_by (str | None) – The column to order by
order_asc (bool) – Whether 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:
The records found (emtpy list if none were found)
- Return type:
list[Any]
- table_init(cr: Cursor, columns: list[ColumnInfo], no_update: bool) None ¶
Initializes the database table
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to usecolumns (list[
sillyorm.sql.ColumnInfo
]) – The columns the table should haveno_update (bool) – If True, do not update anything, just check and if something needs to be updated, throw an exception
- update_records(cr: Cursor, column_vals: dict[str, Any], extra_sql: SQL) None ¶
Updates records
- Parameters:
cr (
sillyorm.sql.Cursor
) – The cursor to usecolumn_vals (dict[str, Any]) – The values for the columns
extra_sql (
sillyorm.sql.SQL
) – Some extra SQL to use for selecting which records to update. Would typically be some WHERE.