A minimalistic ORM with basic features.
Project description
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.
Usage
DB Connection
Establish connection to database by calling .connect()
method of connector
object, with certain db handler.
Connecting to sqlite database:
from minorm import connector, SQLiteSpec
connector.connect(SQLiteSpec('example.db'))
Connecting to postgresql database (requires psycopg2 to be installed):
from minorm import connector, PostgreSQLSpec
connection_string = "host=localhost port=5432 dbname=mydb user=admin password=secret"
connector.connect(PostgreSQLSpec(connection_string))
Close connection by calling .disconnect()
method:
connector.disconnect()
Models
Create a model class that represents a single table in a database:
from minorm import Model
from minorm.fields import CharField, IntegerField
class Person(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:
class Book(Model):
title = CharField(max_length=90)
class Meta:
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 calling save
method:
person = Person()
person.name = "John" # set field values as attributes
person.age = 33
person.save()
book = Book(title="foobar") # or pass it in init method
book.save()
Remove a row from db by calling delete
method:
person.delete()
Create a model with foreign relation by using ForeignKey
field:
class Book(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 methods
Use queryset, accessible by model’s qs
property, 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 > 18 filtered_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 string
It’s also possible to filter by foreign relation fields:
qs = Book.qs.filter(author__name="Mark Twain") # will perform join of `author` table
aswell(**lookups)
:Make query result to include items that also matches lookups listed in the method:
# age > 18 OR user is admin filtered_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 newest Person.qs.order_by('-id') # reverse ordering by id
- Slicing (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 items
all()
:Get a copy of the queryset:
qs = Person.qs.filter(age=42) new_qs = qs.all() # a copy of filtered qs
values(*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 keys
exists()
:Return boolean, that indicates presence of rows that match filters:
Person.qs.filter(name="mike").exists() # True if there is such name, otherwise False Book.qs.exists() # check if there is at least one row in the table
get(**lookups)
:Get single row as an instance of the model class:
person = Person.qs.get(id=7) # model instance object book = Book.qs.get(pk=7) # you could use `pk` instead of pk field name
raises
Model.DoesNotExists
if corresponding row not found in db, andMultipleQueryResult
if more than one row matches query filters.fetch()
:Get all rows as a list of namedtuple objects:
persons = Person.qs.fetch() # list of namedtuples adults = Person.qs.filter(age__gte=18).fetch()
- Iterating queryset:
Queryset supports iterator interface, so it’s possible to iterate results:
for adult in Persons.qs.filter(age__gte=18): print(adult.pk, adult.name) # each item is a model instance
create(**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 query
select_related(*fk_fields)
:Prepare queryset to perform select query with join of foreign relation:
for book in Book.qs.select_related('author'): # without select_related call, each related object hits db author = book.author print(book.title, author.name)
Transactions support
It’s possible to perform multiple model/queryset operations in transaction by using transaction module:
from minorm import transaction
with transaction.atomic():
# all db operations inside `atomic` block will run in one transaction
author = 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:
with transaction.atomic():
instance.save() # instance is set for saving in transaction
if want_to_keep:
transaction.commit() # permanently save instance in db
else:
transaction.rollback() # remove instance from saving
# do more stuff if it's required
TODO
add more model fields
test Postgresql support
add basic aggregation functions (SUM, COUNT, etc)
Running tests
To run tests, create virtual environment and then run:
make test
License
The MIT License (MIT)
Contributed by Campos Ilya
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file minorm-0.6.0.tar.gz
.
File metadata
- Download URL: minorm-0.6.0.tar.gz
- Upload date:
- Size: 17.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.3.0 pkginfo/1.6.1 requests/2.25.1 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.7.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 308cb000b3017d478fb06731c6d20d8d5e2fcbe23f4674c80d8231a1da59eb5a |
|
MD5 | 1c992afd463c7258c05db5de1e4bafda |
|
BLAKE2b-256 | 7db0d256dd7ff6d9c6bed342a7815af3ed97e01a98fa5c7d92a439684f28cc20 |
File details
Details for the file minorm-0.6.0-py3-none-any.whl
.
File metadata
- Download URL: minorm-0.6.0-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.3.0 pkginfo/1.6.1 requests/2.25.1 setuptools/40.8.0 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.7.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 24bf6405dd0439f47eb82fb5ad72bdb57473ae28d4f2d715e74d4581abd9f22c |
|
MD5 | fd5045a2a7c7357ef3a01560d5e67671 |
|
BLAKE2b-256 | 8860ef4d8c3f3b2d802467b9e415bf7d807cb2782cd74ebd1fc0ae9861021d93 |