Merge pull request #134 from collerek/fix_pg_query
fix quoting in order_by, add get_or_none
This commit is contained in:
@ -429,6 +429,7 @@ metadata.drop_all(engine)
|
|||||||
|
|
||||||
* `create(**kwargs): -> Model`
|
* `create(**kwargs): -> Model`
|
||||||
* `get(**kwargs): -> Model`
|
* `get(**kwargs): -> Model`
|
||||||
|
* `get_or_none(**kwargs): -> Optional[Model]`
|
||||||
* `get_or_create(**kwargs) -> Model`
|
* `get_or_create(**kwargs) -> Model`
|
||||||
* `first(): -> Model`
|
* `first(): -> Model`
|
||||||
* `update(each: bool = False, **kwargs) -> int`
|
* `update(each: bool = False, **kwargs) -> int`
|
||||||
|
|||||||
@ -429,6 +429,7 @@ metadata.drop_all(engine)
|
|||||||
|
|
||||||
* `create(**kwargs): -> Model`
|
* `create(**kwargs): -> Model`
|
||||||
* `get(**kwargs): -> Model`
|
* `get(**kwargs): -> Model`
|
||||||
|
* `get_or_none(**kwargs): -> Optional[Model]`
|
||||||
* `get_or_create(**kwargs) -> Model`
|
* `get_or_create(**kwargs) -> Model`
|
||||||
* `first(): -> Model`
|
* `first(): -> Model`
|
||||||
* `update(each: bool = False, **kwargs) -> int`
|
* `update(each: bool = False, **kwargs) -> int`
|
||||||
|
|||||||
@ -5,6 +5,7 @@ You can use following methods to filter the data (sql where clause).
|
|||||||
* `filter(**kwargs) -> QuerySet`
|
* `filter(**kwargs) -> QuerySet`
|
||||||
* `exclude(**kwargs) -> QuerySet`
|
* `exclude(**kwargs) -> QuerySet`
|
||||||
* `get(**kwargs) -> Model`
|
* `get(**kwargs) -> Model`
|
||||||
|
* `get_or_none(**kwargs) -> Optional[Model]`
|
||||||
* `get_or_create(**kwargs) -> Model`
|
* `get_or_create(**kwargs) -> Model`
|
||||||
* `all(**kwargs) -> List[Optional[Model]]`
|
* `all(**kwargs) -> List[Optional[Model]]`
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ You can use following methods to filter the data (sql where clause).
|
|||||||
* `QuerysetProxy.filter(**kwargs)` method
|
* `QuerysetProxy.filter(**kwargs)` method
|
||||||
* `QuerysetProxy.exclude(**kwargs)` method
|
* `QuerysetProxy.exclude(**kwargs)` method
|
||||||
* `QuerysetProxy.get(**kwargs)` method
|
* `QuerysetProxy.get(**kwargs)` method
|
||||||
|
* `QuerysetProxy.get_or_none(**kwargs)` method
|
||||||
* `QuerysetProxy.get_or_create(**kwargs)` method
|
* `QuerysetProxy.get_or_create(**kwargs)` method
|
||||||
* `QuerysetProxy.all(**kwargs)` method
|
* `QuerysetProxy.all(**kwargs)` method
|
||||||
|
|
||||||
@ -397,6 +399,11 @@ When any kwargs are passed it's a shortcut equivalent to calling `filter(**kwarg
|
|||||||
|
|
||||||
To read more about `get` go to [read/get](../read/#get)
|
To read more about `get` go to [read/get](../read/#get)
|
||||||
|
|
||||||
|
## get_or_none
|
||||||
|
|
||||||
|
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
|
||||||
|
|
||||||
|
|
||||||
## get_or_create
|
## get_or_create
|
||||||
|
|
||||||
`get_or_create(**kwargs) -> Model`
|
`get_or_create(**kwargs) -> Model`
|
||||||
@ -461,6 +468,11 @@ objects from other side of the relation.
|
|||||||
!!!tip
|
!!!tip
|
||||||
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||||
|
|
||||||
|
#### get_or_none
|
||||||
|
|
||||||
|
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
|
||||||
|
|
||||||
|
|
||||||
#### get_or_create
|
#### get_or_create
|
||||||
|
|
||||||
Works exactly the same as [get_or_create](./#get_or_create) function above but allows
|
Works exactly the same as [get_or_create](./#get_or_create) function above but allows
|
||||||
|
|||||||
@ -46,6 +46,7 @@ To read more about any specific section or function please refer to the details
|
|||||||
### [Read data from database](./read.md)
|
### [Read data from database](./read.md)
|
||||||
|
|
||||||
* `get(**kwargs) -> Model`
|
* `get(**kwargs) -> Model`
|
||||||
|
* `get_or_none(**kwargs) -> Optional[Model]`
|
||||||
* `get_or_create(**kwargs) -> Model`
|
* `get_or_create(**kwargs) -> Model`
|
||||||
* `first() -> Model`
|
* `first() -> Model`
|
||||||
* `all(**kwargs) -> List[Optional[Model]]`
|
* `all(**kwargs) -> List[Optional[Model]]`
|
||||||
@ -57,6 +58,7 @@ To read more about any specific section or function please refer to the details
|
|||||||
|
|
||||||
* `QuerysetProxy`
|
* `QuerysetProxy`
|
||||||
* `QuerysetProxy.get(**kwargs)` method
|
* `QuerysetProxy.get(**kwargs)` method
|
||||||
|
* `QuerysetProxy.get_or_none(**kwargs)` method
|
||||||
* `QuerysetProxy.get_or_create(**kwargs)` method
|
* `QuerysetProxy.get_or_create(**kwargs)` method
|
||||||
* `QuerysetProxy.first()` method
|
* `QuerysetProxy.first()` method
|
||||||
* `QuerysetProxy.all(**kwargs)` method
|
* `QuerysetProxy.all(**kwargs)` method
|
||||||
@ -122,6 +124,7 @@ To read more about any specific section or function please refer to the details
|
|||||||
* `exclude(**kwargs) -> QuerySet`
|
* `exclude(**kwargs) -> QuerySet`
|
||||||
* `order_by(columns:Union[List, str]) -> QuerySet`
|
* `order_by(columns:Union[List, str]) -> QuerySet`
|
||||||
* `get(**kwargs) -> Model`
|
* `get(**kwargs) -> Model`
|
||||||
|
* `get_or_none(**kwargs) -> Optional[Model]`
|
||||||
* `get_or_create(**kwargs) -> Model`
|
* `get_or_create(**kwargs) -> Model`
|
||||||
* `all(**kwargs) -> List[Optional[Model]]`
|
* `all(**kwargs) -> List[Optional[Model]]`
|
||||||
|
|
||||||
@ -131,6 +134,7 @@ To read more about any specific section or function please refer to the details
|
|||||||
* `QuerysetProxy.exclude(**kwargs)` method
|
* `QuerysetProxy.exclude(**kwargs)` method
|
||||||
* `QuerysetProxy.order_by(columns:Union[List, str])` method
|
* `QuerysetProxy.order_by(columns:Union[List, str])` method
|
||||||
* `QuerysetProxy.get(**kwargs)` method
|
* `QuerysetProxy.get(**kwargs)` method
|
||||||
|
* `QuerysetProxy.get_or_none(**kwargs)` method
|
||||||
* `QuerysetProxy.get_or_create(**kwargs)` method
|
* `QuerysetProxy.get_or_create(**kwargs)` method
|
||||||
* `QuerysetProxy.all(**kwargs)` method
|
* `QuerysetProxy.all(**kwargs)` method
|
||||||
|
|
||||||
|
|||||||
@ -55,6 +55,13 @@ track == track2
|
|||||||
|
|
||||||
If there are multiple rows meeting the criteria the `MultipleMatches` exception is raised.
|
If there are multiple rows meeting the criteria the `MultipleMatches` exception is raised.
|
||||||
|
|
||||||
|
## get_or_none
|
||||||
|
|
||||||
|
`get_or_none(**kwargs) -> Model`
|
||||||
|
|
||||||
|
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
|
||||||
|
|
||||||
|
|
||||||
## get_or_create
|
## get_or_create
|
||||||
|
|
||||||
`get_or_create(**kwargs) -> Model`
|
`get_or_create(**kwargs) -> Model`
|
||||||
@ -190,6 +197,14 @@ objects from other side of the relation.
|
|||||||
!!!tip
|
!!!tip
|
||||||
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||||
|
|
||||||
|
### get_or_none
|
||||||
|
|
||||||
|
Exact equivalent of get described above but instead of raising the exception returns `None` if no db record matching the criteria is found.
|
||||||
|
|
||||||
|
!!!tip
|
||||||
|
To read more about `QuerysetProxy` visit [querysetproxy][querysetproxy] section
|
||||||
|
|
||||||
|
|
||||||
### get_or_create
|
### get_or_create
|
||||||
|
|
||||||
Works exactly the same as [get_or_create](./#get_or_create) function above but allows
|
Works exactly the same as [get_or_create](./#get_or_create) function above but allows
|
||||||
|
|||||||
@ -1,3 +1,14 @@
|
|||||||
|
# 0.10.1
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* add `get_or_none(**kwargs)` method to `QuerySet` and `QuerysetProxy`. It is exact equivalent of `get(**kwargs)` but instead of raising `ormar.NoMatch` exception if there is no db record matching the criteria, `get_or_none` simply returns `None`.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
* Fix dialect dependent quoting of column and table names in order_by clauses not working
|
||||||
|
properly in postgres.
|
||||||
|
|
||||||
# 0.10.0
|
# 0.10.0
|
||||||
|
|
||||||
## Breaking
|
## Breaking
|
||||||
|
|||||||
@ -75,7 +75,7 @@ class UndefinedType: # pragma no cover
|
|||||||
|
|
||||||
Undefined = UndefinedType()
|
Undefined = UndefinedType()
|
||||||
|
|
||||||
__version__ = "0.10.0"
|
__version__ = "0.10.1"
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Integer",
|
"Integer",
|
||||||
"BigInteger",
|
"BigInteger",
|
||||||
|
|||||||
@ -69,7 +69,13 @@ class OrderAction(QueryAction):
|
|||||||
:rtype: sqlalchemy.sql.elements.TextClause
|
:rtype: sqlalchemy.sql.elements.TextClause
|
||||||
"""
|
"""
|
||||||
prefix = f"{self.table_prefix}_" if self.table_prefix else ""
|
prefix = f"{self.table_prefix}_" if self.table_prefix else ""
|
||||||
return text(f"{prefix}{self.table}" f".{self.field_alias} {self.direction}")
|
table_name = self.table.name
|
||||||
|
field_name = self.field_alias
|
||||||
|
if not prefix:
|
||||||
|
dialect = self.target_model.Meta.database._backend._dialect
|
||||||
|
table_name = dialect.identifier_preparer.quote(table_name)
|
||||||
|
field_name = dialect.identifier_preparer.quote(field_name)
|
||||||
|
return text(f"{prefix}{table_name}" f".{field_name} {self.direction}")
|
||||||
|
|
||||||
def _split_value_into_parts(self, order_str: str) -> None:
|
def _split_value_into_parts(self, order_str: str) -> None:
|
||||||
if order_str.startswith("-"):
|
if order_str.startswith("-"):
|
||||||
|
|||||||
@ -778,6 +778,26 @@ class QuerySet(Generic[T]):
|
|||||||
self.check_single_result_rows_count(processed_rows)
|
self.check_single_result_rows_count(processed_rows)
|
||||||
return processed_rows[0] # type: ignore
|
return processed_rows[0] # type: ignore
|
||||||
|
|
||||||
|
async def get_or_none(self, **kwargs: Any) -> Optional["T"]:
|
||||||
|
"""
|
||||||
|
Get's the first row from the db meeting the criteria set by kwargs.
|
||||||
|
|
||||||
|
If no criteria set it will return the last row in db sorted by pk.
|
||||||
|
|
||||||
|
Passing a criteria is actually calling filter(**kwargs) method described below.
|
||||||
|
|
||||||
|
If not match is found None will be returned.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: returned model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await self.get(**kwargs)
|
||||||
|
except ormar.NoMatch:
|
||||||
|
return None
|
||||||
|
|
||||||
async def get(self, **kwargs: Any) -> "T":
|
async def get(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
Get's the first row from the db meeting the criteria set by kwargs.
|
Get's the first row from the db meeting the criteria set by kwargs.
|
||||||
|
|||||||
@ -279,6 +279,30 @@ class QuerysetProxy(Generic[T]):
|
|||||||
self._register_related(first)
|
self._register_related(first)
|
||||||
return first
|
return first
|
||||||
|
|
||||||
|
async def get_or_none(self, **kwargs: Any) -> Optional["T"]:
|
||||||
|
"""
|
||||||
|
Get's the first row from the db meeting the criteria set by kwargs.
|
||||||
|
|
||||||
|
If no criteria set it will return the last row in db sorted by pk.
|
||||||
|
|
||||||
|
Passing a criteria is actually calling filter(**kwargs) method described below.
|
||||||
|
|
||||||
|
If not match is found None will be returned.
|
||||||
|
|
||||||
|
:param kwargs: fields names and proper value types
|
||||||
|
:type kwargs: Any
|
||||||
|
:return: returned model
|
||||||
|
:rtype: Model
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
get = await self.queryset.get(**kwargs)
|
||||||
|
except ormar.NoMatch:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self._clean_items_on_load()
|
||||||
|
self._register_related(get)
|
||||||
|
return get
|
||||||
|
|
||||||
async def get(self, **kwargs: Any) -> "T":
|
async def get(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
Get's the first row from the db meeting the criteria set by kwargs.
|
Get's the first row from the db meeting the criteria set by kwargs.
|
||||||
|
|||||||
@ -230,6 +230,8 @@ async def test_fernet_filters_nomatch():
|
|||||||
with pytest.raises(NoMatch):
|
with pytest.raises(NoMatch):
|
||||||
await Filter.objects.get(name="test1")
|
await Filter.objects.get(name="test1")
|
||||||
|
|
||||||
|
assert await Filter.objects.get_or_none(name="test1") is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_hash_filters_works():
|
async def test_hash_filters_works():
|
||||||
|
|||||||
@ -83,6 +83,18 @@ async def test_not_saved_raises_error(cleanup):
|
|||||||
await post.categories.add(news)
|
await post.categories.add(news)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_not_existing_raises_error(cleanup):
|
||||||
|
async with database:
|
||||||
|
guido = await Author(first_name="Guido", last_name="Van Rossum").save()
|
||||||
|
post = await Post.objects.create(title="Hello, M2M", author=guido)
|
||||||
|
|
||||||
|
with pytest.raises(NoMatch):
|
||||||
|
await post.categories.get()
|
||||||
|
|
||||||
|
assert await post.categories.get_or_none() is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_assigning_related_objects(cleanup):
|
async def test_assigning_related_objects(cleanup):
|
||||||
async with database:
|
async with database:
|
||||||
@ -95,6 +107,9 @@ async def test_assigning_related_objects(cleanup):
|
|||||||
# or from the other end:
|
# or from the other end:
|
||||||
await news.posts.add(post)
|
await news.posts.add(post)
|
||||||
|
|
||||||
|
assert await post.categories.get_or_none(name="no exist") is None
|
||||||
|
assert await post.categories.get_or_none(name="News") == news
|
||||||
|
|
||||||
# Creating columns object from instance:
|
# Creating columns object from instance:
|
||||||
await post.categories.create(name="Tips")
|
await post.categories.create(name="Tips")
|
||||||
assert len(post.categories) == 2
|
assert len(post.categories) == 2
|
||||||
|
|||||||
@ -220,6 +220,8 @@ async def test_model_get():
|
|||||||
with pytest.raises(ormar.NoMatch):
|
with pytest.raises(ormar.NoMatch):
|
||||||
await User.objects.get()
|
await User.objects.get()
|
||||||
|
|
||||||
|
assert await User.objects.get_or_none() is None
|
||||||
|
|
||||||
user = await User.objects.create(name="Tom")
|
user = await User.objects.create(name="Tom")
|
||||||
lookup = await User.objects.get()
|
lookup = await User.objects.get()
|
||||||
assert lookup == user
|
assert lookup == user
|
||||||
|
|||||||
99
tests/test_reserved_sql_keywords_escaped.py
Normal file
99
tests/test_reserved_sql_keywords_escaped.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import databases
|
||||||
|
import pytest
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
database = databases.Database(DATABASE_URL, force_rollback=True)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMeta(ormar.ModelMeta):
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
|
||||||
|
class User(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "user"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True, autoincrement=True, nullable=False)
|
||||||
|
user: str = ormar.String(
|
||||||
|
unique=True, index=True, nullable=False, max_length=255
|
||||||
|
) # ID of the user on auth0
|
||||||
|
first: str = ormar.String(nullable=False, max_length=255)
|
||||||
|
last: str = ormar.String(nullable=False, max_length=255)
|
||||||
|
email: str = ormar.String(unique=True, index=True, nullable=False, max_length=255)
|
||||||
|
display_name: str = ormar.String(
|
||||||
|
unique=True, index=True, nullable=False, max_length=255
|
||||||
|
)
|
||||||
|
pic_url: str = ormar.Text(nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Task(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "task"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True, autoincrement=True, nullable=False)
|
||||||
|
from_: str = ormar.String(name="from", nullable=True, max_length=200)
|
||||||
|
user = ormar.ForeignKey(User)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
def create_test_database():
|
||||||
|
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
metadata.create_all(engine)
|
||||||
|
yield
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_single_model_quotes():
|
||||||
|
async with database:
|
||||||
|
await User.objects.create(
|
||||||
|
user="test",
|
||||||
|
first="first",
|
||||||
|
last="last",
|
||||||
|
email="email@com.com",
|
||||||
|
display_name="first last",
|
||||||
|
)
|
||||||
|
|
||||||
|
user = await User.objects.order_by("user").get(first="first")
|
||||||
|
assert user.last == "last"
|
||||||
|
assert user.email == "email@com.com"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_two_model_quotes():
|
||||||
|
async with database:
|
||||||
|
user = await User.objects.create(
|
||||||
|
user="test",
|
||||||
|
first="first",
|
||||||
|
last="last",
|
||||||
|
email="email@com.com",
|
||||||
|
display_name="first last",
|
||||||
|
)
|
||||||
|
|
||||||
|
await Task(user=user, from_="aa").save()
|
||||||
|
await Task(user=user, from_="bb").save()
|
||||||
|
|
||||||
|
task = (
|
||||||
|
await Task.objects.select_related("user")
|
||||||
|
.order_by("user__user")
|
||||||
|
.get(from_="aa")
|
||||||
|
)
|
||||||
|
assert task.user.last == "last"
|
||||||
|
assert task.user.email == "email@com.com"
|
||||||
|
|
||||||
|
tasks = await Task.objects.select_related("user").order_by("-from").all()
|
||||||
|
assert len(tasks) == 2
|
||||||
|
assert tasks[0].user.last == "last"
|
||||||
|
assert tasks[0].user.email == "email@com.com"
|
||||||
|
assert tasks[0].from_ == "bb"
|
||||||
|
|
||||||
|
assert tasks[1].user.last == "last"
|
||||||
|
assert tasks[1].user.email == "email@com.com"
|
||||||
|
assert tasks[1].from_ == "aa"
|
||||||
Reference in New Issue
Block a user