The ormar package is an async mini ORM for Python, with support for Postgres, MySQL, and SQLite.

Overview

ormar

Pypi version Pypi version Build Status Coverage CodeFactor

Overview

The ormar package is an async mini ORM for Python, with support for Postgres, MySQL, and SQLite.

The main benefit of using ormar are:

  • getting an async ORM that can be used with async frameworks (fastapi, starlette etc.)
  • getting just one model to maintain - you don't have to maintain pydantic and other orm model (sqlalchemy, peewee, gino etc.)

The goal was to create a simple ORM that can be used directly (as request and response models) with fastapi that bases it's data validation on pydantic.

Ormar - apart form obvious ORM in name - get it's name from ormar in swedish which means snakes, and ormar(e) in italian which means cabinet.

And what's a better name for python ORM than snakes cabinet :)

Documentation

Check out the 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 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.

Part of the fastapi ecosystem

As part of the fastapi ecosystem ormar is supported in libraries that somehow work with databases.

As of now ormar is supported by:

If you maintain or use different library and would like it to support ormar let us know how we can help.

Dependencies

Ormar is built with:

Migrating from sqlalchemy

If you currently use sqlalchemy and would like to switch to ormar check out the auto-translation tool that can help you with translating existing sqlalchemy orm models so you do not have to do it manually.

Beta versions available at github: sqlalchemy-to-ormar or simply pip install sqlalchemy-to-ormar

Migrations & Database creation

Because ormar is built on SQLAlchemy core, you can use alembic to provide database migrations (and you really should for production code).

For tests and basic applications the sqlalchemy is more than enough:

# note this is just a partial snippet full working example below
# 1. Imports
import sqlalchemy
import databases

# 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 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 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 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 that you can find the same script in examples folder on github.

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.
    # Django style
    book = await Book.objects.get(title="The Hobbit")
    # or python style
    book = await Book.objects.get(Book.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/joins-and-subqueries/


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
    # Django style
    books = await Book.objects.all(author__name="J.R.R. Tolkien")
    # python style
    books = await Book.objects.all(Book.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)
    # Django style
    books = await Book.objects.filter(author__name__icontains="tolkien").all()
    # python style
    books = await Book.objects.filter(Book.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
    # Django style
    books = await Book.objects.filter(author__name__icontains="tolkien").order_by(
        "-year").all()
    # python style
    books = await Book.objects.filter(Book.author.name.icontains("tolkien")).order_by(
        Book.year.desc()).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():
    # count:
    assert 2 == await Author.objects.count()

    # exists:
    assert await Book.objects.filter(title="The Hobbit").exists()

    # max:
    assert 1990 == await Book.objects.max(columns=["year"])

    # min:
    assert 1937 == await Book.objects.min(columns=["year"])

    # avg:
    assert 1964.75 == await Book.objects.avg(columns=["year"])

    # sum:
    assert 7859 == await Book.objects.sum(columns=["year"])

    # to read more about aggregated functions
    # visit: https://collerek.github.io/ormar/queries/aggregations/

    
async def with_connect(function):
    # note that for any other backend than sqlite you actually need to
    # connect to the database to perform db operations
    async with database:
        await function()

    # note that if you use framework like `fastapi` you shouldn't connect
    # in your endpoints but have a global connection pool
    # check https://collerek.github.io/ormar/fastapi/ and section with db connection

# 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(with_connect(func))

# drop the database tables
metadata.drop_all(engine)

Ormar Specification

QuerySet methods

  • create(**kwargs): -> Model
  • get(*args, **kwargs): -> Model
  • get_or_none(*args, **kwargs): -> Optional[Model]
  • get_or_create(*args, **kwargs) -> Model
  • first(*args, **kwargs): -> Model
  • update(each: bool = False, **kwargs) -> int
  • update_or_create(**kwargs) -> Model
  • bulk_create(objects: List[Model]) -> None
  • bulk_update(objects: List[Model], columns: List[str] = None) -> None
  • delete(*args, each: bool = False, **kwargs) -> int
  • all(*args, **kwargs) -> List[Optional[Model]]
  • filter(*args, **kwargs) -> QuerySet
  • exclude(*args, **kwargs) -> QuerySet
  • select_related(related: Union[List, str]) -> QuerySet
  • prefetch_related(related: Union[List, str]) -> QuerySet
  • limit(limit_count: int) -> QuerySet
  • offset(offset: int) -> QuerySet
  • count() -> int
  • exists() -> bool
  • max(columns: List[str]) -> Any
  • min(columns: List[str]) -> Any
  • avg(columns: List[str]) -> Any
  • sum(columns: List[str]) -> Any
  • fields(columns: Union[List, str, set, dict]) -> QuerySet
  • exclude_fields(columns: Union[List, str, set, dict]) -> QuerySet
  • order_by(columns:Union[List, str]) -> QuerySet

Relation types

  • One to many - with ForeignKey(to: Model)
  • Many to many - with ManyToMany(to: Model, Optional[through]: Model)

Model fields types

Available Model Fields (with required args - optional ones in docs):

  • String(max_length)
  • Text()
  • Boolean()
  • Integer()
  • Float()
  • Date()
  • Time()
  • DateTime()
  • JSON()
  • BigInteger()
  • Decimal(scale, precision)
  • UUID()
  • LargeBinary(max_length)
  • EnumField - by passing choices to any other Field type
  • EncryptedString - by passing encrypt_secret and encrypt_backend
  • ForeignKey(to)
  • ManyToMany(to, through)

Available fields options

The following keyword arguments are supported on all field types.

  • primary_key: bool
  • nullable: bool
  • default: Any
  • server_default: Any
  • index: bool
  • unique: bool
  • choices: typing.Sequence
  • name: str
  • pydantic_only: bool

All fields are required unless one of the following is set:

  • nullable - Creates a nullable column. Sets the default to None.
  • default - Set a default value for the field. Not available for relation fields
  • server_default - Set a default value for the field on server side (like sqlalchemy's func.now()). Not available for relation fields
  • primary key with autoincrement - When a column is set to primary key and autoincrement is set on this column. Autoincrement is set by default on int primary keys.
  • pydantic_only - Field is available only as normal pydantic field, not stored in the database.

Available signals

Signals allow to trigger your function for a given event on a given Model.

  • pre_save
  • post_save
  • pre_update
  • post_update
  • pre_delete
  • post_delete
Comments
  • Model.dict() is taking to much time to respond

    Model.dict() is taking to much time to respond

    Describe the bug Im using a model with 1 many to many rel and 1 foreign key, with ormar, but the seralization is taking too much to do it. After disabling dict() the overloaded method, the response model does not have any related model, but it takes almost a 1/100 (I didn't make the exact calculations) of the time to do it. This problem also affects to make the json response, so we should expect a slow response time. Is there any way to disable this override method or is there any plan/idea to optimize it?

    With ormar dict(): Captura de Pantalla 2021-09-06 a la(s) 12 29 23

    With only-pydantic dict(): Captura de Pantalla 2021-09-06 a la(s) 12 28 13

    The list of models that IΒ΄m loading doesn't have any related model loaded, neither items on the many to many relation

    bug 
    opened by luisjimenez6245 20
  • Support Base64 Encoding LargeBinary Field

    Support Base64 Encoding LargeBinary Field

    Is your feature request related to a problem? Please describe.

    In a Model such as:

    class BaseMessageModel(Model):
        class Meta:
            metadata = metadata
            database = database
    
        data: bytes = LargeBinary(max_length=1000)
    

    One of the challenges that comes up is actually serializing this binary data effectively. If there are any UTF-8 incompatible bytes:

    Traceback (most recent call last):
      File "venv/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 398, in run_asgi
        result = await app(self.scope, self.receive, self.send)
      File "venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
        return await self.app(scope, receive, send)
      File "venv/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
        await super().__call__(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/applications.py", line 111, in __call__
        await self.middleware_stack(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
        raise exc from None
      File "venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
        await self.app(scope, receive, _send)
      File "venv/lib/python3.9/site-packages/starlette/middleware/cors.py", line 78, in __call__
        await self.app(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
        raise exc from None
      File "venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
        await self.app(scope, receive, sender)
      File "venv/lib/python3.9/site-packages/starlette/routing.py", line 566, in __call__
        await route.handle(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/routing.py", line 376, in handle
        await self.app(scope, receive, send)
      File "venv/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
        await super().__call__(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/applications.py", line 111, in __call__
        await self.middleware_stack(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
        raise exc from None
      File "venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
        await self.app(scope, receive, _send)
      File "venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
        raise exc from None
      File "venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
        await self.app(scope, receive, sender)
      File "venv/lib/python3.9/site-packages/starlette/routing.py", line 566, in __call__
        await route.handle(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/routing.py", line 227, in handle
        await self.app(scope, receive, send)
      File "venv/lib/python3.9/site-packages/starlette/routing.py", line 41, in app
        response = await func(request)
      File "venv/lib/python3.9/site-packages/fastapi/routing.py", line 209, in app
        response_data = await serialize_response(
      File "venv/lib/python3.9/site-packages/fastapi/routing.py", line 127, in serialize_response
        return jsonable_encoder(
      File "venv/lib/python3.9/site-packages/fastapi/encoders.py", line 104, in jsonable_encoder
        jsonable_encoder(
      File "venv/lib/python3.9/site-packages/fastapi/encoders.py", line 57, in jsonable_encoder
        return jsonable_encoder(
      File "venv/lib/python3.9/site-packages/fastapi/encoders.py", line 90, in jsonable_encoder
        encoded_value = jsonable_encoder(
      File "venv/lib/python3.9/site-packages/fastapi/encoders.py", line 127, in jsonable_encoder
        return ENCODERS_BY_TYPE[type(obj)](obj)
      File "pydantic/json.py", line 43, in pydantic.json.lambda
    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbe in position 1: invalid start byte
    

    This makes sense since you can't send arbitrary bytes through a fundamentally text-based (UTF-8) JSON representation.

    Describe the solution you'd like

    class BaseMessageModel(Model):
        class Meta:
            metadata = metadata
            database = database
    
        data: str = LargeBinary(max_length=1000, represent_as_base64=True)
    

    And internally this would call something like:

        @validator("data")
        def convert_to_base64_str(cls, v: bytes) -> str:
            return base64.b64encode(v).decode()
    

    Edit: It looks like FastAPI doesn't use OpenAPI 3.1 yet (it is newly released), so probably the older 3.0 format should be used:

    https://github.com/OAI/OpenAPI-Specification/blob/fbe62006211838a8bb7bf2433a1d15f1a5838a03/versions/3.0.1.md#considerations-for-file-uploads

    # content transferred with base64 encoding
    schema:
      type: string
      format: base64
    

    Future Considerations

    In OpenAPI 3.1 (not yet supported by FastAPI): https://github.com/OAI/OpenAPI-Specification/issues/1547#issuecomment-595497718

    type: string
    contentEncoding: base64
    

    Describe alternatives you've considered

    Ideally Pydantic would support this serialization upstream: https://github.com/samuelcolvin/pydantic/issues/692 but it's been that issue has been open for 2 years.

    enhancement 
    opened by johnthagen 20
  • Add python_version marker constraint in setup.py

    Add python_version marker constraint in setup.py

    Describe the bug I've found a dependency resolution problem when installing ormar with black using poetry.

      Because no versions of black match >21.9b0,<22.0
       and black (21.9b0) depends on typing-extensions (>=3.10.0.0), black (>=21.9b0,<22.0) requires typing-extensions (>=3.10.0.0).
      And because ormar (0.10.20) depends on typing-extensions (>=3.7,<3.10.0.3)
       and no versions of ormar match >0.10.20,<0.11.0, black (>=21.9b0,<22.0) is incompatible with ormar (>=0.10.20,<0.11.0).
      So, because ormartest depends on both ormar (^0.10.20) and black (^21.9b0), version solving failed.
    

    To Reproduce Steps to reproduce the behavior:

    1. poetry new test
    2. cd test
    3. poetry add black --dev
    4. poetry add ormar
    5. You found the error.

    Expected behavior A clear and concise description of what you expected to happen.

    Versions (please complete the following information):

    • Database backend used (mysql/sqlite/postgress)
    • Python version
    • poetry ^1.1.11
    • ormar ^0.10.20
    • black ^21.9b0

    Possible solution

    Since you use typing_extensions only for providing Protocol and Literal, you may add constraint to avoid this issue. The solution is to specify the typing_extension version in your setup.py as following:

    install_requires=[
            ...
            "typing_extensions>=3.7,<3.10.0.3; python_version < '3.8'",
        ],
    

    Additional context

    This issue is crucial for me, since I want to integrate your super cool library in my project generator. It uses black by default, but I can't get it work with your library because of this issue.

    Thanks in advance.

    bug 
    opened by s3rius 14
  • KeyError when building openapi file

    KeyError when building openapi file

    Describe the bug I deployed a service using FastAPI and Ormar on two different servers. On the first one it works, and on the second one I get this error when accessing /docs or /openapi.json (I believe most of the traceback is not useful):

    Summary: (click to unfold the whole traceback)
      File "/api/pkgs/fastapi/utils.py", line 28, in get_model_definitions
        model_name = model_name_map[model]
                     β”‚              β”” <class 'abc.Team_PJX'>
                     β”” {<class 'abc.Team_TLK'>: 'Team_TLK', <enum 'TicketStatus'>: 'TicketStatus', <class 'abc.Project_RLS'>: 'Project_RLS', <class ...
    
    KeyError: <class 'abc.Team_PJX'>
    
    Exception in ASGI application
    
    Traceback (most recent call last):
    
      File "/api/run.py", line 70, in <module>
        server.run()
        β”‚      β”” <function Server.run at 0x7fbf689e23a0>
        β”” <uvicorn.main.Server object at 0x7fbf6ea4bd00>
    
      File "/api/pkgs/uvicorn/main.py", line 419, in run
        loop.run_until_complete(self.serve(sockets=sockets))
        β”‚    β”‚                  β”‚    β”‚             β”” None
        β”‚    β”‚                  β”‚    β”” <function Server.serve at 0x7fbf689e2430>
        β”‚    β”‚                  β”” <uvicorn.main.Server object at 0x7fbf6ea4bd00>
        β”‚    β”” <function BaseEventLoop.run_until_complete at 0x7fbf6b0f3700>
        β”” <_UnixSelectorEventLoop running=True closed=False debug=False>
    
      File "/usr/lib64/python3.8/asyncio/base_events.py", line 603, in run_until_complete
        self.run_forever()
        β”‚    β”” <function BaseEventLoop.run_forever at 0x7fbf6b0f3670>
        β”” <_UnixSelectorEventLoop running=True closed=False debug=False>
      File "/usr/lib64/python3.8/asyncio/base_events.py", line 570, in run_forever
        self._run_once()
        β”‚    β”” <function BaseEventLoop._run_once at 0x7fbf6b0f61f0>
        β”” <_UnixSelectorEventLoop running=True closed=False debug=False>
      File "/usr/lib64/python3.8/asyncio/base_events.py", line 1859, in _run_once
        handle._run()
        β”‚      β”” <function Handle._run at 0x7fbf6b574f70>
        β”” <Handle <TaskWakeupMethWrapper object at 0x7fbf5c5aaee0>(<Future finished result=None>)>
      File "/usr/lib64/python3.8/asyncio/events.py", line 81, in _run
        self._context.run(self._callback, *self._args)
        β”‚    β”‚            β”‚    β”‚           β”‚    β”” <member '_args' of 'Handle' objects>
        β”‚    β”‚            β”‚    β”‚           β”” <Handle <TaskWakeupMethWrapper object at 0x7fbf5c5aaee0>(<Future finished result=None>)>
        β”‚    β”‚            β”‚    β”” <member '_callback' of 'Handle' objects>
        β”‚    β”‚            β”” <Handle <TaskWakeupMethWrapper object at 0x7fbf5c5aaee0>(<Future finished result=None>)>
        β”‚    β”” <member '_context' of 'Handle' objects>
        β”” <Handle <TaskWakeupMethWrapper object at 0x7fbf5c5aaee0>(<Future finished result=None>)>
    
      File "/api/pkgs/uvicorn/protocols/http/h11_impl.py", line 389, in run_asgi
        result = await app(self.scope, self.receive, self.send)
                       β”‚   β”‚    β”‚      β”‚    β”‚        β”‚    β”” <function RequestResponseCycle.send at 0x7fbf68768790>
                       β”‚   β”‚    β”‚      β”‚    β”‚        β”” <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>
                       β”‚   β”‚    β”‚      β”‚    β”” <function RequestResponseCycle.receive at 0x7fbf68768820>
                       β”‚   β”‚    β”‚      β”” <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>
                       β”‚   β”‚    β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
                       β”‚   β”” <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>
                       β”” <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x7fbf687941f0>
    
      File "/api/pkgs/uvicorn/middleware/proxy_headers.py", line 45, in __call__
        return await self.app(scope, receive, send)
                     β”‚    β”‚   β”‚      β”‚        β”” <bound method RequestResponseCycle.send of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
                     β”‚    β”‚   β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
                     β”‚    β”‚   β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
                     β”‚    β”” <fastapi.applications.FastAPI object at 0x7fbf66e39850>
                     β”” <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x7fbf687941f0>
    
      File "/api/pkgs/fastapi/applications.py", line 199, in __call__
        await super().__call__(scope, receive, send)
                               β”‚      β”‚        β”” <bound method RequestResponseCycle.send of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
                               β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
                               β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
    
      File "/api/pkgs/starlette/applications.py", line 112, in __call__
        await self.middleware_stack(scope, receive, send)
              β”‚    β”‚                β”‚      β”‚        β”” <bound method RequestResponseCycle.send of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚                β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚                β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚    β”” <starlette.middleware.errors.ServerErrorMiddleware object at 0x7fbf64fdd9d0>
              β”” <fastapi.applications.FastAPI object at 0x7fbf66e39850>
    
      File "/api/pkgs/starlette/middleware/errors.py", line 181, in __call__
        raise exc from None
    
      File "/api/pkgs/starlette/middleware/errors.py", line 159, in __call__
        await self.app(scope, receive, _send)
              β”‚    β”‚   β”‚      β”‚        β”” <function ServerErrorMiddleware.__call__.<locals>._send at 0x7fbf64fdeee0>
              β”‚    β”‚   β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚   β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚    β”” <starlette.middleware.base.BaseHTTPMiddleware object at 0x7fbf64fdd9a0>
              β”” <starlette.middleware.errors.ServerErrorMiddleware object at 0x7fbf64fdd9d0>
    
      File "/api/pkgs/starlette/middleware/base.py", line 25, in __call__
        response = await self.dispatch_func(request, self.call_next)
                         β”‚    β”‚             β”‚        β”‚    β”” <function BaseHTTPMiddleware.call_next at 0x7fbf66a819d0>
                         β”‚    β”‚             β”‚        β”” <starlette.middleware.base.BaseHTTPMiddleware object at 0x7fbf64fdd9a0>
                         β”‚    β”‚             β”” <starlette.requests.Request object at 0x7fbf640dd340>
                         β”‚    β”” <function PrometheusFastApiInstrumentator.instrument.<locals>.dispatch_middleware at 0x7fbf65196ee0>
                         β”” <starlette.middleware.base.BaseHTTPMiddleware object at 0x7fbf64fdd9a0>
    
      File "/api/pkgs/prometheus_fastapi_instrumentator/instrumentation.py", line 150, in dispatch_middleware
        raise e from None
    
      File "/api/pkgs/prometheus_fastapi_instrumentator/instrumentation.py", line 145, in dispatch_middleware
        response = await call_next(request)
                         β”‚         β”” <starlette.requests.Request object at 0x7fbf640dd340>
                         β”” <bound method BaseHTTPMiddleware.call_next of <starlette.middleware.base.BaseHTTPMiddleware object at 0x7fbf64fdd9a0>>
    
      File "/api/pkgs/starlette/middleware/base.py", line 45, in call_next
        task.result()
        β”‚    β”” <method 'result' of '_asyncio.Task' objects>
        β”” <Task finished name='Task-5469' coro=<BaseHTTPMiddleware.call_next.<locals>.coro() done, defined at /api/pkgs/starlette/middl...
    
      File "/api/pkgs/starlette/middleware/base.py", line 38, in coro
        await self.app(scope, receive, send)
              β”‚    β”‚   β”‚      β”‚        β”” <bound method Queue.put of <Queue at 0x7fbf64e0ea30 maxsize=0 tasks=1>>
              β”‚    β”‚   β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚   β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚    β”” <starlette.middleware.cors.CORSMiddleware object at 0x7fbf64fdd970>
              β”” <starlette.middleware.base.BaseHTTPMiddleware object at 0x7fbf64fdd9a0>
    
      File "/api/pkgs/starlette/middleware/cors.py", line 78, in __call__
        await self.app(scope, receive, send)
              β”‚    β”‚   β”‚      β”‚        β”” <bound method Queue.put of <Queue at 0x7fbf64e0ea30 maxsize=0 tasks=1>>
              β”‚    β”‚   β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚   β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚    β”” <starlette.exceptions.ExceptionMiddleware object at 0x7fbf64fdda60>
              β”” <starlette.middleware.cors.CORSMiddleware object at 0x7fbf64fdd970>
    
      File "/api/pkgs/starlette/exceptions.py", line 82, in __call__
        raise exc from None
    
      File "/api/pkgs/starlette/exceptions.py", line 71, in __call__
        await self.app(scope, receive, sender)
              β”‚    β”‚   β”‚      β”‚        β”” <function ExceptionMiddleware.__call__.<locals>.sender at 0x7fbf64a939d0>
              β”‚    β”‚   β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚   β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚    β”” <fastapi.routing.APIRouter object at 0x7fbf657a9250>
              β”” <starlette.exceptions.ExceptionMiddleware object at 0x7fbf64fdda60>
    
      File "/api/pkgs/starlette/routing.py", line 580, in __call__
        await route.handle(scope, receive, send)
              β”‚     β”‚      β”‚      β”‚        β”” <function ExceptionMiddleware.__call__.<locals>.sender at 0x7fbf64a939d0>
              β”‚     β”‚      β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚     β”‚      β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚     β”” <function Route.handle at 0x7fbf66ad3b80>
              β”” <starlette.routing.Route object at 0x7fbf653fb5b0>
    
      File "/api/pkgs/starlette/routing.py", line 241, in handle
        await self.app(scope, receive, send)
              β”‚    β”‚   β”‚      β”‚        β”” <function ExceptionMiddleware.__call__.<locals>.sender at 0x7fbf64a939d0>
              β”‚    β”‚   β”‚      β”” <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x7fbf5c5aad90>>
              β”‚    β”‚   β”” {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.1', 'server': ('172.42.56.164', 8080),...
              β”‚    β”” <function request_response.<locals>.app at 0x7fbf653fdee0>
              β”” <starlette.routing.Route object at 0x7fbf653fb5b0>
    
      File "/api/pkgs/starlette/routing.py", line 52, in app
        response = await func(request)
                         β”‚    β”” <starlette.requests.Request object at 0x7fbf5c5aa130>
                         β”” <function FastAPI.setup.<locals>.openapi at 0x7fbf653fdd30>
    
      File "/api/pkgs/fastapi/applications.py", line 152, in openapi
        return JSONResponse(self.openapi())
               β”‚            β”‚    β”” <function FastAPI.openapi at 0x7fbf66a91af0>
               β”‚            β”” <fastapi.applications.FastAPI object at 0x7fbf66e39850>
               β”” <class 'starlette.responses.JSONResponse'>
    
      File "/api/pkgs/fastapi/applications.py", line 130, in openapi
        self.openapi_schema = get_openapi(
        β”‚    β”‚                β”” <function get_openapi at 0x7fbf66a81430>
        β”‚    β”” None
        β”” <fastapi.applications.FastAPI object at 0x7fbf66e39850>
    
      File "/api/pkgs/fastapi/openapi/utils.py", line 363, in get_openapi
        definitions = get_model_definitions(
                      β”” <function get_model_definitions at 0x7fbf66b27af0>
    
      File "/api/pkgs/fastapi/utils.py", line 28, in get_model_definitions
        model_name = model_name_map[model]
                     β”‚              β”” <class 'abc.Team_PJX'>
                     β”” {<class 'abc.Team_TLK'>: 'Team_TLK', <enum 'TicketStatus'>: 'TicketStatus', <class 'abc.Project_RLS'>: 'Project_RLS', <class ...
    
    KeyError: <class 'abc.Team_PJX'>
    

    To Reproduce

    I'm not sure how to reproduce this. As stated above, only one of two identical deployments fails with this error. I'll see what happens when I redeploy the two services (if the failing one is fixed by the update) and report back. In the meanwhile maybe you'll have a hunch πŸ™‚

    Expected behavior No errors, openapi.json can be generated.

    Versions (please complete the following information):

    • Database backend used: sqlite
    • Python version: 3.8
    • ormar version: 0.10.15
    • pydantic version: 1.8.2
    • if applicable fastapi version: 0.65.2
    bug 
    opened by pawamoy 14
  • Bug in request generated

    Bug in request generated

    Hi, I have a bug with a request generated when trying to fetch data from a many 2 many relation:

    • the request generated reference a user.id field as a primary key, but the primary key in the model is user.registrationnumber
    • I also had some errors when using field name containing capitalized letter ou containing '_' You will find all the details in the sample below.

    OS: Centos 7.9 (docker) python version: 3.8.3 ormar version: 0.7.3 database backend: postgresql

    To reproduce the error i made the script below:

    import asyncio
    import uuid
    from datetime import date, datetime
    from os import major
    from typing import List, Optional, Union
    
    import databases
    import ormar
    import sqlalchemy
    from fastapi.encoders import jsonable_encoder
    from sqlalchemy import func, text
    import jsonpickle
    
    
    DATABASE_URL="postgresql://postgres:postgres@db:5432/test"
    database = databases.Database(DATABASE_URL)
    metadata = sqlalchemy.MetaData()
    
    
    class MainMeta(ormar.ModelMeta):
        metadata = metadata
        database = database
    
    
    class Role(ormar.Model):
        class Meta(MainMeta):
            pass
        name                : str = ormar.Text(primary_key=True)
        order               : int = ormar.Integer(default=0)
        description         : str = ormar.Text()
    
    
    class Company(ormar.Model):
        class Meta(MainMeta):
            pass
        name                : str = ormar.Text(primary_key=True)
    
    
    class UserRoleCompany(ormar.Model):
        class Meta(MainMeta):
            pass
    
    
    class User(ormar.Model):
        class Meta(MainMeta):
            pass
        registrationnumber  : str = ormar.Text(primary_key=True)
        company             : Company = ormar.ForeignKey(Company)
        name                : str = ormar.Text()
        role                : Optional[Role] = ormar.ForeignKey(Role)
        roleforcompanies    : Optional[Union[Company, List[Company]]] = ormar.ManyToMany(Company, through=UserRoleCompany)
        lastupdate          : date = ormar.DateTime(server_default=sqlalchemy.func.now())
    
    
    async def main():
        if not database.is_connected:
            print("connection to db {}.".format(DATABASE_URL))
            await database.connect()
        ##########################################################################################
        try:
            print("adding role")
            role_0 = await Role.objects.create(name="user", order=0, description = "no administration right")
            role_1 = await Role.objects.create(name="admin", order=1, description = "standard administration right")
            role_2 = await Role.objects.create(name="super_admin", order=2, description = "super administration right")
            assert await Role.objects.count() == 3
    
            print("adding company")
            company_0 = await Company.objects.create(name="Company")
            company_1 = await Company.objects.create(name="Subsidiary Company 1")
            company_2 = await Company.objects.create(name="Subsidiary Company 2")
            company_3 = await Company.objects.create(name="Subsidiary Company 3")
            assert await Company.objects.count() == 4
    
            print("adding user")
            user = await User.objects.create(registrationnumber="00-00000", company=company_0, name="admin", role=role_1)
            assert await User.objects.count() == 1
    
            print("removing user")
            await user.delete()
            assert await User.objects.count() == 0
    
            print("adding user with company-role")
            companies: List[Company] = [company_1, company_2]
            # user = await User.objects.create(registrationnumber="00-00000", company=company_0, name="admin", role=role_1, roleforcompanies=companies)
            user = await User.objects.create(registrationnumber="00-00000", company=company_0, name="admin", role=role_1)
            # print(User.__fields__)
            await user.roleforcompanies.add(company_1)
            await user.roleforcompanies.add(company_2)
    
            users = await User.objects.select_related("roleforcompanies").all()
            print(jsonpickle.encode(jsonable_encoder(users), unpicklable=False, keys=True ))
    
        except Exception as error:
            print(error)
    
    
        """
    
        This is the request generated:
        'SELECT
        users.registrationnumber as registrationnumber,
        users.company as company,
        users.name as name, users.role as role,
        users.lastupdate as lastupdate,
        cy24b4_userrolecompanys.id as cy24b4_id,
        cy24b4_userrolecompanys.company as cy24b4_company,
        cy24b4_userrolecompanys.user as cy24b4_user,
        jn50a4_companys.name as jn50a4_name \n
        FROM users
        LEFT OUTER JOIN userrolecompanys cy24b4_userrolecompanys ON cy24b4_userrolecompanys.user=users.id
        LEFT OUTER JOIN companys jn50a4_companys ON jn50a4_companys.name=cy24b4_userrolecompanys.company
        ORDER BY users.registrationnumber, jn50a4_companys.name'
    
        There is an error in the First LEFT OUTER JOIN generated:
        ... companys.user=users.id
        should be:
       ... companys.user=users.registrationnumber
    
        There is also a \n in the midle of the string...
    
        The execution produce the error: column users.id does not exist
        """
    
        ##########################################################################################
        if database.is_connected:
            await database.disconnect()
            print("db closed.")
    
    
    
    
    if __name__ == '__main__':
        asyncio.run(main())
    
    

    I'm new to python, sqlalchemy, fastapi anr ormar.... maybe i made some mistakes... Thanks for this great project.

    bug 
    opened by schaulet 14
  • RuntimeError: dictionary changed size during iteration

    RuntimeError: dictionary changed size during iteration

    Describe the bug Not sure Ormar will be able to do anything.

    To Reproduce

    Upon running a query, there's some Pydantic validation happening, and it ends up with a runtime error when deep-copying data. It seems that it is due to an element (of the model being deep-copied) being a submodel, and that upon accessing it, or another attribute of the parent model, it is loaded, changing the dictionary size somehow.

    Here's the query:

    packages = await Package.objects.select_related(["library", "tickets__ticket"]).exclude(version__contains=".x").all()
    

    That query triggers the runtime error mentioned above. If I either remove tickets__ticket, or go down to the submodel with tickets__ticket__team, the error disappears.

    packages = await Package.objects.select_related(["library"]).exclude(version__contains=".x").all()  # ok
    packages = await Package.objects.select_related(["library", "tickets__ticket__team"]).exclude(version__contains=".x").all()  # ok
    

    Traceback:

    $ python sara.py 
    Traceback (most recent call last):
      File "sara.py", line 26, in <module>
        asyncio.run(amain())
      File "/home/user/.basher-packages/pyenv/pyenv/versions/3.8.11/lib/python3.8/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/home/user/.basher-packages/pyenv/pyenv/versions/3.8.11/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
        return future.result()
      File "sara.py", line 9, in amain
        monitoring_ticket_id = await suggest_monitoring()
      File "/home/user/dev/project/src/project/sara/monitoring.py", line 114, in suggest
        libraries = await suggest_monitoring_libraries()
      File "/home/user/dev/project/src/project/suggest.py", line 150, in suggest_monitoring_libraries
        await Package.objects.select_related(["library", "tickets__ticket"]).exclude(version__contains=".x").all()
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/queryset/queryset.py", line 1053, in all
        result_rows = self._process_query_result_rows(rows)
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/queryset/queryset.py", line 184, in _process_query_result_rows
        result_rows = [
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/queryset/queryset.py", line 185, in <listcomp>
        self.model.from_row(
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/models/model_row.py", line 84, in from_row
        item = cls._populate_nested_models_from_row(
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/models/model_row.py", line 203, in _populate_nested_models_from_row
        child = model_cls.from_row(
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/models/model_row.py", line 104, in from_row
        instance = cast("Model", cls(**item))
      File "/home/user/dev/project/__pypackages__/3.8/lib/ormar/models/newbasemodel.py", line 140, in __init__
        values, fields_set, validation_error = pydantic.validate_model(
      File "pydantic/main.py", line 1038, in pydantic.main.validate_model
      File "pydantic/fields.py", line 857, in pydantic.fields.ModelField.validate
      File "pydantic/fields.py", line 1067, in pydantic.fields.ModelField._validate_singleton
      File "pydantic/fields.py", line 857, in pydantic.fields.ModelField.validate
      File "pydantic/fields.py", line 1074, in pydantic.fields.ModelField._validate_singleton
      File "pydantic/fields.py", line 1121, in pydantic.fields.ModelField._apply_validators
      File "pydantic/class_validators.py", line 313, in pydantic.class_validators._generic_validator_basic.lambda12
      File "pydantic/main.py", line 679, in pydantic.main.BaseModel.validate
      File "pydantic/main.py", line 605, in pydantic.main.BaseModel._copy_and_set_values
      File "/home/user/.basher-packages/pyenv/pyenv/versions/3.8.11/lib/python3.8/copy.py", line 146, in deepcopy
        y = copier(x, memo)
      File "/home/user/.basher-packages/pyenv/pyenv/versions/3.8.11/lib/python3.8/copy.py", line 229, in _deepcopy_dict
        for key, value in x.items():
    RuntimeError: dictionary changed size during iteration
    

    Expected behavior No runtime error.

    Versions (please complete the following information):

    • Database backend used: sqlite
    • Python version: 3.8.11
    • ormar version: 0.11.0
    • pydantic version: 1.9.1
    • if applicable fastapi version: 0.78.0

    Additional context The code triggering the error did not always trigger it. It might be due to a dependency upgrade (which one? I don't know). Did anyone encounter the same issue?

    bug 
    opened by pawamoy 13
  • FastAPI JSON weird

    FastAPI JSON weird

    So, I'm not sure which library bears responsibility here, but I'll start with ormar and you can forward me somewhere else if the problem lies elsewhere. Consider the following code:

    ...
    class Thing(ormar.Model):
        class Meta(BaseMeta):
            tablename = "things"
        id: UUID = ormar.UUID(primary_key=True, default=uuid4)
        name: str = ormar.Text(default="")
        js: pydantic.Json = ormar.JSON()
    ...
    @app.get("/things")
    async def read_things():
        return await Thing.objects.all()
    ...
    

    What I get when I call this endpoint is e.g.

    [
      {
        "id": "1932caad-1157-4224-9688-e280f9623e67",
        "name": "",
        "js": "[\"asdf\", \"asdf\", \"bobby\", \"nigel\"]"
      },
      {
        "id": "3e6a15b2-2cd5-456b-a4dc-24e3cd76d96e",
        "name": "test",
        "js": "[\"lemon\", \"raspberry\", \"lime\", \"pumice\"]"
      }
    ]
    

    Note how rather than being JSON, the js field is a plain string containing JSON. Is this on purpose? Does it HAVE to be that way? It seems to me like it would make more sense for a JSON field, when its container is serialized to JSON, to just...be JSON. (I note that thing.json() preserves the "convert json to string" behavior.) Is there an easy way around this behavior, perhaps a flag or setting? Is this actually a result of a different library?

    bug 
    opened by Erhannis 13
  • QuerySet operations returns Sequence instead of List

    QuerySet operations returns Sequence instead of List

    Describe the bug The QuerySet operations return value is Sequence["Model"] even though on the DocString it is stated as List["Model"] It is causing for lint errors as mismatch types when trying to retrieve the returned object as List.

    for example:

    rows: List[User] = await User.objects.all()
    

    Will cause a lint error on mismatch type.

    Looking into Ormar source code it seems the first time where this is starting to get confused is at: ormar.models.mixins.merge_mixin.py ==> merge_instances_list class method

    from source-code:

    @classmethod
        def merge_instances_list(cls, result_rows: Sequence["Model"]) -> Sequence["Model"]:
            """
            Merges a list of models into list of unique models.
    
            Models can duplicate during joins when parent model has multiple child rows,
            in the end all parent (main) models should be unique.
    
            :param result_rows: list of already initialized Models with child models
            populated, each instance is one row in db and some models can duplicate
            :type result_rows: List["Model"]
            :return: list of merged models where each main model is unique
            :rtype: List["Model"]
            """
    

    as you can see rtype on DocString is List["Model"] while the annotated return type is Sequence["Model"].

    If there is any specific reason and I can assist with fixing - I will be very happy to do so! The package is very helpful and it is much appreciated!

    bug 
    opened by dudil 12
  • Proper annotations

    Proper annotations

    Right now mypy, pyright and other static type analyzers are screaming from annotations like this: id: Integer(primary_key=True)

    mypy error:

    lmat/db/models.py:33: error: Invalid type comment or annotation  [valid-type]
    lmat/db/models.py:33: note: Suggestion: use Integer[...] instead of Integer(...)
    

    pyright error:

    Expected class type but received "Integer" Pyright (reportGeneralTypeIssues)
    

    Why id = Integer(primary_key=True) syntax is not used (like in orm)? I guess it is because of the way you extract annotations, do you consider changing the approach? I would like to use this library in one project, but it is unusable in this state (I cannot just put # type: ignore[valid-type] on every line in models)

    Can help with this when I have some spare time, unfortunately I am forced to go back to orm.

    opened by HarrySky 12
  • Declaring specific class as response_model in FastAPI produces RecursionError

    Declaring specific class as response_model in FastAPI produces RecursionError

    Describe the bug I get a RecursionError if the Quiz from below is set as the response_model in a FasAPI route-decorator.

    from uuid import uuid4 as uuid
    from datetime import datetime
    from typing import Optional
    from pydantic import BaseModel, Json
    from . import metadata, database
    import ormar
    
    class User(ormar.Model):
        """
        The user model
        """
        id: uuid = ormar.UUID(primary_key=True, default=uuid)
        email: str = ormar.String(unique=True, max_length=100)
        username: str = ormar.String(unique=True, max_length=100)
        password: str = ormar.String(unique=True, max_length=100)
        verified: bool = ormar.Boolean(default=False)
        verify_key: str = ormar.String(unique=True, max_length=100, nullable=True)
        created_at: datetime = ormar.DateTime(default=datetime.now())
    
        class Meta:
            tablename = 'users'
            metadata = metadata
            database = database
    
    
    class UserSession(ormar.Model):
        """
        The user session model
        """
        id: uuid = ormar.UUID(primary_key=True, default=uuid)
        user: uuid = ormar.ForeignKey(User)
        session_key: str = ormar.String(unique=True, max_length=64)
        created_at: datetime = ormar.DateTime(default=datetime.now())
    
        class Meta:
            tablename = 'user_sessions'
            metadata = metadata
            database = database
    class QuizAnswer(BaseModel):
        right: bool
        answer: str
    
    
    class QuizQuestion(BaseModel):
        question: str
        answers: list[QuizAnswer]
    
    
    class QuizInput(BaseModel):
        title: str
        description: str
        questions: list[QuizQuestion]
    
    
    class Quiz(ormar.Model):
        id: uuid = ormar.UUID(primary_key=True, default=uuid)
        title: str = ormar.String(max_length=100)
        description: str = ormar.String(max_length=300, nullable=True)
        created_at: datetime = ormar.DateTime(default=datetime.now())
        updated_at: datetime = ormar.DateTime(default=datetime.now())
        user_id: uuid = ormar.UUID(foreign_key=User.id)
        questions: Json[list[QuizQuestion]] = ormar.JSON(nullable=False)
    
        class Meta:
            tablename = 'quiz'
            metadata = metadata
            database = database
    

    Like this:

    from fastapi import APIRouter
    router = APIRouter()
    
    The example **doesn't** run "as is"
    
    @router.post("/create", response_model=Quiz)
    async def create_quiz_lol(quiz_input: QuizInput):
        quiz = Quiz(**quiz_input.dict(), user_id=user.id)
        return await quiz.save()
    

    Versions (please complete the following information):

    • Database backend used (mysql/sqlite/postgress)
    • Python 3.10.2
    • ormar 0.10.25
    • pydantic 1.9.0
    • FastAPI 0.74.1
    bug 
    opened by mawoka-myblock 10
  • `ValidationError` is not thrown out correctly with `get_pydantic` method

    `ValidationError` is not thrown out correctly with `get_pydantic` method

    Description

    According to the documentation I noticed that the ValidationError is not thrown out correctly if models generated with get_pydantic are used in FastAPI requests.

    Example:

    class EnumExample(str, enum.Enum):
        A = 'A'
        B = 'B'
        C = 'C'
    
    
    class ModelExample(ormar.Model):
        class Meta(ormar.ModelMeta):
            database = database
            metadata = metadata
            tablename = "examples"
        
        id: int = ormar.Integer(primary_key=True)
        str_field: str = ormar.String(min_length=5, max_length=10, nullable=False)
        enum_field: str = ormar.String(max_length=1, nullable=False, choices=list(EnumExample))
    
        @pydantic.validator('str_field')
        def validate_str_field(cls, v):
            if ' ' not in v:
                raise ValueError('must contain a space')
            return v
    
    
    ModelExampleCreate = ModelExample.get_pydantic(exclude={'id'})
    
    @app.post("/examples/", response_model=ModelExample)
    async def create_example(example: ModelExampleCreate):
        return await ModelExample(**example.dict()).save()
    

    Result:

    Client receives an Internal Server Error, the ValidationError is only output in the error log.

      File "/home/vscode/.local/lib/python3.9/site-packages/ormar/models/newbasemodel.py", line 143, in __init__
        raise validation_error
    pydantic.error_wrappers.ValidationError: 1 validation error for ModelExample
    __root__
      enum_field: 'D' not in allowed choices set: ['A', 'B', 'C'] (type=value_error)
    
      File "/home/vscode/.local/lib/python3.9/site-packages/ormar/models/newbasemodel.py", line 143, in __init__
        raise validation_error
    pydantic.error_wrappers.ValidationError: 1 validation error for ModelExample
    str_field
      must contain a space (type=value_error)
    

    Expected result:

    Client receives the ValidationError.

    Note:

    Everything goes as expected with the original model:

    @app.post("/examples/", response_model=ModelExample)
    async def create_example(example: ModelExample):
        return await example.save()
    

    Versions:

    • ormar 0.10.20
    • pydantic 1.8.2
    • fastapi 0.68.2
    bug 
    opened by derzinn 10
  • build(deps-dev): bump mkdocs-material from 8.5.11 to 9.0.2

    build(deps-dev): bump mkdocs-material from 8.5.11 to 9.0.2

    Bumps mkdocs-material from 8.5.11 to 9.0.2.

    Release notes

    Sourced from mkdocs-material's releases.

    mkdocs-material-9.0.2

    • Fixed #4823: Improved contrast ratio in footer to meet WCAG guidelines
    • Fixed #4819: Social plugin crashes when card generation is disabled
    • Fixed #4817: Search plugin crashes on numeric page titles in nav

    mkdocs-material-9.0.1

    • Removed pipdeptree dependency for built-in info plugin
    • Fixed appearance of linked tags when hovered (9.0.0 regression)
    • Fixed #4810: Abbreviations run out of screen on touch devices
    • Fixed #4813: View source and edit button links are the same

    mkdocs-material-9.0.0

    Additions and improvements

    • Added support for rich search previews
    • Added support for tokenizer lookahead
    • Added support for better search highlighting
    • Added support for excluding content from search
    • Added support for configurable search pipeline
    • Added support for offline search via offline plugin
    • Added support for multiple instances of built-in tags plugin
    • Added support for removing copy-to-clipboard button
    • Added support for removing footer navigation
    • Added support for button to view the source of a page
    • Improved readability of query string for search sharing
    • Improved stability of search plugin when using --dirtyreload
    • Improved search result group button, now sticky and stable
    • Updated Norwegian translations
    • Updated MkDocs to 1.4.2

    Removals

    • Removed deprecated alternative admonition qualifiers
    • Removed :is() selectors (in output) for easier overriding
    • Removed .title suffix on translations
    • Removed legacy method for providing page title in feedback URL
    • Removed support for indexing only titles in search
    • Removed support for custom search transforms
    • Removed support for custom search workers
    • Removed temporary snow feature (easter egg)

    Fixes

    • Fixed Norwegian and Korean language code
    • Fixed detection of composition events in search interface
    • Fixed search plugin not using title set via front matter
    • Fixed search highlighting of tags
    • Fixed search sharing URL using post transformed string
    • Fixed theme-color meta tag getting out-of-sync with palette toggle
    • Fixed prev/next page keyboard navigation when footer is not present

    ... (truncated)

    Changelog

    Sourced from mkdocs-material's changelog.

    mkdocs-material-9.0.2 (2022-01-04)

    • Fixed #4823: Improved contrast ratio in footer to meet WCAG guidelines
    • Fixed #4819: Social plugin crashes when card generation is disabled
    • Fixed #4817: Search plugin crashes on numeric page titles in nav

    mkdocs-material-9.0.1 (2022-01-03)

    • Removed pipdeptree dependency for built-in info plugin
    • Fixed appearance of linked tags when hovered (9.0.0 regression)
    • Fixed #4810: Abbreviations run out of screen on touch devices
    • Fixed #4813: View source and edit button links are the same

    mkdocs-material-9.0.0 (2023-01-02)

    Additions and improvements

    • Added support for rich search previews
    • Added support for tokenizer lookahead
    • Added support for better search highlighting
    • Added support for excluding content from search
    • Added support for configurable search pipeline
    • Added support for offline search via offline plugin
    • Added support for multiple instances of built-in tags plugin
    • Added support for removing copy-to-clipboard button
    • Added support for removing footer navigation
    • Added support for button to view the source of a page
    • Improved readability of query string for search sharing
    • Improved stability of search plugin when using --dirtyreload
    • Improved search result group button, now sticky and stable
    • Updated Norwegian translations
    • Updated MkDocs to 1.4.2

    Removals

    • Removed deprecated alternative admonition qualifiers
    • Removed :is() selectors (in output) for easier overriding
    • Removed .title suffix on translations
    • Removed legacy method for providing page title in feedback URL
    • Removed support for indexing only titles in search
    • Removed support for custom search transforms
    • Removed support for custom search workers
    • Removed temporary snow feature (easter egg)

    Fixes

    • Fixed Norwegian and Korean language code
    • Fixed detection of composition events in search interface
    • Fixed search plugin not using title set via front matter
    • Fixed search highlighting of tags

    ... (truncated)

    Upgrade guide

    Sourced from mkdocs-material's upgrade guide.

    How to upgrade

    Upgrade to the latest version with:

    pip install --upgrade --force-reinstall mkdocs-material
    

    Show the currently installed version with:

    pip show mkdocs-material
    

    Upgrading from 8.x to 9.x

    This major release includes a brand new search implementation that is faster and allows for rich previews, advanced tokenization and better highlighting. It was available as part of Insiders for over a year, and now that the funding goal was hit, makes its way into the community edition.

    Changes to mkdocs.yml

    content.code.copy

    The copy-to-clipboard buttons are now opt-in and can be enabled or disabled per block. If you wish to enable them for all code blocks, add the following lines to mkdocs.yml:

    theme:
      features:
        - content.code.copy
    

    content.action.*

    A "view source" button can be shown next to the "edit this page" button, both of which must now be explicitly enabled. Add the following lines to mkdocs.yml:

    theme:
      features:
        - content.action.edit
        - content.action.view
    

    navigation.footer

    ... (truncated)

    Commits
    • 9df1bee Merge pull request #4826 from squidfunk/dependabot/npm_and_yarn/json5-1.0.2
    • 7ca35b9 Bump json5 from 1.0.1 to 1.0.2
    • f8a3e83 Prepare 9.0.2 release
    • 157b4f2 Updated distribution files
    • bc8a070 Merge branch 'master' of github.com:squidfunk/mkdocs-material
    • 38e2914 Improved contrast ratio in footer to meet WCAG guidelines
    • b007fbb Documentation (#4825)
    • c8fb426 Fixed crashing of social plugin when cards are disabled
    • 491bd0a Fixed wrong content.action.* feature flags
    • 4548afb Fixed search plugin crashing on page titles
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 1
  • build(deps): bump cryptography from 38.0.4 to 39.0.0

    build(deps): bump cryptography from 38.0.4 to 39.0.0

    Bumps cryptography from 38.0.4 to 39.0.0.

    Changelog

    Sourced from cryptography's changelog.

    39.0.0 - 2023-01-01

    
    * **BACKWARDS INCOMPATIBLE:** Support for OpenSSL 1.1.0 has been removed.
      Users on older version of OpenSSL will need to upgrade.
    * **BACKWARDS INCOMPATIBLE:** Dropped support for LibreSSL < 3.5. The new
      minimum LibreSSL version is 3.5.0. Going forward our policy is to support
      versions of LibreSSL that are available in versions of OpenBSD that are
      still receiving security support.
    * **BACKWARDS INCOMPATIBLE:** Removed the ``encode_point`` and
      ``from_encoded_point`` methods on
      :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers`,
      which had been deprecated for several years.
      :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.public_bytes`
      and
      :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point`
      should be used instead.
    * **BACKWARDS INCOMPATIBLE:** Support for using MD5 or SHA1 in
      :class:`~cryptography.x509.CertificateBuilder`, other X.509 builders, and
      PKCS7 has been removed.
    * **BACKWARDS INCOMPATIBLE:** Dropped support for macOS 10.10 and 10.11, macOS
      users must upgrade to 10.12 or newer.
    * **ANNOUNCEMENT:** The next version of ``cryptography`` (40.0) will change
      the way we link OpenSSL. This will only impact users who build
      ``cryptography`` from source (i.e., not from a ``wheel``), and specify their
      own version of OpenSSL. For those users, the ``CFLAGS``, ``LDFLAGS``,
      ``INCLUDE``, ``LIB``, and ``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS`` environment
      variables will no longer be respected. Instead, users will need to
      configure their builds `as documented here`_.
    * Added support for
      :ref:`disabling the legacy provider in OpenSSL 3.0.x<legacy-provider>`.
    * Added support for disabling RSA key validation checks when loading RSA
      keys via
      :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`,
      :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`,
      and
      :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers.private_key`.
      This speeds up key loading but is :term:`unsafe` if you are loading potentially
      attacker supplied keys.
    * Significantly improved performance for
      :class:`~cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305`
      when repeatedly calling ``encrypt`` or ``decrypt`` with the same key.
    * Added support for creating OCSP requests with precomputed hashes using
      :meth:`~cryptography.x509.ocsp.OCSPRequestBuilder.add_certificate_by_hash`.
    * Added support for loading multiple PEM-encoded X.509 certificates from
      a single input via :func:`~cryptography.x509.load_pem_x509_certificates`.
    

    .. _v38-0-4:

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 1
  • build(deps): bump importlib-metadata from 5.2.0 to 6.0.0

    build(deps): bump importlib-metadata from 5.2.0 to 6.0.0

    Bumps importlib-metadata from 5.2.0 to 6.0.0.

    Changelog

    Sourced from importlib-metadata's changelog.

    v6.0.0

    • #419: Declared Distribution as an abstract class, enforcing definition of abstract methods in instantiated subclasses. It's no longer possible to instantiate a Distribution or any subclasses unless they define the abstract methods.

      Please comment in the issue if this change breaks any projects. This change will likely be rolled back if it causes significant disruption.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies python 
    opened by dependabot[bot] 1
  • Support for MySQL Set Type (choices wont work)

    Support for MySQL Set Type (choices wont work)

    Hey, I'm trying to find a solution how to use the MySQL SET field column type in the model of ormar.
    How about adding a new field called ormar.SetField (it should inherit from enum.IntFlag). Choices wont work btw.

    How about this implementation:

    from enum import IntFlag
    
    class SetFieldClass(IntFlag):
        A = 1
        B  = 2
    
    import ormar
    
    ormar.SetField(type_class=SetFieldClass)? 
    
    enhancement 
    opened by 0x6369616e 0
  • Querying DB Views using Ormar

    Querying DB Views using Ormar

    Is your feature request related to a problem? Please describe. I have a complex DB view that relies on other views and would like to use Ormar to query data in that view. What worked partially is to create the model class and set the primary_key=True in one of the fields (although it's duplicated everywhere). I could fetch data but I was only receiving entries with unique primary_key.

    Describe the solution you'd like Allow model descriptions without primary_key

    Describe alternatives you've considered Receive entries in which the primary_key field is duplicated

    Additional context I considered bypassing the DB Views and use the original tables with ormar models, but it brought some additional complexity that I was trying to keep in the DB.

    enhancement 
    opened by idrissbellil 0
Releases(0.12.0)
  • 0.12.0(Oct 21, 2022)

    0.12.0

    ✨ Breaking Changes

    • Queryset.bulk_create will now raise ModelListEmptyError on empty list of models (by @ponytailer - thanks!) #853

    ✨ Features

    • Model.upsert() now handles a flag __force_save__: bool that allow upserting the models regardless of the fact if they have primary key set or not. Note that setting this flag will cause two queries for each upserted model -> get to check if model exists and later update/insert accordingly. #889

    πŸ› Fixes

    • Fix for empty relations breaking construct method (by @Abdeldjalil-H - thanks!) #870
    • Fix save related not saving models with already set pks (including uuid) #885
    • Fix for wrong relations exclusions depending on the order of exclusions #779
    • Fix property_fields not being inherited properly #774
    Source code(tar.gz)
    Source code(zip)
  • 0.11.3(Sep 7, 2022)

    0.11.3

    ✨ Features

    • Document onupdate and ondelete referential actions in ForeignKey and provide ReferentialAction enum to specify the behavior of the relationship (by @SepehrBazyar - thanks!) #724
    • Add CheckColumn to supported constraints in models Meta (by @SepehrBazyar - thanks!) #729

    πŸ› Fixes

    • Fix limiting query result to 0 should return empty list (by @SepehrBazyar - thanks!) #766

    πŸ’¬ Other

    • Add dark mode to docs (by @SepehrBazyar - thanks!) #717
    • Update aiomysql dependency #778
    Source code(tar.gz)
    Source code(zip)
  • 0.11.2(Jun 26, 2022)

    0.11.2

    πŸ› Fixes

    • Fix database drivers being required, while they should be optional #713
    • Fix boolean field problem in limit queries in postgres without limit_raw_sql flag #704
    • Fix enum_class spilling to schema causing errors in OpenAPI #699
    Source code(tar.gz)
    Source code(zip)
  • 0.11.1(Jun 8, 2022)

  • 0.11.0(Mar 28, 2022)

    0.11.0

    ✨ Breaking Changes

    • Dropped support for python 3.6
    • Queryset.get_or_create returns now a tuple with model and bool value indicating if the model was created (by @MojixCoder - thanks!) #554
    • Queryset.count() now counts the number of distinct parent model rows by default, counting all rows is possible by setting distinct=False (by @erichaydel - thanks) #588

    ✨ Features

    • Added support for python 3.10

    πŸ› Fixes

    • Fix inconsistent JSON fields behaviour in save and bulk_create #584
    • Fix maximum recursion error #580
    Source code(tar.gz)
    Source code(zip)
  • 0.10.25(Feb 25, 2022)

    0.10.25

    ✨ Features

    • Add queryset_class option to Model.Meta that allows you to easily swap QuerySet for your Model (by @ponytailer - thanks!) #538
    • Allow passing extra kwargs to IndexColumns that will be passed to sqlalchemy Index (by @zevisert - thanks) #575

    πŸ› Fixes

    • Fix nullable setting on JSON fields #529
    • Fix bytes/str mismatch in bulk operations when using orjson instead of json (by @ponytailer - thanks!) #538
    Source code(tar.gz)
    Source code(zip)
  • 0.10.24(Jan 14, 2022)

    0.10.24

    ✨ Features

    • Add post_bulk_update signal (by @ponytailer - thanks!) #524

    πŸ› Fixes

    πŸ’¬ Other

    • Improve performance of bulk_create by bypassing databases execute_many suboptimal implementation. (by @Mng-dev-ai thanks!) #520
    • Bump min. required databases version for sqlalchemy 1.4 to >=5.4.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.23(Dec 16, 2021)

    0.10.23

    ✨ Features

    • Add ability to pass comment to sqlalchemy when creating a column #485

    πŸ› Fixes

    • Fix LargeBinary fields that can be nullable #409
    • Make ormar.Model pickable #413
    • Make first() and get() without arguments respect ordering of main model set by user, fallback to primary key (asc, and desc respectively) #453
    • Fix improper quoting of non-aliased join on clauses in postgress #455
    Source code(tar.gz)
    Source code(zip)
  • 0.10.22(Oct 15, 2021)

  • 0.10.21(Oct 13, 2021)

    0.10.21

    πŸ› Fixes

    • Add ormar implementation of construct classmethod that allows to build Model instances without validating the input to speed up the whole flow, if your data is already validated #318
    • Fix for "inheriting" field validators from ormar model when newly created pydanic model is generated with get_pydantic #365
    Source code(tar.gz)
    Source code(zip)
  • 0.10.20(Sep 26, 2021)

    0.10.20

    ✨ Features

    • Add extra parameter in Model.Meta that accepts Extra.ignore and Extra.forbid (default) and either ignores the extra fields passed to ormar model or raises an exception if one is encountered #358

    πŸ› Fixes

    • Allow None if field is nullable and have choices set #354
    • Always set primary_key to not null regardless of autoincrement and explicit nullable setting to avoid problems with migrations #348
    Source code(tar.gz)
    Source code(zip)
  • 0.10.19(Sep 13, 2021)

    0.10.19

    ✨ Features

    • Add support for multi-column non-unique IndexColumns in Meta.constraints #307
    • Add sql_nullable field attribute that allows to set different nullable setting for pydantic model and for underlying sql column #308

    πŸ› Fixes

    • Enable caching of relation map to increase performance #337
    • Clarify and fix documentation in regard of nullable fields #339

    πŸ’¬ Other

    • Bump supported databases version to <=5.2.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.18(Sep 1, 2021)

  • 0.10.17(Aug 30, 2021)

  • 0.10.16(Aug 6, 2021)

    0.10.16

    ✨ Features

    • Allow passing your own pydantic Config to ormar.Model that will be merged with the default one by @naturalethic (thanks!) #285
    • Add SmallInteger field type by @ProgrammerPlus1998 (thanks!) #297

    πŸ› Fixes

    • Fix generating openapi schema by removing obsolete pydantic field parameters that were directly exposed in schema #291
    • Fix unnecessary warning for auto generated through models #295
    Source code(tar.gz)
    Source code(zip)
  • 0.10.15(Jul 21, 2021)

    0.10.15

    πŸ› Fixes

    • Fix generating pydantic models tree with nested models (by @pawamoy - thanks!) #278
    • Fix missing f-string in warning about missing primary key field #274
    • Fix passing foreign key value as relation (additional guard, fixed already in the latest release) #270
    Source code(tar.gz)
    Source code(zip)
  • 0.10.14(Jul 6, 2021)

    0.10.14

    ✨ Features

    • Allow passing timezone:bool = False parameter to DateTime and Time fields for timezone aware database columns #264
    • Allow passing datetime, date and time for filter on DateTime, Time and Date fields to allow filtering by datetimes instead of converting the filter value to string #79

    πŸ› Fixes

    • Fix dependencies from psycopg2 to psycopg2-binary #255
    Source code(tar.gz)
    Source code(zip)
  • 0.10.13(Jun 25, 2021)

    0.10.13

    ✨ Features

    • Allow passing field accessors in select_related and prefetch_related aka. python style select_related #225.
      • Previously:
        await Post.objects.select_related(["author", "categories"]).get()
        await Author.objects.prefetch_related("posts__categories").get()
      
      • Now also:
        await Post.objects.select_related([Post.author, Post.categories]).get()
        await Author.objects.prefetch_related(Author.posts.categories).get()
      

    πŸ› Fixes

    • Fix overwriting default value for inherited primary key #253
    Source code(tar.gz)
    Source code(zip)
  • 0.10.12(Jun 22, 2021)

  • 0.10.11(Jun 8, 2021)

    0.10.11

    ✨ Features

    • Add values and values_list to QuerySet and QuerysetProxy that allows to return raw data from query #223.
      • Allow returning list of tuples or list of dictionaries from a query
      • Skips parsing the data to ormar model so skips also the validation
      • Allow excluding models in between in chain of relations, so you can extract only needed columns
      • values_list allows you to flatten the result if you extract only one column.

    πŸ› Fixes

    • Fix creation of auto through model for m2m relation with ForwardRef #226
    Source code(tar.gz)
    Source code(zip)
  • 0.10.10(Jun 2, 2021)

    0.10.10

    ✨ Features

    • Add get_pydantic function that allows you to auto generate equivalent pydantic models tree from ormar.Model. This newly generated model tree can be used in requests and responses to exclude fields you do not want to include in the data.
    • Add exclude_parent_fields parameter to model Meta that allows you to exclude fields from parent models during inheritance. Note that best practice is to combine models and mixins but if you have many similar models and just one that differs it might be useful tool to achieve that.

    πŸ› Fixes

    • Fix is null filter with pagination and relations (by @erichaydel) #214
    • Fix not saving child object on reverse side of the relation if not saved before #216

    πŸ’¬ Other

    • Expand fastapi part of the documentation to show samples of using ormar in requests and responses in fastapi.
    • Improve the docs in regard of default, ForeignKey.add etc.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.9(May 30, 2021)

    0.10.9

    Important security fix

    • Update pin for pydantic to fix security vulnerability CVE-2021-29510

    You are advised to update to version of pydantic that was patched. In 0.10.9 ormar excludes versions with vulnerability in pinned dependencies.

    πŸ› Fixes

    • Fix OpenAPi schema for LargeBinary #204
    Source code(tar.gz)
    Source code(zip)
  • 0.10.8(May 18, 2021)

    0.10.8

    πŸ› Fixes

    • Fix populating default values in pk_only child models #202
    • Fix mypy for LargeBinary fields with base64 str representation #199
    • Fix OpenAPI schema format for LargeBinary fields with base64 str representation #199
    • Fix OpenAPI choices encoding for LargeBinary fields with base64 str representation
    Source code(tar.gz)
    Source code(zip)
  • 0.10.7(May 17, 2021)

    0.10.7

    ✨ Features

    • Add exclude_primary_keys: bool = False flag to dict() method that allows to exclude all primary key columns in the resulting dictionaru. #164
    • Add exclude_through_models: bool = False flag to dict() that allows excluding all through models from ManyToMany relations #164
    • Add represent_as_base64_str: bool = False parameter that allows conversion of bytes LargeBinary field to base64 encoded string. String is returned in dict(), on access to attribute and string is converted to bytes on setting. Data in database is stored as bytes. #187
    • Add pk alias to allow field access by Model.pk in filters and order by clauses (python style)

    πŸ› Fixes

    • Remove default None option for max_length for LargeBinary field #186
    • Remove default None option for max_length for String field

    πŸ’¬ Other

    • Provide a guide and samples of dict() parameters in the docs
    • Major refactor of getting/setting attributes from magic methods into descriptors -> noticeable performance improvement
    Source code(tar.gz)
    Source code(zip)
  • 0.10.6(May 2, 2021)

    0.10.6

    ✨ Features

    • Add LargeBinary(max_length) field type #166

    • Add support for normal pydantic fields (including Models) instead of pydantic_only attribute which is now deprecated #160. Pydantic fields should be declared normally as in pydantic model next to ormar fields, note that (obviously) ormar does not save and load the value for this field in database that mean that ONE of the following has to be true:

      • pydantic field declared on ormar model has to be Optional (defaults to None)
      • pydantic field has to have a default value set
      • pydantic field has default_factory function set
      • ormar.Model with pydantic field has to overwrite __init__() and provide the value there

      If none of the above ormar (or rather pydantic) will fail during loading data from the database, with missing required value for declared pydantic field.

    • Ormar provides now a meaningful examples in openapi schema, including nested models. The same algorithm is used to iterate related models without looks as with dict() and select/load_all. Examples appear also in fastapi. #157

    πŸ› Fixes

    • By default pydantic is not validating fields during assignment, which is not a desirable setting for an ORM, now all ormar.Models have validation turned-on during assignment (like model.column = 'value')

    πŸ’¬ Other

    • Add connecting to the database in QuickStart in readme #180
    • OpenAPI schema does no longer include ormar.Model docstring as description, instead just model name is provided if you do not provide your own docstring.
    • Some performance improvements.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.5(Apr 23, 2021)

    0.10.5

    πŸ› Fixes

    • Fix bug in fastapi-pagination #73
    • Remove unnecessary Optional in List[Optional[T]] in return value for QuerySet.all() and Querysetproxy.all() return values #174
    • Run tests coverage publish only on internal prs instead of all in github action.
    Source code(tar.gz)
    Source code(zip)
  • 0.10.4(Apr 21, 2021)

    0.10.4

    ✨ Features

    • Add Python style to filter and order_by with field access instead of dunder separated strings. #51
      • Accessing a field with attribute access (chain of dot notation) can be used to construct FilterGroups (ormar.and_ and ormar.or_)
      • Field access overloads set of python operators and provide a set of functions to allow same functionality as with dunder separated param names in **kwargs, that means that querying from sample model Track related to model Album now you have more options:
        • exact - exact match to value, sql column = <VALUE>
          • OLD: album__name__exact='Malibu'
          • NEW: can be also written as Track.album.name == 'Malibu
        • iexact - exact match sql column = <VALUE> (case insensitive)
          • OLD: album__name__iexact='malibu'
          • NEW: can be also written as Track.album.name.iexact('malibu')
        • contains - sql column LIKE '%<VALUE>%'
          • OLD: album__name__contains='Mal'
          • NEW: can be also written as Track.album.name % 'Mal')
          • NEW: can be also written as Track.album.name.contains('Mal')
        • icontains - sql column LIKE '%<VALUE>%' (case insensitive)
          • OLD: album__name__icontains='mal'
          • NEW: can be also written as Track.album.name.icontains('mal')
        • in - sql column IN (<VALUE1>, <VALUE2>, ...)
          • OLD: album__name__in=['Malibu', 'Barclay']
          • NEW: can be also written as Track.album.name << ['Malibu', 'Barclay']
          • NEW: can be also written as Track.album.name.in_(['Malibu', 'Barclay'])
        • isnull - sql column IS NULL (and sql column IS NOT NULL)
          • OLD: album__name__isnull=True (isnotnull album__name__isnull=False)
          • NEW: can be also written as Track.album.name >> None
          • NEW: can be also written as Track.album.name.is_null(True)
          • NEW: not null can be also written as Track.album.name.is_null(False)
          • NEW: not null can be also written as ~(Track.album.name >> None)
          • NEW: not null can be also written as ~(Track.album.name.is_null(True))
        • gt - sql column > <VALUE> (greater than)
          • OLD: position__gt=3
          • NEW: can be also written as Track.album.name > 3
        • gte - sql column >= <VALUE> (greater or equal than)
          • OLD: position__gte=3
          • NEW: can be also written as Track.album.name >= 3
        • lt - sql column < <VALUE> (lower than)
          • OLD: position__lt=3
          • NEW: can be also written as Track.album.name < 3
        • lte - sql column <= <VALUE> (lower equal than)
          • OLD: position__lte=3
          • NEW: can be also written as Track.album.name <= 3
        • startswith - sql column LIKE '<VALUE>%' (exact start match)
          • OLD: album__name__startswith='Mal'
          • NEW: can be also written as Track.album.name.startswith('Mal')
        • istartswith - sql column LIKE '<VALUE>%' (case insensitive)
          • OLD: album__name__istartswith='mal'
          • NEW: can be also written as Track.album.name.istartswith('mal')
        • endswith - sql column LIKE '%<VALUE>' (exact end match)
          • OLD: album__name__endswith='ibu'
          • NEW: can be also written as Track.album.name.endswith('ibu')
        • iendswith - sql column LIKE '%<VALUE>' (case insensitive)
          • OLD: album__name__iendswith='IBU'
          • NEW: can be also written as Track.album.name.iendswith('IBU')
    • You can provide FilterGroups not only in filter() and exclude() but also in:
      • get()
      • get_or_none()
      • get_or_create()
      • first()
      • all()
      • delete()
    • With FilterGroups (ormar.and_ and ormar.or_) you can now use:
      • & - as and_ instead of next level of nesting
      • | - as `or_' instead of next level of nesting
      • ~ - as negation of the filter group
    • To combine groups of filters into one set of conditions use & (sql AND) and | (sql OR)
      # Following queries are equivalent:
      # sql: ( product.name = 'Test'  AND  product.rating >= 3.0 ) 
      
      # ormar OPTION 1 - OLD one
      Product.objects.filter(name='Test', rating__gte=3.0).get()
      
      # ormar OPTION 2 - OLD one
      Product.objects.filter(ormar.and_(name='Test', rating__gte=3.0)).get()
      
      # ormar OPTION 3 - NEW one (field access)
      Product.objects.filter((Product.name == 'Test') & (Product.rating >=3.0)).get()
      
    • Same applies to nested complicated filters
      # Following queries are equivalent:
      # sql: ( product.name = 'Test' AND product.rating >= 3.0 ) 
      #       OR (categories.name IN ('Toys', 'Books'))
      
      # ormar OPTION 1 - OLD one
      Product.objects.filter(ormar.or_(
                                ormar.and_(name='Test', rating__gte=3.0), 
                                categories__name__in=['Toys', 'Books'])
                            ).get()
      
      # ormar OPTION 2 - NEW one (instead of nested or use `|`)
      Product.objects.filter(
                            ormar.and_(name='Test', rating__gte=3.0) | 
                            ormar.and_(categories__name__in=['Toys', 'Books'])
                            ).get()
      
      # ormar OPTION 3 - NEW one (field access)
      Product.objects.filter(
                            ((Product.name='Test') & (Product.rating >= 3.0)) | 
                            (Product.categories.name << ['Toys', 'Books'])
                            ).get()
      
    • Now you can also use field access to provide OrderActions to order_by()
      • Order ascending:
        • OLD: Product.objects.order_by("name").all()
        • NEW: Product.objects.order_by(Product.name.asc()).all()
      • Order descending:
        • OLD: Product.objects.order_by("-name").all()
        • NEW: Product.objects.order_by(Product.name.desc()).all()
      • You can of course also combine different models and many order_bys: Product.objects.order_by([Product.category.name.asc(), Product.name.desc()]).all()

    πŸ› Fixes

    • Not really a bug but rather inconsistency. Providing a filter with nested model i.e. album__category__name = 'AA' is checking if album and category models are included in select_related() and if not it's auto-adding them there. The same functionality was not working for FilterGroups (and_ and or_), now it works (also for python style filters which return FilterGroups).
    Source code(tar.gz)
    Source code(zip)
  • 0.10.3(Apr 16, 2021)

    0.10.3

    ✨ Features

    • ForeignKey and ManyToMany now support skip_reverse: bool = False flag #118. If you set skip_reverse flag internally the field is still registered on the other side of the relationship so you can:

      • filter by related models fields from reverse model
      • order_by by related models fields from reverse model

      But you cannot:

      • access the related field from reverse model with related_name
      • even if you select_related from reverse side of the model the returned models won't be populated in reversed instance (the join is not prevented so you still can filter and order_by)
      • the relation won't be populated in dict() and json()
      • you cannot pass the nested related objects when populating from dict() or json() (also through fastapi). It will be either ignored or raise error depending on extra setting in pydantic Config.
    • Model.save_related() now can save whole data tree in once #148 meaning:

      • it knows if it should save main Model or related Model first to preserve the relation

      • it saves main Model if

        • it's not saved,
        • has no pk value
        • or save_all=True flag is set

        in those cases you don't have to split save into two calls (save() and save_related())

      • it supports also ManyToMany relations

      • it supports also optional Through model values for m2m relations

    • Add possibility to customize Through model relation field names.

    • By default Through model relation names default to related model name in lowercase. So in example like this:

      ... # course declaration ommited
      class Student(ormar.Model):
          class Meta:
              database = database
              metadata = metadata
      
          id: int = ormar.Integer(primary_key=True)
          name: str = ormar.String(max_length=100)
          courses = ormar.ManyToMany(Course)
      
      # will produce default Through model like follows (example simplified)
      class StudentCourse(ormar.Model):
          class Meta:
              database = database
              metadata = metadata
              tablename = "students_courses"
      
          id: int = ormar.Integer(primary_key=True)
          student = ormar.ForeignKey(Student) # default name
          course = ormar.ForeignKey(Course)  # default name
      
    • To customize the names of fields/relation in Through model now you can use new parameters to ManyToMany:

      • through_relation_name - name of the field leading to the model in which ManyToMany is declared
      • through_reverse_relation_name - name of the field leading to the model to which ManyToMany leads to

      Example:

      ... # course declaration ommited
      class Student(ormar.Model):
          class Meta:
              database = database
              metadata = metadata
      
          id: int = ormar.Integer(primary_key=True)
          name: str = ormar.String(max_length=100)
          courses = ormar.ManyToMany(Course,
                                     through_relation_name="student_id",
                                     through_reverse_relation_name="course_id")
      
      # will produce default Through model like follows (example simplified)
      class StudentCourse(ormar.Model):
          class Meta:
              database = database
              metadata = metadata
              tablename = "students_courses"
      
          id: int = ormar.Integer(primary_key=True)
          student_id = ormar.ForeignKey(Student) # set by through_relation_name
          course_id = ormar.ForeignKey(Course)  # set by through_reverse_relation_name
      
    Source code(tar.gz)
    Source code(zip)
  • 0.10.2(Apr 6, 2021)

    0.10.2

    ✨ Features

    • Model.save_related(follow=False) now accept also two additional arguments: Model.save_related(follow=False, save_all=False, exclude=None).
      • save_all:bool -> By default (so with save_all=False) ormar only upserts models that are not saved (so new or updated ones), with save_all=True all related models are saved, regardless of saved status, which might be useful if updated models comes from api call, so are not changed in the backend.
      • exclude: Union[Set, Dict, None] -> set/dict of relations to exclude from save, those relation won't be saved even with follow=True and save_all=True. To exclude nested relations pass a nested dictionary like: exclude={"child":{"sub_child": {"exclude_sub_child_realtion"}}}. The allowed values follow the fields/exclude_fields (from QuerySet) methods schema so when in doubt you can refer to docs in queries -> selecting subset of fields -> fields.
    • Model.update() method now accepts _columns: List[str] = None parameter, that accepts list of column names to update. If passed only those columns will be updated in database. Note that update() does not refresh the instance of the Model, so if you change more columns than you pass in _columns list your Model instance will have different values than the database!
    • Model.dict() method previously included only directly related models or nested models if they were not nullable and not virtual, now all related models not previously visited without loops are included in dict(). This should be not breaking as just more data will be dumped to dict, but it should not be missing.
    • QuerySet.delete(each=False, **kwargs) previously required that you either pass a filter (by **kwargs or as a separate filter() call) or set each=True now also accepts exclude() calls that generates NOT filter. So either each=True needs to be set to delete whole table or at least one of filter/exclude clauses.
    • Same thing applies to QuerySet.update(each=False, **kwargs) which also previously required that you either pass a filter (by **kwargs or as a separate filter() call) or set each=True now also accepts exclude() calls that generates NOT filter. So either each=True needs to be set to update whole table or at least one of filter/exclude clauses.
    • Same thing applies to QuerysetProxy.update(each=False, **kwargs) which also previously required that you either pass a filter (by **kwargs or as a separate filter() call) or set each=True now also accepts exclude() calls that generates NOT filter. So either each=True needs to be set to update whole table or at least one of filter/exclude clauses.

    πŸ› Fixes

    • Fix improper relation field resolution in QuerysetProxy if fk column has different database alias.
    • Fix hitting recursion error with very complicated models structure with loops when calling dict().
    • Fix bug when two non-relation fields were merged (appended) in query result when they were not relation fields (i.e. JSON)
    • Fix bug when during translation to dict from list the same relation name is used in chain but leads to different models
    • Fix bug when bulk_create would try to save also property_field decorated methods and pydantic fields
    • Fix wrong merging of deeply nested chain of reversed relations

    πŸ’¬ Other

    • Performance optimizations
    • Split tests into packages based on tested area
    Source code(tar.gz)
    Source code(zip)
  • 0.10.1(Mar 23, 2021)

    0.10.1

    Features

    • add get_or_none(**kwargs) method to QuerySet and QuerysetProxy. It is exact equivalent of get(**kwargs) but instead of raising ormar.NoMatch exception if there is no db record matching the criteria, get_or_none simply returns None.

    Fixes

    • Fix dialect dependent quoting of column and table names in order_by clauses not working properly in postgres.
    Source code(tar.gz)
    Source code(zip)
A PostgreSQL or SQLite orm for Python

Prom An opinionated lightweight orm for PostgreSQL or SQLite. Prom has been used in both single threaded and multi-threaded environments, including en

Jay Marcyes 18 Dec 1, 2022
Tortoise ORM is an easy-to-use asyncio ORM inspired by Django.

Tortoise ORM was build with relations in mind and admiration for the excellent and popular Django ORM. It's engraved in it's design that you are working not with just tables, you work with relational data.

Tortoise 3.3k Jan 7, 2023
An async ORM. πŸ—ƒ

ORM The orm package is an async ORM for Python, with support for Postgres, MySQL, and SQLite. ORM is built with: SQLAlchemy core for query building. d

Encode 1.7k Dec 28, 2022
Bringing Async Capabilities to django ORM

Bringing Async Capabilities to django ORM

Skander BM 119 Dec 1, 2022
Pydantic model support for Django ORM

Pydantic model support for Django ORM

Jordan Eremieff 318 Jan 3, 2023
Solrorm : A sort-of solr ORM for python

solrorm : A sort-of solr ORM for python solrpy - deprecated solrorm - currently in dev Usage Cores The first step to interact with solr using solrorm

Aj 1 Nov 21, 2021
ORM for Python for PostgreSQL.

New generation (or genius) ORM for Python for PostgreSQL. Fully-typed for any query with Pydantic and auto-model generation, compatible with any sync or async driver

Yan Kurbatov 3 Apr 13, 2022
A new ORM for Python specially for PostgreSQL

A new ORM for Python specially for PostgreSQL. Fully-typed for any query with Pydantic and auto-model generation, compatible with any sync or async driver

Yan Kurbatov 3 Apr 13, 2022
Piccolo - A fast, user friendly ORM and query builder which supports asyncio.

A fast, user friendly ORM and query builder which supports asyncio.

null 919 Jan 4, 2023
The Orator ORM provides a simple yet beautiful ActiveRecord implementation.

Orator The Orator ORM provides a simple yet beautiful ActiveRecord implementation. It is inspired by the database part of the Laravel framework, but l

SΓ©bastien Eustace 1.4k Jan 1, 2023
A simple project to explore the number of GCs when doing basic ORM work.

Question: Does Python do extremely too many GCs for ORMs? YES, OMG YES. Check this out Python Default GC Settings: SQLAlchemy - 20,000 records in one

Michael Kennedy 26 Jun 5, 2022
A dataclasses-based ORM framework

dcorm A dataclasses-based ORM framework. [WIP] - Work in progress This framework is currently under development. A first release will be announced in

HOMEINFO - Digitale Informationssysteme GmbH 1 Dec 24, 2021
Easy-to-use data handling for SQL data stores with support for implicit table creation, bulk loading, and transactions.

dataset: databases for lazy people In short, dataset makes reading and writing data in databases as simple as reading and writing JSON files. Read the

Friedrich Lindenberg 4.2k Dec 26, 2022
MongoEngine flask extension with WTF model forms support

Flask-MongoEngine Info: MongoEngine for Flask web applications. Repository: https://github.com/MongoEngine/flask-mongoengine About Flask-MongoEngine i

MongoEngine 815 Jan 3, 2023
Adds SQLAlchemy support to Flask

Flask-SQLAlchemy Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It aims to simplify using SQLAlchemy

The Pallets Projects 3.9k Jan 9, 2023
SQLAlchemy support for aiohttp.

aiohttp-sqlalchemy SQLAlchemy 1.4 / 2.0 support for AIOHTTP. The library provides the next features: initializing asynchronous sessions through a midd

Ruslan Ilyasovich Gilfanov 5 Dec 11, 2022
SQLModel is a library for interacting with SQL databases from Python code, with Python objects.

SQLModel is a library for interacting with SQL databases from Python code, with Python objects. It is designed to be intuitive, easy to use, highly compatible, and robust.

SebastiΓ‘n RamΓ­rez 9.1k Dec 31, 2022
A Python Library for Simple Models and Containers Persisted in Redis

Redisco Python Containers and Simple Models for Redis Description Redisco allows you to store objects in Redis. It is inspired by the Ruby library Ohm

sebastien requiem 436 Nov 10, 2022
Python 3.6+ Asyncio PostgreSQL query builder and model

windyquery - A non-blocking Python PostgreSQL query builder Windyquery is a non-blocking PostgreSQL query builder with Asyncio. Installation $ pip ins

null 67 Sep 1, 2022