Skip to content

Commit

Permalink
Merge pull request #76 from marier-nico/feat/callable-processors
Browse files Browse the repository at this point in the history
feat: callable processors
  • Loading branch information
marier-nico authored Jun 11, 2022
2 parents 7ff06b6 + 9c58dd1 commit 65115f4
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 53 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,7 @@ Thumbs.db #thumbnail cache on Windows
# End of https://www.toptal.com/developers/gitignore/api/pycharm,vscode,python

.pants.d
.pids
.pids

.DS_Store

1 change: 1 addition & 0 deletions docs/content/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- v3.1.4: Simplify calling processors by making ``EventProcessor`` callable.
- v3.1.3: Fix a bug with invocation when an event matches several filters.
- v3.1.2: Fix an incorrect import.
- v3.1.1: Relax pydantic requirements to help dependency management for downstream projects.
Expand Down
8 changes: 4 additions & 4 deletions docs/content/core_concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,21 @@ a stub for the SSM client and assume that the ``admin_email`` parameter has a va
from event_processor import EventProcessor, Event, Depends
from event_processor.filters import Exists

event_processor = EventProcessor()
processor = EventProcessor()


def get_ssm():
return FakeSSMClient()


@event_processor.processor(Exists("user.email"))
@processor(Exists("user.email"))
def user_is_admin(raw_event: Event, ssm_client: FakeSSMClient = Depends(get_ssm)) -> bool:
ssm_response = ssm_client.get_parameter(Name="admin_email")
admin_email = ssm_response["Parameter"]["Value"]
return raw_event["user"]["email"] == admin_email

print("admin@example.com is admin:", event_processor.invoke({"user": {"email": "admin@example.com"}}).returned_value)
print("user@example.com is admin:", event_processor.invoke({"user": {"email": "user@example.com"}}).returned_value)
print("admin@example.com is admin:", processor.invoke({"user": {"email": "admin@example.com"}}).returned_value)
print("user@example.com is admin:", processor.invoke({"user": {"email": "user@example.com"}}).returned_value)

.. testoutput:: core_concepts

Expand Down
50 changes: 25 additions & 25 deletions docs/content/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ ______________
from event_processor import EventProcessor, Depends
from event_processor.filters import Accept

event_processor = EventProcessor()
processor = EventProcessor()


def get_my_value():
return 42


@event_processor.processor(Accept())
@processor(Accept())
def my_processor(my_value : int = Depends(get_my_value)):
print(my_value)


event_processor.invoke({})
processor.invoke({})

.. testoutput::

Expand All @@ -60,7 +60,7 @@ You can also nest dependencies as deep as you want to go, so you can easily re-u
from event_processor import EventProcessor, Depends
from event_processor.filters import Accept

event_processor = EventProcessor()
processor = EventProcessor()


def get_zero():
Expand All @@ -72,12 +72,12 @@ You can also nest dependencies as deep as you want to go, so you can easily re-u
return zero + 1


@event_processor.processor(Accept())
@processor(Accept())
def my_processor_with_caching(my_value : int = Depends(get_my_value)):
print(my_value)


event_processor.invoke({})
processor.invoke({})

.. testoutput::

Expand All @@ -94,7 +94,7 @@ classes as dependencies as well.
from event_processor import EventProcessor, Depends, Event
from event_processor.filters import Exists

event_processor = EventProcessor()
processor = EventProcessor()


class MyThing:
Expand All @@ -105,12 +105,12 @@ classes as dependencies as well.
return self.username


@event_processor.processor(Exists("username"))
@processor(Exists("username"))
def my_processor_with_caching(my_thing : MyThing = Depends(MyThing)):
print(my_thing.get_username())


event_processor.invoke({"username": "someone"})
processor.invoke({"username": "someone"})

.. testoutput::

Expand All @@ -133,15 +133,15 @@ Here's an example of a simple event dependency :
from event_processor import EventProcessor, Event
from event_processor.filters import Accept

event_processor = EventProcessor()
processor = EventProcessor()


@event_processor.processor(Accept())
@processor(Accept())
def my_processor_with_caching(event: Event):
print(event)


event_processor.invoke({"hello": "world"})
processor.invoke({"hello": "world"})

.. testoutput::

Expand All @@ -154,7 +154,7 @@ And here's an example where a dependency depends on the event :
from event_processor import EventProcessor, Event
from event_processor.filters import Exists

event_processor = EventProcessor()
processor = EventProcessor()


# This function could also query a database (in which case it might depend
Expand All @@ -163,12 +163,12 @@ And here's an example where a dependency depends on the event :
return event["email"]


@event_processor.processor(Exists("email"))
@processor(Exists("email"))
def my_processor_with_caching(email: str = Depends(extract_email)):
print(email)


event_processor.invoke({"email": "someone@example.com"})
processor.invoke({"email": "someone@example.com"})

.. testoutput::

Expand All @@ -190,21 +190,21 @@ Here's a simple example to illustrate how the event might be parsed for use in a
from event_processor.filters import Eq
from pydantic import BaseModel

event_processor = EventProcessor()
processor = EventProcessor()


class CreateUserQuery(BaseModel):
email: str
password: str


@event_processor.processor(Eq("query", "create_user"))
@processor(Eq("query", "create_user"))
def handle_user_creation(query: CreateUserQuery):
print(query.email)
print(query.password)


event_processor.invoke(
processor.invoke(
{"query": "create_user", "email": "someone@example.com", "password": "hunter2"}
)

Expand Down Expand Up @@ -237,15 +237,15 @@ Here's a very basic example :
from event_processor import EventProcessor
from event_processor.filters import Exists

event_processor = EventProcessor()
processor = EventProcessor()


@event_processor.processor(Exists("email"))
@processor(Exists("email"))
def handle_user(email: str):
print(email)


event_processor.invoke({"email": "someone@example.com"})
processor.invoke({"email": "someone@example.com"})

.. testoutput::

Expand All @@ -260,18 +260,18 @@ Here's an example with a pydantic field type :
from pydantic import ValidationError
from pydantic.color import Color

event_processor = EventProcessor()
processor = EventProcessor()


@event_processor.processor(Exists("my_color"))
@processor(Exists("my_color"))
def handle_user(my_color: Color):
print(my_color.as_hex())


event_processor.invoke({"my_color": "white"})
processor.invoke({"my_color": "white"})

try:
event_processor.invoke({"my_color": "not-a-color"})
processor.invoke({"my_color": "not-a-color"})
except ValidationError as e:
print(e.errors()[0]["msg"])

Expand Down
38 changes: 19 additions & 19 deletions docs/content/processors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ This is why you can use error handling strategies. Here's an example :
from event_processor import EventProcessor, ErrorHandlingStrategies
from event_processor.filters import Accept

event_processor = EventProcessor(error_handling_strategy=ErrorHandlingStrategies.CAPTURE)
processor = EventProcessor(error_handling_strategy=ErrorHandlingStrategies.CAPTURE)


@event_processor.processor(Accept())
@processor(Accept())
def my_failing_processor():
raise RuntimeError("Oh no, I failed!")


result = event_processor.invoke({})
result = processor.invoke({})

if result.has_exception:
print(str(result.raised_exception))
Expand Down Expand Up @@ -102,19 +102,19 @@ For example,
from event_processor import EventProcessor, InvocationError
from event_processor.filters import Accept

event_processor = EventProcessor()
other_event_processor = EventProcessor()
processor = EventProcessor()
other_processor = EventProcessor()


@event_processor.processor(Accept())
@processor(Accept())
def my_processor():
pass


event_processor.invoke({}) # This is fine, a processor exists for the event
processor.invoke({}) # This is fine, a processor exists for the event

try:
other_event_processor.invoke({}) # This will raise
other_processor.invoke({}) # This will raise
except InvocationError:
print("Raised!")

Expand Down Expand Up @@ -145,7 +145,7 @@ sub-processors which get merged with a main processor.
sub_processor = EventProcessor()


@sub_processor.processor(Accept())
@sub_processor(Accept())
def my_processor():
pass

Expand All @@ -160,7 +160,7 @@ sub-processors which get merged with a main processor.
sub_processor = EventProcessor()


@sub_processor.processor(Accept())
@sub_processor(Accept())
def my_processor():
return "sub_processing!"

Expand Down Expand Up @@ -248,21 +248,21 @@ Here's an example of how you can use ranking :
from event_processor import EventProcessor
from event_processor.filters import Exists, Eq

event_processor = EventProcessor()
processor = EventProcessor()


@event_processor.processor(Exists("a"))
@processor(Exists("a"))
def processor_a():
print("Processor a!")


@event_processor.processor(Eq("a", "b"), rank=1)
@processor(Eq("a", "b"), rank=1)
def processor_b():
print("Processor b!")


event_processor.invoke({"a": "b"})
event_processor.invoke({"a": "not b"})
processor.invoke({"a": "b"})
processor.invoke({"a": "not b"})

.. testoutput:: processors

Expand Down Expand Up @@ -314,20 +314,20 @@ To use a non-default invocation strategy, use the provided ``InvocationStrategie
from event_processor import EventProcessor, InvocationStrategies
from event_processor.filters import Exists, Eq

event_processor = EventProcessor(invocation_strategy=InvocationStrategies.ALL_MATCHES)
processor = EventProcessor(invocation_strategy=InvocationStrategies.ALL_MATCHES)


@event_processor.processor(Exists("a"))
@processor(Exists("a"))
def processor_a():
print("Processor a!")


@event_processor.processor(Eq("a", "b"))
@processor(Eq("a", "b"))
def processor_b():
print("Processor b!")


event_processor.invoke({"a": "b"})
processor.invoke({"a": "b"})

.. testoutput::

Expand Down
8 changes: 4 additions & 4 deletions docs/content/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Here's an example of how you might test a processor :
from event_processor import EventProcessor, Event, Depends
from event_processor.filters import Exists

event_processor = EventProcessor()
processor = EventProcessor()


class FakeDatabase:
Expand Down Expand Up @@ -41,7 +41,7 @@ Here's an example of how you might test a processor :
return event["email"]


@event_processor.processor(Exists("email"))
@processor(Exists("email"))
def user_is_admin(
email: str = Depends(extract_email, cache=False),
db_client: FakeDatabase = Depends(get_database),
Expand All @@ -50,8 +50,8 @@ Here's an example of how you might test a processor :
return user_role == "admin"


print(event_processor.invoke({"email": "user@example.com"}).returned_value)
print(event_processor.invoke({"email": "admin@example.com"}).returned_value)
print(processor.invoke({"email": "user@example.com"}).returned_value)
print(processor.invoke({"email": "admin@example.com"}).returned_value)


#################### Tests #####################
Expand Down
8 changes: 8 additions & 0 deletions src/event_processor/event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ def add_subprocessor(self, subprocessor: "EventProcessor"):
else:
self.processors[filter_with_rank] = processors

def __call__(self, event_filter: Filter, rank: int = 0):
"""Register a new processor with the given filter and rank.
:param event_filter: The filter for which to match events
:param rank: This processor's rank (when there are multiple matches for a single event)
"""
return self.processor(event_filter, rank)

def processor(self, event_filter: Filter, rank: int = 0):
"""Register a new processor with the given filter and rank.
Expand Down
10 changes: 10 additions & 0 deletions src/tests/test_event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ def a_test():
assert len(event_processor.processors) == 1


def test_callable_processor_registers_a_processor(event_processor):
filter_ = Exists("a")

@event_processor(filter_)
def a_test():
pass

assert len(event_processor.processors) == 1


def test_processor_registers_multiple_processors_with_identical_filters(event_processor):
filter_a = Accept()
filter_b = Accept()
Expand Down

0 comments on commit 65115f4

Please sign in to comment.