Skip to content

Commit

Permalink
Remove support for python 3.8 (#532)
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurio authored May 7, 2024
1 parent 15d89c6 commit feb524d
Show file tree
Hide file tree
Showing 10 changed files with 862 additions and 1,201 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

**Required:**

- Python: >=3.8, <4.0
- Python: >=3.9, <4.0
- Fastapi: >=0.100, <1.0
- Pydantic: >=2.0.0, <3.0.0

Expand Down
12 changes: 6 additions & 6 deletions examples/fastapi_filter_mongoengine.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Any, List, Optional
from typing import Any, Optional

import click
import uvicorn
Expand Down Expand Up @@ -81,8 +81,8 @@ class AddressFilter(Filter):
street: Optional[str] = None
country: Optional[str] = None
city: Optional[str] = None
city__in: Optional[List[str]] = None
custom_order_by: Optional[List[str]] = None
city__in: Optional[list[str]] = None
custom_order_by: Optional[list[str]] = None
custom_search: Optional[str] = None

class Constants(Filter.Constants):
Expand All @@ -101,7 +101,7 @@ class UserFilter(Filter):
See: https://github.com/tiangolo/fastapi/issues/4700 for why we need to wrap `Query` in `Field`.
"""
order_by: List[str] = ["age"]
order_by: list[str] = ["age"]
search: Optional[str] = None

class Constants(Filter.Constants):
Expand Down Expand Up @@ -132,15 +132,15 @@ async def on_shutdown() -> None:
User.drop_collection()


@app.get("/users", response_model=List[UserOut])
@app.get("/users", response_model=list[UserOut])
async def get_users(user_filter: UserFilter = FilterDepends(UserFilter)) -> Any:
query = user_filter.filter(User.objects())
query = user_filter.sort(query)
query = query.select_related()
return [{**user.to_mongo(), "address": user.address.to_mongo()} for user in query]


@app.get("/addresses", response_model=List[AddressOut])
@app.get("/addresses", response_model=list[AddressOut])
async def get_addresses(
address_filter: AddressFilter = FilterDepends(with_prefix("my_custom_prefix", AddressFilter), by_alias=True),
) -> Any:
Expand Down
12 changes: 6 additions & 6 deletions examples/fastapi_filter_sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Any, AsyncIterator, List, Optional
from typing import Any, AsyncIterator, Optional

import click
import uvicorn
Expand Down Expand Up @@ -78,8 +78,8 @@ class AddressFilter(Filter):
street: Optional[str] = None
country: Optional[str] = None
city: Optional[str] = None
city__in: Optional[List[str]] = None
custom_order_by: Optional[List[str]] = None
city__in: Optional[list[str]] = None
custom_order_by: Optional[list[str]] = None
custom_search: Optional[str] = None

class Constants(Filter.Constants):
Expand All @@ -101,7 +101,7 @@ class UserFilter(Filter):
See: https://github.com/tiangolo/fastapi/issues/4700 for why we need to wrap `Query` in `Field`.
"""
order_by: List[str] = ["age"]
order_by: list[str] = ["age"]
search: Optional[str] = None

class Constants(Filter.Constants):
Expand Down Expand Up @@ -142,7 +142,7 @@ async def get_db() -> AsyncIterator[AsyncSession]:
yield session


@app.get("/users", response_model=List[UserOut])
@app.get("/users", response_model=list[UserOut])
async def get_users(
user_filter: UserFilter = FilterDepends(UserFilter),
db: AsyncSession = Depends(get_db),
Expand All @@ -154,7 +154,7 @@ async def get_users(
return result.scalars().all()


@app.get("/addresses", response_model=List[AddressOut])
@app.get("/addresses", response_model=list[AddressOut])
async def get_addresses(
address_filter: AddressFilter = FilterDepends(with_prefix("my_custom_prefix", AddressFilter), by_alias=True),
db: AsyncSession = Depends(get_db),
Expand Down
26 changes: 10 additions & 16 deletions fastapi_filter/base/filter.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import sys
from collections import defaultdict
from copy import deepcopy
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union, get_args, get_origin
from typing import Any, Iterable, Optional, Type, Union, get_args, get_origin

from fastapi import Depends
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel, ConfigDict, ValidationError, ValidationInfo, create_model, field_validator
from pydantic.fields import FieldInfo

UNION_TYPES: List = [Union]
UNION_TYPES: list = [Union]

if sys.version_info >= (3, 10):
from types import UnionType
Expand Down Expand Up @@ -46,10 +46,10 @@ class BaseFilterModel(BaseModel, extra="forbid"):
class Constants: # pragma: no cover
model: Type
ordering_field_name: str = "order_by"
search_model_fields: List[str]
search_model_fields: list[str]
search_field_name: str = "search"
prefix: str
original_filter: Type["BaseFilterModel"]
original_filter: type["BaseFilterModel"]

def filter(self, query): # pragma: no cover
...
Expand Down Expand Up @@ -127,7 +127,7 @@ def validate_order_by(cls, value, field: ValidationInfo):
return value


def with_prefix(prefix: str, Filter: Type[BaseFilterModel]) -> Type[BaseFilterModel]:
def with_prefix(prefix: str, Filter: type[BaseFilterModel]) -> type[BaseFilterModel]:
"""Allow re-using existing filter under a prefix.
Example:
Expand Down Expand Up @@ -182,8 +182,8 @@ class Constants(Filter.Constants): # type: ignore[name-defined]
return NestedFilter


def _list_to_str_fields(Filter: Type[BaseFilterModel]):
ret: Dict[str, Tuple[Union[object, Type], Optional[FieldInfo]]] = {}
def _list_to_str_fields(Filter: type[BaseFilterModel]):
ret: dict[str, tuple[Union[object, type], Optional[FieldInfo]]] = {}
for name, f in Filter.model_fields.items():
field_info = deepcopy(f)
annotation = f.annotation
Expand Down Expand Up @@ -211,7 +211,7 @@ def _list_to_str_fields(Filter: Type[BaseFilterModel]):
return ret


def FilterDepends(Filter: Type[BaseFilterModel], *, by_alias: bool = False, use_cache: bool = True) -> Any:
def FilterDepends(Filter: type[BaseFilterModel], *, by_alias: bool = False, use_cache: bool = True) -> Any:
"""Use a hack to support lists in filters.
FastAPI doesn't support it yet: https://github.com/tiangolo/fastapi/issues/50
Expand All @@ -223,7 +223,7 @@ def FilterDepends(Filter: Type[BaseFilterModel], *, by_alias: bool = False, use_
and formatted as a list of <type>?)
"""
fields = _list_to_str_fields(Filter)
GeneratedFilter: Type[BaseFilterModel] = create_model(Filter.__class__.__name__, **fields)
GeneratedFilter: type[BaseFilterModel] = create_model(Filter.__class__.__name__, **fields)

class FilterWrapper(GeneratedFilter): # type: ignore[misc,valid-type]
def __new__(cls, *args, **kwargs):
Expand All @@ -232,13 +232,7 @@ def __new__(cls, *args, **kwargs):
data = instance.model_dump(exclude_unset=True, exclude_defaults=True, by_alias=by_alias)
if original_filter := getattr(Filter.Constants, "original_filter", None):
prefix = f"{Filter.Constants.prefix}__"
stripped = {}
# TODO: replace with `removeprefix` when python 3.8 is no longer supported
# stripped = {k.removeprefix(NestedFilter.Constants.prefix): v for k, v in value.items()}
for k, v in data.items():
if k.startswith(prefix):
k = k.replace(prefix, "", 1)
stripped[k] = v
stripped = {k.removeprefix(prefix): v for k, v in data.items()}
return original_filter(**stripped)
return Filter(**data)
except ValidationError as e:
Expand Down
Loading

0 comments on commit feb524d

Please sign in to comment.