Skip to content

Commit 7c560a6

Browse files
refactor typing, fix serializer mapping issue
1 parent 79147ca commit 7c560a6

File tree

7 files changed

+59
-27
lines changed

7 files changed

+59
-27
lines changed

fastapi_sa_orm_filter/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""FastAPI-SQLAlchemy filter, transform request query string to SQLAlchemy orm query"""
22

3-
__version__ = "0.2.1"
3+
__version__ = "0.2.2"

fastapi_sa_orm_filter/main.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pydantic._internal._model_construction import ModelMetaclass
99
from sqlalchemy_to_pydantic import sqlalchemy_to_pydantic
1010
from sqlalchemy import select, inspect
11-
from sqlalchemy.orm import InstrumentedAttribute, DeclarativeMeta
11+
from sqlalchemy.orm import InstrumentedAttribute, DeclarativeBase
1212
from sqlalchemy.sql.elements import BinaryExpression, UnaryExpression
1313
from sqlalchemy.sql.expression import and_, or_
1414
from starlette import status
@@ -27,9 +27,9 @@ class FilterCore:
2727

2828
def __init__(
2929
self,
30-
model: Type[DeclarativeMeta],
30+
model: Type[DeclarativeBase],
3131
allowed_filters: dict[str, list[ops]],
32-
select_query_part: Select[Any] = None
32+
select_query_part: Select[Any] | None = None
3333
) -> None:
3434
"""
3535
Produce a class:`FilterCore` object against a function
@@ -57,6 +57,7 @@ def get_query(self, custom_filter: str) -> Select[Any]:
5757
created_at__between=2023-05-01,2023-05-05|
5858
category__eq=Medicine&
5959
order_by=-id
60+
:param select_query_part: custom select query part (select(model).join(model1))
6061
6162
:return:
6263
select(model)
@@ -69,16 +70,16 @@ def get_query(self, custom_filter: str) -> Select[Any]:
6970
model.category == 'Medicine'
7071
).order_by(model.id.desc())
7172
"""
72-
split_query = self.split_by_order_by(custom_filter)
73+
split_query = self._split_by_order_by(custom_filter)
7374
try:
74-
complete_query = self.get_complete_query(*split_query)
75+
complete_query = self._get_complete_query(*split_query)
7576
except SAFilterOrmException as e:
7677
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=e.args[0])
7778
return complete_query
7879

79-
def get_complete_query(self, filter_query_str: str, order_by_query_str: str | None = None) -> Select[Any]:
80+
def _get_complete_query(self, filter_query_str: str, order_by_query_str: str | None = None) -> Select[Any]:
8081
select_query_part = self.get_select_query_part()
81-
filter_query_part = self.get_filter_query_part(filter_query_str)
82+
filter_query_part = self._get_filter_query_part(filter_query_str)
8283
complete_query = select_query_part.filter(*filter_query_part)
8384
group_query_part = self.get_group_by_query_part()
8485
if group_query_part:
@@ -89,11 +90,11 @@ def get_complete_query(self, filter_query_str: str, order_by_query_str: str | No
8990
return complete_query
9091

9192
def get_select_query_part(self) -> Select[Any]:
92-
if self.select_query_part:
93+
if self.select_query_part is not None:
9394
return self.select_query_part
9495
return select(self.model)
9596

96-
def get_filter_query_part(self, filter_query_str: str) -> list[Any]:
97+
def _get_filter_query_part(self, filter_query_str: str) -> list[Any]:
9798
conditions = self._get_filter_query(filter_query_str)
9899
if len(conditions) == 0:
99100
return conditions
@@ -123,7 +124,7 @@ def _get_filter_query(self, custom_filter: str) -> list[BinaryExpression]:
123124
filter_conditions.append(and_(*and_condition))
124125
return filter_conditions
125126

126-
def _create_pydantic_serializers(self) -> dict[str, ModelMetaclass]:
127+
def _create_pydantic_serializers(self) -> dict[str, dict[str, ModelMetaclass]]:
127128
"""
128129
Create two pydantic models (optional and list field types)
129130
for value: str serialization
@@ -139,7 +140,7 @@ class model.__name__(BaseModel):
139140
"""
140141

141142
models = [self.model]
142-
models.extend(self.get_relations())
143+
models.extend(self._get_relations())
143144

144145
serializers = {}
145146

@@ -154,7 +155,7 @@ class model.__name__(BaseModel):
154155

155156
return serializers
156157

157-
def get_relations(self) -> list:
158+
def _get_relations(self) -> list:
158159
return [relation[1].mapper.class_ for relation in self.relationships]
159160

160161
def _get_orm_for_field(
@@ -192,7 +193,7 @@ def _format_expression(
192193
raise SAFilterOrmException(f"Incorrect filter value '{value}'")
193194

194195
@staticmethod
195-
def split_by_order_by(query) -> list:
196+
def _split_by_order_by(query) -> list:
196197
split_query = [query_part.strip("&") for query_part in query.split("order_by=")]
197198
if len(split_query) > 2:
198199
raise SAFilterOrmException("Use only one order_by directive")

fastapi_sa_orm_filter/parsers.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from fastapi import HTTPException
44
from sqlalchemy import inspect
5-
from sqlalchemy.orm import InstrumentedAttribute, DeclarativeMeta
5+
from sqlalchemy.orm import InstrumentedAttribute, DeclarativeBase
66
from sqlalchemy.sql.elements import UnaryExpression
77

88
from fastapi_sa_orm_filter.exceptions import SAFilterOrmException
@@ -14,7 +14,7 @@ class _OrderByQueryParser:
1414
"""
1515
Class parse order by part of request query string.
1616
"""
17-
def __init__(self, model: Type[DeclarativeMeta]) -> None:
17+
def __init__(self, model: Type[DeclarativeBase]) -> None:
1818
self._model = model
1919

2020
def get_order_by_query(self, order_by_query_str: str) -> List[UnaryExpression]:
@@ -52,7 +52,7 @@ class _FilterQueryParser:
5252
Class parse filter part of request query string.
5353
"""
5454

55-
def __init__(self, query: str, model: Type[DeclarativeMeta], allowed_filters: dict[str, list[ops]]) -> None:
55+
def __init__(self, query: str, model: Type[DeclarativeBase], allowed_filters: dict[str, list[ops]]) -> None:
5656
self._query = query
5757
self._model = model
5858
self._relationships = inspect(model).relationships.items()
@@ -114,11 +114,12 @@ def _parse_expression(
114114
raise SAFilterOrmException(f"DB model {model.__name__} doesn't have field '{field_name}'")
115115
return table, column, operator, value
116116

117-
def _get_relation_model(self, field_name: str) -> tuple:
117+
def _get_relation_model(self, field_name: str) -> tuple[DeclarativeBase, str, str]:
118118
relation, field_name = field_name.split(".")
119119
for relationship in self._relationships:
120120
if relationship[0] == relation:
121-
return relationship[1].mapper.class_, relation, field_name
121+
model = relationship[1].mapper.class_
122+
return model, model.__tablename__, field_name
122123
raise SAFilterOrmException(f"Can not find relation {relation} in {self._model.__name__} model")
123124

124125
def _validate_query_params(

poetry.lock

Lines changed: 34 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "fastapi-sqlalchemy-filters"
3-
version = "0.2.1"
3+
version = "0.2.2"
44
description = ""
55
authors = ["Alexandr Zhydyk <zhydykalex@ukr.net>"]
66
readme = "README.md"

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from datetime import date, datetime
2+
from typing import Any
23

34
import pytest
45
import pytest_asyncio
56

6-
from sqlalchemy import select, func, ForeignKey
7+
from sqlalchemy import select, func, ForeignKey, Select
78
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
89
from sqlalchemy.orm import declarative_base, relationship, joinedload
910
from sqlalchemy.orm import Mapped

tests/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from enum import Enum
22

33

4-
class JobCategory(str, Enum):
4+
class JobCategory(Enum):
55
finance = "Finance"
66
marketing = "Marketing"
77
agro = "Agriculture"

0 commit comments

Comments
 (0)