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

Add listener_runner to context object to enable developers to leverage lazy listeners in middleware #1142

Merged

Conversation

seratch
Copy link
Member

@seratch seratch commented Aug 30, 2024

This pull request adds "listener_runner," a foundational module for running listener matchers/middleware and lazy listeners in Bolt apps, to the context object. This enhancement enables third-party app developers to start lazy listeners within global custom middleware. Without it, developers need to manually pass the App's current listener_runner to their global middleware, which is quite inflexible and hard to maintain.

Additionally, our new project (the one our team members should know but we cannot publicly mention yet!) requires this functionality.

To see what the use case looks like, please take a look at the test code in this PR. While the LazyListenerStarter middleware in test code is quite simple, the actual use case would involve creating a more complex object that can accept a few functions and dispatch requests to the relevant ones. If none of the functions matches, it would pass the request to other middleware and listeners by calling next().

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Document pages under /docs
  • Others

Requirements (place an x in each [ ])

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

  • I've read and understood the Contributing Guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've run ./scripts/install_all_and_run_tests.sh after making the changes.

@seratch seratch added this to the 1.20.2 milestone Aug 30, 2024
@seratch seratch self-assigned this Aug 30, 2024
Copy link

codecov bot commented Aug 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.02%. Comparing base (64eedee) to head (e82649d).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1142      +/-   ##
==========================================
+ Coverage   91.99%   92.02%   +0.02%     
==========================================
  Files         195      195              
  Lines        6621     6631      +10     
==========================================
+ Hits         6091     6102      +11     
+ Misses        530      529       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -14,6 +14,5 @@ then
black slack_bolt/ tests/ && \
pytest -vv $1
else
black slack_bolt/ tests/ && pytest
fi
Copy link
Member Author

Choose a reason for hiding this comment

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

unrelated minor bug in the script

@@ -735,7 +735,7 @@ def step(
elif not isinstance(step, WorkflowStep):
raise BoltError(f"Invalid step object ({type(step)})")

self.use(WorkflowStepMiddleware(step, self.listener_runner))
self.use(WorkflowStepMiddleware(step))
Copy link
Member Author

Choose a reason for hiding this comment

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

While steps from apps is no longer supported, this change shows the benefit well

@@ -13,9 +12,8 @@
class AsyncWorkflowStepMiddleware(AsyncMiddleware):
"""Base middleware for step from app specific ones"""

def __init__(self, step: AsyncWorkflowStep, listener_runner: AsyncioListenerRunner):
Copy link
Member Author

Choose a reason for hiding this comment

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

While this is a public class, I am sure that no developer relies on this inconvenient constructor, and more importantly steps from apps is no longer available for Workflow Builder. Thus, this minor breaking change is okay but others feel we should avoid this, I am happy to revert it.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's a good thing to note, in case we get issues filed on the repo as a result. Technically from an API perspective, if I understand correctly, the current version of bolt-python is then the last one to support workflow steps from apps as-is.

def lazy_listener(self):
self.lazy_called = True

def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]) -> Optional[BoltResponse]:
Copy link
Member Author

Choose a reason for hiding this comment

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

As you can see, this is not so simple and easy, but for advanced developers who want to take full control (including myself), the enhancement by this PR would be greatly valuable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow this does provide full control

@seratch seratch requested a review from filmaj August 30, 2024 07:05
@staticmethod
def _build_lazy_request(request: BoltRequest, lazy_func_name: str) -> BoltRequest:
copied_request = create_copy(request.to_copyable())
copied_request.method = "NONE"
Copy link
Member Author

Choose a reason for hiding this comment

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

mypy detected this property is not defined in the request class and actually the property is never used

@staticmethod
def _build_lazy_request(request: AsyncBoltRequest, lazy_func_name: str) -> AsyncBoltRequest:
copied_request = create_copy(request.to_copyable())
copied_request.method = "NONE"
Copy link
Member Author

Choose a reason for hiding this comment

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

mypy detected this property is not defined in the request class and actually the property is never used

Copy link
Contributor

@filmaj filmaj left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for the review request.

@@ -13,9 +12,8 @@
class AsyncWorkflowStepMiddleware(AsyncMiddleware):
"""Base middleware for step from app specific ones"""

def __init__(self, step: AsyncWorkflowStep, listener_runner: AsyncioListenerRunner):
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a good thing to note, in case we get issues filed on the repo as a result. Technically from an API perspective, if I understand correctly, the current version of bolt-python is then the last one to support workflow steps from apps as-is.

Copy link
Contributor

@WilliamBergamin WilliamBergamin left a comment

Choose a reason for hiding this comment

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

Looks good 💯 thank you for the context provided with the PR

@WilliamBergamin WilliamBergamin merged commit 8188956 into slackapi:main Aug 30, 2024
12 checks passed
@seratch seratch deleted the provide-listener-runner-to-context branch September 2, 2024 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants