-
-
Notifications
You must be signed in to change notification settings - Fork 63
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
Middleware feature #1292
Comments
Aha, you assume that the There's a 3rd alternative (not saying it's better, but I think it's simpler): async def middleware(process_job: ProcessTask, context: job_context.JobContext):
# Do something before the job is processed
result = await process_job(context)
# Do something after the job is processed
return result (your implementation highlight the fact that we will always have the same The 3 implementation put the focus on the idea that the middleware could modify the job_context (well, it's immutable, but it could send a different instance). I'm still trying to try and think whether it's a good idea. A 4th alternative could be: async def middleware(process_job: ..., context: job_context.JobContext):
# Do something before the job is processed
result = await process_job()
# Do something after the job is processed
return result (note that not much value would be lost if we didn't bother returning the job's result) (Or even, 5th: async def middleware(process_job: Awaitable, context: job_context.JobContext):
# Do something before the job is processed
result = await process_job
# Do something after the job is processed
return result but in this case, in the event one would choose not to execute the task, they'd get a Warning for awaitble never awaited) I think my favorite one might be the 3rd, but I'm open to discussion if you think the 2nd (your preferred?) is better. |
So, you mean that the context we pass to the middleware is not the same context we internally use to process the task? We re-create the context in our
Yes, I have taken the Django middleware as a model (https://docs.djangoproject.com/en/5.1/topics/http/middleware/#writing-your-own-middleware) and thought it would be a good fit as our If we recreate the |
I am not sure that rate limiting is best solved by middleware. If all we want with the middleware is to inject logic before/after the task, I would lean towards what @ewjoachim labels option 3. async def middleware(next: ProcessTask, context: job_context.JobContext):
# Do something before the job is processed
result = await next(context)
# Do something after the job is processed
return result I would be interested to see a few examples of useful middleware. |
Things we currently do by monkey patching procrastinate that this could be useful for:
I could also envision us using middleware to wrap certain tasks in an asyncio timeout. |
Yes, trying different implementations, I came to the same conclusion. I even wonder if a middleware feature is flexible enough or if we should not better implement several callback hooks the user can plug into, e.g.:
Or both, a middleware that wraps the job processing and a |
My preference for rate limiting is to make it a distinct feature that is implemented at the DB level. I will try something out and report back so we have something to discuss. In the meantime, the middleware feature could be limited to chaining functions as part of task processing, which would help with examples shared above. |
Yes, a dedicated rate-limiting feature probably makes the most sense. And then provide a rate limit per task? |
@onlyann my only concern with the chaining approach is that it will make it harder to use python's native context managers, which in my experience is a large part of the usefulness of middleware-style patterns. |
Context managers from Can you please give me an example where it wouldn't work with middleware? |
Sorry, I might have misinterpreted the above conversation - it seemed that the discussion had turned to implementing distinct before/after callbacks rather than a single middleware interface (i.e. your original sketch here). If that's still the plan then ignore me 😄 |
After thinking about it even more, I will implement a middleware that wraps the |
If this is what is wrapped, there will be no ability for a middleware to modify the outcome of a task. |
What would you suggest? Wrap the |
Yes. That would be similar to the hand rolled middleware from the docs. |
The main reason why I wrap |
In #1262 and #1289 we mentioned that a middleware feature would be helpful in some scenarios. In the
middleware
branch, I have already started to implement it, but I would like to discuss the implementation (@ewjoachim). I use a similar implementation to Django for its middleware. I would like to discuss two API alternatives.After some thought, 2) is better because
process_job
also wraps the part where the job status is saved back to the db. This is especially important if we allow the user to raise aStopWorker
exception (#1262 (comment)).One thing that bothers me a bit is that in both scenarios, the
context
was already created, and thereby astart_timestamp
is already set. So when the user adds some pause in the middleware (e.g. for #1289) before the job is processed, this pause will be included in the overall job duration. I'm not sure if this is what we want or not.The text was updated successfully, but these errors were encountered: