Package: dataloom
dataloomdataloomis a lightweight and versatile Object-Relational Mapping (ORM) library for Python. With support forPostgreSQL,MySQL, andSQLite3databases,dataloomsimplifies database interactions, providing a seamless experience for developers.Why choosedataloom?Ease of Use:dataloomoffers a user-friendly interface, making it straightforward to work with.Flexible SQL Driver: Write one codebase and seamlessly switch betweenPostgreSQL,MySQL, andSQLite3drivers as needed.Lightweight: Despite its powerful features,dataloomremains lightweight, ensuring efficient performance.Comprehensive Documentation: Benefit from extensive documentation that guides users through various functionalities and use cases.Active Maintenance:dataloomis actively maintained, ensuring ongoing support and updates for a reliable development experience.Cross-platform Compatibility:dataloomworks seamlessly across different operating systems, includingWindows,macOS, andLinux.Scalability: Scale your application effortlessly withdataloom, whether it's a small project or a large-scale enterprise application.Table of ContentsdataloomWhy choosedataloom?Table of ContentsKey Features:InstallationPython Version CompatibilityUsageConnectionPostgresMySQLSQLiteDataloom ClassesLoomClassModelClassColumnClassColumn Datatypes1.mysql2.postgres3.sqlitePrimaryKeyColumnClassForeignKeyColumnClassCreatedAtColumnClassUpdatedAtColumnClassFilterClassColumnValueClassOrderClassIncludeClassGroupClassHavingClassSyncing Tables1. Thesyncmethod.2. Theconnect_and_syncmethod.CRUD Operations with Dataloom1. Creating a Record1.insert_one()2.insert_bulk().2. Reading records1.find_all()2.find_many()3.find_one()4.find_by_pk()3. Updating a record1.update_by_pk()2.update_one()3.update_bulk()4. Deleting a record1.delete_by_pk()2.delete_one()3.delete_bulk()Warning: Potential Risk withdelete_bulk()Guidelines for Safe UsageOrderingFiltersOperatorsData AggregationAggregation FunctionsUtilities1.inspect()2.decorators@initialize()3.count()4.min()5.max()6.avg()7.sum()Associations1.1-1AssociationInsertingRetrieving Records2.N-1AssociationInsertingRetrieving Records3.1-NAssociationInsertingRetrieving Records4. What about bidirectional queries?1. Child to Parent2. Parent to Child5.SelfAssociationInsertingRetrieving Records6.N-NRelationshipInsertingRetrieving RecordsQuery Builder.Why Use Query Builder?ContributingLicenseKey Features:Lightweight:dataloomis designed to be minimalistic and easy to use, ensuring a streamlinedORMexperience without unnecessary complexities.Database Support:dataloomsupports popular relational databases such asPostgreSQL,MySQL, andSQLite3, making it suitable for a variety of projects.Simplified Querying: TheORMsimplifies the process of database querying, allowing developers to interact with the database using Python classes and methods rather than raw SQL queries.Intuitive Syntax:dataloom's syntax is intuitive andPythonic, making it accessible for developers familiar with the Python language.Flexible Data Types: TheORMseamlessly handles various data types, offering flexibility in designing database schemas.InstallationTo installdataloom, you just need to run the following command usingpip:pipinstalldataloomPython Version CompatibilitydataloomsupportsPythonversion3.12and above. Ensure that you are using a compatible version ofPythonbefore installing or usingdataloom.You can check yourPythonversion by running:python--versionUsageIn this section we are going to go through how you can use ourormpackage in your project.ConnectionTo use Dataloom, you need to establish a connection with a specific databasedialect. The available dialect options aremysql,postgres, andsqlite.PostgresThe following is an example of how you can establish a connection with postgres database.fromdataloomimportLoom# Create a Loom instance with PostgreSQL configurationpg_loom=Loom(dialect="postgres",database="hi",password="root",user="postgres",host="localhost",sql_logger="console",logs_filename="logs.sql",port=5432,)# Connect to the PostgreSQL databaseconn=pg_loom.connect()# Close the connection when the script completesif__name__=="__main__":conn.close()Indataloomyou can use connection uris to establish a connection to the database inpostgresas follows:pg_loom=Loom(dialect="postgres",connection_uri="postgressql://root:root@localhost:5432/hi",# ...)This will establish a connection withpostgreswith the databasehi.MySQLTo establish a connection with aMySQLdatabase usingLoom, you can use the following example:fromdataloomimportLoom# Create a Loom instance with MySQL configurationmysql_loom=Loom(dialect="mysql",database="hi",password="root",user="root",host="localhost",sql_logger="console",logs_filename="logs.sql",port=3306,)# Connect to the MySQL databaseconn=mysql_loom.connect()# Close the connection when the script completesif__name__=="__main__":conn.close()Indataloomyou can use connection uris to establish a connection to the database inmysqlas follows:mysql_loom=Loom(dialect="mysql",connection_uri="mysql://root:root@localhost:3306/hi",# ...)This will establish a connection withmysqlwith the databasehi.SQLiteTo establish a connection with anSQLitedatabase usingLoom, you can use the following example:fromdataloomimportLoom# Create a Loom instance with SQLite configurationsqlite_loom=Loom(dialect="sqlite",database="hi.db",logs_filename="sqlite-logs.sql",logging=True,sql_logger="console",)# Connect to the SQLite databaseconn=sqlite_loom.connect()# Close the connection when the script completesif__name__=="__main__":conn.close()Indataloomyou can use connection uris to establish a connection to the database insqliteas follows:sqlite_loom=Loom(dialect="sqlite",connection_uri="sqlite:///hi.db",# ...)This will establish a connection withsqlitewith the databasehi.Dataloom ClassesThe following are the list of classes that are available indataloom.LoomClassThis class is used to create a loom object that will be use to perform actions to a database. The following example show how you can create aloomobject using this class.fromdataloomimportLoomloom=Loom(dialect="postgres",database="hi",password="root",user="postgres",host="localhost",sql_logger="console",logs_filename="logs.sql",port=5432,)# OR with connection_uriloom=Loom(dialect="mysql",connection_uri="mysql://root:root@localhost:3306/hi",# ...)TheLoomclass takes in the following options:ParameterDescriptionValue TypeDefault ValueRequiredconnection_uriThe connectionurifor the specified dialect.strorNoneNoneNodialectDialect for the database connection. Options aremysql,postgres, orsqlite"mysql" | "postgres" | "sqlite"NoneYesdatabaseName of the database formysqlandpostgres, filename forsqlitestrorNoneNoneNopasswordPassword for the database user (only formysqlandpostgres)strorNoneNoneNouserDatabase user (only formysqlandpostgres)strorNoneNoneNohostDatabase host (only formysqlandpostgres)strorNonelocalhostNosql_loggerEnable logging for the database queries. If you don't want to see the sql logs you can set this option toNonewhich is the default value. If you set it tofilethen you will see the logs in the defaultdataloom.sqlfile, you can overide this by passing alogs_filenameoption. Setting this option toconsole, then sql statements will be printed on the console.consoleorfileorNoneTrueNologs_filenameFilename for the query logsstrorNonedataloom.sqlNoportPort number for the database connection (only formysqlandpostgres)intorNoneNoneNoModelClassA model in Dataloom is a top-level class that facilitates the creation of complex SQL tables using regular Python classes. This example demonstrates how to define two tables,UserandPost, by creating classes that inherit from theModelclass.fromdataloomimport(Loom,Model,PrimaryKeyColumn,Column,CreatedAtColumn,UpdatedAtColumn,TableColumn,ForeignKeyColumn,)classUser(Model):__tablename__:TableColumn=TableColumn(name="users")id=PrimaryKeyColumn(type="int",auto_increment=True)name=Column(type="text",nullable=False,default="Bob")username=Column(type="varchar",unique=True,length=255)# timestampscreatedAt=CreatedAtColumn()updatedAt=UpdatedAtColumn()classPost(Model):__tablename__:TableColumn=TableColumn(name="posts")id=PrimaryKeyColumn(type="int",auto_increment=True,nullable=False,unique=True)completed=Column(type="boolean",default=False)title=Column(type="varchar",length=255,nullable=False,)# timestampscreatedAt=CreatedAtColumn()updatedAt=UpdatedAtColumn()# relationsuserId=ForeignKeyColumn(User,type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE")Within theUsermodel definition, the table name is explicitly specified using the__tablename__property, set to"users". This informsdataloomto use the provided name instead of automatically deriving it from the class name. IfTableColumnis not specified, the class name becomes the default table name during the synchronization of tables. To achieve this, theTableColumnclass is used, accepting the specified table name as an argument.👉:Note:When defining a table name, it's not necessary to specify the property as__tablename__. However, it's considered good practice to name your table column like that to avoid potential clashes with other columns in the table.Every table must include exactly one primary key column unless it is ajoint_tableforN-Nrelations that requires no primary key column. To define this, thePrimaryKeyColumnclass is employed, signaling todataloomthat the specified field is a primary key.TheColumnclass represents a regular column, allowing the inclusion of various options such as type and whether it is required.TheCreatedAtColumnandUpdatedAtcolumn types are automatically generated by the database as timestamps. If timestamps are unnecessary or only one of them is needed, they can be omitted.TheForeignKeyColumnestablishes a relationship between the current (child) table and a referenced (parent) table.ColumnClassIn the context of a database table, each property marked as a column in a model is treated as an individual attribute. Here's an example of how to define a column in a table using theColumnclass:username=Column(type="text",nullable=False,default="Hello there!!")Here are some other options that you can pass to theColumn:ArgumentDescriptionTypeDefaulttypeRequired datatype of a columnanydatatypenullableOptional to specify if the column will allow null values or not.boolFalselengthOptional to specify the length of the type. If passed asNwith typeT, it yields an SQL statement with typeT(N).int|NoneNoneauto_incrementOptional to specify if the column will automatically increment or not.boolFalsedefaultOptional to specify the default value in a column.anyNoneuniqueOptional to specify if the column will contain unique values or not.boolFalseTalking about data types, eachdialecthas its own accepted values. Here is a list of types supported by each and everydialect:Column DatatypesIn this section we will list all thedatatypesthat are supported for each dialect.1.mysqlData TypeDescription"int"Integer data type."smallint"Small integer data type."bigint"Big integer data type."float"Floating-point number data type."double"Double-precision floating-point number data type."numeric"Numeric or decimal data type."text"Text data type."varchar"Variable-length character data type."char"Fixed-length character data type."boolean"Boolean data type."date"Date data type."time"Time data type."timestamp"Timestamp data type."json"JSON (JavaScript Object Notation) data type."blob"Binary Large Object (BLOB) data type.2.postgresData TypeDescription"int"Integer data type (Alias:"INTEGER")."smallint"Small integer data type (Alias:"SMALLINT")."bigint"Big integer data type (Alias:"BIGINT")."serial"Auto-incrementing integer data type (Alias:"SERIAL")."bigserial"Auto-incrementing big integer data type (Alias:"BIGSERIAL")."smallserial"Auto-incrementing small integer data type (Alias:"SMALLSERIAL")."float"Real number data type (Alias:"REAL")."double precision"Double-precision floating-point number data type (Alias:"DOUBLE PRECISION")."numeric"Numeric data type (Alias:"NUMERIC")."text"Text data type."varchar"Variable-length character data type."char"Fixed-length character data type."boolean"Boolean data type."date"Date data type."time"Time data type."timestamp"Timestamp data type."interval"Time interval data type."uuid"UUID (Universally Unique Identifier) data type."json"JSON (JavaScript Object Notation) data type."jsonb"Binary JSON (JavaScript Object Notation) data type."bytea"Binary data type (Array of bytes)."array"Array data type."inet"IP network address data type."cidr"Classless Inter-Domain Routing (CIDR) address data type."macaddr"MAC (Media Access Control) address data type."tsvector"Text search vector data type."point"Geometric point data type."line"Geometric line data type."lseg"Geometric line segment data type."box"Geometric box data type."path"Geometric path data type."polygon"Geometric polygon data type."circle"Geometric circle data type."hstore"Key-value pair store data type.3.sqliteData TypeDescription"int"Integer data type."smallint"Small integer data type."bigint"Big integer data type."float"Real number data type."double precision"Double-precision floating-point number data type."numeric"Numeric data type."text"Text data type."varchar"Variable-length character data type."char"Fixed-length character data type."boolean"Boolean data type."date"Date data type."time"Time data type."timestamp"Timestamp data type."json"JSON (JavaScript Object Notation) data type."blob"Binary Large Object (BLOB) data type.Note: Every table that is not ajoint_tableis required to have a primary key column and this column should be 1. Let's talk about thePrimaryKeyColumnPrimaryKeyColumnClassThis class is used to create a unique index in every table you create. In the context of a table that inherits from theModelclass, exactly onePrimaryKeyColumnis required. Below is an example of creating anidcolumn as a primary key in a table namedPost:classPost(Model):__tablename__:Optional[TableColumn]=TableColumn(name="users")id=PrimaryKeyColumn(type="int",auto_increment=True)#...rest of your columnsThe following are the arguments that thePrimaryKeyColumnclass accepts.ArgumentDescriptionTypeDefaulttypeThe datatype of your primary key.str"int"lengthOptional to specify the length of the type. If passed asNwith typeT, it yields an SQL statement with typeT(N).int|NoneNoneauto_incrementOptional to specify if the column will automatically increment or not.boolFalsedefaultOptional to specify the default value in a column.anyNonenullableOptional to specify if the column will allow null values or not.boolFalseuniqueOptional to specify if the column will contain unique values or not.boolTrueForeignKeyColumnClassThis class is utilized when informingdataloomthat a column has a relationship with a primary key in another table. Consider the following model definition of aPost:classPost(Model):__tablename__:Optional[TableColumn]=TableColumn(name="posts")id=PrimaryKeyColumn(type="int",auto_increment=True,nullable=False,unique=True)completed=Column(type="boolean",default=False)title=Column(type="varchar",length=255,nullable=False)# timestampscreatedAt=CreatedAtColumn()updatedAt=UpdatedAtColumn()# relationsuserId=ForeignKeyColumn(User,type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE")userIdis a foreign key in the tableposts, indicating it has a relationship with a primary key in theuserstable.This column accepts the following arguments:ArgumentDescriptionTypeDefaulttableRequired. This is the parent table that the current model references. In our example, this is referred to asUser. It can be used as a string in self relations.Model|strtypeOptional. Specifies the data type of the foreign key. If not provided, dataloom can infer it from the parent table.str|NoneNonerequiredOptional. Indicates whether the foreign key is required or not.boolFalseonDeleteOptional. Specifies the action to be taken when the associated record in the parent table is deleted."NO ACTION","SET NULL","CASCADE""NO ACTION"onUpdateOptional. Specifies the action to be taken when the associated record in the parent table is updated."NO ACTION","SET NULL","CASCADE""NO ACTION"It is crucial to specify the actions foronDeleteandonUpdateto ensure thatdataloommanages your model's relationship actions appropriately. The available actions are:"NO ACTION"- If you delete or update the parent table, no changes will occur in the child table."SET NULL"- If you delete or update the parent table, the corresponding value in the child table will be set tonull."CASCADE"- If you delete or update the table, the same action will also be applied to the child table.CreatedAtColumnClassWhen a column is designated asCreatedAtColumn, its value will be automatically generated each time you create a new record in a database, serving as a timestamp.UpdatedAtColumnClassWhen a column is designated asUpdatedAtColumn, its value will be automatically generated each time you create a new record or update an existing record in a database table, acting as a timestamp.FilterClassThisFilterclass indataloomis designed to facilitate the application of filters when executing queries and mutations. It allows users to specify conditions that must be met for the operation to affect certain rows in a database table. Below is an example demonstrating how this class can be used:affected_rows=pg_loom.update_one(Post,values=[ColumnValue(name="title",value="Hey"),ColumnValue(name="completed",value=True),],filters=[Filter(column="id",value=1,join_next_with="AND"),Filter(column="userId",value=1,join_next_with="AND"),],)So from the above example we are applying filters while updating aPosthere are the options that you can pass on that filter class:ArgumentDescriptionTypeDefaultcolumnThe name of the column to apply the filter onString-valueThe value to filter againstAny-operatorThe comparison operator to use for the filter'eq','neq'.'lt','gt','leq','geq','in','notIn','like','between','not''eq'join_next_withThe logical operator to join this filter with the next one'AND','OR''AND'👍Pro Tip:Note You can apply either a list of filters or a single filter when filtering records.ColumnValueClassJust like theFilterclass,dataloomalso provides aColumnValueclass. This class acts as a setter to update the values of columns in your database table.The following code snippet demonstrates how theColumnValueclass is used to update records in the database:re=pg_loom.update_one(Post,values=[ColumnValue(name="title",value="Hey"),ColumnValue(name="completed",value=True),],filters=[Filter(column="id",value=1,join_next_with="AND"),Filter(column="userId",value=1,join_next_with="AND"),],)It accepts two arguments:nameandvalue. name represents the column name, while value corresponds to the new value to be assigned to that column.ArgumentDescriptionTypeDefaultnameThe name of the column to be updated or inserted.str-valueThe value to assign to the column during update or insert.Any-OrderClassTheOrderclass enables us to specify the desired order in which documents should be returned. Below is an example illustrating its usage:posts=pg_loom.find_all(Post,select=["id","completed","title","createdAt"],limit=3,offset=0,order=[Order(column="createdAt",order="ASC"),Order(column="id",order="DESC"),])👍Pro Tip:Note when utilizing a list of orders, they are applied sequentially, one after the other:ArgumentDescriptionTypeDefaultcolumnThe name of the column to order by.str-orderThe order direction, either"ASC"(ascending) or"DESC"(descending)."ASC"or"DESC""ASC"IncludeClassTheIncludeclass facilitates eager loading for models with relationships. Below is a table detailing the parameters available for theIncludeclass:ArgumentDescriptionTypeDefaultRequiredmodelThe model to be included when eagerly fetching records.Model-Yesjunction_tableThejunction_tablemodel that is used as a reference table in a many to many association.ModelNoneNoorderThe list of order specifications for sorting the included data.list[Order][]NolimitThe maximum number of records to include.int | None0NooffsetThe number of records to skip before including.int | None0NoselectThe list of columns to include.list[str] | NoneNoneNohasThe relationship type between the current model and the included model.INCLUDE_LITERAL"many"NoincludeThe extra included models.list[Include][]NoaliasThe alias name for the included model. Very important when mapping self relations.strNoneNoGroupClassThis class is used for dataaggregationand grouping data indataloom. Below is a table detailing the parameters available for theGroupclass:ArgumentDescriptionTypeDefaultRequiredcolumnThe name of the column to group by.strYesfunctionThe aggregation function to apply on the grouped data."COUNT" | "AVG" | "SUM" | "MIN" | "MAX""COUNT"NohavingFilters to apply to the grouped data.list[Having] | Having | NoneNoneNoreturn_aggregation_columnWhether to return the aggregation column in the result.boolFalseNoHavingClassThis class method is used to specify the filters to be applied onGroupeddata duringaggregationindataloom. Below is a table detailing the parameters available for theGroupclass:ArgumentDescriptionTypeDefaultRequiredcolumnThe name of the column to filter on.strYesoperatorThe operator to use for the filter.OPERATOR_LITERAL|None"eq"NovalueThe value to compare against.AnyYesjoin_next_withThe SQL operand to join the next filter with."AND" | "OR"|None"AND"NoSyncing TablesSyncing tables involves the process of creating tables from models and saving them to a database. After defining your tables, you will need to synchronize your database tables using thesyncmethod.1. Thesyncmethod.This method enables you to create and save tables into the database. For instance, if you have two models,UserandPost, and you want to synchronize them with the database, you can achieve it as follows:tables=sqlite_loom.sync([Post,User],drop=True,force=True)print(tables)The method returns a list of table names that have been created or that exist in your database. Thesyncmethod accepts the following arguments:ArgumentDescriptionTypeDefaultmodelsA collection or a single instance(s) of your table classes that inherit from the Model class.list[Model]|Model[]dropWhether to drop tables during syncing or not.boolFalseforceForcefully drop tables during syncing. Inmysqlthis will temporarily disable foreign key checks when droping the tables.boolFalsealterAlter tables instead of dropping them during syncing or not.boolFalse🥇We recommend you to usedroporforceif you are going to change or modifyforeignandprimarykeys. This is because setting the optionalterdoes not have an effect onprimarykey columns.We've noticed two steps involved in starting to work with ourorm. Initially, you need to create a connection and then synchronize the tables in another step.2. Theconnect_and_syncmethod.Theconnect_and_syncfunction proves to be very handy as it handles both the database connection and table synchronization. Here is an example demonstrating its usage:# ....sqlite_loom=Loom(dialect="sqlite",database="hi.db",logs_filename="sqlite-logs.sql",logging=True)conn,tables=sqlite_loom.connect_and_sync([Post,User],drop=True,force=True)print(tables)if__name__=="__main__":conn.close()Returns aconnand the list oftablenamesthat exist in the database. The method accepts the same arguments as thesyncmethod.CRUD Operations with DataloomIn this section of the documentation, we will illustrate how to perform basicCRUDoperations usingdataloomon simpleModels. Please note that in the following code snippets, I will be utilizingsqlite_loom. However, it's important to highlight that you can use anyloomof your choice to follow along.1. Creating a RecordTo insert a single or multiple records in a database you make use of the following functions:insert_one()insert_bulk()1.insert_one()Theinsert_onemethod allows you to save a single row in a specific table. Upon saving, it will return the primary key (pk) value of the inserted document. The following example shows how theinsert_one()method works.# Example: Creating a user recorduserId=pg_loom.insert_one(instance=User,values=ColumnValue(name="username",value="@miller"))userId=pg_loom.insert_one(instance=User,values=[ColumnValue(name="username",value="@miller"),ColumnValue(name="name",value="Jonh"),],)This function takes in two arguments which areinstanceandvalues. Where values are the column values that you are inserting in a user table or a single column value.ArgumentDescriptionTypeRequiredDefaultinstanceThe instance of the table where the row will be inserted.ModelYesNonevaluesThe column values to be inserted into the table. It can be a single column value or a list of column values.list[ColumnValue]orColumnValueYesNone2.insert_bulk().Theinsert_bulkmethod facilitates the bulk insertion of records, as its name suggests. The following example illustrates how you can add3posts to the database table simultaneously.# Example: Inserting multiple postsrows=pg_loom.insert_bulk(User,values=[[ColumnValue(name="username",value="@miller"),ColumnValue(name="name",value="Jonh"),],[ColumnValue(name="username",value="@brown"),ColumnValue(name="name",value="Jonh"),],[ColumnValue(name="username",value="@blue"),ColumnValue(name="name",value="Jonh"),],],)The argument parameters for theinsert_bulkmethods are as follows.ArgumentDescriptionTypeRequiredDefaultinstanceThe instance of the table where the row will be inserted.ModelYesNonevaluesThe column values to be inserted into the table.It must be a list of list of column values with the same length, otherwise dataloom will fail to map the values correctly during the insert operation.list[list[ColumnValue]]orColumnValueYesNoneIn contrast to theinsert_onemethod, theinsert_bulkmethod returns the row count of the inserted documents rather than the individual primary keys (pks) of those documents.2. Reading recordsTo retrieve documents or a document from the database, you can make use of the following functions:find_all(): This function is used to retrieve all documents from the database.find_by_pk(): This function is used to retrieve a document by its primary key (or ID).find_one(): This function is used to retrieve a single document based on a specific condition.find_many(): This function is used to retrieve multiple documents based on a specific condition.1.find_all()This method is used to retrieve all the records that are in the database table. Below are examples demonstrating how to do it:users=pg_loom.find_all(instance=User,select=["id","username"],limit=3,offset=0,order=[Order(column="id",order="DESC")],)print(users)# ? [{'id': 1, 'username': '@miller'}]Thefind_all()method takes in the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYesselectCollection or a string of fields to select from the documents.list[str]|strNoneNolimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before retrieving.int0NoorderCollection ofOrderor a singleOrderto order the documents when querying.list[Order]|OrderNoneNoincludeCollection or aIncludeof related models to eagerly load.list[Include]|IncludeNoneNogroupCollection ofGroupwhich specifies how you want your data to be grouped during queries.list[Group]|GroupNoneNodistinctBoolean telling dataloom to return distinct row values based on selected fields or not.boolFalseNo👍Pro Tip: A collection can be any python iterable, the supported iterables arelist,set,tuple.2.find_many()Here is an example demonstrating the usage of thefind_many()function with specific filters.users=mysql_loom.find_many(User,filters=[Filter(column="username",value="@miller")],select=["id","username"],offset=0,limit=10,)print(users)# ? [{'id': 1, 'username': '@miller'}]Thefind_many()method takes in the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYesselectCollection or a string of fields to select from the documents.list[str]|strNoneNolimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before retrieving.int0NoorderCollection ofOrderor a singleOrderto order the documents when querying.list[Order]|OrderNoneNoincludeCollection or aIncludeof related models to eagerly load.list[Include]|IncludeNoneNogroupCollection ofGroupwhich specifies how you want your data to be grouped during queries.list[Group]|GroupNoneNofiltersCollection ofFilteror aFilterto apply to the query.list[Filter] | FilterNoneNodistinctBoolean telling dataloom to return distinct row values based on selected fields or not.boolFalseNo👍Pro Tip: The distinction between thefind_all()andfind_many()methods lies in the fact thatfind_many()enables you to apply specific filters, whereasfind_all()retrieves all the documents within the specified model.3.find_one()Here is an example showing you how you can usefind_by_pk()locate a single record in the database.user=mysql_loom.find_one(User,filters=[Filter(column="username",value="@miller")],select=["id","username"],)print(user)# ? {'id': 1, 'username': '@miller'}This method take the following as argumentsArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve instances from.ModelYesfiltersFilteror a collection ofFilterto apply to the query.Filter | list[Filter] | NoneNoneNoselectCollection ofstrorstrof which is the name of the columns or column to be selected.list[str]|str[]NoincludeCollection ofIncludeor a singleIncludeof related models to eagerly load.list[Include]|Include[]NooffsetNumber of instances to skip before retrieving.int | NoneNoneNo4.find_by_pk()Here is an example showing how you can use thefind_by_pk()to locate a single record in the database.user=mysql_loom.find_by_pk(User,pk=userId,select=["id","username"])print(user)# ? {'id': 1, 'username': '@miller'}The method takes the following as arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve instances from.ModelYespkThe primary key value to use for retrieval.AnyYesselectCollection column names to select from the instances.list[str][]NoincludeA Collection ofIncludeor a singleIncludeof related models to eagerly load.list[Include]|Include[]No3. Updating a recordTo update records in your database table you make use of the following functions:update_by_pk()update_one()update_bulk()1.update_by_pk()Theupdate_pk()method can be used as follows:affected_rows=mysql_loom.update_by_pk(instance=Post,pk=1,values=[ColumnValue(name="title",value="Updated?"),],)The above method takes in the following as arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class for which to update the instance.ModelYespkThe primary key value of the instance to update.AnyYesvaluesSingle or Collection ofColumnValueto update in the instance.ColumnValue | list[ColumnValue]Yes2.update_one()Here is an example illustrating how to use theupdate_one()method:affected_rows=mysql_loom.update_one(instance=Post,filters=[Filter(column="id",value=8,join_next_with="OR"),Filter(column="userId",value=1,join_next_with="OR"),],values=[ColumnValue(name="title",value="Updated?"),],)The method takes the following as arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class for which to update the instance(s).ModelYesfiltersFilter or collection of filters to apply to the update query.Filter | list[Filter] | NoneYesvaluesSingle or collection of column-value pairs to update in the instance.ColumnValue | list[ColumnValue]Yes3.update_bulk()Theupdate_bulk()method updates all records that match a filter in a database table.affected_rows=mysql_loom.update_bulk(instance=Post,filters=[Filter(column="id",value=8,join_next_with="OR"),Filter(column="userId",value=1,join_next_with="OR"),],values=[ColumnValue(name="title",value="Updated?"),],)The above method takes in the following as argument:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class for which to update instances.ModelYesfiltersFilter or collection of filters to apply to the update query.Filter | list[Filter] | NoneYesvaluesSingle or collection of column-value pairs to update in the instance.ColumnValue | list[ColumnValue]Yes4. Deleting a recordTo delete a record or records in a database table you make use of the following functions:delete_by_pk()delete_one()delete_bulk()1.delete_by_pk()Using thedelete_by_pk()method, you can delete a record in a database based on the primary-key value.affected_rows=mysql_loom.delete_by_pk(instance=User,pk=1)The above take the following as arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class from which to delete the instance.ModelYespkThe primary key value of the instance to delete.AnyYes2.delete_one()You can also usefiltersto delete a record in a database. Thedelete_one()function enables you to delete a single record in a database that matches a filter.affected_rows=mysql_loom.delete_one(instance=User,filters=[Filter(column="username",value="@miller")])The method takes in the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class from which to delete the instance(s).ModelYesfiltersFilter or collection of filters to apply to the deletion query.Filter | list[Filter] | NoneNoneNooffsetNumber of instances to skip before deleting.int | NoneNoneNoorderCollection ofOrderor as singleOrderto order the instances by before deletion.list[Order] | Order| None[]No3.delete_bulk()You can also use thedelete_bulk()method to delete a multitude of records that match a given filter:affected_rows=mysql_loom.delete_bulk(instance=User,filters=[Filter(column="username",value="@miller")])The method takes the following as arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class from which to delete instances.ModelYesfiltersFilter or collection of filters to apply to the deletion query.Filter | list[Filter] | NoneNoneNolimitMaximum number of instances to delete.int | NoneNoneNooffsetNumber of instances to skip before deleting.int | NoneNoneNoorderCollection ofOrderor a singleOrderto order the instances by before deletion.list[Order] |Order| None[]NoWarning: Potential Risk withdelete_bulk()⚠️Warning:When using thedelete_bulk()function, exercise caution as it can be aggressive. If the filter is not explicitly provided, there is a risk of mistakenly deleting all records in the table.Guidelines for Safe UsageTo mitigate the potential risks associated withdelete_bulk(), follow these guidelines:Always Provide a Filter:When callingdelete_bulk(), make sure to provide a filter to specify the subset of records to be deleted. This helps prevent unintentional deletions.# Example: Delete records where 'status' is 'inactive'sqlite_loom.delete_bulk(filter={'status':'inactive'})Consider Usage When Necessary:When contemplating data deletion, it is advisable to consider more targeted methods before resorting todelete_bulk(). Prioritize the use ofdelete_one()ordelete_by_pk()methods to remove specific records based on your needs. This ensures a more precise and controlled approach to data deletion.Use limit and offsets optionsYou can consider using thelimitand offset options during invocation ofdelete_bulkaffected_rows=mysql_loom.delete_bulk(instance=Post,order=[Order(column="id",order="DESC"),Order(column="createdAt",order="ASC")],filters=[Filter(column="id",operator="gt",value=0)],offset=0,limit=10,)By following these guidelines, you can use thedelete_bulk()function safely and minimize the risk of unintended data loss. Always exercise caution and adhere to best practices when performing bulk deletion operations.OrderingIn dataloom you can order documents in eitherDESC(descending) orASC(ascending) order using the helper classOrder.posts=mysql_loom.find_all(instance=Post,order=[Order(column="id",order="DESC")],)You can apply multiple and these orders will ba applied in sequence of application here is an example:posts=mysql_loom.find_all(instance=Post,order=[Order(column="id",order="DESC"),Order(column="createdAt",order="ASC")],)FiltersThere are different find of filters that you can use when filtering documents for mutations and queries. Filters are very important to use when updating and deleting documents as they give you control on which documents should be updated or deleted. When doing a mutation you can use a single or multiple filters. Bellow is an example that shows you how you can use a single filter in deleting a single record that has anidgreater than1from the database.res2=mysql_loom.delete_one(instance=Post,offset=0,order=Order(column="id",order="DESC"),filters=Filter(column="id",value=1,operator="gt"),)Or you can use it as follows:res2=mysql_loom.delete_one(instance=Post,offset=0,order=[Order(column="id",order="DESC")],filters=[Filter(column="id",value=1,operator="gt")],)As you have noticed, you can join your filters together and they will be applied sequentially using thejoin_next_withwhich can be eitherORorANDte default value isAND. Here is an of filter usage in sequential.res2=mysql_loom.delete_one(instance=Post,offset=0,order=[Order(column="id",order="DESC")],filters=[Filter(column="id",value=1,operator="gt"),Filter(column="userId",value=1,operator="eq",join_next_with="OR"),Filter(column="title",value='"What are you doing general?"',operator="=",join_next_with="AND",),],)OperatorsYou can use theoperatorto match the values. Here is the table of description for these filters.OperatorExplanationExpect'eq'Indicates equality. It checks if the value is equal to the specified criteria.Value == Criteria'lt'Denotes less than. It checks if the value is less than the specified criteria.Value < Criteria'gt'Denotes greater than. It checks if the value is greater than the specified criteria.Value > Criteria'leq'Denotes less than or equal to. It checks if the value is less than or equal to the specified criteria.Value <= Criteria'geq'Denotes greater than or equal to. It checks if the value is greater than or equal to the specified criteria.Value >= Criteria'in'Checks if the value is included in a specified list of values.Value in List'notIn'Checks if the value is not included in a specified list of values.Value not in List'like'Performs a pattern matching operation. It checks if the value is similar to a specified pattern.Value matches Pattern'not'Indicates non-equality. It checks if the column value that does not equal to the specified criteria.NOT id = Criteria'neq'Indicates non-equality. It checks if the value is not equal to the specified criteria.Value != Criteria'between'It checks range values that matches a given range between the minimum and maximum.id BETWEEN (min, max)Let's talk about these filters in detail of code by example. Let's say you want to update aPostwhere theidmatches1you can do it as follows:res2=mysql_loom.update_one(instance=Post,filters=Filter(column="id",value=1,operator="eq",),values=[ColumnValue(name="title",value="Bob")],)What if you want to update a post whereidis not equal to1you can do it as followsres2=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=1,operator="neq",),values=[ColumnValue(name="title",value="Bob")],)What if i want to update the records that have anidless than3?res2=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=3,operator="lt",),values=[ColumnValue(name="title",value="Bob")],)What if i want to update the records that have anidless than or equal3?res2=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=1,operator="neq",),values=[ColumnValue(name="title",value="Bob")],)What if i want to update the records that have anidgreater than3?res=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=3,operator="gt",),values=[ColumnValue(name="title",value="Bob")],)What if i want to update the records that have anidgreater or equal to3?res=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=3,operator="geq",),values=[ColumnValue(name="title",value="Bob")],)You can use theinto update or query records that matches values in a specifiedlistof values ortuple. Here is an example showing you how you can update records that does matchesidin[1, 2].res=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=[1,2],operator="in",),values=[ColumnValue(name="title",value="Bob")],)You can use thenotInto update or query records that does not matches values in a specifiedlistof values ortuple. Here is an example showing you how you can update records that does not matchesidin[1, 2].res=mysql_loom.update_bulk(instance=Post,filters=Filter(column="id",value=[1,2],operator="notIn",),values=[ColumnValue(name="title",value="Bob")],)You can use thelikeoperator to match some patens in your query filters. Let's say we want to match a post that has the title ends withgeneralwe can use thelikeoperator as followsgeneral=mysql_loom.find_one(instance=Post,filters=Filter(column="title",value="% general?",operator="like",),select=["id","title"],)print(general)# ?  {'id': 1, 'title': 'What are you doing general?'}The following table show you some expression that you can use with thislikeoperator.ValueDescription%patternFinds values that end with the specified pattern.pattern%Finds values that start with the specified pattern.%pattern%Finds values that contain the specified pattern anywhere within the string._patternFinds values that have any single character followed by the specified pattern.pattern_Finds values that have the specified pattern followed by any single character.[charlist]%Finds values that start with any character in the specified character list.[!charlist]%Finds values that start with any character not in the specified character list._pattern_Finds values that have any single character followed by the specified pattern and then followed by any single character.Data AggregationWith theHavingand theGroupclasses you can perform some powerful powerful queries. In this section we are going to demonstrate an example of how we can do the aggregate queries.posts=mysql_loom.find_many(Post,select="id",filters=Filter(column="id",operator="gt",value=1),group=Group(column="id",function="MAX",having=Having(column="id",operator="in",value=(2,3,4)),return_aggregation_column=True,),)The following will be the output from the above query.[{'id':2,'MAX(`id`)':2},{'id':3,'MAX(`id`)':3},{'id':4,'MAX(`id`)':4}]However you can remove the aggregation column from the above query by specifying thereturn_aggregation_columnto beFalse:posts=mysql_loom.find_many(Post,select="id",filters=Filter(column="id",operator="gt",value=1),group=Group(column="id",function="MAX",having=Having(column="id",operator="in",value=(2,3,4)),return_aggregation_column=False,),)print(posts)This will output:[{'id':2},{'id':3},{'id':4}]Aggregation FunctionsYou can use the following aggregation functions that dataloom supports:Aggregation FunctionDescription"AVG"Computes the average of the values in the group."COUNT"Counts the number of items in the group."SUM"Computes the sum of the values in the group."MAX"Retrieves the maximum value in the group."MIN"Retrieves the minimum value in the group.👍Pro Tip: Note that data aggregation only works withouteagerloading and also works only withfind_may()andfind_all()in dataloom.UtilitiesDataloom comes up with some utility functions that works on an instance of a model. This is very useful when debuging your tables to see how do they look like. These function include:1.inspect()This function takes in a model as argument and inspect the model fields or columns. The following examples show how we can use this handy function in inspecting table names.table=mysql_loom.inspect(instance=User,fields=["name","type"],print_table=False)print(table)The above snippet returns a list of dictionaries containing the column name and the arguments that were passed.[{'id':{'type':'int'}},{'name':{'type':'varchar'}},{'tokenVersion':{'type':'int'}},{'username':{'type':'varchar'}}]You can print table format these fields with their types as followsmysql_loom.inspect(instance=User)Output:+--------------+---------+----------+---------+|name|type|nullable|default|+--------------+---------+----------+---------+|id|int|NO|None||name|varchar|NO|Bob||tokenVersion|int|YES|0||username|varchar|YES|None|+--------------+---------+----------+---------+Theinspectfunction take the following arguments.ArgumentDescriptionTypeDefaultRequiredinstanceThe model instance to inspect.Model-YesfieldsThe list of fields to include in the inspection.list[str]["name", "type", "nullable", "default"]Noprint_tableFlag indicating whether to print the inspection table.boolTrueNo2.decoratorsThese modules contain several decorators that can prove useful when creating models. These decorators originate fromdataloom.decorators, and at this stage, we are referring to them as "experimental."@initialize()Let's examine a model namedProfile, which appears as follows:classProfile(Model):__tablename__:Optional[TableColumn]=TableColumn(name="profiles")id=PrimaryKeyColumn(type="int",auto_increment=True)avatar=Column(type="text",nullable=False)userId=ForeignKeyColumn(User,maps_to="1-1",type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE",)This is simply a Python class that inherits from the top-level classModel. However, it lacks some usefuldundermethods such as__init__and__repr__. In standard Python, we can achieve this functionality by usingdataclasses. For example, we can modify our class as follows:fromdataclassesimportdataclass@dataclassclassProfile(Model):# ....However, this approach doesn't function as expected indataloomcolumns. Hence, we've devised these experimental decorators to handle the generation of essential dunder methods required for working withdataloom. If you prefer not to use decorators, you always have the option to manually create these dunder methods. Here's an example:classProfile(Model):# ...def__init__(self,id:int|None,avatar:str|None,userId:int|None)->None:self.id=idself.avatar=avatarself.userId=userIddef__repr__(self)->str:returnf"<{self.__class__.__name__}:id={self.id}>"@propertydefto_dict(self):return{"id":self.id,"avatar":self.avatar,"userId":self.userId}However, by using theinitializedecorator, this functionality will be automatically generated for you. Here's all you need to do:fromdataloom.decoratorsimportinitialize@initialize(repr=True,to_dict=True,init=True,repr_identifier="id")classProfile(Model):# ...👉Tip: Dataloom has a clever way of skipping theTableColumnbecause it doesn't matter in this case.Theinitializedecorator takes the following arguments:ArgumentDescriptionTypeDefaultRequiredto_dictFlag indicating whether to generate ato_dictmethod.boolFalseNoinitFlag indicating whether to generate an__init__method.boolTrueNoreprFlag indicating whether to generate a__repr__method.boolFalseNorepr_identifierIdentifier for the attribute used in the__repr__method.strorNoneNoneNo👍Pro Tip:Note that thisdecoratorfunction allows us to interact with our data from the database in an object-oriented way in Python. Below is an example illustrating this concept:profile=mysql_loom.find_by_pk(Profile,pk=1,select=["avatar","id"])profile=Profile(**profile)print(profile)# ? = <Profile:id=1>print(profile.avatar)# ? hello.jpg3.count()This is a utility function that comes within theloomobject that is used to count rows in a database table that meets a specific criteria. Here is an example on how to use this utility function.# examplecount=mysql_loom.count(instance=Post,filters=Filter(column="id",operator="between",value=[1,7],),column="id",limit=3,offset=0,distinct=True,)print(count)Thecountfunction takes the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYescolumnA string of column to count values based on.strNoneYeslimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before counting.int0NofiltersCollection ofFilteror aFilterto apply to the rows to be counted.list[Filter] | FilterNoneNodistinctBoolean telling dataloom to count distinct rows of values based on selected column or not.boolFalseNo4.min()This is a utility function that comes within theloomobject that is used to find the minimum value in rows of data in a database table that meets a specific criteria. Here is an example on how to use this utility function.# example_min=mysql_loom.min(instance=Post,filters=Filter(column="id",operator="between",value=[1,7],),column="id",limit=3,offset=0,distinct=True,)print(_min)Theminfunction takes the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYescolumnA string of column to find minimum values based on.strNoneYeslimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before finding the minimum.int0NofiltersCollection ofFilteror aFilterto apply to the rows to be used.list[Filter] | FilterNoneNodistinctBoolean telling dataloom to find minimum value in distinct rows values based on selected column or not.boolFalseNo5.max()This is a utility function that comes within theloomobject that is used to find the maximum value in rows of data in a database table that meets a specific criteria. Here is an example on how to use this utility function.# example_max=mysql_loom.max(instance=Post,filters=Filter(column="id",operator="between",value=[1,7],),column="id",limit=3,offset=0,distinct=True,)print(_max)Themaxfunction takes the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYescolumnA string of column to find maximum values based on.strNoneYeslimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before finding the maximum.int0NofiltersCollection ofFilteror aFilterto apply to the rows to be used.list[Filter] | FilterNoneNodistinctBoolean telling dataloom to find maximum value in distinct rows of values based on selected column or not.boolFalseNo6.avg()This is a utility function that comes within theloomobject that is used to calculate the average value in rows of data in a database table that meets a specific criteria. Here is an example on how to use this utility function.# example_avg=mysql_loom.avg(instance=Post,filters=Filter(column="id",operator="between",value=[1,7],),column="id",limit=3,offset=0,distinct=True,)print(_avg)Themaxfunction takes the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYescolumnA string of column to calculate average values based on.strNoneYeslimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before finding the calculating the average.int0NofiltersCollection ofFilteror aFilterto apply to the rows to be used.list[Filter] | FilterNoneNodistinctBoolean telling dataloom to calculate the average value in distinct rows of values based on selected column or not.boolFalseNo7.sum()This is a utility function that comes within theloomobject that is used to find the total sum in rows of data in a database table that meets a specific criteria. Here is an example on how to use this utility function.# example_sum=mysql_loom.sum(instance=Post,filters=Filter(column="id",operator="between",value=[1,7],),column="id",limit=3,offset=0,distinct=True,)print(_sum)Thesumfunction takes the following arguments:ArgumentDescriptionTypeDefaultRequiredinstanceThe model class to retrieve documents from.ModelNoneYescolumnA string of column to sum values based on.strNoneYeslimitMaximum number of documents to retrieve.intNoneNooffsetNumber of documents to skip before summing.int0NofiltersCollection ofFilteror aFilterto apply to the rows to be used.list[Filter] | FilterNoneNodistinctBoolean telling dataloom to sum value in distinct rows values based on selected column or not.boolFalseNoAssociationsIn dataloom you can create association using theforeign-keyscolumn during model creation. You just have to specify a single model to have a relationship with another model using theForeignKeyColum. Just by doing that dataloom will be able to learn bidirectional relationship between your models. Let's have a look at the following examples:1.1-1AssociationLet's consider an example where we want to map the relationship between aUserand aProfile:classUser(Model):__tablename__:Optional[TableColumn]=TableColumn(name="users")id=PrimaryKeyColumn(type="int",auto_increment=True)name=Column(type="text",nullable=False,default="Bob")username=Column(type="varchar",unique=True,length=255)tokenVersion=Column(type="int",default=0)classProfile(Model):__tablename__:Optional[TableColumn]=TableColumn(name="profiles")id=PrimaryKeyColumn(type="int",auto_increment=True)avatar=Column(type="text",nullable=False)userId=ForeignKeyColumn(User,maps_to="1-1",type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE",)The above code demonstrates how to establish aone-to-onerelationship between aUserand aProfileusing thedataloom.UserandProfileare two model classes inheriting fromModel.Each model is associated with a corresponding table in the database, defined by the__tablename__attribute.Both models have a primary key column (id) defined usingPrimaryKeyColumn.Additional columns (name,username,tokenVersionforUserandavatar,userIdforProfile) are defined usingColumn.TheuserIdcolumn in theProfilemodel establishes a foreign key relationship with theidcolumn of theUsermodel usingForeignKeyColumn. This relationship is specified to be aone-to-onerelationship (maps_to="1-1").Various constraints such asnullable,unique,default, and foreign key constraints (onDelete,onUpdate) are specified for the columns.InsertingIn the following code example we are going to demonstrate how we can create auserwith aprofile, first we need to create a user first so that we get reference to the user of the profile that we will create.userId=mysql_loom.insert_one(instance=User,values=ColumnValue(name="username",value="@miller"),)profileId=mysql_loom.insert_one(instance=Profile,values=[ColumnValue(name="userId",value=userId),ColumnValue(name="avatar",value="hello.jpg"),],)This Python code snippet demonstrates how to insert data into the database using themysql_loom.insert_onemethod, it also work on other methods likeinsert_bulk.Inserting a User Record:Themysql_loom.insert_onemethod is used to insert a new record into theUsertable.Theinstance=Userparameter specifies that the record being inserted belongs to theUsermodel.Thevalues=ColumnValue(name="username", value="@miller")parameter specifies the values to be inserted into theUsertable, where theusernamecolumn will be set to"@miller".The ID of the newly inserted record is obtained and assigned to the variableuserId.Inserting a Profile Record:Again, themysql_loom.insert_onemethod is called to insert a new record into theProfiletable.Theinstance=Profileparameter specifies that the record being inserted belongs to theProfilemodel.Thevaluesparameter is a list containing twoColumnValueobjects:The firstColumnValueobject specifies that theuserIdcolumn of theProfiletable will be set to theuserIdvalue obtained from the previous insertion.The secondColumnValueobject specifies that theavatarcolumn of theProfiletable will be set to"hello.jpg".The ID of the newly inserted record is obtained and assigned to the variableprofileId.Retrieving RecordsThe following example shows you how you can retrieve the data in a associationsprofile=mysql_loom.find_one(instance=Profile,select=["id","avatar"],filters=Filter(column="userId",value=userId),)user=mysql_loom.find_by_pk(instance=User,pk=userId,select=["id","username"],)user_with_profile={**user,"profile":profile}print(user_with_profile)# ? = {'id': 1, 'username': '@miller', 'profile': {'id': 1, 'avatar': 'hello.jpg'}}This Python code snippet demonstrates how to query data from the database using themysql_loom.find_oneandmysql_loom.find_by_pkmethods, and combine the results of these two records that have association.Querying a Profile Record:Themysql_loom.find_onemethod is used to retrieve a single record from theProfiletable.Thefilters=Filter(column="userId", value=userId)parameter filters the results to only include records where theuserIdcolumn matches theuserIdvalue obtained from a previous insertion.Querying a User Record:Themysql_loom.find_by_pkmethod is used to retrieve a single record from theUsertable based on its primary key (pk=userId).Theinstance=Userparameter specifies that the record being retrieved belongs to theUsermodel.Theselect=["id", "username"]parameter specifies that only theidandusernamecolumns should be selected.The retrieved user data is assigned to the variableuser.Combining User and Profile Data:The user data (user) and profile data (profile) are combined into a single dictionary (user_with_profile) using dictionary unpacking ({**user, "profile": profile}).This dictionary represents a user with their associated profile.🏒 We have realized that we are performing three steps when querying records, which can be verbose. However, in dataloom, we have introducedeagerdata fetching for all methods that retrieve data from the database. The following example demonstrates how we can achieve the same result as before using eager loading:# With eager loadinguser_with_profile=mysql_loom.find_by_pk(instance=User,pk=userId,select=["id","username"],include=[Include(model=Profile,select=["id","avatar"],has="one")],)print(user_with_profile)# ? = {'id': 1, 'username': '@miller', 'profile': {'id': 1, 'avatar': 'hello.jpg'}}This Python code snippet demonstrates how to use eager loading with themysql_loom.find_by_pkmethod to efficiently retrieve data from theUserandProfiletables in a single query.Eager loading allows us to retrieve related data from multiple tables in a single database query, reducing the need for multiple queries and improving performance.In this example, theincludeparameter is used to specify eager loading for theProfilemodel associated with theUsermodel.By including theProfilemodel with theUsermodel in thefind_by_pkmethod call, we instruct the database to retrieve both the user data (idandusername) and the associated profile data (idandavatar) in a single query.This approach streamlines the data retrieval process and minimizes unnecessary database calls, leading to improved efficiency and performance in applications.2.N-1AssociationModels can haveManytoOnerelationship, it depends on how you define them. Let's have a look at the relationship between aCategoryand aPost. Many categories can belong to a single post.classPost(Model):__tablename__:Optional[TableColumn]=TableColumn(name="posts")id=PrimaryKeyColumn(type="int",auto_increment=True,nullable=False,unique=True)completed=Column(type="boolean",default=False)title=Column(type="varchar",length=255,nullable=False)# timestampscreatedAt=CreatedAtColumn()# relationsuserId=ForeignKeyColumn(User,maps_to="1-N",type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE",)classCategory(Model):__tablename__:Optional[TableColumn]=TableColumn(name="categories")id=PrimaryKeyColumn(type="int",auto_increment=True,nullable=False,unique=True)type=Column(type="varchar",length=255,nullable=False)postId=ForeignKeyColumn(Post,maps_to="N-1",type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE",)In the provided code, we have two models:PostandCategory. The relationship between these two models can be described as aMany-to-Onerelationship.This means that many categories can belong to a single post. In other words:For eachPostinstance, there can be multipleCategoryinstances associated with it.However, eachCategoryinstance can only be associated with onePost.For example, consider a blogging platform where eachPostrepresents an article and eachCategoryrepresents a topic or theme. Each article (post) can be assigned to multiple topics (categories), such as "Technology", "Travel", "Food", etc. However, each topic (category) can only be associated with one specific article (post).This relationship allows for a hierarchical organization of data, where posts can be categorized into different topics or themes represented by categories.InsertingLet's illustrate the following example where we insert categories into a post with theid1.fortitlein["Hey","Hello","What are you doing","Coding"]:mysql_loom.insert_one(instance=Post,values=[ColumnValue(name="userId",value=userId),ColumnValue(name="title",value=title),],)forcatin["general","education","tech","sport"]:mysql_loom.insert_one(instance=Category,values=[ColumnValue(name="postId",value=1),ColumnValue(name="type",value=cat),],)Inserting PostsWe're inserting new posts into thePosttable. Each post is associated with a user (userId), and we're iterating over a list of titles to insert multiple posts.Inserting CategoriesWe're inserting new categories into theCategorytable. Each category is associated with a specific post (postId), and we're inserting categories for a post withid1.In summary, we're creating a relationship between posts and categories by inserting records into their respective tables. Each category record is linked to a specific post record through thepostIdattribute.Retrieving RecordsLet's attempt to retrieve a post with an ID of1along with its corresponding categories. We can achieve this as follows:post=mysql_loom.find_by_pk(Post,1,select=["id","title"])categories=mysql_loom.find_many(Category,select=["type","id"],filters=Filter(column="postId",value=1),order=[Order(column="id",order="DESC"),],)post_with_categories={**post,"categories":categories}print(post_with_categories)# ? = {'id': 1, 'title': 'Hey', 'categories': [{'type': 'sport', 'id': 4}, {'type': 'tech', 'id': 3}, {'type': 'education', 'id': 2}, {'type': 'general', 'id': 1}]}We use themysql_loom.find_by_pk()method to retrieve a single post (Post) with anidequal to 1. We select only specific columns (idandtitle) for the post.We use themysql_loom.find_many()method to retrieve multiple categories (Category) associated with the post. We select only specific columns (typeandid) for the categories. We apply a filter to only fetch categories associated with the post withpostIdequal to 1. We sort the categories based on theidcolumn in descending order.We create a dictionary (post_with_categories) that contains the retrieved post and its associated categories. The post information is stored under the keypost, and the categories information is stored under the keycategories.The above task can be accomplished usingeagerdocument retrieval as shown below.post_with_categories=mysql_loom.find_by_pk(Post,1,select=["id","title"],include=[Include(model=Category,select=["type","id"],order=[Order(column="id",order="DESC"),],)],)The code snippet queries a database to retrieve a post with anidof1along with its associated categories. Here's a breakdown:Querying for Post:Themysql_loom.find_by_pk()method fetches a single post from the database.It specifies thePostmodel and ID1, retrieving only theidandtitlecolumns.Including Categories:Theincludeparameter specifies additional related data to fetch.Insideinclude, anIncludeinstance is created for categories related to the post.It specifies theCategorymodel and selects only thetypeandidcolumns.Categories are ordered byidin descending order.Result:The result is stored inpost_with_categories, containing the post information and associated categories.In summary, this code is retrieving a specific post along with its categories from the database, and it's usingeagerloading to efficiently fetch related data in a single query.3.1-NAssociationLet's consider a scenario where aUserhas multiplePost. here is how the relationships are mapped.classUser(Model):__tablename__:Optional[TableColumn]=TableColumn(name="users")id=PrimaryKeyColumn(type="int",auto_increment=True)name=Column(type="text",nullable=False,default="Bob")username=Column(type="varchar",unique=True,length=255)tokenVersion=Column(type="int",default=0)classPost(Model):__tablename__:Optional[TableColumn]=TableColumn(name="posts")id=PrimaryKeyColumn(type="int",auto_increment=True,nullable=False,unique=True)completed=Column(type="boolean",default=False)title=Column(type="varchar",length=255,nullable=False)# timestampscreatedAt=CreatedAtColumn()# relationsuserId=ForeignKeyColumn(User,maps_to="1-N",type="int",required=True,onDelete="CASCADE",onUpdate="CASCADE")So clearly we can see that when creating apostwe need to have auserIdInsertingHere is how we can insert a user and a post to the database tables.userId=mysql_loom.insert_one(instance=User,values=ColumnValue(name="username",value="@miller"),)fortitlein["Hey","Hello","What are you doing","Coding"]:mysql_loom.insert_one(instance=Post,values=[ColumnValue(name="userId",value=userId),ColumnValue(name="title",value=title),],)We're performing database operations to insert records for a user and multiple posts associated with that user.We insert a user record into the database usingmysql_loom.insert_one()method.We iterate over a list of titles.For each title in the list, we insert a new post record into the database.Each post is associated with the user we inserted earlier, identified by theuserId.The titles for the posts are set based on the titles in the list.Retrieving RecordsNow let's query the user with his respective posts. we can do it as follows:user=mysql_loom.find_by_pk(User,1,select=["id","username"],)posts=mysql_loom.find_many(Post,filters=Filter(column="userId",value=userId,operator="eq"),select=["id","title"],order=[Order(column="id",order="DESC")],limit=2,offset=1,)user_with_posts={**user,"posts":posts}print(user_with_posts)# ? = {'id': 1, 'username': '@miller', 'posts': [{'id': 3, 'title': 'What are you doing'}, {'id': 2, 'title': 'Hello'}]}We're querying the database to retrieve information about auserand their associatedposts.Querying User:We usemysql_loom.find_by_pk()to fetch a single user record from the database.The user's ID is specified as1.We select only theidandusernamecolumns for the user.Querying Posts:We usemysql_loom.find_many()to retrieve multiple post records associated with the user.A filter is applied to only fetch posts where theuserIdmatches the ID of the user retrieved earlier.We select only theidandtitlecolumns for the posts.The posts are ordered by theidcolumn in descending order.We set a limit of2posts to retrieve, and we skip the first post using an offset of1.We create a dictionaryuser_with_postscontaining the user information and a list of their associated posts under the key"posts".With eager loading this can be done as follows the above can be done as follows:user_with_posts=mysql_loom.find_by_pk(User,1,select=["id","username"],include=[Include(model=Post,select=["id","title"],order=[Order(column="id",order="DESC")],limit=2,offset=1,)],)print(user_with_posts)# ? = {'id': 1, 'username': '@miller', 'posts': [{'id': 3, 'title': 'What are you doing'}, {'id': 2, 'title': 'Hello'}]}We usemysql_loom.find_by_pk()to fetch a single user record from the database.The user's ID is specified as1.We select only theidandusernamecolumns for the user.Additionally, we include associated post records usingeagerloading.Inside theincludeparameter, we specify thePostmodel and select only theidandtitlecolumns for the posts.The posts are ordered by theidcolumn in descending order.We set a limit of2posts to retrieve, and we skip the first post using an offset of1.4. What about bidirectional queries?In Dataloom, we support bidirectional relations with eager loading on-the-fly. You can query from aparentto achildand from achildto aparent. You just need to know how the relationship is mapped between these two models. In this case, thehasoption is very important in theIncludeclass. Here are some examples demonstrating bidirectional querying betweenuserandpost, where theuseris the parent table and thepostis the child table in this case.1. Child to ParentHere is an example illustrating how we can query a parent from child table.posts_users=mysql_loom.find_many(Post,limit=2,offset=3,order=[Order(column="id",order="DESC")],select=["id","title"],include=[Include(model=User,select=["id","username"],has="one",include=[Include(model=Profile,select=["id","avatar"],has="one")],),Include(model=Category,select=["id","type"],order=[Order(column="id",order="DESC")],has="many",limit=2,),],)print(posts_users)# ? = [{'id': 1, 'title': 'Hey', 'user': {'id': 1, 'username': '@miller', 'profile': {'id': 1, 'avatar': 'hello.jpg'}}, 'categories': [{'id': 4, 'type': 'sport'}, {'id': 3, 'type': 'tech'}]}]2. Parent to ChildHere is an example of how we can query a child table from parent tableuser_post=mysql_loom.find_by_pk(User,pk=userId,select=["id","username"],include=[Include(model=Post,limit=2,offset=3,order=[Order(column="id",order="DESC")],select=["id","title"],include=[Include(model=User,select=["id","username"],has="one",include=[Include(model=Profile,select=["id","avatar"],has="one")],),Include(model=Category,select=["id","type"],order=[Order(column="id",order="DESC")],has="many",limit=2,),],),Include(model=Profile,select=["id","avatar"],has="one"),],)print(user_post)""" ? ={'id': 1, 'username': '@miller', 'user': {'id': 1, 'username': '@miller', 'profile': {'id': 1, 'avatar': 'hello.jpg'}}, 'categories': [{'id': 4, 'type': 'sport'}, {'id': 3, 'type': 'tech'}], 'posts': [{'id': 1, 'title': 'Hey', 'user': {'id': 1, 'username': '@miller', 'profile': {'id': 1, 'avatar': 'hello.jpg'}}, 'categories': [{'id': 4, 'type': 'sport'}, {'id': 3, 'type': 'tech'}]}], 'profile': {'id': 1, 'avatar': 'hello.jpg'}}"""5.SelfAssociationLet's consider a scenario where we have a tableEmployee, where an employee can have a supervisor, which in this case a supervisor is also an employee. This is an example of self relations. The model definition for this can be done as follows indataloom.classEmployee(Model):__tablename__:TableColumn=TableColumn(name="employees")id=PrimaryKeyColumn(type="int",auto_increment=True)name=Column(type="text",nullable=False,default="Bob")supervisorId=ForeignKeyColumn("Employee",maps_to="1-1",type="int",required=False)So clearly we can see that when creating aemployeeit is not a must to have asupervisorIdas this relationship is optional.👍Pro Tip:Note that when doing self relations the referenced table must be a string that matches the table class name irrespective of case. In our case we used"Employee"and also"employee"and"EMPLOYEe"will be valid, however"Employees"and also"employees"and"EMPLOYEEs"are invalid.InsertingHere is how we can insert employees to this table and we will makeJohnthe supervisor of other employees.empId=mysql_loom.insert_one(instance=Employee,values=ColumnValue(name="name",value="John Doe"))rows=mysql_loom.insert_bulk(instance=Employee,values=[[ColumnValue(name="name",value="Michael Johnson"),ColumnValue(name="supervisorId",value=empId),],[ColumnValue(name="name",value="Jane Smith"),ColumnValue(name="supervisorId",value=empId),],],)Some employees is are associated with a supervisorJohnwhich areJaneandMichael.However the employeeJohndoes not have a supervisor.Retrieving RecordsNow let's query employeeMichaelwith his supervisor.emp=mysql_loom.find_by_pk(instance=Employee,pk=2,select=["id","name","supervisorId"])sup=mysql_loom.find_by_pk(instance=Employee,select=["id","name"],pk=emp["supervisorId"])emp_and_sup={**emp,"supervisor":sup}print(emp_and_sup)# ? = {'id': 2, 'name': 'Michael Johnson', 'supervisorId': 1, 'supervisor': {'id': 1, 'name': 'John Doe'}}We're querying the database to retrieve information about aemployeeand their associatedsupervisor.Querying an Employee:We usemysql_loom.find_by_pk()to fetch a single employee record from the database.The employee's ID is specified as2.Querying Supervisor:We usemysql_loom.find_by_pk()to retrieve a supervisor that is associated with this employee.We create a dictionaryemp_and_supcontaining theemployeeinformation and theirsupervisor.With eager loading this can be done in one query as follows the above can be done as follows:emp_and_sup=mysql_loom.find_by_pk(instance=Employee,pk=2,select=["id","name","supervisorId"],include=Include(model=Employee,has="one",select=["id","name"],alias="supervisor",),)print(emp_and_sup)# ? = {'id': 2, 'name': 'Michael Johnson', 'supervisorId': 1, 'supervisor': {'id': 1, 'name': 'John Doe'}}We usemysql_loom.find_by_pk()to fetch a single an employee record from the database.Additionally, we include associatedemployeerecord usingeagerloading with analiasofsupervisor.👍Pro Tip:Note that thealiasis very important in this situation because it allows you to get the included relationships with objects that are named well, if you don't give an alias dataloom will just use the model class name as the alias of your included models, in this case you will get an object that looks like{'id': 2, 'name': 'Michael Johnson', 'supervisorId': 1, 'employee': {'id': 1, 'name': 'John Doe'}}, which practically and theoretically doesn't make sense.6.N-NRelationshipLet's consider a scenario where we have tables forStudentsandCourses. In this scenario, a student can enroll in many courses, and a single course can have many students enrolled. This represents aMany-to-Manyrelationship. The model definitions for this scenario can be done as follows indataloom:Table: StudentColumn NameData TypeidINTnameVARCHARTable: CourseColumn NameData TypeidINTnameVARCHAR......Table: Student_Courses(junction table)Column NameData TypestudentIdINTcourseIdINT👍Pro Tip:Note that thejunctiontable can also be calledassociation-table orreference-table orjoint-table.Indataloomwe can model the above relations as follows:classCourse(Model):__tablename__:TableColumn=TableColumn(name="courses")id=PrimaryKeyColumn(type="int",auto_increment=True)name=Column(type="text",nullable=False,default="Bob")classStudent(Model):__tablename__:TableColumn=TableColumn(name="students")id=PrimaryKeyColumn(type="int",auto_increment=True)name=Column(type="text",nullable=False,default="Bob")classStudentCourses(Model):__tablename__:TableColumn=TableColumn(name="students_courses")studentId=ForeignKeyColumn(table=Student,type="int")courseId=ForeignKeyColumn(table=Course,type="int")The tablesstudentsandcourseswill not have foreign keys.Thestudents_coursestable will have two columns that joins these two tables together in anN-Nrelational mapping.👍Pro Tip:In a joint table no other columns such asCreateAtColumn,UpdatedAtColumn,ColumnandPrimaryKeyColumnare allowed and only exactly2foreign keys should be in this table.InsertingHere is how we can insertstudentsandcoursesin their respective tables.# insert the coursesmathId=mysql_loom.insert_one(instance=Course,values=ColumnValue(name="name",value="Mathematics"))engId=mysql_loom.insert_one(instance=Course,values=ColumnValue(name="name",value="English"))phyId=mysql_loom.insert_one(instance=Course,values=ColumnValue(name="name",value="Physics"))# create studentsstud1=mysql_loom.insert_one(instance=Student,values=ColumnValue(name="name",value="Alice"))stud2=mysql_loom.insert_one(instance=Student,values=ColumnValue(name="name",value="Bob"))stud3=mysql_loom.insert_one(instance=Student,values=ColumnValue(name="name",value="Lisa"))You will notice that we are keeping in track of thestudentIdsand thecourseIdsbecause we will need them in thejoint-tableorassociation-table.Now we can enrol students to their courses by inserting them in their id's in the association table.# enrolling studentsmysql_loom.insert_bulk(instance=StudentCourses,values=[[ColumnValue(name="studentId",value=stud1),ColumnValue(name="courseId",value=mathId),],# enrolling Alice to mathematics[ColumnValue(name="studentId",value=stud1),ColumnValue(name="courseId",value=phyId),],# enrolling Alice to physics[ColumnValue(name="studentId",value=stud1),ColumnValue(name="courseId",value=engId),],# enrolling Alice to english[ColumnValue(name="studentId",value=stud2),ColumnValue(name="courseId",value=engId),],# enrolling Bob to english[ColumnValue(name="studentId",value=stud3),ColumnValue(name="courseId",value=phyId),],# enrolling Lisa to physics[ColumnValue(name="studentId",value=stud3),ColumnValue(name="courseId",value=engId),],# enrolling Lisa to english],)Retrieving RecordsNow let's query a student calledAlicewith her courses. We can do it as follows:s=mysql_loom.find_by_pk(Student,pk=stud1,select=["id","name"],)c=mysql_loom.find_many(StudentCourses,filters=Filter(column="studentId",value=stud1),select=["courseId"],)courses=mysql_loom.find_many(Course,filters=Filter(column="id",operator="in",value=[list(i.values())[0]foriinc]),select=["id","name"],)alice={**s,"courses":courses}print(courses)# ? = {'id': 1, 'name': 'Alice', 'courses': [{'id': 1, 'name': 'Mathematics'}, {'id': 2, 'name': 'English'}, {'id': 3, 'name': 'Physics'}]}We're querying the database to retrieve information about astudentand their associatedcourses. Here are the steps in achieving that:Querying Student:We usemysql_loom.find_by_pk()to fetch a singlestudentrecord from the database in the tablestudents.Querying Course Id's:Next we are going to query all the course ids of that student and store them incin the joint tablestudents_courses.We usemysql_loom.find_many()to retrieve the courseidsofalice.Querying Course:Next we will query all the courses using the operatorinin thecoursestable based on the id's we obtained previously.As you can see we are doing a lot of work to get the information aboutAlice. With eager loading this can be done in one query as follows the above can be done as follows:alice=mysql_loom.find_by_pk(Student,pk=stud1,select=["id","name"],include=Include(model=Course,junction_table=StudentCourses,alias="courses",has="many"),)print(alice)# ? = {'id': 1, 'name': 'Alice', 'courses': [{'id': 1, 'name': 'Mathematics'}, {'id': 2, 'name': 'English'}, {'id': 3, 'name': 'Physics'}]}We usemysql_loom.find_by_pk()to retrieve a single student record from the database.Furthermore, we include the associatedcourserecords usingeagerloading with analiasofcourses.We specify ajunction_tablein ourIncludestatement. This allows dataloom to recognize the relationship between thestudentsandcoursestables through thisjunction_table.👍Pro Tip:It is crucial to specify thejunction_tablewhen querying in a many-to-many (N-N) relationship. This is because, by default, the models will not establish a direct many-to-many relationship without referencing thejunction_table. They lack foreign key columns within them to facilitate this relationship.As for our last example let's query all the students that are enrolled in theEnglishclass. We can easily do it as follows:english=mysql_loom.find_by_pk(Course,pk=engId,select=["id","name"],include=Include(model=Student,junction_table=StudentCourses,has="many"),)print(english)# ? = {'id': 2, 'name': 'English', 'students': [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}, {'id': 3, 'name': 'Lisa'}]}Query Builder.Dataloom exposes a method calledgetQueryBuilder, which allows you to obtain aqbobject. This object enables you to execute SQL queries directly from SQL scripts.qb=loom.getQueryBuilder()print(qb)# ? = Loom QB<mysql>Theqbobject contains the method calledrun, which is used to execute SQL scripts or SQL queries.ids=qb.run("select id from posts;",fetchall=True)print(ids)# ? = [(1,), (2,), (3,), (4,)]You can also execute SQL files. In the following example, we will demonstrate how you can execute SQL scripts using theqb. Let's say we have an SQL file calledqb.sqlwhich contains the following SQL code:SELECTid,titleFROMpostsWHEREidIN(1,3,2,4)LIMIT4OFFSET1;SELECTCOUNT(*)FROM(SELECTDISTINCT`id`FROM`posts`WHERE`id`<5LIMIT3OFFSET2)ASsubquery;We can use the query builder to execute the SQL as follows:withopen("qb.sql","r")asreader:sql=reader.read()res=qb.run(sql,fetchall=True,is_script=True,)print(res)👍Pro Tip:Executing a script using query builder does not return a result. The result value is alwaysNone.Therunmethod takes the following as arguments:ArgumentDescriptionTypeRequiredDefaultsqlSQL query to execute.strYesargsParameters for the SQL query.Any | NoneNoNonefetchoneWhether to fetch only one result.boolNoFalsefetchmanyWhether to fetch multiple results.boolNoFalsefetchallWhether to fetch all results.boolNoFalsemutationWhether the query is a mutation (insert, update, delete).boolNoTruebulkWhether the query is a bulk operation.boolNoFalseaffected_rowsWhether to return affected rows.boolNoFalseoperationType of operation being performed.'insert', 'update', 'delete', 'read' | NoneNoNoneverboseVerbosity level for logging . Set this option to0if you don't want logging at all.intNo1is_scriptWhether the SQL is a script.boolNoFalseWhy Use Query Builder?The query builder empowers developers to seamlessly executeSQLqueries directly.While Dataloom primarily utilizessubqueriesfor eager data fetching on models, developers may prefer to employ JOIN operations, which are achievable through theqbobject.qb=loom.getQueryBuilder()result=qb.run("SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.table1_id;")print(result)ContributingContributions todataloomare welcome! Feel free to submit bug reports, feature requests, or pull requests onGitHub.LicenseThis project is licensed under the MIT License - see theLICENSEfile for details.