From 85a191bb6dec47f20299bdeb98ff8f28323f2786 Mon Sep 17 00:00:00 2001
From: collerek
Date: Sat, 6 Feb 2021 13:46:12 +0100
Subject: [PATCH] update badges, docs, quick start
---
.gitignore | 1 +
README.md | 409 ++++++++++++++++++++++++++++-----
docs/index.md | 408 +++++++++++++++++++++++++++-----
docs/queries/index.md | 27 +++
docs/releases.md | 8 +
examples/script_from_readme.py | 310 +++++++++++++++++++++++++
ormar/__init__.py | 2 +-
7 files changed, 1044 insertions(+), 121 deletions(-)
create mode 100644 examples/script_from_readme.py
diff --git a/.gitignore b/.gitignore
index b35dcd6..fc07f13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ dist
/ormar.egg-info/
site
profile.py
+*.db
diff --git a/README.md b/README.md
index 2a94149..c6c5f41 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,10 @@
+
+
+
+
### Overview
@@ -35,6 +39,14 @@ And what's a better name for python ORM than snakes cabinet :)
Check out the [documentation][documentation] for details.
+**Note that for brevity most of the documentation snippets omit the creation of the database
+and scheduling the execution of functions for asynchronous run.**
+
+If you want more real life examples than in the documentation you can see [tests][tests] folder,
+since they actually have to create and connect to database in most of the tests.
+
+Yet remember that those are - well - tests and not all solutions are suitable to be used in real life applications.
+
### Dependencies
Ormar is built with:
@@ -44,99 +56,371 @@ Ormar is built with:
* [`pydantic`][pydantic] for data validation.
* `typing_extensions` for python 3.6 - 3.7
-### Migrations
+### Migrations & Database creation
Because ormar is built on SQLAlchemy core, you can use [`alembic`][alembic] to provide
-database migrations.
+database migrations (and you really should for production code).
+For tests and basic applications the `sqlalchemy` is more than enough:
+```python
+# note this is just a partial snippet full working example below
+# 1. Imports
+import sqlalchemy
+import databases
-**ormar is still under development:**
-We recommend pinning any dependencies (with i.e. `ormar~=0.5.2`)
+# 2. Initialization
+DATABASE_URL = "sqlite:///db.sqlite"
+database = databases.Database(DATABASE_URL)
+metadata = sqlalchemy.MetaData()
+
+# Define models here
+
+# 3. Database creation and tables creation
+engine = sqlalchemy.create_engine(DATABASE_URL)
+metadata.create_all(engine)
+```
+
+For a sample configuration of alembic and more information regarding migrations and
+database creation visit [migrations][migrations] documentation section.
+
+### Package versions
+**ormar is still under development:**
+We recommend pinning any dependencies (with i.e. `ormar~=0.9.1`)
+
+`ormar` also follows the release numeration that breaking changes bump the major number,
+while other changes and fixes bump minor number, so with the latter you should be safe to
+update, yet always read the [releases][releases] docs before.
+`example: (0.5.2 -> 0.6.0 - breaking, 0.5.2 -> 0.5.3 - non breaking)`.
+
+### Asynchronous Python
+
+Note that `ormar` is an asynchronous ORM, which means that you have to `await` the calls to
+the methods, that are scheduled for execution in an event loop. Python has a builtin module
+[`asyncio`][asyncio] that allows you to do just that.
+
+Note that most of "normal" python interpreters do not allow execution of `await`
+outside of a function (cause you actually schedule this function for delayed execution
+and don't get the result immediately).
+
+In a modern web frameworks (like `fastapi`), the framework will handle this for you, but if
+you plan to do this on your own you need to perform this manually like described in a
+quick start below.
### Quick Start
-**Note**: Use `ipython` to try this from the console, since it supports `await`.
+Note that you can find the same script in examples folder on github.
```python
+from typing import Optional
+
import databases
+import pydantic
+
import ormar
import sqlalchemy
-database = databases.Database("sqlite:///db.sqlite")
+DATABASE_URL = "sqlite:///db.sqlite"
+database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
-class Album(ormar.Model):
- class Meta:
- tablename = "album"
- metadata = metadata
- database = database
-
- # note that type hints are optional so
- # id = ormar.Integer(primary_key=True)
- # is also valid
+# note that this step is optional -> all ormar cares is a internal
+# class with name Meta and proper parameters, but this way you do not
+# have to repeat the same parameters if you use only one database
+class BaseMeta(ormar.ModelMeta):
+ metadata = metadata
+ database = database
+
+
+# Note that all type hints are optional
+# below is a perfectly valid model declaration
+# class Author(ormar.Model):
+# class Meta(BaseMeta):
+# tablename = "authors"
+#
+# id = ormar.Integer(primary_key=True) # <= notice no field types
+# name = ormar.String(max_length=100)
+
+class Author(ormar.Model):
+ class Meta(BaseMeta):
+ tablename = "authors"
+
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
-class Track(ormar.Model):
- class Meta:
- tablename = "track"
- metadata = metadata
- database = database
+class Book(ormar.Model):
+ class Meta(BaseMeta):
+ tablename = "books"
id: int = ormar.Integer(primary_key=True)
- album: Optional[Album] = ormar.ForeignKey(Album)
+ author: Optional[Author] = ormar.ForeignKey(Author)
title: str = ormar.String(max_length=100)
- position: int = ormar.Integer()
+ year: int = ormar.Integer(nullable=True)
-# Create some records to work with.
-malibu = await Album.objects.create(name="Malibu")
-await Track.objects.create(album=malibu, title="The Bird", position=1)
-await Track.objects.create(album=malibu, title="Heart don't stand a chance", position=2)
-await Track.objects.create(album=malibu, title="The Waters", position=3)
-
-# alternative creation of object divided into 2 steps
-fantasies = Album(name="Fantasies")
-await fantasies.save()
-await Track.objects.create(album=fantasies, title="Help I'm Alive", position=1)
-await Track.objects.create(album=fantasies, title="Sick Muse", position=2)
+# create the database
+# note that in production you should use migrations
+# note that this is not required if you connect to existing database
+engine = sqlalchemy.create_engine(DATABASE_URL)
+# just to be sure we clear the db before
+metadata.drop_all(engine)
+metadata.create_all(engine)
-# Fetch an instance, without loading a foreign key relationship on it.
-track = await Track.objects.get(title="The Bird")
+# all functions below are divided into functionality categories
+# note how all functions are defined with async - hence can use await AND needs to
+# be awaited on their own
+async def create():
+ # Create some records to work with through QuerySet.create method.
+ # Note that queryset is exposed on each Model's class as objects
+ tolkien = await Author.objects.create(name="J.R.R. Tolkien")
+ await Book.objects.create(author=tolkien,
+ title="The Hobbit",
+ year=1937)
+ await Book.objects.create(author=tolkien,
+ title="The Lord of the Rings",
+ year=1955)
+ await Book.objects.create(author=tolkien,
+ title="The Silmarillion",
+ year=1977)
-# We have an album instance, but it only has the primary key populated
-print(track.album) # Album(id=1) [sparse]
-print(track.album.pk) # 1
-print(track.album.name) # None
+ # alternative creation of object divided into 2 steps
+ sapkowski = Author(name="Andrzej Sapkowski")
+ # do some stuff
+ await sapkowski.save()
-# Load the relationship from the database
-await track.album.load()
-assert track.album.name == "Malibu"
+ # or save() after initialization
+ await Book(author=sapkowski, title="The Witcher", year=1990).save()
+ await Book(author=sapkowski, title="The Tower of Fools", year=2002).save()
-# This time, fetch an instance, loading the foreign key relationship.
-track = await Track.objects.select_related("album").get(title="The Bird")
-assert track.album.name == "Malibu"
+ # to read more about inserting data into the database
+ # visit: https://collerek.github.io/ormar/queries/create/
-# By default you also get a second side of the relation
-# constructed as lowercase source model name +'s' (tracks in this case)
-# you can also provide custom name with parameter related_name
-album = await Album.objects.select_related("tracks").all()
-assert len(album.tracks) == 3
-# Fetch instances, with a filter across an FK relationship.
-tracks = await Track.objects.filter(album__name="Fantasies").all()
-assert len(tracks) == 2
+async def read():
+ # Fetch an instance, without loading a foreign key relationship on it.
+ book = await Book.objects.get(title="The Hobbit")
+ book2 = await Book.objects.first()
-# Fetch instances, with a filter and operator across an FK relationship.
-tracks = await Track.objects.filter(album__name__iexact="fantasies").all()
-assert len(tracks) == 2
+ # first() fetch the instance with lower primary key value
+ assert book == book2
-# Limit a query
-tracks = await Track.objects.limit(1).all()
-assert len(tracks) == 1
+ # you can access all fields on loaded model
+ assert book.title == "The Hobbit"
+ assert book.year == 1937
+
+ # when no condition is passed to get()
+ # it behaves as last() based on primary key column
+ book3 = await Book.objects.get()
+ assert book3.title == "The Tower of Fools"
+
+ # When you have a relation, ormar always defines a related model for you
+ # even when all you loaded is a foreign key value like in this example
+ assert isinstance(book.author, Author)
+ # primary key is populated from foreign key stored in books table
+ assert book.author.pk == 1
+ # since the related model was not loaded all other fields are None
+ assert book.author.name is None
+
+ # Load the relationship from the database when you already have the related model
+ # alternatively see joins section below
+ await book.author.load()
+ assert book.author.name == "J.R.R. Tolkien"
+
+ # get all rows for given model
+ authors = await Author.objects.all()
+ assert len(authors) == 2
+
+ # to read more about reading data from the database
+ # visit: https://collerek.github.io/ormar/queries/read/
+
+
+async def update():
+ # read existing row from db
+ tolkien = await Author.objects.get(name="J.R.R. Tolkien")
+ assert tolkien.name == "J.R.R. Tolkien"
+ tolkien_id = tolkien.id
+
+ # change the selected property
+ tolkien.name = "John Ronald Reuel Tolkien"
+ # call update on a model instance
+ await tolkien.update()
+
+ # confirm that object was updated
+ tolkien = await Author.objects.get(name="John Ronald Reuel Tolkien")
+ assert tolkien.name == "John Ronald Reuel Tolkien"
+ assert tolkien.id == tolkien_id
+
+ # alternatively update data without loading
+ await Author.objects.filter(name__contains="Tolkien").update(name="J.R.R. Tolkien")
+
+ # to read more about updating data in the database
+ # visit: https://collerek.github.io/ormar/queries/update/
+
+
+async def delete():
+ silmarillion = await Book.objects.get(year=1977)
+ # call delete() on instance
+ await silmarillion.delete()
+
+ # alternatively delete without loading
+ await Book.objects.delete(title="The Tower of Fools")
+
+ # note that when there is no record ormar raises NoMatch exception
+ try:
+ await Book.objects.get(year=1977)
+ except ormar.NoMatch:
+ print("No book from 1977!")
+
+ # to read more about deleting data from the database
+ # visit: https://collerek.github.io/ormar/queries/delete/
+
+ # note that despite the fact that record no longer exists in database
+ # the object above is still accessible and you can use it (and i.e. save()) again.
+ tolkien = silmarillion.author
+ await Book.objects.create(author=tolkien,
+ title="The Silmarillion",
+ year=1977)
+
+
+async def joins():
+ # Tho join two models use select_related
+ book = await Book.objects.select_related("author").get(title="The Hobbit")
+ # now the author is already prefetched
+ assert book.author.name == "J.R.R. Tolkien"
+
+ # By default you also get a second side of the relation
+ # constructed as lowercase source model name +'s' (books in this case)
+ # you can also provide custom name with parameter related_name
+ author = await Author.objects.select_related("books").all(name="J.R.R. Tolkien")
+ assert len(author[0].books) == 3
+
+ # for reverse and many to many relations you can also prefetch_related
+ # that executes a separate query for each of related models
+
+ author = await Author.objects.prefetch_related("books").get(name="J.R.R. Tolkien")
+ assert len(author.books) == 3
+
+ # to read more about relations
+ # visit: https://collerek.github.io/ormar/relations/
+
+ # to read more about joins and subqueries
+ # visit: https://collerek.github.io/ormar/queries/delete/
+
+
+async def filter_and_sort():
+ # to filter the query you can use filter() or pass key-value pars to
+ # get(), all() etc.
+ # to use special methods or access related model fields use double
+ # underscore like to filter by the name of the author use author__name
+ books = await Book.objects.all(author__name="J.R.R. Tolkien")
+ assert len(books) == 3
+
+ # filter can accept special methods also separated with double underscore
+ # to issue sql query ` where authors.name like "%tolkien%"` that is not
+ # case sensitive (hence small t in Tolkien)
+ books = await Book.objects.filter(author__name__icontains="tolkien").all()
+ assert len(books) == 3
+
+ # to sort use order_by() function of queryset
+ # to sort decreasing use hyphen before the field name
+ # same as with filter you can use double underscores to access related fields
+ books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
+ "-year").all()
+ assert len(books) == 3
+ assert books[0].title == "The Silmarillion"
+ assert books[2].title == "The Hobbit"
+
+ # to read more about filtering and ordering
+ # visit: https://collerek.github.io/ormar/queries/filter-and-sort/
+
+
+async def subset_of_columns():
+ # to exclude some columns from loading when querying the database
+ # you can use fileds() method
+ hobbit = await Book.objects.fields(["title"]).get(title="The Hobbit")
+ # note that fields not included in fields are empty (set to None)
+ assert hobbit.year is None
+ assert hobbit.author is None
+
+ # selected field is there
+ assert hobbit.title == "The Hobbit"
+
+ # alternatively you can provide columns you want to exclude
+ hobbit = await Book.objects.exclude_fields(["year"]).get(title="The Hobbit")
+ # year is still not set
+ assert hobbit.year is None
+ # but author is back
+ assert hobbit.author is not None
+
+ # also you cannot exclude primary key column - it's always there
+ # even if you EXPLICITLY exclude it it will be there
+
+ # note that each model have a shortcut for primary_key column which is pk
+ # and you can filter/access/set the values by this alias like below
+ assert hobbit.pk is not None
+
+ # note that you cannot exclude fields that are not nullable
+ # (required) in model definition
+ try:
+ await Book.objects.exclude_fields(["title"]).get(title="The Hobbit")
+ except pydantic.ValidationError:
+ print("Cannot exclude non nullable field title")
+
+ # to read more about selecting subset of columns
+ # visit: https://collerek.github.io/ormar/queries/select-columns/
+
+
+async def pagination():
+ # to limit number of returned rows use limit()
+ books = await Book.objects.limit(1).all()
+ assert len(books) == 1
+ assert books[0].title == "The Hobbit"
+
+ # to offset number of returned rows use offset()
+ books = await Book.objects.limit(1).offset(1).all()
+ assert len(books) == 1
+ assert books[0].title == "The Lord of the Rings"
+
+ # alternatively use paginate that combines both
+ books = await Book.objects.paginate(page=2, page_size=2).all()
+ assert len(books) == 2
+ # note that we removed one book of Sapkowski in delete()
+ # and recreated The Silmarillion - by default when no order_by is set
+ # ordering sorts by primary_key column
+ assert books[0].title == "The Witcher"
+ assert books[1].title == "The Silmarillion"
+
+ # to read more about pagination and number of rows
+ # visit: https://collerek.github.io/ormar/queries/pagination-and-rows-number/
+
+
+async def aggregations():
+ # ormar currently supports count:
+ assert 2 == await Author.objects.count()
+
+ # and exists
+ assert await Book.objects.filter(title="The Hobbit").exists()
+
+ # to read more about aggregated functions
+ # visit: https://collerek.github.io/ormar/queries/aggregations/
+
+
+# gather and execute all functions
+# note - normally import should be at the beginning of the file
+import asyncio
+
+# note that normally you use gather() function to run several functions
+# concurrently but we actually modify the data and we rely on the order of functions
+for func in [create, read, update, delete, joins,
+ filter_and_sort, subset_of_columns,
+ pagination, aggregations]:
+ print(f"Executing: {func.__name__}")
+ asyncio.run(func())
+
+# drop the database tables
+metadata.drop_all(engine)
```
## Ormar Specification
@@ -187,6 +471,7 @@ Available Model Fields (with required args - optional ones in docs):
* `BigInteger()`
* `Decimal(scale, precision)`
* `UUID()`
+* `EnumField` - by passing `choices` to any other Field type
* `ForeignKey(to)`
* `ManyToMany(to, through)`
@@ -230,4 +515,8 @@ Signals allow to trigger your function for a given event on a given Model.
[encode/orm]: https://github.com/encode/orm/
[alembic]: https://alembic.sqlalchemy.org/en/latest/
[fastapi]: https://fastapi.tiangolo.com/
-[documentation]: https://collerek.github.io/ormar/
\ No newline at end of file
+[documentation]: https://collerek.github.io/ormar/
+[migrations]: https://collerek.github.io/ormar/models/migrations/
+[asyncio]: https://docs.python.org/3/library/asyncio.html
+[releases]: https://collerek.github.io/ormar/releases/
+[tests]: https://github.com/collerek/ormar/tree/master/tests
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index d871c06..c6c5f41 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -13,6 +13,10 @@
+
+
+
+
### Overview
@@ -35,6 +39,14 @@ And what's a better name for python ORM than snakes cabinet :)
Check out the [documentation][documentation] for details.
+**Note that for brevity most of the documentation snippets omit the creation of the database
+and scheduling the execution of functions for asynchronous run.**
+
+If you want more real life examples than in the documentation you can see [tests][tests] folder,
+since they actually have to create and connect to database in most of the tests.
+
+Yet remember that those are - well - tests and not all solutions are suitable to be used in real life applications.
+
### Dependencies
Ormar is built with:
@@ -44,99 +56,371 @@ Ormar is built with:
* [`pydantic`][pydantic] for data validation.
* `typing_extensions` for python 3.6 - 3.7
-### Migrations
+### Migrations & Database creation
Because ormar is built on SQLAlchemy core, you can use [`alembic`][alembic] to provide
-database migrations.
+database migrations (and you really should for production code).
+For tests and basic applications the `sqlalchemy` is more than enough:
+```python
+# note this is just a partial snippet full working example below
+# 1. Imports
+import sqlalchemy
+import databases
-**ormar is still under development:**
-We recommend pinning any dependencies (with i.e. `ormar~=0.5.2`)
+# 2. Initialization
+DATABASE_URL = "sqlite:///db.sqlite"
+database = databases.Database(DATABASE_URL)
+metadata = sqlalchemy.MetaData()
+
+# Define models here
+
+# 3. Database creation and tables creation
+engine = sqlalchemy.create_engine(DATABASE_URL)
+metadata.create_all(engine)
+```
+
+For a sample configuration of alembic and more information regarding migrations and
+database creation visit [migrations][migrations] documentation section.
+
+### Package versions
+**ormar is still under development:**
+We recommend pinning any dependencies (with i.e. `ormar~=0.9.1`)
+
+`ormar` also follows the release numeration that breaking changes bump the major number,
+while other changes and fixes bump minor number, so with the latter you should be safe to
+update, yet always read the [releases][releases] docs before.
+`example: (0.5.2 -> 0.6.0 - breaking, 0.5.2 -> 0.5.3 - non breaking)`.
+
+### Asynchronous Python
+
+Note that `ormar` is an asynchronous ORM, which means that you have to `await` the calls to
+the methods, that are scheduled for execution in an event loop. Python has a builtin module
+[`asyncio`][asyncio] that allows you to do just that.
+
+Note that most of "normal" python interpreters do not allow execution of `await`
+outside of a function (cause you actually schedule this function for delayed execution
+and don't get the result immediately).
+
+In a modern web frameworks (like `fastapi`), the framework will handle this for you, but if
+you plan to do this on your own you need to perform this manually like described in a
+quick start below.
### Quick Start
-**Note**: Use `ipython` to try this from the console, since it supports `await`.
+Note that you can find the same script in examples folder on github.
```python
+from typing import Optional
+
import databases
+import pydantic
+
import ormar
import sqlalchemy
-database = databases.Database("sqlite:///db.sqlite")
+DATABASE_URL = "sqlite:///db.sqlite"
+database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
-class Album(ormar.Model):
- class Meta:
- tablename = "album"
- metadata = metadata
- database = database
-
- # note that type hints are optional so
- # id = ormar.Integer(primary_key=True)
- # is also valid
+# note that this step is optional -> all ormar cares is a internal
+# class with name Meta and proper parameters, but this way you do not
+# have to repeat the same parameters if you use only one database
+class BaseMeta(ormar.ModelMeta):
+ metadata = metadata
+ database = database
+
+
+# Note that all type hints are optional
+# below is a perfectly valid model declaration
+# class Author(ormar.Model):
+# class Meta(BaseMeta):
+# tablename = "authors"
+#
+# id = ormar.Integer(primary_key=True) # <= notice no field types
+# name = ormar.String(max_length=100)
+
+class Author(ormar.Model):
+ class Meta(BaseMeta):
+ tablename = "authors"
+
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
-class Track(ormar.Model):
- class Meta:
- tablename = "track"
- metadata = metadata
- database = database
+class Book(ormar.Model):
+ class Meta(BaseMeta):
+ tablename = "books"
id: int = ormar.Integer(primary_key=True)
- album: Optional[Album] = ormar.ForeignKey(Album)
+ author: Optional[Author] = ormar.ForeignKey(Author)
title: str = ormar.String(max_length=100)
- position: int = ormar.Integer()
+ year: int = ormar.Integer(nullable=True)
-# Create some records to work with.
-malibu = await Album.objects.create(name="Malibu")
-await Track.objects.create(album=malibu, title="The Bird", position=1)
-await Track.objects.create(album=malibu, title="Heart don't stand a chance", position=2)
-await Track.objects.create(album=malibu, title="The Waters", position=3)
-
-# alternative creation of object divided into 2 steps
-fantasies = Album(name="Fantasies")
-await fantasies.save()
-await Track.objects.create(album=fantasies, title="Help I'm Alive", position=1)
-await Track.objects.create(album=fantasies, title="Sick Muse", position=2)
+# create the database
+# note that in production you should use migrations
+# note that this is not required if you connect to existing database
+engine = sqlalchemy.create_engine(DATABASE_URL)
+# just to be sure we clear the db before
+metadata.drop_all(engine)
+metadata.create_all(engine)
-# Fetch an instance, without loading a foreign key relationship on it.
-track = await Track.objects.get(title="The Bird")
+# all functions below are divided into functionality categories
+# note how all functions are defined with async - hence can use await AND needs to
+# be awaited on their own
+async def create():
+ # Create some records to work with through QuerySet.create method.
+ # Note that queryset is exposed on each Model's class as objects
+ tolkien = await Author.objects.create(name="J.R.R. Tolkien")
+ await Book.objects.create(author=tolkien,
+ title="The Hobbit",
+ year=1937)
+ await Book.objects.create(author=tolkien,
+ title="The Lord of the Rings",
+ year=1955)
+ await Book.objects.create(author=tolkien,
+ title="The Silmarillion",
+ year=1977)
-# We have an album instance, but it only has the primary key populated
-print(track.album) # Album(id=1) [sparse]
-print(track.album.pk) # 1
-print(track.album.name) # None
+ # alternative creation of object divided into 2 steps
+ sapkowski = Author(name="Andrzej Sapkowski")
+ # do some stuff
+ await sapkowski.save()
-# Load the relationship from the database
-await track.album.load()
-assert track.album.name == "Malibu"
+ # or save() after initialization
+ await Book(author=sapkowski, title="The Witcher", year=1990).save()
+ await Book(author=sapkowski, title="The Tower of Fools", year=2002).save()
-# This time, fetch an instance, loading the foreign key relationship.
-track = await Track.objects.select_related("album").get(title="The Bird")
-assert track.album.name == "Malibu"
+ # to read more about inserting data into the database
+ # visit: https://collerek.github.io/ormar/queries/create/
-# By default you also get a second side of the relation
-# constructed as lowercase source model name +'s' (tracks in this case)
-# you can also provide custom name with parameter related_name
-album = await Album.objects.select_related("tracks").all()
-assert len(album.tracks) == 3
-# Fetch instances, with a filter across an FK relationship.
-tracks = await Track.objects.filter(album__name="Fantasies").all()
-assert len(tracks) == 2
+async def read():
+ # Fetch an instance, without loading a foreign key relationship on it.
+ book = await Book.objects.get(title="The Hobbit")
+ book2 = await Book.objects.first()
-# Fetch instances, with a filter and operator across an FK relationship.
-tracks = await Track.objects.filter(album__name__iexact="fantasies").all()
-assert len(tracks) == 2
+ # first() fetch the instance with lower primary key value
+ assert book == book2
-# Limit a query
-tracks = await Track.objects.limit(1).all()
-assert len(tracks) == 1
+ # you can access all fields on loaded model
+ assert book.title == "The Hobbit"
+ assert book.year == 1937
+
+ # when no condition is passed to get()
+ # it behaves as last() based on primary key column
+ book3 = await Book.objects.get()
+ assert book3.title == "The Tower of Fools"
+
+ # When you have a relation, ormar always defines a related model for you
+ # even when all you loaded is a foreign key value like in this example
+ assert isinstance(book.author, Author)
+ # primary key is populated from foreign key stored in books table
+ assert book.author.pk == 1
+ # since the related model was not loaded all other fields are None
+ assert book.author.name is None
+
+ # Load the relationship from the database when you already have the related model
+ # alternatively see joins section below
+ await book.author.load()
+ assert book.author.name == "J.R.R. Tolkien"
+
+ # get all rows for given model
+ authors = await Author.objects.all()
+ assert len(authors) == 2
+
+ # to read more about reading data from the database
+ # visit: https://collerek.github.io/ormar/queries/read/
+
+
+async def update():
+ # read existing row from db
+ tolkien = await Author.objects.get(name="J.R.R. Tolkien")
+ assert tolkien.name == "J.R.R. Tolkien"
+ tolkien_id = tolkien.id
+
+ # change the selected property
+ tolkien.name = "John Ronald Reuel Tolkien"
+ # call update on a model instance
+ await tolkien.update()
+
+ # confirm that object was updated
+ tolkien = await Author.objects.get(name="John Ronald Reuel Tolkien")
+ assert tolkien.name == "John Ronald Reuel Tolkien"
+ assert tolkien.id == tolkien_id
+
+ # alternatively update data without loading
+ await Author.objects.filter(name__contains="Tolkien").update(name="J.R.R. Tolkien")
+
+ # to read more about updating data in the database
+ # visit: https://collerek.github.io/ormar/queries/update/
+
+
+async def delete():
+ silmarillion = await Book.objects.get(year=1977)
+ # call delete() on instance
+ await silmarillion.delete()
+
+ # alternatively delete without loading
+ await Book.objects.delete(title="The Tower of Fools")
+
+ # note that when there is no record ormar raises NoMatch exception
+ try:
+ await Book.objects.get(year=1977)
+ except ormar.NoMatch:
+ print("No book from 1977!")
+
+ # to read more about deleting data from the database
+ # visit: https://collerek.github.io/ormar/queries/delete/
+
+ # note that despite the fact that record no longer exists in database
+ # the object above is still accessible and you can use it (and i.e. save()) again.
+ tolkien = silmarillion.author
+ await Book.objects.create(author=tolkien,
+ title="The Silmarillion",
+ year=1977)
+
+
+async def joins():
+ # Tho join two models use select_related
+ book = await Book.objects.select_related("author").get(title="The Hobbit")
+ # now the author is already prefetched
+ assert book.author.name == "J.R.R. Tolkien"
+
+ # By default you also get a second side of the relation
+ # constructed as lowercase source model name +'s' (books in this case)
+ # you can also provide custom name with parameter related_name
+ author = await Author.objects.select_related("books").all(name="J.R.R. Tolkien")
+ assert len(author[0].books) == 3
+
+ # for reverse and many to many relations you can also prefetch_related
+ # that executes a separate query for each of related models
+
+ author = await Author.objects.prefetch_related("books").get(name="J.R.R. Tolkien")
+ assert len(author.books) == 3
+
+ # to read more about relations
+ # visit: https://collerek.github.io/ormar/relations/
+
+ # to read more about joins and subqueries
+ # visit: https://collerek.github.io/ormar/queries/delete/
+
+
+async def filter_and_sort():
+ # to filter the query you can use filter() or pass key-value pars to
+ # get(), all() etc.
+ # to use special methods or access related model fields use double
+ # underscore like to filter by the name of the author use author__name
+ books = await Book.objects.all(author__name="J.R.R. Tolkien")
+ assert len(books) == 3
+
+ # filter can accept special methods also separated with double underscore
+ # to issue sql query ` where authors.name like "%tolkien%"` that is not
+ # case sensitive (hence small t in Tolkien)
+ books = await Book.objects.filter(author__name__icontains="tolkien").all()
+ assert len(books) == 3
+
+ # to sort use order_by() function of queryset
+ # to sort decreasing use hyphen before the field name
+ # same as with filter you can use double underscores to access related fields
+ books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
+ "-year").all()
+ assert len(books) == 3
+ assert books[0].title == "The Silmarillion"
+ assert books[2].title == "The Hobbit"
+
+ # to read more about filtering and ordering
+ # visit: https://collerek.github.io/ormar/queries/filter-and-sort/
+
+
+async def subset_of_columns():
+ # to exclude some columns from loading when querying the database
+ # you can use fileds() method
+ hobbit = await Book.objects.fields(["title"]).get(title="The Hobbit")
+ # note that fields not included in fields are empty (set to None)
+ assert hobbit.year is None
+ assert hobbit.author is None
+
+ # selected field is there
+ assert hobbit.title == "The Hobbit"
+
+ # alternatively you can provide columns you want to exclude
+ hobbit = await Book.objects.exclude_fields(["year"]).get(title="The Hobbit")
+ # year is still not set
+ assert hobbit.year is None
+ # but author is back
+ assert hobbit.author is not None
+
+ # also you cannot exclude primary key column - it's always there
+ # even if you EXPLICITLY exclude it it will be there
+
+ # note that each model have a shortcut for primary_key column which is pk
+ # and you can filter/access/set the values by this alias like below
+ assert hobbit.pk is not None
+
+ # note that you cannot exclude fields that are not nullable
+ # (required) in model definition
+ try:
+ await Book.objects.exclude_fields(["title"]).get(title="The Hobbit")
+ except pydantic.ValidationError:
+ print("Cannot exclude non nullable field title")
+
+ # to read more about selecting subset of columns
+ # visit: https://collerek.github.io/ormar/queries/select-columns/
+
+
+async def pagination():
+ # to limit number of returned rows use limit()
+ books = await Book.objects.limit(1).all()
+ assert len(books) == 1
+ assert books[0].title == "The Hobbit"
+
+ # to offset number of returned rows use offset()
+ books = await Book.objects.limit(1).offset(1).all()
+ assert len(books) == 1
+ assert books[0].title == "The Lord of the Rings"
+
+ # alternatively use paginate that combines both
+ books = await Book.objects.paginate(page=2, page_size=2).all()
+ assert len(books) == 2
+ # note that we removed one book of Sapkowski in delete()
+ # and recreated The Silmarillion - by default when no order_by is set
+ # ordering sorts by primary_key column
+ assert books[0].title == "The Witcher"
+ assert books[1].title == "The Silmarillion"
+
+ # to read more about pagination and number of rows
+ # visit: https://collerek.github.io/ormar/queries/pagination-and-rows-number/
+
+
+async def aggregations():
+ # ormar currently supports count:
+ assert 2 == await Author.objects.count()
+
+ # and exists
+ assert await Book.objects.filter(title="The Hobbit").exists()
+
+ # to read more about aggregated functions
+ # visit: https://collerek.github.io/ormar/queries/aggregations/
+
+
+# gather and execute all functions
+# note - normally import should be at the beginning of the file
+import asyncio
+
+# note that normally you use gather() function to run several functions
+# concurrently but we actually modify the data and we rely on the order of functions
+for func in [create, read, update, delete, joins,
+ filter_and_sort, subset_of_columns,
+ pagination, aggregations]:
+ print(f"Executing: {func.__name__}")
+ asyncio.run(func())
+
+# drop the database tables
+metadata.drop_all(engine)
```
## Ormar Specification
@@ -231,4 +515,8 @@ Signals allow to trigger your function for a given event on a given Model.
[encode/orm]: https://github.com/encode/orm/
[alembic]: https://alembic.sqlalchemy.org/en/latest/
[fastapi]: https://fastapi.tiangolo.com/
-[documentation]: https://collerek.github.io/ormar/
\ No newline at end of file
+[documentation]: https://collerek.github.io/ormar/
+[migrations]: https://collerek.github.io/ormar/models/migrations/
+[asyncio]: https://docs.python.org/3/library/asyncio.html
+[releases]: https://collerek.github.io/ormar/releases/
+[tests]: https://github.com/collerek/ormar/tree/master/tests
\ No newline at end of file
diff --git a/docs/queries/index.md b/docs/queries/index.md
index eb112c5..cd74990 100644
--- a/docs/queries/index.md
+++ b/docs/queries/index.md
@@ -39,6 +39,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy.create(**kwargs)` method
* `QuerysetProxy.get_or_create(**kwargs)` method
* `QuerysetProxy.update_or_create(**kwargs)` method
+
+!!!tip
+ To read more about any or all of those functions visit [create](./create.md) section.
### [Read data from database](./read.md)
@@ -57,6 +60,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy.get_or_create(**kwargs)` method
* `QuerysetProxy.first()` method
* `QuerysetProxy.all(**kwargs)` method
+
+!!!tip
+ To read more about any or all of those functions visit [read](./read.md) section.
### [Update data in database](./update.md)
@@ -73,6 +79,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy`
* `QuerysetProxy.update_or_create(**kwargs)` method
+
+!!!tip
+ To read more about any or all of those functions visit [update](./update.md) section.
### [Delete data from database](./delete.md)
@@ -86,6 +95,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy`
* `QuerysetProxy.remove()` method
* `QuerysetProxy.clear()` method
+
+!!!tip
+ To read more about any or all of those functions visit [delete](./delete.md) section.
### [Joins and subqueries](./joins-and-subqueries.md)
@@ -100,6 +112,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy`
* `QuerysetProxy.select_related(related: Union[List, str])` method
* `QuerysetProxy.prefetch_related(related: Union[List, str])` method
+
+!!!tip
+ To read more about any or all of those functions visit [joins and subqueries](./joins-and-subqueries.md) section.
### [Filtering and sorting](./filter-and-sort.md)
@@ -118,6 +133,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy.get(**kwargs)` method
* `QuerysetProxy.get_or_create(**kwargs)` method
* `QuerysetProxy.all(**kwargs)` method
+
+!!!tip
+ To read more about any or all of those functions visit [filtering and sorting](./filter-and-sort.md) section.
### [Selecting columns](./select-columns.md)
@@ -128,6 +146,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy`
* `QuerysetProxy.fields(columns: Union[List, str, set, dict])` method
* `QuerysetProxy.exclude_fields(columns: Union[List, str, set, dict])` method
+
+!!!tip
+ To read more about any or all of those functions visit [selecting columns](./select-columns.md) section.
### [Pagination and rows number](./pagination-and-rows-number.md)
@@ -142,6 +163,9 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy.paginate(page: int)` method
* `QuerysetProxy.limit(limit_count: int)` method
* `QuerysetProxy.offset(offset: int)` method
+
+!!!tip
+ To read more about any or all of those functions visit [pagination](./pagination-and-rows-number.md) section.
### [Aggregated functions](./aggregations.md)
@@ -153,5 +177,8 @@ To read more about any specific section or function please refer to the details
* `QuerysetProxy.count()` method
* `QuerysetProxy.exists()` method
+!!!tip
+ To read more about any or all of those functions visit [aggregations](./aggregations.md) section.
+
[relations]: ../relations/index.md
\ No newline at end of file
diff --git a/docs/releases.md b/docs/releases.md
index c1a07bf..1cd5407 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1,3 +1,11 @@
+# 0.9.2
+
+## Other
+* Updated the Quick Start in docs/readme
+* Updated docs with links to queries subpage
+* Added badges for code climate and pepy downloads
+
+
# 0.9.1
## Features
diff --git a/examples/script_from_readme.py b/examples/script_from_readme.py
new file mode 100644
index 0000000..b0a0f82
--- /dev/null
+++ b/examples/script_from_readme.py
@@ -0,0 +1,310 @@
+from typing import Optional
+
+import databases
+import pydantic
+
+import ormar
+import sqlalchemy
+
+DATABASE_URL = "sqlite:///db.sqlite"
+database = databases.Database(DATABASE_URL)
+metadata = sqlalchemy.MetaData()
+
+
+# note that this step is optional -> all ormar cares is a internal
+# class with name Meta and proper parameters, but this way you do not
+# have to repeat the same parameters if you use only one database
+class BaseMeta(ormar.ModelMeta):
+ metadata = metadata
+ database = database
+
+
+# Note that all type hints are optional
+# below is a perfectly valid model declaration
+# class Author(ormar.Model):
+# class Meta(BaseMeta):
+# tablename = "authors"
+#
+# id = ormar.Integer(primary_key=True) # <= notice no field types
+# name = ormar.String(max_length=100)
+
+class Author(ormar.Model):
+ class Meta(BaseMeta):
+ tablename = "authors"
+
+ id: int = ormar.Integer(primary_key=True)
+ name: str = ormar.String(max_length=100)
+
+
+class Book(ormar.Model):
+ class Meta(BaseMeta):
+ tablename = "books"
+
+ id: int = ormar.Integer(primary_key=True)
+ author: Optional[Author] = ormar.ForeignKey(Author)
+ title: str = ormar.String(max_length=100)
+ year: int = ormar.Integer(nullable=True)
+
+
+# create the database
+# note that in production you should use migrations
+# note that this is not required if you connect to existing database
+engine = sqlalchemy.create_engine(DATABASE_URL)
+# just to be sure we clear the db before
+metadata.drop_all(engine)
+metadata.create_all(engine)
+
+
+# all functions below are divided into functionality categories
+# note how all functions are defined with async - hence can use await AND needs to
+# be awaited on their own
+async def create():
+ # Create some records to work with through QuerySet.create method.
+ # Note that queryset is exposed on each Model's class as objects
+ tolkien = await Author.objects.create(name="J.R.R. Tolkien")
+ await Book.objects.create(author=tolkien,
+ title="The Hobbit",
+ year=1937)
+ await Book.objects.create(author=tolkien,
+ title="The Lord of the Rings",
+ year=1955)
+ await Book.objects.create(author=tolkien,
+ title="The Silmarillion",
+ year=1977)
+
+ # alternative creation of object divided into 2 steps
+ sapkowski = Author(name="Andrzej Sapkowski")
+ # do some stuff
+ await sapkowski.save()
+
+ # or save() after initialization
+ await Book(author=sapkowski, title="The Witcher", year=1990).save()
+ await Book(author=sapkowski, title="The Tower of Fools", year=2002).save()
+
+ # to read more about inserting data into the database
+ # visit: https://collerek.github.io/ormar/queries/create/
+
+
+async def read():
+ # Fetch an instance, without loading a foreign key relationship on it.
+ book = await Book.objects.get(title="The Hobbit")
+ book2 = await Book.objects.first()
+
+ # first() fetch the instance with lower primary key value
+ assert book == book2
+
+ # you can access all fields on loaded model
+ assert book.title == "The Hobbit"
+ assert book.year == 1937
+
+ # when no condition is passed to get()
+ # it behaves as last() based on primary key column
+ book3 = await Book.objects.get()
+ assert book3.title == "The Tower of Fools"
+
+ # When you have a relation, ormar always defines a related model for you
+ # even when all you loaded is a foreign key value like in this example
+ assert isinstance(book.author, Author)
+ # primary key is populated from foreign key stored in books table
+ assert book.author.pk == 1
+ # since the related model was not loaded all other fields are None
+ assert book.author.name is None
+
+ # Load the relationship from the database when you already have the related model
+ # alternatively see joins section below
+ await book.author.load()
+ assert book.author.name == "J.R.R. Tolkien"
+
+ # get all rows for given model
+ authors = await Author.objects.all()
+ assert len(authors) == 2
+
+ # to read more about reading data from the database
+ # visit: https://collerek.github.io/ormar/queries/read/
+
+
+async def update():
+ # read existing row from db
+ tolkien = await Author.objects.get(name="J.R.R. Tolkien")
+ assert tolkien.name == "J.R.R. Tolkien"
+ tolkien_id = tolkien.id
+
+ # change the selected property
+ tolkien.name = "John Ronald Reuel Tolkien"
+ # call update on a model instance
+ await tolkien.update()
+
+ # confirm that object was updated
+ tolkien = await Author.objects.get(name="John Ronald Reuel Tolkien")
+ assert tolkien.name == "John Ronald Reuel Tolkien"
+ assert tolkien.id == tolkien_id
+
+ # alternatively update data without loading
+ await Author.objects.filter(name__contains="Tolkien").update(name="J.R.R. Tolkien")
+
+ # to read more about updating data in the database
+ # visit: https://collerek.github.io/ormar/queries/update/
+
+
+async def delete():
+ silmarillion = await Book.objects.get(year=1977)
+ # call delete() on instance
+ await silmarillion.delete()
+
+ # alternatively delete without loading
+ await Book.objects.delete(title="The Tower of Fools")
+
+ # note that when there is no record ormar raises NoMatch exception
+ try:
+ await Book.objects.get(year=1977)
+ except ormar.NoMatch:
+ print("No book from 1977!")
+
+ # to read more about deleting data from the database
+ # visit: https://collerek.github.io/ormar/queries/delete/
+
+ # note that despite the fact that record no longer exists in database
+ # the object above is still accessible and you can use it (and i.e. save()) again.
+ tolkien = silmarillion.author
+ await Book.objects.create(author=tolkien,
+ title="The Silmarillion",
+ year=1977)
+
+
+async def joins():
+ # Tho join two models use select_related
+ book = await Book.objects.select_related("author").get(title="The Hobbit")
+ # now the author is already prefetched
+ assert book.author.name == "J.R.R. Tolkien"
+
+ # By default you also get a second side of the relation
+ # constructed as lowercase source model name +'s' (books in this case)
+ # you can also provide custom name with parameter related_name
+ author = await Author.objects.select_related("books").all(name="J.R.R. Tolkien")
+ assert len(author[0].books) == 3
+
+ # for reverse and many to many relations you can also prefetch_related
+ # that executes a separate query for each of related models
+
+ author = await Author.objects.prefetch_related("books").get(name="J.R.R. Tolkien")
+ assert len(author.books) == 3
+
+ # to read more about relations
+ # visit: https://collerek.github.io/ormar/relations/
+
+ # to read more about joins and subqueries
+ # visit: https://collerek.github.io/ormar/queries/delete/
+
+
+async def filter_and_sort():
+ # to filter the query you can use filter() or pass key-value pars to
+ # get(), all() etc.
+ # to use special methods or access related model fields use double
+ # underscore like to filter by the name of the author use author__name
+ books = await Book.objects.all(author__name="J.R.R. Tolkien")
+ assert len(books) == 3
+
+ # filter can accept special methods also separated with double underscore
+ # to issue sql query ` where authors.name like "%tolkien%"` that is not
+ # case sensitive (hence small t in Tolkien)
+ books = await Book.objects.filter(author__name__icontains="tolkien").all()
+ assert len(books) == 3
+
+ # to sort use order_by() function of queryset
+ # to sort decreasing use hyphen before the field name
+ # same as with filter you can use double underscores to access related fields
+ books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
+ "-year").all()
+ assert len(books) == 3
+ assert books[0].title == "The Silmarillion"
+ assert books[2].title == "The Hobbit"
+
+ # to read more about filtering and ordering
+ # visit: https://collerek.github.io/ormar/queries/filter-and-sort/
+
+
+async def subset_of_columns():
+ # to exclude some columns from loading when querying the database
+ # you can use fileds() method
+ hobbit = await Book.objects.fields(["title"]).get(title="The Hobbit")
+ # note that fields not included in fields are empty (set to None)
+ assert hobbit.year is None
+ assert hobbit.author is None
+
+ # selected field is there
+ assert hobbit.title == "The Hobbit"
+
+ # alternatively you can provide columns you want to exclude
+ hobbit = await Book.objects.exclude_fields(["year"]).get(title="The Hobbit")
+ # year is still not set
+ assert hobbit.year is None
+ # but author is back
+ assert hobbit.author is not None
+
+ # also you cannot exclude primary key column - it's always there
+ # even if you EXPLICITLY exclude it it will be there
+
+ # note that each model have a shortcut for primary_key column which is pk
+ # and you can filter/access/set the values by this alias like below
+ assert hobbit.pk is not None
+
+ # note that you cannot exclude fields that are not nullable
+ # (required) in model definition
+ try:
+ await Book.objects.exclude_fields(["title"]).get(title="The Hobbit")
+ except pydantic.ValidationError:
+ print("Cannot exclude non nullable field title")
+
+ # to read more about selecting subset of columns
+ # visit: https://collerek.github.io/ormar/queries/select-columns/
+
+
+async def pagination():
+ # to limit number of returned rows use limit()
+ books = await Book.objects.limit(1).all()
+ assert len(books) == 1
+ assert books[0].title == "The Hobbit"
+
+ # to offset number of returned rows use offset()
+ books = await Book.objects.limit(1).offset(1).all()
+ assert len(books) == 1
+ assert books[0].title == "The Lord of the Rings"
+
+ # alternatively use paginate that combines both
+ books = await Book.objects.paginate(page=2, page_size=2).all()
+ assert len(books) == 2
+ # note that we removed one book of Sapkowski in delete()
+ # and recreated The Silmarillion - by default when no order_by is set
+ # ordering sorts by primary_key column
+ assert books[0].title == "The Witcher"
+ assert books[1].title == "The Silmarillion"
+
+ # to read more about pagination and number of rows
+ # visit: https://collerek.github.io/ormar/queries/pagination-and-rows-number/
+
+
+async def aggregations():
+ # ormar currently supports count:
+ assert 2 == await Author.objects.count()
+
+ # and exists
+ assert await Book.objects.filter(title="The Hobbit").exists()
+
+ # to read more about aggregated functions
+ # visit: https://collerek.github.io/ormar/queries/aggregations/
+
+
+# gather and execute all functions
+# note - normally import should be at the beginning of the file
+import asyncio
+
+# note that normally you use gather() function to run several functions
+# concurrently but we actually modify the data and we rely on the order of functions
+for func in [create, read, update, delete, joins,
+ filter_and_sort, subset_of_columns,
+ pagination, aggregations]:
+ print(f"Executing: {func.__name__}")
+ asyncio.run(func())
+
+# drop the database tables
+metadata.drop_all(engine)
diff --git a/ormar/__init__.py b/ormar/__init__.py
index 7904111..147b40b 100644
--- a/ormar/__init__.py
+++ b/ormar/__init__.py
@@ -65,7 +65,7 @@ class UndefinedType: # pragma no cover
Undefined = UndefinedType()
-__version__ = "0.9.1"
+__version__ = "0.9.2"
__all__ = [
"Integer",
"BigInteger",