Skip to content

Commit

Permalink
Remove filter_macro...for now 😿
Browse files Browse the repository at this point in the history
  • Loading branch information
todofixthis committed Oct 19, 2024
1 parent 6097d6f commit ffc480a
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 439 deletions.
77 changes: 0 additions & 77 deletions docs/writing_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,6 @@ Although the Filters library comes with
:doc:`lots of built-in filters </simple_filters>`, oftentimes it is useful to
be able to write your own.

There are three ways that you can create new filters:

* Macros
* Partials
* Custom Filters

Macros
------
If you find yourself using a particular filter chain over and over, you can
create a macro to save yourself some typing.

To create a macro, define a function that returns a filter chain, then decorate
it with the ``filters.filter_macro`` decorator:

.. code-block:: python
import filters as f
@f.filter_macro
def String(allowed_types=None):
return f.Type(allowed_types or str) | f.Unicode | f.Strip
You can now use your filter macro just like any other filter:

.. code-block:: python
runner = f.FilterRunner(String | f.Required, ' Hello, world! ')
assert runner.is_valid() is True
assert runner.cleaned_data == 'Hello, world!'
Partials
--------
A partial is a special kind of macro. Instead of returning a filter chain,
it returns a single filter, but with different configuration values.

Here's an example of a partial that can be used to validate datetimes from New
Zealand, convert to UTC, and strip ``tzinfo`` from the result:

.. code-block:: python
import filters as f
# Create a partial for ``f.Datetime(timezone=13, naive=True)``.
NZ_Datetime = f.filter_macro(f.Datetime, timezone=13, naive=True)
Just like with macros, you can use a partial anywhere you can use a regular
filter:

.. code-block:: python
from datetime import datetime
runner = f.FilterRunner(NZ_Datetime | f.Required, '2016-12-11 15:00:00')
assert runner.is_valid() is True
assert runner.cleaned_data == datetime(2016, 12, 11, 2, 0, 0, tzinfo=None)
Additionally, partials act just like :py:func:`functools.partial` objects; you
can invoke them with different parameters if you want:

.. code-block:: python
from pytz import utc
# Override the ``naive`` parameter for the ``NZ_Datetime`` partial.
filter_ = NZ_Datetime(naive=False) | f.Required
runner = f.FilterRunner(filter_, '2016-12-11 15:00:00')
assert runner.is_valid() is True
assert runner.cleaned_data == datetime(2016, 12, 11, 2, 0, 0, tzinfo=utc)
Custom Filters
--------------
Sometimes you just can't get what you want by assembling existing filters, and
you need to write your own.

To create a new filter, write a class that extends
:py:class:`filters.BaseFilter` and implement the ``_apply`` method:

Expand Down
69 changes: 20 additions & 49 deletions src/filters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"FilterChain",
"FilterCompatible",
"FilterError",
"FilterMeta",
"Type",
]

Expand All @@ -19,7 +18,6 @@
FilterCompatible = typing.Optional[
typing.Union[
"BaseFilter[T]",
"FilterMeta",
typing.Callable[[], "BaseFilter[T]"],
]
]
Expand All @@ -29,59 +27,14 @@
"""


class FilterMeta(ABCMeta):
"""
Metaclass for filters.
"""

# noinspection PyShadowingBuiltins
def __init__(
cls,
what: str,
bases: tuple[type, ...],
dict: dict[str, typing.Any],
**kwargs: typing.Any,
):
super().__init__(what, bases, dict, **kwargs)

if not hasattr(cls, "templates"):
cls.templates = {}

# Copy error templates from base class to derived class, but in the
# event of a conflict, preserve the derived class' template.
templates = {}
for base in bases:
if isinstance(base, FilterMeta):
templates.update(base.templates)

if templates:
templates.update(cls.templates)
cls.templates = templates

def __or__(self, next_filter: FilterCompatible) -> "FilterChain":
"""
Convenience alias for adding a Filter with default
configuration to a FilterChain.
E.g., the following statements do the same thing::
Int | Max(32) # FilterMeta.__or__
Int() | Max(32) # Filter.__or__
References:
- http://stackoverflow.com/a/10773232
"""
return FilterChain(self) | next_filter


class BaseFilter(typing.Generic[T], metaclass=FilterMeta):
class BaseFilter(typing.Generic[T]):
"""
Base functionality for all Filters, macros, etc.
"""

CODE_EXCEPTION = "exception"

templates = {
templates: dict[str, str] = {
CODE_EXCEPTION: "An error occurred while processing this value.",
}

Expand All @@ -105,6 +58,24 @@ def __init__(self):
#
self._has_errors = False

def __init_subclass__(cls, **kwargs: typing.Any) -> None:
"""
Pre-compute some values to improve performance in derived classes.
"""
if not hasattr(cls, "templates"):
cls.templates: dict[str, str] = {}

# Copy error templates from base class to derived class, but in the
# event of a conflict, preserve the derived class' template.
templates: dict[str, str] = {}
for base in cls.__bases__:
if issubclass(base, BaseFilter):
templates.update(base.templates)

if templates:
templates.update(cls.templates)
cls.templates = templates

@classmethod
def __copy__(cls, the_filter: "BaseFilter") -> "BaseFilter":
"""
Expand Down
110 changes: 0 additions & 110 deletions src/filters/macros.py

This file was deleted.

10 changes: 0 additions & 10 deletions test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import typing

import filters as f
from filters.base import BaseFilter
from filters.macros import filter_macro


class FilterAlpha(BaseFilter):
Expand All @@ -26,11 +24,3 @@ def __init__(self, name: typing.Optional[str] = None) -> None:

def _apply(self, value):
return value


@filter_macro
def FilterCharlie():
"""
A filter macro that can be used for testing.
"""
return f.NoOp
1 change: 0 additions & 1 deletion test/filter_extension.egg-info/entry_points.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[filters.extensions_test]
Alfred=test:FilterAlpha
Bruce=test:FilterBravo
Catwoman=test:FilterCharlie
Loading

0 comments on commit ffc480a

Please sign in to comment.