Package: minorm
A minimalistic ORM with basic features.Inspired by Django ORM.What’s the point?MinORM was designed as minimalistic ORM, to be as simple as possible.
It’s not production-ready solution, rather a proof of concept. The goal is to demonstrate example of an ORM,
more-less applicable for usage, that could be created with python in a short term of time.UsageDB ConnectionEstablish connection to database by calling.connect()method ofconnectorobject, with certain db handler.Connecting to sqlite database:fromminormimportconnector,SQLiteSpecconnector.connect(SQLiteSpec('example.db'))Connecting to postgresql database (requires psycopg2 to be installed):fromminormimportconnector,PostgreSQLSpecconnection_string="host=localhost port=5432 dbname=mydb user=admin password=secret"connector.connect(PostgreSQLSpec(connection_string))Close connection by calling.disconnect()method:connector.disconnect()ModelsCreate a model class that represents a single table in a database:fromminormimportModelfromminorm.fieldsimportCharField,IntegerFieldclassPerson(Model):name=CharField(max_length=120)age=IntegerField()It’s possible to create a new table in a database:Person.create_table()Or to use existing one, by set table name in model meta:classBook(Model):title=CharField(max_length=90)classMeta:table_name="some_table"It’s possible to drop a table:Person.drop_table()Create a new instance or update existing one in db by callingsavemethod:person=Person()person.name="John"# set field values as attributesperson.age=33person.save()book=Book(title="foobar")# or pass it in init methodbook.save()Remove a row from db by callingdeletemethod:person.delete()Create a model with foreign relation by usingForeignKeyfield:classBook(Model):title=CharField(max_length=90)author=ForeignKey(Person)Pass an instance of related model when saving a new one:book=Book(title="foobar",author=person)book.save()Queryset methodsUse queryset, accessible by model’sqsproperty, to perform db operations on multiple rows:filter(**lookups):Filter query, result will contain only items that matches all lookups:# user type is "member" AND age > 18filtered_qs=Person.qs.filter(user_type='member',age__gt=18)List of supported lookup expressions:lt,lte- less than (or equal)gt,gte- greater than (or equal)neq- not equalin- checks if value is between given optionsstartswith,endswith,contains- check inclusion of a stringIt’s also possible to filter by foreign relation fields:qs=Book.qs.filter(author__name="Mark Twain")# will perform join of `author` tableaswell(**lookups):Make query result to include items that also matches lookups listed in the method:# age > 18 OR user is adminfiltered_qs=Person.qs.filter(age__gt=18).aswell(user_type="admin")order_by(*fields):Set ordering of queried rows. Use-prefix to reverse order:Book.qs.order_by('created')# for oldest to newestPerson.qs.order_by('-id')# reverse ordering by idSlicing (limit number of row):it’s possible to limit number of selected rows by using slices:persons=Person.qs[:3]# will limit results number to 3 itemsall():Get a copy of the queryset:qs=Person.qs.filter(age=42)new_qs=qs.all()# a copy of filtered qsvalues(*fields):Prepare qs to get rows as dictionaries with fields, passed to the method:qs=Book.qs.values('title','author__name')# items will be dicts with this two keysexists():Return boolean, that indicates presence of rows that match filters:Person.qs.filter(name="mike").exists()# True if there is such name, otherwise FalseBook.qs.exists()# check if there is at least one row in the tableget(**lookups):Get single row as an instance of the model class:person=Person.qs.get(id=7)# model instance objectbook=Book.qs.get(pk=7)# you could use `pk` instead of pk field nameraisesModel.DoesNotExistsif corresponding row not found in db,
andMultipleQueryResultif more than one row matches query filters.fetch():Get all rows as a list of namedtuple objects:persons=Person.qs.fetch()# list of namedtuplesadults=Person.qs.filter(age__gte=18).fetch()Iterating queryset:Queryset supports iterator interface, so it’s possible to iterate results:foradultinPersons.qs.filter(age__gte=18):print(adult.pk,adult.name)# each item is a model instancecreate(**field_values):Create a new instance in db:person=Person.qs.create(name="John",age=33)is a shortcut for two calls:person=Person(name="John",age=33)person.save()update(**field_values):Update field values of existing rows in db:Book.qs.filter(price__lt=200).update(price=250)delete():Remove all rows of queryset from db:Product.qs.filter(created__lt=date(2020,11,10)).delete()bulk_create(instances):Create multiple instances in one db query:Book.qs.bulk_create([Book(title="foo",author=1),Book(title="bar",author=2),Book(title="baz",author=1),])# creates all these books in one queryselect_related(*fk_fields):Prepare queryset to perform select query with join of foreign relation:forbookinBook.qs.select_related('author'):# without select_related call, each related object hits dbauthor=book.authorprint(book.title,author.name)Transactions supportIt’s possible to perform multiple model/queryset operations in transaction by usingtransactionmodule:fromminormimporttransactionwithtransaction.atomic():# all db operations inside `atomic` block will run in one transactionauthor=Person.objects.create(name="Steven King",age=19)Book.objects.create(title="The Dark Tower: The Gunslinger",author=author)It’s also possible to manually commit/rollback changes inside transaction block:withtransaction.atomic():instance.save()# instance is set for saving in transactionifwant_to_keep:transaction.commit()# permanently save instance in dbelse:transaction.rollback()# remove instance from saving# do more stuff if it's requiredTODOadd more model fieldstest Postgresql supportadd basic aggregation functions (SUM, COUNT, etc)Running testsTo run tests, create virtual environment and then run:maketestLicenseThe MIT License (MIT)Contributed byCampos Ilya