Extending the Filters Namespace

Once you’ve written your own filters, you can start using them right away!

In [1]: from filters_iso import Currency

In [2]: Currency().apply('pen')
Out[2]: PEN

In [3]: Currency().apply('foo')
FilterError: This is not a valid ISO 4217 currency code.

Depending on your situation (and preferences), you might not mind importing your custom filters explicitly.

However, sometimes all those imports start to get unwieldy, especially if you have to use namespaces in order to keep them all straight:

import filters as f
import filters_iso as iso_filters
import api.filters as api_filters

request_filter = f.FilterMapper({
    'locale': f.Unicode | f.Strip | iso_filters.Locale,
    'username': f.Unicode | f.Strip | api_filters.User | f.Required,
})

And so on.

Some developers don’t mind this; others can’t stand it.

For those of you who fall into the latter group, the Filters library provides an extensions framework that allows you to add your filters to the (nearly) top-level filters.ext namespace:

import filters as f

request_filter = f.FilterMapper({
    'locale': f.Unicode | f.Strip | f.ext.Locale,
    'username': f.Unicode | f.Strip | f.ext.User | f.Required,
})

Note in the above example that the Locale and User filters do not need to be imported explicitly, and they are added automatically to the f.ext namespace.

Trade-Offs

There is one major downside to using the Extensions framework: IDE autocompletion won’t work (or at least, I haven’t figured out how to make it work yet 😇).

Extension filters are registered at runtime, so your IDE’s static analysis has no way to know what’s available in filters.ext.

Depending on your IDE, however, there may be ways to work around this. For example, PyCharm’s debugger can be configured to collect type information at runtime.

Prerequisites

In order to register your filters with the Extensions framework, your project must use setuptools and have a valid pyproject.toml or setup.py file.

Registering Your Filters

To add custom filters to the filters.ext namespace, register them as entry points using the filters.extensions key.

Here’s an example using pyproject.toml:

[project.entry-points."filters.extensions"]
Country = "filters_iso:Country"
Currency = "filters_iso:Currency"
Locale = "filters_iso:Locale"

If your project is using setup.py, it looks like this instead:

from setuptools import setup

setup(
  ...
  entry_points = {
    'filters.extensions': [
      'Country = filters_iso:Country',
      'Currency = filters_iso:Currency',
      'Locale = filters_iso:Locale',
    ],
  },
)

Note in the examples above that you can register as many filters as you want.

Tip

The name that you assign to each entry point is used as the attribute name when the corresponding filter is registered.

To use an absurd example, if you register a filter like this:

[project.entry-points."filters.extensions"]
HelloWorld = "filters_iso:Currency"

Then it will be registered like this:

In [1]: import filters as f

In [1]: f.ext.HelloWorld().apply('NZD')
Out[1]: NZD

This feature may be useful to resolve conflicts, in the event that two filter classes have the same name (see below).

Conflicts

In the event that two filters are registered with the same name, one of them will replace the other. The order that entry points are processed is not defined, so it is not predictable which filter will “win”.

Troubleshooting

Remember to pip install -e . each time you modify your entry points; this is required in order to install the new entry points into your project’s egg-info directory.

If your filter is still not showing up in f.ext, try turning on debug logging. You will see log messages as the Filters library searches for extension filters to load:

In [1]: import logging, sys

In [2]: logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)

In [3]: import filters as f

In [4]: dir(f.ext)
DEBUG:filters.extensions:Registering extension filter filters_iso.Country as Country.
DEBUG:filters.extensions:Registering extension filter filters_iso.Currency as Currency.
DEBUG:filters.extensions:Registering extension filter filters_iso.Locale as Locale.
Out[4]: ['Country', 'Currency', 'Locale']