diff --git a/examples/base.py b/examples/base.py index a1e12e7..13cce2a 100644 --- a/examples/base.py +++ b/examples/base.py @@ -5,8 +5,8 @@ import click import uvicorn from faker import Faker -from fastapi import Depends, FastAPI, Query -from pydantic import BaseModel, ConfigDict, Field +from fastapi import Depends, FastAPI +from pydantic import BaseModel, ConfigDict from sqlalchemy import Column, ForeignKey, Integer, String, event, select from sqlalchemy.engine import Engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine @@ -91,16 +91,15 @@ class Constants(Filter.Constants): class UserFilter(Filter): name: str | None = None - name__ilike: str | None = None - name__like: str | None = None + name__contains: str | None = None + name__icontains: str | None = None name__neq: str | None = None address: AddressFilter | None = FilterDepends(with_prefix("address", AddressFilter)) age__lt: int | None = None - age__gte: int = Field(Query(description="this is a nice description")) - """Required field with a custom description. + age__gt: int | None = None + age__contains: str | None = None + age__icontains: str | None = None - See: https://github.com/tiangolo/fastapi/issues/4700 for why we need to wrap `Query` in `Field`. - """ order_by: list[str] = ["age"] search: str | None = None diff --git a/fastapi_filter_sqlalchemy/filter_sqlalchemy.py b/fastapi_filter_sqlalchemy/filter_sqlalchemy.py index 23a1a9d..38e0efe 100644 --- a/fastapi_filter_sqlalchemy/filter_sqlalchemy.py +++ b/fastapi_filter_sqlalchemy/filter_sqlalchemy.py @@ -165,15 +165,22 @@ def _custom_filter_field( :param field_name: Name of the filter field. :return: Returns the query. """ - # Делаем провреку, что такого поля нету в модели - # Если нету то вызываем метод get_field_name - # Передаем в него запрос и значение. + # We check that such a field does not exist in the models. + # If not, we call the get_field_name method. + # We pass the request and value to it. if hasattr(self.Constants.model, field_name): model_field = getattr(self.Constants.model, field_name) if to_date: model_field = func.date(model_field) if operator != "likein" and operator != "between": - query = query.filter(getattr(model_field, operator)(value)) + if ( + operator in ("like", "ilike") + and not isinstance(model_field.type, String) + and isinstance(value, str) + ): + query = query.filter(getattr(func.cast(model_field, String), operator)(value)) + else: + query = query.filter(getattr(model_field, operator)(value)) elif operator == "between": query = query.filter(getattr(model_field, operator)(*value)) else: diff --git a/pyproject.toml b/pyproject.toml index 0733d5c..73aab41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ ignore_missing_imports = true [tool.poetry] name = "fastapi-filter-sqlalchemy" -version = "0.0.2" +version = "0.0.3" description = "FastAPI filter SQLAlchemy" authors = ["Sergey V. Elfimov "] packages = [{include = "fastapi_filter_sqlalchemy"}] diff --git a/tests/conftest.py b/tests/conftest.py index 4ed584b..7403272 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -336,6 +336,8 @@ class UserFilter(Filter): # type: ignore[misc, valid-type] age__gte: int | None = None age__in: list[int] | None = None age__range: list[int] | None = None + age__contains: str | None = None + age__icontains: str | None = None address: AddressFilter | None = FilterDepends( # type: ignore[valid-type] with_prefix("address", AddressFilter), by_alias=True ) diff --git a/tests/test_filter.py b/tests/test_filter.py index f286924..60757d0 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -25,6 +25,10 @@ [{"name__not": "Mr Praline", "age__gte": 21, "age__lt": 50}, 2], [{"age__in": [1]}, 1], [{"age__in": [21, 33]}, 3], + [{"age__icontains": "2"}, 2], + [{"age__contains": "21"}, 2], + [{"age__icontains": "150"}, 0], + [{"age__icontains": "1"}, 3], [{"address": {"country__not_in": ["France"]}}, 3], [{"age__in": "1"}, 1], [{"age__in": "21,33"}, 3],