Skip to content

Commit

Permalink
Merge pull request #94 from marier-nico/feat/dyn-filter-modifies-event
Browse files Browse the repository at this point in the history
feat: support modifying events in dyn filters
  • Loading branch information
marier-nico authored Dec 13, 2022
2 parents e55062d + 67a041d commit 892e31c
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 18 deletions.
9 changes: 6 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
Expand All @@ -27,11 +27,14 @@ jobs:
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tox
run: tox -e py
run: tox -ve py
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
- name: Package
run: inv build -d package check-package
run: |
python setup.py sdist bdist_wheel
sphinx-build docs docs/_build
twine check --strict dist/*
19 changes: 19 additions & 0 deletions docs/content/filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,25 @@ be valid for a dependency (see :ref:`Dependencies` for details). For example :
True
False

Since the Dyn filter is basically a way to do anything you can't do with static filters, it also allows modifying the
event before it gets passed to a processor function. For example :

.. testcode::

from event_processor import Depends, Event
from event_processor.filters import Dyn

my_event = {"a": "b"}
a_filter = Dyn(lambda e: "value", inject_as="modified-key")

a_filter.matches(my_event)

print(my_event)

.. testoutput::

{'a': 'b', 'modified-key': 'value'}

And
---

Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
black
invoke
mypy
pydantic ~= 1.10.2
pylama
pytest
pytest-cov
requests
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
keywords="event decorators development",

Expand Down
17 changes: 13 additions & 4 deletions src/event_processor/filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Contains many different filters to conveniently filter through events."""
from abc import ABC, abstractmethod
from typing import Any, Union, Callable
from typing import Any, Union, Callable, Optional

from .dependencies import call_with_injection, Event
from .exceptions import FilterError, NoValueError
Expand Down Expand Up @@ -197,6 +197,9 @@ def _compare(event_value: float, target_value: float) -> bool:
class Dyn(Filter):
"""Accept events based on a dynamic condition which is resolved by a callable.
This filter also allows dynamically changing the event by adding the returned value of the resolver to the event
under a user-supplied key. This will overwrite a previously existing key.
Note that the equality check with this filter is a bit special. The filter will only be equal if the resolver is
the same (two functions with the same code are not equal, they need to be the same object in memory).
Expand All @@ -205,17 +208,23 @@ class Dyn(Filter):
strategies.
"""

def __init__(self, resolver: Callable):
def __init__(self, resolver: Callable, inject_as: Optional[str] = None):
if not callable(resolver):
raise FilterError("The resolver for a dyn filter must be callable")

self.resolver = resolver
self.inject_as = inject_as

def matches(self, event: dict) -> bool:
if hasattr(self.resolver, "__name__") and self.resolver.__name__ == "<lambda>":
return bool(self.resolver(event))
result = self.resolver(event)
else:
result = call_with_injection(self.resolver, event=Event(event), cache={})

if self.inject_as:
event[self.inject_as] = result

return bool(call_with_injection(self.resolver, event=Event(event), cache={}))
return bool(result)

def __hash__(self):
return hash(self.resolver)
Expand Down
9 changes: 9 additions & 0 deletions src/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,15 @@ def dyn_filter():
assert hash(filter_) == hash(dyn_filter)


def test_dyn_filter_updates_event_when_inject_as_is_specified():
mock_event = {}
filter_ = Dyn(lambda e: "my-value", inject_as="my-key")

filter_.matches(mock_event)

assert mock_event["my-key"] == "my-value"


def test_eq_filter_matches_when_resolvers_are_equal():
mock_resolver = Mock()

Expand Down
2 changes: 1 addition & 1 deletion tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def build(c, docs=False):
@task
def test(c):
c.run("pytest -v --cov=src/event_processor/ --cov-fail-under=100 --cov-report html src/tests")
c.run("cd docs && make doctest && cd ..")
c.run("make -C docs doctest")


@task
Expand Down
15 changes: 5 additions & 10 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
[tox]
envlist = py{37,38,39}
envlist = py{37,38,39,310,3.11}
skip_missing_interpreters = true

[testenv]
deps =
black
invoke
mypy
pydantic
pylama
pytest
pytest-cov
allowlist_externals=make
deps = -r requirements.txt
commands =
black -l 120 --check src/
mypy --ignore-missing-imports src/event_processor/
pylama src/event_processor/
inv test
pytest -v --cov=src/event_processor/ --cov-fail-under=100 --cov-report html src/tests
make -C docs doctest

0 comments on commit 892e31c

Please sign in to comment.