Skip to content

Commit 8e58b8c

Browse files
authored
Merge pull request #190 from igorbenav/some-fixes
Some fixes
2 parents 7b9a4d1 + 37e92bf commit 8e58b8c

File tree

7 files changed

+112
-75
lines changed

7 files changed

+112
-75
lines changed

fastcrud/crud/fast_crud.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import Any, Dict, Generic, Union, Optional, Callable
22
from datetime import datetime, timezone
3-
import warnings
43

54
from pydantic import ValidationError
65
from sqlalchemy import (
@@ -33,6 +32,8 @@
3332
SelectSchemaType,
3433
UpdateSchemaInternalType,
3534
UpdateSchemaType,
35+
GetMultiResponseModel,
36+
GetMultiResponseDict,
3637
)
3738

3839
from .helper import (
@@ -1149,7 +1150,7 @@ async def get_multi(
11491150
return_as_model: bool = False,
11501151
return_total_count: bool = True,
11511152
**kwargs: Any,
1152-
) -> dict[str, Any]:
1153+
) -> Union[GetMultiResponseModel[SelectSchemaType], GetMultiResponseDict]:
11531154
"""
11541155
Fetches multiple records based on filters, supporting sorting, pagination.
11551156
@@ -1167,7 +1168,10 @@ async def get_multi(
11671168
**kwargs: Filters to apply to the query, including advanced comparison operators for more detailed querying.
11681169
11691170
Returns:
1170-
A dictionary containing `"data"` with fetched records and `"total_count"` indicating the total number of records matching the filters.
1171+
A dictionary containing the data list and optionally the total count:
1172+
- With return_as_model=True: Dict with "data": List[SelectSchemaType]
1173+
- With return_as_model=False: Dict with "data": List[Dict[str, Any]]
1174+
- If return_total_count=True, includes "total_count": int
11711175
11721176
Raises:
11731177
ValueError: If `limit` or `offset` is negative, or if `schema_to_select` is required but not provided or invalid.
@@ -2206,12 +2210,7 @@ async def update(
22062210
"""
22072211
total_count = await self.count(db, **kwargs)
22082212
if total_count == 0:
2209-
warnings.warn(
2210-
"Passing non-existing records to `update` will raise NoResultFound on version 0.15.3.",
2211-
DeprecationWarning,
2212-
stacklevel=2,
2213-
)
2214-
# raise NoResultFound("No record found to update.")
2213+
raise NoResultFound("No record found to update.")
22152214
if not allow_multiple and total_count > 1:
22162215
raise MultipleResultsFound(
22172216
f"Expected exactly one record to update, found {total_count}."

fastcrud/endpoint/helper.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ def _get_column_types(
138138
column_types = {}
139139
for column in inspector_result.mapper.columns:
140140
column_type = _get_python_type(column)
141-
if hasattr(column.type, "__visit_name__") and column.type.__visit_name__ == "uuid":
141+
if (
142+
hasattr(column.type, "__visit_name__")
143+
and column.type.__visit_name__ == "uuid"
144+
):
142145
column_type = UUID
143146
column_types[column.name] = column_type
144147
return column_types
@@ -191,15 +194,13 @@ def wrapper(endpoint):
191194
inspect.Parameter(
192195
name=k,
193196
annotation=Annotated[UUID, Path(...)],
194-
kind=inspect.Parameter.POSITIONAL_ONLY
197+
kind=inspect.Parameter.POSITIONAL_ONLY,
195198
)
196199
)
197200
else:
198201
extra_positional_params.append(
199202
inspect.Parameter(
200-
name=k,
201-
annotation=v,
202-
kind=inspect.Parameter.POSITIONAL_ONLY
203+
name=k, annotation=v, kind=inspect.Parameter.POSITIONAL_ONLY
203204
)
204205
)
205206

@@ -223,7 +224,14 @@ def filters(
223224
filtered_params = {}
224225
for key, value in kwargs.items():
225226
if value is not None:
226-
filtered_params[key] = value
227+
parse_func = column_types.get(key)
228+
if parse_func:
229+
try:
230+
filtered_params[key] = parse_func(value)
231+
except (ValueError, TypeError):
232+
filtered_params[key] = value
233+
else:
234+
filtered_params[key] = value
227235
return filtered_params
228236

229237
params = []

fastcrud/types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TypeVar, Any
1+
from typing import TypeVar, Any, Dict, Union, List
22

33
from pydantic import BaseModel
44

@@ -9,3 +9,6 @@
99
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
1010
UpdateSchemaInternalType = TypeVar("UpdateSchemaInternalType", bound=BaseModel)
1111
DeleteSchemaType = TypeVar("DeleteSchemaType", bound=BaseModel)
12+
13+
GetMultiResponseDict = Dict[str, Union[List[Dict[str, Any]], int]]
14+
GetMultiResponseModel = Dict[str, Union[List[SelectSchemaType], int]]

tests/sqlalchemy/core/test_uuid.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from fastapi.testclient import TestClient
99

1010
from fastcrud import crud_router, FastCRUD
11+
from fastcrud import FilterConfig
12+
from fastcrud.endpoint.helper import _create_dynamic_filters
1113
from pydantic import BaseModel
1214

1315
from ..conftest import Base
@@ -202,3 +204,43 @@ async def test_uuid_list_endpoint(uuid_client):
202204
UUID(item["id"])
203205
except ValueError: # pragma: no cover
204206
pytest.fail("Invalid UUID format in list response")
207+
208+
209+
def test_create_dynamic_filters_type_conversion():
210+
filter_config = FilterConfig(uuid_field=None, int_field=None, str_field=None)
211+
column_types = {
212+
"uuid_field": UUID,
213+
"int_field": int,
214+
"str_field": str,
215+
}
216+
217+
filters_func = _create_dynamic_filters(filter_config, column_types)
218+
219+
test_uuid = "123e4567-e89b-12d3-a456-426614174000"
220+
result = filters_func(uuid_field=test_uuid, int_field="123", str_field=456)
221+
222+
assert isinstance(result["uuid_field"], UUID)
223+
assert result["uuid_field"] == UUID(test_uuid)
224+
assert isinstance(result["int_field"], int)
225+
assert result["int_field"] == 123
226+
assert isinstance(result["str_field"], str)
227+
assert result["str_field"] == "456"
228+
229+
result = filters_func(
230+
uuid_field="not-a-uuid", int_field="not-an-int", str_field=456
231+
)
232+
233+
assert result["uuid_field"] == "not-a-uuid"
234+
assert result["int_field"] == "not-an-int"
235+
assert isinstance(result["str_field"], str)
236+
237+
result = filters_func(uuid_field=None, int_field="123", str_field=None)
238+
assert "uuid_field" not in result
239+
assert result["int_field"] == 123
240+
assert "str_field" not in result
241+
242+
result = filters_func(unknown_field="test")
243+
assert result["unknown_field"] == "test"
244+
245+
empty_filters_func = _create_dynamic_filters(None, {})
246+
assert empty_filters_func() == {}

tests/sqlalchemy/crud/test_update.py

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pytest
33

44
from sqlalchemy import select
5-
from sqlalchemy.exc import MultipleResultsFound
5+
from sqlalchemy.exc import MultipleResultsFound, NoResultFound
66

77
from fastcrud.crud.fast_crud import FastCRUD
88
from ...sqlalchemy.conftest import ModelTest, UpdateSchemaTest, ModelTestWithTimestamp
@@ -51,24 +51,11 @@ async def test_update_non_existent_record(async_session, test_data):
5151
crud = FastCRUD(ModelTest)
5252
non_existent_id = 99999
5353
updated_data = {"name": "New Name"}
54-
"""
55-
In version 0.15.3, the `update` method will raise a `NoResultFound` exception:
5654

57-
```
5855
with pytest.raises(NoResultFound) as exc_info:
5956
await crud.update(db=async_session, object=updated_data, id=non_existent_id)
6057

6158
assert "No record found to update" in str(exc_info.value)
62-
```
63-
64-
For 0.15.2, the test will check if the record is not updated.
65-
"""
66-
await crud.update(db=async_session, object=updated_data, id=non_existent_id)
67-
68-
record = await async_session.execute(
69-
select(ModelTest).where(ModelTest.id == non_existent_id)
70-
)
71-
assert record.scalar_one_or_none() is None
7259

7360

7461
@pytest.mark.asyncio
@@ -81,26 +68,10 @@ async def test_update_invalid_filters(async_session, test_data):
8168
updated_data = {"name": "New Name"}
8269

8370
non_matching_filter = {"name": "NonExistingName"}
84-
"""
85-
In version 0.15.3, the `update` method will raise a `NoResultFound` exception:
86-
87-
```
8871
with pytest.raises(NoResultFound) as exc_info:
8972
await crud.update(db=async_session, object=updated_data, **non_matching_filter)
9073

9174
assert "No record found to update" in str(exc_info.value)
92-
```
93-
94-
For 0.15.2, the test will check if the record is not updated.
95-
"""
96-
await crud.update(db=async_session, object=updated_data, **non_matching_filter)
97-
98-
for item in test_data:
99-
record = await async_session.execute(
100-
select(ModelTest).where(ModelTest.id == item["id"])
101-
)
102-
fetched_record = record.scalar_one()
103-
assert fetched_record.name != "New Name"
10475

10576

10677
@pytest.mark.asyncio

tests/sqlmodel/core/test_uuid.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from sqlmodel import Field, SQLModel
1010

1111
from fastcrud import crud_router, FastCRUD
12+
from fastcrud import FilterConfig
13+
from fastcrud.endpoint.helper import _create_dynamic_filters
1214
from pydantic import ConfigDict
1315

1416

@@ -204,3 +206,43 @@ async def test_uuid_list_endpoint(uuid_client):
204206
UUID(item["id"])
205207
except ValueError: # pragma: no cover
206208
pytest.fail("Invalid UUID format in list response")
209+
210+
211+
def test_create_dynamic_filters_type_conversion():
212+
filter_config = FilterConfig(uuid_field=None, int_field=None, str_field=None)
213+
column_types = {
214+
"uuid_field": UUID,
215+
"int_field": int,
216+
"str_field": str,
217+
}
218+
219+
filters_func = _create_dynamic_filters(filter_config, column_types)
220+
221+
test_uuid = "123e4567-e89b-12d3-a456-426614174000"
222+
result = filters_func(uuid_field=test_uuid, int_field="123", str_field=456)
223+
224+
assert isinstance(result["uuid_field"], UUID)
225+
assert result["uuid_field"] == UUID(test_uuid)
226+
assert isinstance(result["int_field"], int)
227+
assert result["int_field"] == 123
228+
assert isinstance(result["str_field"], str)
229+
assert result["str_field"] == "456"
230+
231+
result = filters_func(
232+
uuid_field="not-a-uuid", int_field="not-an-int", str_field=456
233+
)
234+
235+
assert result["uuid_field"] == "not-a-uuid"
236+
assert result["int_field"] == "not-an-int"
237+
assert isinstance(result["str_field"], str)
238+
239+
result = filters_func(uuid_field=None, int_field="123", str_field=None)
240+
assert "uuid_field" not in result
241+
assert result["int_field"] == 123
242+
assert "str_field" not in result
243+
244+
result = filters_func(unknown_field="test")
245+
assert result["unknown_field"] == "test"
246+
247+
empty_filters_func = _create_dynamic_filters(None, {})
248+
assert empty_filters_func() == {}

tests/sqlmodel/crud/test_update.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pytest
33

44
from sqlalchemy import select
5-
from sqlalchemy.exc import MultipleResultsFound
5+
from sqlalchemy.exc import MultipleResultsFound, NoResultFound
66

77
from fastcrud.crud.fast_crud import FastCRUD
88
from ...sqlmodel.conftest import ModelTest, UpdateSchemaTest, ModelTestWithTimestamp
@@ -51,24 +51,11 @@ async def test_update_non_existent_record(async_session, test_data):
5151
crud = FastCRUD(ModelTest)
5252
non_existent_id = 99999
5353
updated_data = {"name": "New Name"}
54-
"""
55-
In version 0.15.3, the `update` method will raise a `NoResultFound` exception:
5654

57-
```
5855
with pytest.raises(NoResultFound) as exc_info:
5956
await crud.update(db=async_session, object=updated_data, id=non_existent_id)
6057

6158
assert "No record found to update" in str(exc_info.value)
62-
```
63-
64-
For 0.15.2, the test will check if the record is not updated.
65-
"""
66-
await crud.update(db=async_session, object=updated_data, id=non_existent_id)
67-
68-
record = await async_session.execute(
69-
select(ModelTest).where(ModelTest.id == non_existent_id)
70-
)
71-
assert record.scalar_one_or_none() is None
7259

7360

7461
@pytest.mark.asyncio
@@ -81,26 +68,11 @@ async def test_update_invalid_filters(async_session, test_data):
8168
updated_data = {"name": "New Name"}
8269

8370
non_matching_filter = {"name": "NonExistingName"}
84-
"""
85-
In version 0.15.3, the `update` method will raise a `NoResultFound` exception:
8671

87-
```
8872
with pytest.raises(NoResultFound) as exc_info:
8973
await crud.update(db=async_session, object=updated_data, **non_matching_filter)
9074

9175
assert "No record found to update" in str(exc_info.value)
92-
```
93-
94-
For 0.15.2, the test will check if the record is not updated.
95-
"""
96-
await crud.update(db=async_session, object=updated_data, **non_matching_filter)
97-
98-
for item in test_data:
99-
record = await async_session.execute(
100-
select(ModelTest).where(ModelTest.id == item["id"])
101-
)
102-
fetched_record = record.scalar_one()
103-
assert fetched_record.name != "New Name"
10476

10577

10678
@pytest.mark.asyncio

0 commit comments

Comments
 (0)