Giter Site home page Giter Site logo

collerek / ormar Goto Github PK

View Code? Open in Web Editor NEW
1.6K 15.0 77.0 11.63 MB

python async orm with fastapi in mind and pydantic validation

Home Page: https://collerek.github.io/ormar/

License: MIT License

Shell 0.03% Python 99.88% Makefile 0.08%
orm sqlalchemy async-orm python-orm fastapi pydantic alembic databases

ormar's Introduction

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 benefits 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 models (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 from the obvious "ORM" in name - gets its name from ormar in Swedish which means snakes, and ormar in Croatian which means cabinet.

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

If you like ormar remember to star the repository in github!

The bigger community we build, the easier it will be to catch bugs and attract contributors ;)

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 the tests folder, since they actually have to create and connect to a 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 a different library and would like it to support ormar let us know how we can help.

Dependencies

Ormar is built with:

License

ormar is built as open-sorce software and will remain completely free (MIT license).

As I write open-source code to solve everyday problems in my work or to promote and build strong python community you can say thank you and buy me a coffee or sponsor me with a monthly amount to help ensure my work remains free and maintained.

Sponsor - Github Sponsors

Migrating from sqlalchemy and existing databases

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

sqlalchemy-to-ormar can be used in pair with sqlacodegen to auto-map/ generate ormar models from existing database, even if you don't use sqlalchemy for your project.

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 "normal" python interpreters do not allow execution of await outside of a function (because you actually schedule this function for delayed execution and don't get the result immediately).

In a modern web framework (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 the 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"
base_ormar_config = ormar.OrmarConfig(
    database=databases.Database(DATABASE_URL),
    metadata=sqlalchemy.MetaData(),
    engine=sqlalchemy.create_engine(DATABASE_URL),
)


# Note that all type hints are optional
# below is a perfectly valid model declaration
# class Author(ormar.Model):
#     ormar_config = base_ormar_config.copy(tablename="authors")
#
#     id = ormar.Integer(primary_key=True) # <= notice no field types
#     name = ormar.String(max_length=100)


class Author(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="authors")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Book(ormar.Model):
    ormar_config = base_ormar_config.copy(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
# just to be sure we clear the db before
base_ormar_config.metadata.drop_all(base_ormar_config.engine)
base_ormar_config.metadata.create_all(base_ormar_config.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

    # Django style
    book = await Book.objects.select_related("author").get(title="The Hobbit")
    # Python style
    book = await Book.objects.select_related(Book.author).get(
        Book.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

    # Django style
    author = await Author.objects.select_related("books").all(name="J.R.R. Tolkien")
    # Python style
    author = await Author.objects.select_related(Author.books).all(
        Author.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

    # Django style
    author = await Author.objects.prefetch_related("books").get(name="J.R.R. Tolkien")
    # Python style
    author = await Author.objects.prefetch_related(Author.books).get(
        Author.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 fields() 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()

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

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

    # average
    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 raw_data():
    # extract raw data in a form of dicts or tuples
    # note that this skips the validation(!) as models are
    # not created from parsed data

    # get list of objects as dicts
    assert await Book.objects.values() == [
        {"id": 1, "author": 1, "title": "The Hobbit", "year": 1937},
        {"id": 2, "author": 1, "title": "The Lord of the Rings", "year": 1955},
        {"id": 4, "author": 2, "title": "The Witcher", "year": 1990},
        {"id": 5, "author": 1, "title": "The Silmarillion", "year": 1977},
    ]

    # get list of objects as tuples
    assert await Book.objects.values_list() == [
        (1, 1, "The Hobbit", 1937),
        (2, 1, "The Lord of the Rings", 1955),
        (4, 2, "The Witcher", 1990),
        (5, 1, "The Silmarillion", 1977),
    ]

    # filter data - note how you always get a list
    assert await Book.objects.filter(title="The Hobbit").values() == [
        {"id": 1, "author": 1, "title": "The Hobbit", "year": 1937}
    ]

    # select only wanted fields
    assert await Book.objects.filter(title="The Hobbit").values(["id", "title"]) == [
        {"id": 1, "title": "The Hobbit"}
    ]

    # if you select only one column you could flatten it with values_list
    assert await Book.objects.values_list("title", flatten=True) == [
        "The Hobbit",
        "The Lord of the Rings",
        "The Witcher",
        "The Silmarillion",
    ]

    # to read more about extracting raw values
    # 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 base_ormar_config.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,
    raw_data,
]:
    print(f"Executing: {func.__name__}")
    asyncio.run(with_connect(func))

# drop the database tables
base_ormar_config.metadata.drop_all(base_ormar_config.engine)

Ormar Specification

QuerySet methods

  • create(**kwargs): -> Model
  • get(*args, **kwargs): -> Model
  • get_or_none(*args, **kwargs): -> Optional[Model]
  • get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]
  • 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]]
  • iterate(*args, **kwargs) -> AsyncGenerator[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(distinct: bool = True) -> 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
  • values(fields: Union[List, str, Set, Dict])
  • values_list(fields: Union[List, str, Set, Dict])

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()
  • SmallInteger()
  • Decimal(scale, precision)
  • UUID()
  • LargeBinary(max_length)
  • Enum(enum_class)
  • Enum like Field - by passing choices to any other Field type
  • EncryptedString - by passing encrypt_secret and encrypt_backend
  • ForeignKey(to)
  • ManyToMany(to)

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

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

  • nullable - Creates a nullable column. Sets the default to False. Read the fields common parameters for details.
  • sql_nullable - Used to set different setting for pydantic and the database. Sets the default to nullable value. Read the fields common parameters for details.
  • 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.

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
  • pre_relation_add
  • post_relation_add
  • pre_relation_remove
  • post_relation_remove
  • post_bulk_update

ormar's People

Contributors

abdeldjalil-h avatar boemjay avatar christopherjhart avatar collerek avatar dependabot[bot] avatar edwardbetts avatar erichaydel avatar es3n1n avatar espenalbert avatar harrysky avatar johnthagen avatar kurbezz avatar luisjimenez6245 avatar mojixcoder avatar naturalethic avatar nikonez1 avatar pawamoy avatar peerchemist avatar pmdevita avatar ponytailer avatar programmerplus1998 avatar roansong avatar s3rius avatar sepehrbazyar avatar smorokin avatar solastri avatar sondrelg avatar touwastar avatar voice1 avatar zevisert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ormar's Issues

UUIDs cannot be queried using get as primary key

Recently, I started using ormar instead of tortoise-orm in my projects, but have encountered a strange problem, if I use uuid as the primary key of a model, I can't match the data when using get query.

example:

class MainMeta(ormar.ModelMeta):
    metadata = metadata
    database = database


class UserModel(ormar.Model):

    class Meta(MainMeta):
        tablename = "usermodel"

    id: ormar.UUID(primary_key=True)
    username = ormar.String(index=True, unique=True, null=False, max_length=255)
    email = ormar.String(index=True, unique=True, nullable=False, max_length=255)
    hashed_password = ormar.String(null=False, max_length=255)
    is_active = ormar.Boolean(default=True, nullable=False)
    is_superuser = ormar.Boolean(default=False, nullable=False)

user = await UserModel.objects.first()
await UserModel.objects.get(id=user.id) # raise NoMatch
await UserModel.objects.get(username=user.username) # Match user

Using a String as primary key will override the value with item_id by insert statement

In the code sample below the test fails since the instance receives the item_id

import databases
import ormar
import sqlalchemy
from typing import Optional, List, Union
from ormar import String, Float, Boolean, ManyToMany, Integer, ForeignKey
from models_metadata.sql import MainMeta, DbModel



DATABASE_URL = "sqlite:///test.db"
database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()


class MainMeta(ormar.ModelMeta):
    metadata = metadata
    database = database


class DbModel(ormar.Model):
    pass


class PositionOrm(DbModel):
    class Meta(MainMeta):
        pass

    name: str = String(primary_key=True, max_length=50)
    x: float = Float()
    y: float = Float()
    degrees: float = Float()


@pytest.fixture(autouse=True, scope="module")
def create_test_database():
    engine = create_engine(DATABASE_URL)
    metadata.create_all(engine)
    yield
    metadata.drop_all(engine)


@pytest.fixture(scope="function")
async def cleanup():
    yield
    async with database:
        await PositionOrm.objects.delete(each=True)


@pytest.mark.asyncio
async def test_creating_a_position(cleanup):
    async with database:
        instance = PositionOrm(
            name="my_pos",
            x=1.0,
            y=2.0,
            degrees=3.0,
        )
        await instance.save()
        assert instance.saved
        assert instance.name == "my_pos"

Error message:

1 != my_pos

Expected :my_pos
Actual   :1

BTW: Awesome library. I am looking forward to start using it :)

Cascade Delete Issue

I can't seem to get cascade deletes working with FKs on a postgres DB. Should it work by default?

I tried adding the ondelete="CASCADE" kwarg to my FK relations, but nothing seemed to happen on the other side of the model.

In short, I have:

class Project(BaseModel):
    class Meta(ormar.ModelMeta):
        tablename = "projects"

    name: str = ormar.String(max_length=256)
    # <SNIP>

class Quote(BaseModel):
    class Meta(ormar.ModelMeta):
        tablename = "quotes"

    # <SNIP>
    project: Project = ormar.ForeignKey(
        Project, related_name="quotes", ondelete="CASCADE"
    )

And the routing endpoint I have in FastAPI:

@router.delete("/{project_id}")
async def delete_project(request: Request, project_id: uuid.UUID):
    project = await Project.objects.filter(client=request.state.client).get(
        id=project_id
    )
    await project.delete()
    return {"deleted": True, "id": project.id}

But this seems to orphan the affected quote that was attached to the project. This looks to be a known caveat to SQLAlchemy when using filter().delete(). Is there an easy way to build in the same fixes?
https://stackoverflow.com/questions/5033547/sqlalchemy-cascade-delete
https://stackoverflow.com/questions/19243964/sqlalchemy-delete-doesnt-cascade

FastAPI Users/ormar integration

Reference: #84 (comment)

If you feel like it you can try on your own with FastapiUsers, I can help if you get stuck on the way :)

Yep, I'd be interested in doing that and thought to open this ticket to avoid that you or someone else also work on this at the same time.

I had a look at FastAPI Users code and I have to say that ormar/fastapi users integration shouldn't be too hard.
Basically they have the concept of "adapter", and the code for already implemented adapters is available here. Being able to provide the ormar adapter should do the trick, and for that I can take the Tortoise adapter as a starting point.

I hope to be able to finish the integration without issues and I'll keep this ticket updated when something will happen, in case of problems I accept your offer to help. I'll anyway refer to you @collerek for proofreading.

Thanks!

Introduce exclude_fields

Idea is that it should work the same as fields() but instead exclude selected fields from the query

some question about ormar

Hello, I'm looking for an asynchronous orm that supports sqlite on edge devices, is ormar available in production service yet?
Also, one question about api, does the sqlalchemy meta information have to be passed manually, as it seems redundant to pass it on for each model.

class Item(ormar.Model):
    class Meta:
        tablename = "items"

    id: ormar.Integer(primary_key=True)
    name: ormar.String(max_length=100)
    category: ormar.ForeignKey(Category, nullable=True)

Probably more friendly.

Record is imported even if not using PostgreSQL

Currently in model.py Record is imported without checks in place whether user even have asyncpg installed:

from databases.backends.postgres import Record

It causes my app that use SQLite to crash with ModuleNotFoundError: No module named 'asyncpg'

One possible solution is to wrap it in try-except ModuleNotFoundError block, another - refactor check in Model.extract_prefixed_table_columns. Your thoughts?

It seems that tests are running with all backends and those kind of errors are not catched

Question: How to completely replace Pydantic models with ormar models?

I know one could use the ormar models as is, for both Pydantic and database.

However, how would one go about if it's necessary to only include some of the model information when e.g. creating a record or updating one?

E.g. as in the FastAPI examples, there are schemas (Pydantic models) for ModelBase, ModelCreate and Model. The models inherit the ModelBase, and are then used as input models when creating (ModelCreate), as well as response models (Model). These models can have different parameters when input/output.

Is it possible to achieve with just the ormar models in some way? I'd like to get rid of the duplication that I have to do atm, since I don't know how to modify the response parameters/request parameters for the one model.

Optional[Department] vs Optional[Union[Department, Dict]]

Question: the docs sometimes have e.g.

department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)

and sometimes

department: Optional[Department] = ormar.ForeignKey(Department)

Is there a reason for the difference?

Also, is this the best place to ask questions, or is there a forum or chat or something? Or should I use StackOverflow?

"Value not declarable with JSON Schema" on FastAPI /docs

Hi, I'm following the tutorial to use ormar with FastAPI but have an issue on the /docs endpoint. My main.py contains the code found on the docs and the corresponding database was created as well (btw, thanks @collerek for the clarification).

Nevertheless I still get an error on the /docs endpoint (every other works fine, after this change).

INFO:     127.0.0.1:57856 - "GET /openapi.json HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
.....
ValueError: Value not declarable with JSON Schema, field: name='category' type=Optional[ForeignKey] required=False default=None

Full traceback:

INFO:     127.0.0.1:57856 - "GET /docs HTTP/1.1" 200 OK
INFO:     127.0.0.1:57856 - "GET /serviceworker.js HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:57856 - "GET /openapi.json HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 389, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/fastapi/applications.py", line 179, in __call__
    await super().__call__(scope, receive, send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/fastapi/applications.py", line 128, in openapi
    return JSONResponse(self.openapi())
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/fastapi/applications.py", line 113, in openapi
    servers=self.servers,
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/fastapi/openapi/utils.py", line 344, in get_openapi
    flat_models=flat_models, model_name_map=model_name_map  # type: ignore
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/fastapi/utils.py", line 25, in get_model_definitions
    model, model_name_map=model_name_map, ref_prefix=REF_PREFIX  # type: ignore
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/pydantic/schema.py", line 468, in model_process_schema
    model, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/pydantic/schema.py", line 504, in model_type_schema
    f, by_alias=by_alias, model_name_map=model_name_map, ref_prefix=ref_prefix, known_models=known_models
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/pydantic/schema.py", line 198, in field_schema
    known_models=known_models or set(),
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/pydantic/schema.py", line 425, in field_type_schema
    known_models=known_models,
  File "/home/paolo/devel/fastapi/.venv/lib/python3.7/site-packages/pydantic/schema.py", line 740, in field_singleton_schema
    raise ValueError(f'Value not declarable with JSON Schema, field: {field}')
ValueError: Value not declarable with JSON Schema, field: name='category' type=Optional[ForeignKey] required=False default=None
$ pip freeze

aiosqlite==0.15.0
asyncpg==0.21.0
click==7.1.2
databases==0.4.0
fastapi==0.61.1
flake8==3.8.4
h11==0.11.0
importlib-metadata==2.0.0
mccabe==0.6.1
ormar==0.3.8
pkg-resources==0.0.0
psycopg2==2.8.6
pycodestyle==2.6.0
pydantic==1.6.1
pyflakes==2.2.0
SQLAlchemy==1.3.20
starlette==0.13.6
typing-extensions==3.7.4.3
uvicorn==0.12.2
zipp==3.4.0

Any help greatly appreciated.

Enum Support

Does Ormar natively support sqlalchemy enums and the Pydantic counterparts?

Something like:

class ThingType(str, Enum):
    ONE = "ONE"
    TWO = "TWO"
    THREE = "THREE"

class Thing(BaseModel):
    class Meta(ormar.ModelMeta):
        tablename = "things"

    thing_type: ThingType = ormar.StringEnum(
        ThingType,
        default=ThingType.ONE
    )

They're nicely validated by FastAPI + Pydantic, I'd imagine in the meantime you could create the inputs yourself, but Ormar seems tooled around reducing work, so I think it'd be a cool feature if it doesn't exist already.

EDIT: I noticed the choices array (can be inserted as choices=list(ThingType) above), which validates during database processing, but I'm hoping we can bubble it up to Pydantic.

Fix two-steps example

These lines

fantasies = Album.objects.create(name="Fantasies")
await fantasies.save()

should look like

fantasies = Album(name="Fantasies")
await fantasies.save()

Introduce save_related() method

Traverses all related models and saves them if they are not saved.
Define save status - when the models should be treated as clean from db and when dirty (modified) to not save already saved models.

How to use distributed models

Liking Ormar so far - thanks for finally trying to bridge the Pydantic gap!

I had a question about how to get a production-like FastAPI design working with Ormar. If I have a file structure such as the following:

.
├── Dockerfile
├── REQUIREMENTS.txt
├── alembic
│   ├── README
│   ├── env.py
│   ├── script.py.mako
│   └── versions
├── alembic.ini
└── app
    ├── api
    │   ├── category.py
    │   ├── health.py
    │   └── item.py
    ├── config.py
    ├── db
    │   └── models
    │       ├── category.py
    │       └── item.py
    └── main.py

Where main.py looks something like:

def get_db_uri(user, passwd, host, port, db):
    return f"postgres://{user}:{passwd}@{host}:{port}/{db}"


app = FastAPI(
    title=settings.APP_NAME,
    version=settings.APP_VERSION,
    description=settings.APP_DESCRIPTION,
)

db_url = get_db_uri(
    user=settings.POSTGRES_USER,
    passwd=settings.POSTGRES_PASSWORD,
    host=settings.POSTGRES_HOST,
    port=settings.POSTGRES_PORT,
    db=settings.POSTGRES_DB
)

metadata = sqlalchemy.MetaData()
database = databases.Database(db_url)
app.state.database = database


@app.on_event("startup")
async def startup() -> None:
    database_ = app.state.database
    if not database_.is_connected:
        await database_.connect()


@app.on_event("shutdown")
async def shutdown() -> None:
    database_ = app.state.database
    if database_.is_connected:
        await database_.disconnect()

<SNIP: See https://fastapi.tiangolo.com/tutorial/bigger-applications/ for an example>

In app/db/models/category.py I might have:

from typing import Optional
from enum import Enum
import ormar

class Category(ormar.Model):
    class Meta:
        tablename = "categories"
        metadata = metadata
        database = database

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
  1. How can I get the references to metadata and database? Do I need to init them in every model?
  2. Is the database reference how Alembic should be learning about the models? Think I'm a bit confused as to how best to lay out the application state and pass the right objects around.

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?

Alembic does not carry the uuid format parameter

Thanks for previous fixes for 57, After that I found out that alembic does not carry the uuid format parameter when creating the migration file for uuid.

generate:

op.create_table('usermodel',
    sa.Column('id', ormar.fields.sqlalchemy_uuid.UUID(), nullable=False),

should be:

op.create_table('usermodel',
    sa.Column('id', ormar.fields.sqlalchemy_uuid.UUID(uuid_format="string"), nullable=False),

Allow filtering by column names and python operators

Support not only current django like dunder separated names filtering:

albums = Album.objects.filter(tracks__order__gte=2).all()

But also something more like sqlalchemy:

albums = Album.objects.filter(Album.tracks.order >= 2).all()

Do not populate default values on excluded fields

When you exclude fields from result (so field is nullable) and it has default (value or function) it comes as None from db and is set to default value during model initialization after loading from db, while should be kept as None.

Confusing behavior with nullable=True on model update

I have a model with a foreign key relation, and I only want to update some properties on the model where the relation is defined. For some reason, if the relation model has a field defined with nullable=True the model.update(name="Updated") fails with validation errors for the related model.

The following test cases fails, see comments for where the problem lies:

from typing import Optional

import ormar
import pytest

from app.db.database import db, engine, metadata


class PrimaryModel(ormar.Model):
    class Meta:
        metadata = metadata
        database = db
        tablename = "primary_models"

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=255, index=True)
    some_text: str = ormar.Text()
    # NOTE: Removing nullable=True makes the test pass.
    some_other_text: Optional[str] = ormar.Text(nullable=True)


class SecondaryModel(ormar.Model):
    class Meta:
        metadata = metadata
        database = db
        tablename = "secondary_models"

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    primary_model: PrimaryModel = ormar.ForeignKey(
        PrimaryModel,
        related_name="secondary_models",
    )


@pytest.mark.asyncio
async def test_create_models():
    primary = await PrimaryModel(
        name="Foo", some_text="Bar", some_other_text="Baz"
    ).save()
    assert primary.id == 1

    secondary = await SecondaryModel(name="Foo", primary_model=primary).save()
    assert secondary.id == 1
    assert secondary.primary_model.id == 1


@pytest.mark.asyncio
async def test_update_secondary():
    secondary = await SecondaryModel.objects.get(id=1)
    assert secondary.name == "Foo"
    await secondary.update(name="Updated")
    assert secondary.name == "Updated"


@pytest.fixture(autouse=True, scope="module")
def create_test_database():
    metadata.create_all(engine)
    yield
    metadata.drop_all(engine)

Running this will generate the following errors:

E           pydantic.error_wrappers.ValidationError: 2 validation errors for PrimaryModel
E           name
E             field required (type=value_error.missing)
E           some_text
E             field required (type=value_error.missing)

FAILED ...::test_update_secondary - pydantic.error_wrappers.ValidationError: 2 validation errors for PrimaryModel

The field some_other_text should be nullable in my case. If I remove nullable=True and create the PrimaryModel with some_other_text=None or leait fails with validation error:

E           pydantic.error_wrappers.ValidationError: 1 validation error for PrimaryModel
E           some_other_text
E             field required (type=value_error.missing)

Any help appreciated. I'm quite confused about this functionality.

Add py.typed to mark package as type annotated

Hi, thank you for your work on this nice ORM 😄
Can you please add file py.typed to ormar folder?
I already see it is mentioned in setup.py package_data={PACKAGE: ["py.typed"]}, but no actual file is present, which causes mypy to give errors:

error: Skipping analyzing 'ormar': found module but no type hints or library stubs  [import]

having trouble handling foreign keys with uuid

I'm having trouble handling foreign keys with uuid, here is the definition of the model

class User(ormar.Model):
    class Meta(MainMeta):
        tablename = "user"

    id: ormar.UUID(primary_key=True, default=uuid.uuid4, uuid_format='string')
    username = ormar.String(index=True, unique=True, null=False, max_length=255)
    email = ormar.String(index=True, unique=True, nullable=False, max_length=255)
    hashed_password = ormar.String(null=False, max_length=255)
    is_active = ormar.Boolean(default=True, nullable=False)
    is_superuser = ormar.Boolean(default=False, nullable=False)


class Token(ormar.Model):
    class Meta(MainMeta):
        tablename = "token"

    id = ormar.Integer(primary_key=True)
    text = ormar.String(max_length=4, unique=True)
    user = ormar.ForeignKey(User, related_name='tokens')
    created_at = ormar.DateTime(server_default=sqlalchemy.func.now())

    def __str__(self):
        return self.text

when i perform

await Token.objects.order_by('-created_at').limit(page_size).offset(page_size * (page_num - 1)).all()

raise

RelationshipInstanceError: Relationship error - ForeignKey OrmarBaseUserModel is of type <class 'uuid.UUID'> while <class 'str'> passed as a parameter.

I looked at the source code ormar/fields/foreign_key.py#L123 and it seems to be because the actual storage of uuid is string, but the judgment still uses uuid.

Bug in handling select_related with limit and all() because of many2many relation

When a model has a many2many relationship and you want to fetch all records including the related models, with a limit, the .all() function returns the wrong amount of records.

I debugged the issue until this part, where the instances are merged in modelproxy.py: merge_instances_list(result_rows).

When entering the merge_instances_list function, the result_rows includes all the records, but it seems that the query that it runs includes multiple rows of the same instance, e.g. for the many2many relation.

Now this is unexpected behavior, since I'd expect to get all the rows from the database, not the grouped instances of the record set.

Here's a test setup for proving the issue:

from typing import List, Optional

import ormar
import pytest

from app.db.database import db, engine, metadata


class Keyword(ormar.Model):
    class Meta:
        metadata = metadata
        database = db
        tablename = "keywords"

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=50)


class KeywordPrimaryModel(ormar.Model):
    class Meta:
        metadata = metadata
        database = db
        tablename = "primary_models_keywords"

    id: int = ormar.Integer(primary_key=True)


class PrimaryModel(ormar.Model):
    class Meta:
        metadata = metadata
        database = db
        tablename = "primary_models"

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=255, index=True)
    some_text: str = ormar.Text()
    some_other_text: Optional[str] = ormar.Text(nullable=True)
    keywords: Optional[List[Keyword]] = ormar.ManyToMany(
        Keyword, through=KeywordPrimaryModel
    )


class SecondaryModel(ormar.Model):
    class Meta:
        metadata = metadata
        database = db
        tablename = "secondary_models"

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    primary_model: PrimaryModel = ormar.ForeignKey(
        PrimaryModel,
        related_name="secondary_models",
    )


@pytest.mark.asyncio
@pytest.mark.parametrize("tag_id", [1, 2, 3, 4, 5])
async def test_create_keywords(tag_id):
    await Keyword.objects.create(name=f"Tag {tag_id}")


@pytest.mark.asyncio
@pytest.mark.parametrize(
    "name, some_text, some_other_text",
    [
        ("Primary 1", "Some text 1", "Some other text 1"),
        ("Primary 2", "Some text 2", "Some other text 2"),
        ("Primary 3", "Some text 3", "Some other text 3"),
        ("Primary 4", "Some text 4", "Some other text 4"),
        ("Primary 5", "Some text 5", "Some other text 5"),
        ("Primary 6", "Some text 6", "Some other text 6"),
        ("Primary 7", "Some text 7", "Some other text 7"),
        ("Primary 8", "Some text 8", "Some other text 8"),
        ("Primary 9", "Some text 9", "Some other text 9"),
        ("Primary 10", "Some text 10", "Some other text 10"),
    ],
)
async def test_create_primary_models(name, some_text, some_other_text):
    await PrimaryModel(
        name=name, some_text=some_text, some_other_text=some_other_text
    ).save()


@pytest.mark.asyncio
async def test_add_keywords():
    p1 = await PrimaryModel.objects.get(pk=1)

    p2 = await PrimaryModel.objects.get(pk=2)

    for i in range(1, 6):
        keyword = await Keyword.objects.get(pk=i)
        if i % 2 == 0:
            await p1.keywords.add(keyword)
        else:
            await p2.keywords.add(keyword)


@pytest.mark.asyncio
async def test_create_secondary_model():
    secondary = await SecondaryModel(name="Foo", primary_model=1).save()
    assert secondary.id == 1
    assert secondary.primary_model.id == 1


@pytest.mark.asyncio
async def test_list_primary_models_with_keywords_and_limit():
    models = await PrimaryModel.objects.select_related("keywords").limit(5).all()

    # This test fails, because of the keywords relation.
    assert len(models) == 5


@pytest.mark.asyncio
async def test_list_primary_models_without_keywords_and_limit():
    models = await PrimaryModel.objects.all()
    assert len(models) == 10


@pytest.mark.asyncio
async def test_list_primary_models_without_keywords_but_with_limit():
    models = await PrimaryModel.objects.limit(5).all()
    assert len(models) == 5


@pytest.mark.asyncio
async def test_update_secondary():
    secondary = await SecondaryModel.objects.get(id=1)
    assert secondary.name == "Foo"
    await secondary.update(name="Updated")
    assert secondary.name == "Updated"


@pytest.fixture(autouse=True, scope="module")
def create_test_database():
    metadata.create_all(engine)
    yield
    metadata.drop_all(engine)

Here the test fails with len(models) being 2, not 5 as it should.

The grouping should probably happen in the query so that all records are returned.

server_default documentation examples and read-only field

Hey, found this library through your comment in orm repo, want to try it, as it seems it is not abandoned 😄

I want to set field that is updated by PostgreSQL triggers, not directly, so I thought that server_default would be the option I need to pass to just ignore the field when creating/updating object. But I found no examples in documentation that I can just grab and try.

So, my field is defined in database as:

dt_changed timestamp with time zone NOT NULL DEFAULT NOW()

How can I pass NOW() function to server_default?

Related feature proposal: Can we introduce something like "read-only" field (so that it does not try to create/update it)?

Can submit PR for documentation and/or "read-only" field if you like the idea

Support pre/post-save and pre/post-update

Any thoughts on supporting some kind of pre-save, and pre-update (and why not pre/post-delete) functionality? Either as functions to override, or events?

As a use case, I've got created_at and updated_at datetimes for my models. Setting the default value works, but e.g. when updating a model, I'd like to automatically set the updated_at to current datetime. Now I have to manually add the timestamp to the model for it to update.

Of course I can make a base class of my own, and override the save and update functions, but it seems a bit overkill. Also a lot of the libraries out there has some kind of functionality for these kind of situations.

Thoughts?

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.

Many to many Relation error.

Hi, im getting an error while trying to use Many to Many Relation.

Process SpawnProcess-1: Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/process.py", line 315, in _bootstrap self.run() File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/process.py", line 108, in run self._target(*self._args, **self._kwargs) File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/uvicorn/subprocess.py", line 62, in subprocess_started target(sockets=sockets) File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/uvicorn/main.py", line 390, in run loop.run_until_complete(self.serve(sockets=sockets)) File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/uvicorn/main.py", line 397, in serve config.load() File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/uvicorn/config.py", line 278, in load self.loaded_app = import_from_string(self.app) File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/uvicorn/importer.py", line 20, in import_from_string module = importlib.import_module(module_str) File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "<frozen importlib._bootstrap>", line 1030, in _gcd_import File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "<frozen importlib._bootstrap_external>", line 790, in exec_module File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed File "./main.py", line 5, in <module> from routes.utils import origins, paths, methods File "./routes/__init__.py", line 1, in <module> from .models import models_routes File "./routes/models/__init__.py", line 1, in <module> from .Users import router as user_router File "./routes/models/Users.py", line 3, in <module> from models import User File "./models/__init__.py", line 1, in <module> from .User import User File "./models/User.py", line 7, in <module> class User(orm.Model): File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/ormar/models/metaclass.py", line 327, in __new__ expand_reverse_relationships(new_model) File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/ormar/models/metaclass.py", line 72, in expand_reverse_relationships register_reverse_model_fields( File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/ormar/models/metaclass.py", line 88, in register_reverse_model_fields adjust_through_many_to_many_model(model, child, model_field) File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/ormar/models/metaclass.py", line 105, in adjust_through_many_to_many_model create_and_append_m2m_fk(model, model_field) File "/Users/luis/Documents/GitHub/agenda-api/venv/lib/python3.9/site-packages/ormar/models/metaclass.py", line 137, in create_and_append_m2m_fk model_field.through.Meta.table.append_column(column) AttributeError: 'str' object has no attribute 'append_column'

Here are my models:

`
class User(orm.Model):

id: int = orm.Integer(name="user_id", primary_key=True)
username: str = orm.String(max_length=50, unique=False)
password: str = orm.Text()
user_type: str = orm.String(max_length=100)
contact_info: Union[Contact, Dict] = orm.ForeignKey(Contact)
contacts: Optional[Union[Contact, List[Contact] ]]  = orm.ManyToMany(Contact, through = UserContacts)

class Meta(MainMeta):
    table_name = "users"

`

`
class Contact(orm.Model):

id: int = orm.Integer(name="contact_id", primary_key=True)
name: str = orm.String(max_length=200, server_default="")


class Meta(MainMeta):
    table_name = "contacts"

`

class UserContacts(orm.Model): id:int = orm.Integer(name = "rel_user_contact_id", primary_key = True) class Meta(MainMeta): table = "rel_user_contacts"

Multi ForeignKey not working

Thanks for the quick fix and the elaboration on the previous issue
I hope to contribute some code at some point.

I think I found one more bug:

import databases
import pytest
import sqlalchemy
from ormar import ModelMeta, Model, Integer, Boolean, Float, String, ForeignKey

from typing import Optional

from sqlalchemy import create_engine

DATABASE_URL = "sqlite:///test.db"
database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()


class MainMeta(ModelMeta):
    metadata = metadata
    database = database


class PositionOrm(Model):
    class Meta(MainMeta):
        pass

    id: int = Integer(primary_key=True, autoincrement=True)
    name: str = String(max_length=50, unique=True, index=True)
    x: float = Float()
    y: float = Float()
    degrees: float = Float()
    is_charging_station: bool = Boolean()
    is_parallell: bool = Boolean()


class ChargingStationOrm(Model):
    class Meta(MainMeta):
        pass

    id: int = Integer(primary_key=True, autoincrement=True)


class ChargingPad(Model):
    class Meta(MainMeta):
        pass

    id: int = Integer(primary_key=True, autoincrement=True)
    wheel_type: int = Integer()
    position: Optional[PositionOrm] = ForeignKey(PositionOrm)
    charging_station: Optional[ChargingStationOrm] = ForeignKey(ChargingStationOrm)


@pytest.fixture(autouse=True, scope="module")
def create_test_database():
    engine = create_engine(DATABASE_URL)
    metadata.create_all(engine)
    yield
    metadata.drop_all(engine)


@pytest.fixture(scope="function")
async def cleanup():
    yield
    async with database:
        await PositionOrm.objects.delete(each=True)
        await ChargingStationOrm.objects.delete(each=True)
        await ChargingPad.objects.delete(each=True)


@pytest.fixture()
async def db():
    async with database:
        yield


charging_station = ChargingStationOrm(
    id=1,
)
charging_pads = [
    ChargingPad(id=id, wheel_type=id, charging_station=1, position=id)
    for id in [1, 2, 3, 4]
]
charging_positions = [
    PositionOrm(
        name=f"n{i}",
        x=i * 1.1,
        y=i * 2.2,
        degrees=i * 3.3,
        is_charging_station=True,
        is_parallell=True,
    )
    for i in [1, 2, 3, 4]
]


@pytest.mark.asyncio()
async def test_create_charging_station(cleanup, db):
    await charging_station.save()
    await PositionOrm.objects.bulk_create(charging_positions)
    for charging_pad in charging_pads:
        await charging_pad.save()

    pan_ids_db = await charging_station.chargingpads.all()
    assert len(pan_ids_db) == 4

Stacktrace:

tests_ormar/test_issue_multi_foreign_key.py:92 (test_create_charging_station)
test_issue_multi_foreign_key.py:100: in test_create_charging_station
    pan_ids_db = await charging_station.chargingpads.all()
venv/lib/python3.8/site-packages/ormar/relations/relation_proxy.py:27: in __getattr__
    self._initialize_queryset()
venv/lib/python3.8/site-packages/ormar/relations/relation_proxy.py:32: in _initialize_queryset
    self.queryset_proxy.queryset = self._set_queryset()
venv/lib/python3.8/site-packages/ormar/relations/relation_proxy.py:50: in _set_queryset
    ormar.QuerySet(model_cls=self.relation.to)
venv/lib/python3.8/site-packages/ormar/queryset/queryset.py:133: in filter
    filter_clauses, select_related = qryclause.filter(**kwargs)
venv/lib/python3.8/site-packages/ormar/queryset/clause.py:50: in filter
    filter_clauses, select_related = self._populate_filter_clauses(**kwargs)
venv/lib/python3.8/site-packages/ormar/queryset/clause.py:77: in _populate_filter_clauses
    ) = self._determine_filter_target_table(
venv/lib/python3.8/site-packages/ormar/queryset/clause.py:136: in _determine_filter_target_table
    if issubclass(model_cls.Meta.model_fields[part], ManyToManyField):
E   KeyError: 'chargingstationorms'

Use sqlalchemy extensions with ormar

Hello,

Can I use Sqlalchemy extensions like sqlalchemy-mptt among others with ormar?

Also, in a different scenario, can I utilize Sqlalchemy mixins? I have a project with tens of mixins, including fields, computed properties, and functions?

I'm planning to use ormar for the next project as soon as it's production-ready. I like the project. It's one of a kind that can make us satisfied to replace Django as a high-performance alternative when performance is a prerequisite.

Thanks

Question: How to use ormar with alembic

Thanks for your work on the library!

One question that came to my mind, that I don't really find in the docs, is that how should this be used with alembic?
I mean, at least the --autogenerate produces some really weird results for me.

Disregarding the autogenerate for alembic, should one use ormar.* types when creating the tables:

def upgrade():
    alembic.op.create_table("table", sa.Column("column", ormar.Integer, primary_key=True), sa.Column("name", ormar.String(max_length=50)))

Some examples in the documentation would be nice.

Possibility for multitenancy / 'set search path' support for postgres?

Looked through the docs and the source for any way to handle this but I couldn't see anything as such.

Is it possible to implement multi-tenant schemas along the lines of the alembic example in their cookbook?

https://alembic.sqlalchemy.org/en/latest/cookbook.html

Essentially I guess we'd need a way to obtain a reference to the active db connection and execute set search path ... with some parameter ( ideally in the context of fastapi this would be a dependency ).

Secondly it would require a way to pass __table_args__ in the Meta class definition for a Model.

Happy to have a stab at a PR for this if someone could point me at where to start, or even if there is a way to obtain a ref to the connection in a given context that would probably get me a long way towards what i need

many thanks for this ORM by the way, have hunted high and low for a decent async ORM for FastAPI and this is really impressive.

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.

Saving related model broke in latest release

I have a test that started failing after I updated to the latest 0.7.0 release.

c = await domain.Category(name="Foo", code=123).save()
ws = await domain.Workshop(topic="Topic 1", category=c).save()

assert ws.id == 1
assert ws.topic == "Topic 1"
assert ws.category.name == "Foo"

Simple test, and the error is

E       AssertionError: assert None == 'Foo'
E         +None
E         -'Foo'

I can see that the category gets saved and is also saved in the database for the workshop, but it's not updated in the model correctly. In the ws.category I see only the id column, and saved = False.

First impression and improvements-issues on documentation

Hi @collerek, thank you for sharing this effort with the rest of us, I have just started experimenting with ormar. I have managed to run your demo with some problems see below, and I have also managed to test it successfully in a couple of alembic migrations .

  • OS: Ubuntu 18.04
  • python version: 3.8.6
  • ormar version: 0.7.3
  • database backend: sqlite and postgresql

Quick start demo

tracks = await Track.objects.filter(album__name="Fantasies").all()
assert len(tracks) == 2

tracks = await Track.objects.filter(album__name__iexact="fantasies").all()
assert len(tracks) == 2

In the documentation, you are missing await keywords and the ".all()" at the end. Apart from that I managed to run it without any problem, excluding the await peculiarities (read below).

Problems with await

I am a newcomer in async/await use but I noticed that in python 3.8.6, you cannot have await outside function. Is that right ? So for this bulk_create example from the documentation I had to do

import asyncio

async def main():
    # create multiple instances at once with bulk_create
    await ToDo.objects.bulk_create(
        [
            ToDo(text="Buy the groceries."),
            ToDo(text="Call Mum.", completed=True),
            ToDo(text="Send invoices.", completed=True),
        ]
    )

if __name__ == '__main__':
    asyncio.run(main())

In the same example I noticed that you haven't connected to the database and/or you didn't create an engine.
But It needs

engine = create_engine("sqlite:///db.sqlite")
...

metadata.create_all(engine)

otherwise it results in error, unless the database and the tables have been created. Also in SQLite it seems you don't need to connect using e.g. database.connect() command but I tried a similar example in postgresql when I created the tables and I had to use connect() otherwise you get the following error.

 File "postgres.py", line 148, in acquire
    assert self._database._pool is not None, "DatabaseBackend is not running"
AssertionError: DatabaseBackend is not running

Process finished with exit code 1

OK that is all I wanted to report for your interest and the interest of those newcomers like me that are starting using ormar. Thank you again for your kindness to share your code with all of us.

I am planning to use ormar to build a large scale web app that is based on microservices architecture. I would like to ask you whether you have used it in production for your projects or you know others that have used it successfully ?

Quick start example failing

I used pip install ormar and got version 0.9.1. I'm on Windows 10, with Python 3.8.0. When i try to copy the Quick Start example code into the REPL, I get errors. First, I get an error that Optional is not defined. This is easily fixed with from typing import Optional. However, once I get to the first create call, I get the error "no such table: album". Is there some step I'm missing? Is there a dependency that I'm missing that it's not telling me about? Here's my full console session, from the point I open python:

C:\(folders)>ipython
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.19.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import databases^M
   ...: import ormar^M
   ...: import sqlalchemy

In [2]: database = databases.Database("sqlite:///db.sqlite")

In [3]: metadata = sqlalchemy.MetaData()

In [4]: class Album(ormar.Model):^M
   ...:     class Meta:^M
   ...:         tablename = "album"^M
   ...:         metadata = metadata^M
   ...:         database = database^M
   ...: ^M
   ...:     # note that type hints are optional so ^M
   ...:     # id = ormar.Integer(primary_key=True) ^M
   ...:     # is also valid^M
   ...:     id: int = ormar.Integer(primary_key=True)^M
   ...:     name: str = ormar.String(max_length=100)
   ...:

In [5]: class Track(ormar.Model):
   ...:     class Meta:
   ...:         tablename = "track"
   ...:         metadata = metadata
   ...:         database = database
   ...:
   ...:     id: int = ormar.Integer(primary_key=True)
   ...:     album: Optional[Album] = ormar.ForeignKey(Album)
   ...:     title: str = ormar.String(max_length=100)
   ...:     position: int = ormar.Integer()
   ...:
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-487cf076daa5> in <module>
----> 1 class Track(ormar.Model):
      2     class Meta:
      3         tablename = "track"
      4         metadata = metadata
      5         database = database

<ipython-input-5-487cf076daa5> in Track()
      6
      7     id: int = ormar.Integer(primary_key=True)
----> 8     album: Optional[Album] = ormar.ForeignKey(Album)
      9     title: str = ormar.String(max_length=100)
     10     position: int = ormar.Integer()

NameError: name 'Optional' is not defined

In [6]: from typing import Optional

In [7]: class Track(ormar.Model):^M
   ...:     class Meta:^M
   ...:         tablename = "track"^M
   ...:         metadata = metadata^M
   ...:         database = database^M
   ...: ^M
   ...:     id: int = ormar.Integer(primary_key=True)^M
   ...:     album: Optional[Album] = ormar.ForeignKey(Album)^M
   ...:     title: str = ormar.String(max_length=100)^M
   ...:     position: int = ormar.Integer()^M
   ...:

In [8]: malibu = await Album.objects.create(name="Malibu")
---------------------------------------------------------------------------
OperationalError                          Traceback (most recent call last)
<ipython-input-8-1d1c0a3dace2> in <module>
----> 1 malibu = await Album.objects.create(name="Malibu")

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\ormar\queryset\queryset.py in create(self, **kwargs)
    819             sender=self.model, instance=instance
    820         )
--> 821         pk = await self.database.execute(expr)
    822
    823         pk_name = self.model.get_column_alias(self.model_meta.pkname)

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\databases\core.py in execute(self, query, values)
    159     ) -> typing.Any:
    160         async with self.connection() as connection:
--> 161             return await connection.execute(query, values)
    162
    163     async def execute_many(

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\databases\core.py in execute(self, query, values)
    261         built_query = self._build_query(query, values)
    262         async with self._query_lock:
--> 263             return await self._connection.execute(built_query)
    264
    265     async def execute_many(

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\databases\backends\sqlite.py in execute(self, query)
    114         query, args, context = self._compile(query)
    115         async with self._connection.cursor() as cursor:
--> 116             await cursor.execute(query, args)
    117             if cursor.lastrowid == 0:
    118                 return cursor.rowcount

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\aiosqlite\cursor.py in execute(self, sql, parameters)
     35         if parameters is None:
     36             parameters = []
---> 37         await self._execute(self._cursor.execute, sql, parameters)
     38         return self
     39

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\aiosqlite\cursor.py in _execute(self, fn, *args, **kwargs)
     29     async def _execute(self, fn, *args, **kwargs):
     30         """Execute the given function on the shared connection's thread."""
---> 31         return await self._conn._execute(fn, *args, **kwargs)
     32
     33     async def execute(self, sql: str, parameters: Iterable[Any] = None) -> "Cursor":

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\aiosqlite\core.py in _execute(self, fn, *args, **kwargs)
    120         self._tx.put_nowait((future, function))
    121
--> 122         return await future
    123
    124     async def _connect(self) -> "Connection":

c:\users\mewer\appdata\local\programs\python\python38\lib\site-packages\aiosqlite\core.py in run(self)
     96             try:
     97                 LOG.debug("executing %s", function)
---> 98                 result = function()
     99                 LOG.debug("operation %s completed", function)
    100

OperationalError: no such table: album

In [9]:

Alembic not recognizing models altogether

Hello,

when I put metadata.create_all(engine) in the startup function, it works fine, but when I try to migrate using Alembic, alembic drops the tables created earlier and never recognizes them at all from thereon.

project tree:

├── __init__.py
├── alembic.ini
├── migrations
│   ├── README
│   ├── env.py
│   ├── script.py.mako
│   └── versions
├── pr.db
├── requirements.txt
├── src
│   ├── __init__.py
│   ├── api
│   │   ├── Auth.py
│   │   ├── __init__.py
│   ├── app.py
│   ├── config
│   │   ├── __init__.py
│   │   └── settings.py
│   ├── models
│   │   ├── User.py
│   │   ├── __init__.py
│   ├── pr.db
│   ├── routers.py
│   └── utils
│       ├── Auth.py
│       ├── Models.py
│       ├── __init__.py

Alembic env.py file:

import os
import sys
from logging.config import fileConfig

from alembic import context
from sqlalchemy import create_engine

# add app folder to system path (alternative is running it from parent folder with python -m ...)
myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../../')

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from fastshop.src.config.settings import metadata

target_metadata = metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.

URL = "sqlite:///pr.db"


def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    context.configure(
        url=URL,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
        # if you use UUID field set also this param
        # the prefix has to match sqlalchemy import name in alembic
        # that can be set by sqlalchemy_module_prefix option (default 'sa.')
        user_module_prefix='sa.'
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = create_engine(URL)

    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            # if you use UUID field set also this param
            # the prefix has to match sqlalchemy import name in alembic
            # that can be set by sqlalchemy_module_prefix option (default 'sa.')
            user_module_prefix='sa.'
        )

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

I know that alembic is properly configured because as I said, it does recognize the migrated models and drops the tables that were created with metadata.create_all(engine)!

future migrations don't work, it just keeps dropping after the initial migration and never recognizes future models created!

Any help, please!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.