collerek / ormar Goto Github PK
View Code? Open in Web Editor NEWpython async orm with fastapi in mind and pydantic validation
Home Page: https://collerek.github.io/ormar/
License: MIT License
python async orm with fastapi in mind and pydantic validation
Home Page: https://collerek.github.io/ormar/
License: MIT License
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?
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
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.
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
.
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),
Right now only id (auto generated if not provided) and auto generated ForeigKeys to both ends of relation are allowed on through Model - allow to define more.
Q: How those fields should be exposed? -> Added to both end 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.
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?
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!
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 .
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).
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 ?
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
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
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
.
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 filter by date
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!
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)
How can i set conn_pool
If model inheritance is supported, we can save a lot of repetitive work by leveraging from inheritance.
At the same time, it can be better integrated with the existing ecosystem. Such as fastapi-users.
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.
Betters documentation and checks for setting non db fields on models and returning properties defines on model
Is there any way to exclude specific fields for inputs? Putting an exclude field on the model that informs Pydantic about how the developer wants to modify the model could be useful.
Check out how TortoiseORM does it for an example: https://tortoise-orm.readthedocs.io/en/latest/examples/fastapi.html#models-py
They expose the model creator to the user, but I could see why that might not be desired here.
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?
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.
Hi and thanks for the great package. Testing it with with FastAPI I get 500 Internal Server Error trying to access http://127.0.0.1:8000/docs
or http://127.0.0.1:8000/openapi.json
.
Does anyone have the same problem?
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 :)
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]
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.
Idea is that it should work the same as fields() but instead exclude selected fields from the query
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"
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.
Something more in line with ManyToMany field behavior without saving through models
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()
These lines
fantasies = Album.objects.create(name="Fantasies")
await fantasies.save()
should look like
fantasies = Album(name="Fantasies")
await fantasies.save()
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
Currently in model.py Record is imported without checks in place whether user even have asyncpg
installed:
Line 5 in 250f55a
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
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.
Set translation between model field names and db column names if they are different
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'
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.
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]:
prefetch_related is helpful in many situations, is there a plan to support it?
Add support for Many2Many fields with explicit through table, as described in encode/orm#40
Hi,
I have a bug with a request generated when trying to fetch data from a many 2 many relation:
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.
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.
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.
Allow to pass string value with model name to relation fields and processing it later as a ForwardRefs as pydantic does it.
It would allow to create self-referencing models etc.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.