Giter Club home page Giter Club logo

ormar's People

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

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

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.

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"

"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.

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

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.

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?

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.

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]

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),

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.

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]:

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?

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?

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!

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()

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.

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 :)

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

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 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 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.

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 ?

Introduce exclude_fields

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

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.

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'

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.

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

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.

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.

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.

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.

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

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()

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.

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.