Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Make default filter, sort and split_str a no-op, plus various improvements #272

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.local
key: poetry-${{ steps.setup-python.outputs.python-version }}-1.2.2-0
key: poetry-${{ steps.setup-python.outputs.python-version }}-1.3.2-0

- uses: snok/install-poetry@v1
with:
version: 1.2.2
version: 1.3.2
virtualenvs-create: true
virtualenvs-in-project: true

Expand Down Expand Up @@ -85,11 +85,11 @@ jobs:
uses: actions/cache@v3
with:
path: ~/.local
key: poetry-${{ steps.setup-python.outputs.python-version }}-1.2.2-0
key: poetry-${{ steps.setup-python.outputs.python-version }}-1.3.2-0

- uses: snok/install-poetry@v1
with:
version: 1.2.2
version: 1.3.2
virtualenvs-create: true
virtualenvs-in-project: true
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the poetry upgrade to a separate PR?


Expand Down
20 changes: 18 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
*.sqlite-journal
.DS_Store
.coverage
.pypirc
coverage.xml
dist/
fastapi_filter.sqlite
poetry.toml

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Unit test / coverage reports
test-results/
junit.xml
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
*.cover
.hypothesis/
.pytest_cache/
32 changes: 14 additions & 18 deletions fastapi_filter/base/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,22 @@ class BaseFilterModel(BaseModel, extra=Extra.forbid):

Provides the interface for filtering and ordering.

## Ordering
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why ## instead of #?


# Ordering

## Query string examples:
### Query string examples::

>>> "?order_by=-created_at"
>>> "?order_by=created_at,updated_at"
>>> "?order_by=+created_at,-name"

## Limitation
### Limitation

Sorting doesn't support related fields, you can only use the attributes of the targeted model/collection.
For example, you can't use `related_model__attribute`.

# Filtering
## Filtering

## Query string examples:
### Query string examples::
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never been a docstring formatting expert, why do you use the :: here?


>>> "?my_field__gt=12&my_other_field=Tomato"
>>> "?my_field__in=12,13,15&my_other_field__not_in=Tomato,Pepper"
Expand All @@ -44,7 +43,7 @@ class Constants: # pragma: no cover
prefix: str

def filter(self, query): # pragma: no cover
...
return query

@property
def filtering_fields(self):
Expand All @@ -53,7 +52,7 @@ def filtering_fields(self):
return fields.items()

def sort(self, query): # pragma: no cover
...
return query

@property
def ordering_values(self):
Expand All @@ -68,7 +67,7 @@ def ordering_values(self):

@validator("*", pre=True)
def split_str(cls, value, field): # pragma: no cover
...
return value
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I used ... vs. a no-op operation was to make BaseFilterModel a loose "interface". I'm curious to understand why you need to have to use a no-op in your custom filters. If you don't plan on using one of the methods, you can just not use it, no? Could you provide a use case to illustrate the need of defining a no-op instead?


@validator("*", pre=True, allow_reuse=True, check_fields=False)
def strip_order_by_values(cls, value, values, field):
Expand Down Expand Up @@ -126,10 +125,9 @@ def validate_order_by(cls, value, values, field):
def with_prefix(prefix: str, Filter: Type[BaseFilterModel]):
"""Allow re-using existing filter under a prefix.

Example:
```python
from pydantic import BaseModel
Example::

from pydantic import BaseModel
from fastapi_filter.filter import FilterDepends

class NumberFilter(BaseModel):
Expand All @@ -138,28 +136,26 @@ class NumberFilter(BaseModel):
class MainFilter(BaseModel):
name: str
number_filter: Optional[Filter] = FilterDepends(with_prefix("number_filter", Filter))
```

As a result, you'll get the following filters:
* name
* number_filter__count

# Limitation
## Limitation

The alias generator is the last to be picked in order of prevalence. So if one of the fields has a `Query` as
default and declares an alias already, this will be picked first and you won't get the prefix.

Example:
```python
from pydantic import BaseModel
Example::

from pydantic import BaseModel

class NumberFilter(BaseModel):
count: Optional[int] = Query(default=10, alias=counter)

class MainFilter(BaseModel):
name: str
number_filter: Optional[Filter] = FilterDepends(with_prefix("number_filter", Filter))
```

As a result, you'll get the following filters:
* name
Expand Down
5 changes: 2 additions & 3 deletions fastapi_filter/contrib/mongoengine/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
class Filter(BaseFilterModel):
"""Base filter for mongoengine related filters.

Example:
```python
Example::

class MyModel:
id: PrimaryKey()
name: StringField(null=True)
Expand All @@ -25,7 +25,6 @@ class MyModelFilter(Filter):
name__ne: Optional[str]
name__nin: Optional[list[str]]
name__isnull: Optional[bool]
```
"""

def sort(self, query: QuerySet) -> QuerySet:
Expand Down
9 changes: 4 additions & 5 deletions fastapi_filter/contrib/sqlalchemy/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,15 @@
class Filter(BaseFilterModel):
"""Base filter for orm related filters.

All children must set:
```python
All children must set::

class Constants(Filter.Constants):
model = MyModel
```

It can handle regular field names and Django style operators.

Example:
```python
Example::

class MyModel:
id: PrimaryKey()
name: StringField(nullable=True)
Expand Down
Loading