Skip to content

Commit

Permalink
Merge pull request #112 from mauro-andre/sort
Browse files Browse the repository at this point in the history
✨ find_one and find_many with sort
  • Loading branch information
mauro-andre authored Apr 11, 2024
2 parents f796dcf + f7b1cee commit 9117bc7
Show file tree
Hide file tree
Showing 19 changed files with 374 additions and 54 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,5 @@ dmypy.json
.pyre/
.vscode
test.py
coverage.json
coverage.json
.DS_Store
Binary file modified docs/assets/images/insomnia_request.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions docs/en/fastapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ class MyModel(DbModel):

@app.get("/", response_model=list[MyModel])
async def get_route(request: Request):
query = mount_query_filter(
query, sort = mount_query_filter(
Model=MyModel,
items=request.query_params._dict,
initial_comparison_operators=[],
)
return await engine.find_many(Model=MyModel, query=query)
return await engine.find_many(Model=MyModel, query=query, sort=sort)
```

In the example above, we defined a FastAPI route `GET /` that accepts query parameters. The `mount_query_filter` function, designed for use with FastAPI `Request`, dynamically constructs a query based on the items in these parameters.
Expand All @@ -47,13 +47,14 @@ The function returns a query with the `and` operator applied between all items i

![Image title](./assets/images/insomnia_request.png)

When you trigger the following route with query strings: `http://localhost:8000/?attr1_eq=value_1&attr2_in=%5B'value_2',%20'value_3'%5D&attr3_gte=10`, the `request.query_params._dict` will contain the following dictionary:
When you trigger the following route with query strings: `http://localhost:8000/?attr1_eq=value_1&attr2_in=%5B'value_2',%20'value_3'%5D&attr3_gte=10&_sort=%5B%5B'attr1',%201%5D,%20%5B'attr2',%20-1%5D%5D`, the `request.query_params._dict` will contain the following dictionary:

```python
{
"attr1_eq": "value_1",
"attr2_in": "['value_2', 'value_3']",
"attr3_gte": 10
"attr3_gte": 10,
"_sort": "[['attr1', 1], ['attr2', -1]]",
}
```

Expand Down
67 changes: 62 additions & 5 deletions docs/en/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class Product(DbModel):

async def main():
query = gte(Product.price, 5)
result: Product = await engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = await engine.find_one(Model=Product, query=query, sort=sort_oprator)

asyncio.run(main())
```
Expand All @@ -69,7 +70,8 @@ class Product(DbModel):


query = gte(Product.price, 5)
result: Product = engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = engine.find_one(Model=Product, query=query, sort=sort_oprator)
```
///

Expand Down Expand Up @@ -113,7 +115,8 @@ async def main():
eq(Product.is_available, True),
gte(Product.price, 5)
)
result: Product = await engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = await engine.find_one(Model=Product, query=query, sort=sort_oprator)

asyncio.run(main())
```
Expand All @@ -140,12 +143,66 @@ query = and_(
eq(Product.is_available, True),
gte(Product.price, 5)
)
result: Product = engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = engine.find_one(Model=Product, query=query, sort=sort_oprator)

```
///

In this example, the query returns all documents from the 'products' collection that `is_available` is `True` and that have `price` greater than or equal to 5

!!! tip
The inputs for these Logical Operators can be Comparison Operators or even other Logical Operators. This flexibility allows you to create complex and nested queries, enabling you to express intricate data retrieval conditions with precision.
The inputs for these Logical Operators can be Comparison Operators or even other Logical Operators. This flexibility allows you to create complex and nested queries, enabling you to express intricate data retrieval conditions with precision.

## Sort

/// tab | Async
```python hl_lines="19"
from pyodmongo import AsyncDbEngine, DbModel
from pyodmongo.queries import ( eq, gt, gte, in_, lt, lte, ne, nin, text,
and_, or_, nor)
from typing import ClassVar
import asyncio

engine = AsyncDbEngine(mongo_uri='mongodb://localhost:27017', db_name='my_db')


class Product(DbModel):
name: str
price: float
is_available: bool
_collection: ClassVar = 'products'


async def main():
query = gte(Product.price, 5)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = await engine.find_one(Model=Product, query=query, sort=sort_oprator)

asyncio.run(main())
```
///
/// tab | Sync
```python hl_lines="17"
from pyodmongo import DbEngine, DbModel
from pyodmongo.queries import ( eq, gt, gte, in_, lt, lte, ne, nin, text,
and_, or_, nor)
from typing import ClassVar

engine = DbEngine(mongo_uri='mongodb://localhost:27017', db_name='my_db')


class Product(DbModel):
name: str
price: float
is_available: bool
_collection: ClassVar = 'products'


query = gte(Product.price, 5)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = engine.find_one(Model=Product, query=query, sort=sort_oprator)
```
///

In the provided example, the `sort_operator` is defined using the `sort` function, which takes tuples. Each tuple contains two elements: the first one is the field by which you want to sort, and the second one is the sorting direction, where 1 indicates ascending order and -1 indicates descending order. In the presented case, the `sort_operator` sorts the results first by the name field in ascending order and then by the price field in descending order. Thus, the products are returned in alphabetical order by name and, in case of a tie, in descending order by price.
10 changes: 6 additions & 4 deletions docs/pt-BR/fastapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ class MyModel(DbModel):

@app.get("/", response_model=list[MyModel])
async def get_route(request: Request):
query = mount_query_filter(
query, sort = mount_query_filter(
Model=MyModel,
items=request.query_params._dict,
initial_comparison_operators=[],
)
return await engine.find_many(Model=MyModel, query=query)
return await engine.find_many(Model=MyModel, query=query, sort=sort)
```

No exemplo acima, definimos uma rota FastAPI `GET /` que aceita parâmetros de consulta. A função `mount_query_filter`, projetada para uso com `Request` do FastAPI, constrói dinamicamente uma consulta com base nos itens desses parâmetros.
Expand All @@ -47,15 +47,17 @@ A função retorna uma consulta com o operador `and` aplicado entre todos os ite

![Image title](./assets/images/insomnia_request.png)

Quando você aciona a seguinte rota com query strings: `http://localhost:8000/?attr1_eq=value_1&attr2_in=%5B'value_2',%20'value_3'%5D&attr3_gte=10`, o `request.query_params._dict` irá conter o seguinte dicionário:
Quando você aciona a seguinte rota com query strings: `http://localhost:8000/?attr1_eq=value_1&attr2_in=%5B'value_2',%20'value_3'%5D&attr3_gte=10&_sort=%5B%5B'attr1',%201%5D,%20%5B'attr2',%20-1%5D%5D`, o `request.query_params._dict` irá conter o seguinte dicionário:

```python
{
"attr1_eq": "value_1",
"attr2_in": "['value_2', 'value_3']",
"attr3_gte": 10
"attr3_gte": 10,
"_sort": "[['attr1', 1], ['attr2', -1]]",
}
```
```
Neste dicionário, as chaves devem ser nomes de atributos seguidos por um sublinhado e um operador válido (por exemplo, 'attr1' + '_' + 'eq'). Os operadores válidos são: `"eq", "gt", "gte", "in", "lt", "lte", "ne", "nin"`.
Expand Down
67 changes: 62 additions & 5 deletions docs/pt-BR/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class Product(DbModel):

async def main():
query = gte(Product.price, 5)
result: Product = await engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = await engine.find_one(Model=Product, query=query, sort=sort_oprator)

asyncio.run(main())
```
Expand All @@ -69,7 +70,8 @@ class Product(DbModel):


query = gte(Product.price, 5)
result: Product = engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = engine.find_one(Model=Product, query=query, sort=sort_oprator)
```
///

Expand Down Expand Up @@ -113,7 +115,8 @@ async def main():
eq(Product.is_available, True),
gte(Product.price, 5)
)
result: Product = await engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = await engine.find_one(Model=Product, query=query, sort=sort_oprator)

asyncio.run(main())
```
Expand All @@ -140,12 +143,66 @@ query = and_(
eq(Product.is_available, True),
gte(Product.price, 5)
)
result: Product = engine.find_one(Model=Product, query=query)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = engine.find_one(Model=Product, query=query, sort=sort_oprator)

```
///

Neste exemplo a consulta retornará todos os documentos da collecion 'products' que `is_available` seja `True` e que tenham `price` maior ou igual a 5

!!! tip
As entradas para estes Operadores Lógicos podem ser Operadores de Comparação ou mesmo outros Operadores Lógicos. Essa flexibilidade permite criar consultas complexas e aninhadas, permitindo expressar condições complexas de recuperação de dados com precisão.
As entradas para estes Operadores Lógicos podem ser Operadores de Comparação ou mesmo outros Operadores Lógicos. Essa flexibilidade permite criar consultas complexas e aninhadas, permitindo expressar condições complexas de recuperação de dados com precisão.

## Sort

/// tab | Async
```python hl_lines="19"
from pyodmongo import AsyncDbEngine, DbModel
from pyodmongo.queries import ( eq, gt, gte, in_, lt, lte, ne, nin, text,
and_, or_, nor)
from typing import ClassVar
import asyncio

engine = AsyncDbEngine(mongo_uri='mongodb://localhost:27017', db_name='my_db')


class Product(DbModel):
name: str
price: float
is_available: bool
_collection: ClassVar = 'products'


async def main():
query = gte(Product.price, 5)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = await engine.find_one(Model=Product, query=query, sort=sort_oprator)

asyncio.run(main())
```
///
/// tab | Sync
```python hl_lines="17"
from pyodmongo import DbEngine, DbModel
from pyodmongo.queries import ( eq, gt, gte, in_, lt, lte, ne, nin, text,
and_, or_, nor)
from typing import ClassVar

engine = DbEngine(mongo_uri='mongodb://localhost:27017', db_name='my_db')


class Product(DbModel):
name: str
price: float
is_available: bool
_collection: ClassVar = 'products'


query = gte(Product.price, 5)
sort_oprator = sort((Product.name, 1), (Product.price, -1))
result: Product = engine.find_one(Model=Product, query=query, sort=sort_oprator)
```
///

No exemplo fornecido, o `sort_operator` é definido usando a função `sort`, que aceita tuplas. Cada tupla contém dois elementos: o primeiro é o campo pelo qual você deseja ordenar e o segundo é a direção da ordenação, onde 1 indica ordem ascendente e -1 indica ordem descendente. No caso apresentado, o `sort_operator` ordena os resultados primeiro pelo campo name em ordem ascendente e, em seguida, pelo campo price em ordem descendente. Assim, os produtos são retornados em ordem alfabética pelo nome e, em caso de empate, em ordem decrescente pelo preço.
25 changes: 22 additions & 3 deletions pyodmongo/async_engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from pymongo.results import UpdateResult, DeleteResult
from ..models.responses import SaveResponse, DeleteResponse
from ..engine.utils import consolidate_dict, mount_base_pipeline
from ..services.query_operators import query_dict
from ..services.query_operators import query_dict, sort_dict
from ..models.paginate import ResponsePaginate
from ..models.query_operators import LogicalOperator, ComparisonOperator
from ..models.query_operators import LogicalOperator, ComparisonOperator, SortOperator
from ..models.db_model import DbModel
from datetime import datetime
from typing import TypeVar
Expand Down Expand Up @@ -149,6 +149,8 @@ async def find_one(
Model: type[Model],
query: ComparisonOperator | LogicalOperator = None,
raw_query: dict = None,
sort: SortOperator = None,
raw_sort: dict = None,
populate: bool = False,
as_dict: bool = False,
) -> type[Model]:
Expand All @@ -158,10 +160,18 @@ async def find_one(
raise TypeError(
'query argument must be a ComparisonOperator or LogicalOperator from pyodmongo.queries. If you really need to make a very specific query, use "raw_query" argument'
)
if sort and (type(sort) != SortOperator):
raise TypeError(
'sort argument must be a SortOperator from pyodmongo.queries. If you really need to make a very specific sort, use "raw_sort" argument'
)
raw_query = {} if not raw_query else raw_query
query = query_dict(query_operator=query, dct={}) if query else raw_query
raw_sort = {} if not raw_sort else raw_sort
sort = sort_dict(sort_operators=sort) if sort else raw_sort
pipeline = mount_base_pipeline(
Model=Model,
query=query_dict(query_operator=query, dct={}) if query else raw_query,
query=query,
sort=sort,
populate=populate,
)
pipeline += [{"$limit": 1}]
Expand All @@ -178,6 +188,8 @@ async def find_many(
Model: type[Model],
query: ComparisonOperator | LogicalOperator = None,
raw_query: dict = None,
sort: SortOperator = None,
raw_sort: dict = None,
populate: bool = False,
as_dict: bool = False,
paginate: bool = False,
Expand All @@ -190,11 +202,18 @@ async def find_many(
raise TypeError(
'query argument must be a ComparisonOperator or LogicalOperator from pyodmongo.queries. If you really need to make a very specific query, use "raw_query" argument'
)
if sort and (type(sort) != SortOperator):
raise TypeError(
'sort argument must be a SortOperator from pyodmongo.queries. If you really need to make a very specific sort, use "raw_sort" argument'
)
raw_query = {} if not raw_query else raw_query
query = query_dict(query_operator=query, dct={}) if query else raw_query
raw_sort = {} if not raw_sort else raw_sort
sort = sort_dict(sort_operators=sort) if sort else raw_sort
pipeline = mount_base_pipeline(
Model=Model,
query=query,
sort=sort,
populate=populate,
)
if not paginate:
Expand Down
Loading

0 comments on commit 9117bc7

Please sign in to comment.