-
Notifications
You must be signed in to change notification settings - Fork 13
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
Allow to add context to payload #73
Allow to add context to payload #73
Conversation
Thanks @romank0. I will do my best to look at this and your other PRs this coming week. |
@@ -18,6 +18,9 @@ Channels | |||
Listeners | |||
--------- | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a heads up that I'm currently having troubles deploying updates to readthedocs. The webhook was accidentally removed, and there seems to be no way of getting it back as django-pgpubsub does not appear as a project on my personal readthedocs (even after transferring this library to my own github. I'm trying to get the owner of Opus10 to help me out here, but until then we won't get readthedocs updates unfortunately.
docs/payload_context.rst
Outdated
set_notification_context({'some-key': 'some-value'}) | ||
|
||
The setting is effective till the connection is closed. Alternatively the setting | ||
``PGPUBSUB_TX_BOUND_NOTIFICATION_CONTEXT`` can be used to clean the context at the end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't fully understand the naming PGPUBSUB_TX_BOUND_NOTIFICATION_CONTEXT
here. What does "TX_BOUND" mean? I guess it means "transaction bound" - maybe just a comment in the docs explaining that. Should we also mention what kind of values this setting can take? It makes sense it's a boolean, but when we see context being set as a dict just above, it could be confusing to some.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TX_BOUND means "bound to the transaction" as oposed to bound to the connection. When the context is bound to the transaction it is cleared in the end of the transaction. This config param is a boolean, I'll update this document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TX is common abbreviation for transaction
, I can rename this to be PGPUBSUB_NOTIFICATION_CONTEXT_BOUND_TO_TRANSACTION
if you think this is better.
docs/payload_context.rst
Outdated
Filter by ``context`` field in the trigger listener | ||
--------------------------------------------------- | ||
|
||
Define a class that implements ``ListenerFilterProvider`` protocol and set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be "implements the ..."
return Q(payload__context__tenant='my-tenant') | ||
|
||
# django settings | ||
PGPUBSUB_LISTENER_FILTER = 'myapp.whatever.TenantListenerFilterProvider' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this implicitly imply we can only have one filter provider at a time? If so that seems a little restrictive - I would think there could be many cases that we'd want one listener to filter by one key/value, and another by another key/value. I can see how this could be a good global filter if we wanted one, so it's a good start in that regard.
I think a bit more of versatile API would be something like
@pgpubsub.post_insert_listener(AuthorTriggerChannel, context_filter=TenantListenerFilterProvider())
def create_first_post_for_author(
old: Author, new: Author, context: Dict[str, Any]
):
Such an API would allow different filters per listener. It could also be used to imply that listener with non null context_filter is automatically accepting contexts, hence removing the need for PGPUBSUB_PASS_CONTEXT_TO_LISTENERS. Of course PGPUBSUB_PASS_CONTEXT_TO_LISTENERS is good as a global back up as is the other setting.
I guess we can think of this is an addition to what we have here, so not a blocker. Just thought I'd mention to see what you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true that the filter provider is global and the current implementation is restrictive in this regard. I don't see a use-case for non global filter provider though.
The only use case I have at hand and the motivation for this PR is the deployments where one DB is shared by multilple tenants but each tenant has it's own listener process and needs to process only notifications about changes done by that tenant. For this scenario the global filter provider works fine. In order to generalize this I need at least one real life example how multiple filter providers would be used to design a convenient API.
Re PGPUBSUB_PASS_CONTEXT_TO_LISTENERS
I think it is possible to get rid of it completely by analyzing the signature of the listener callback but I haven't tried that.
|
||
class TenantListenerFilterProvider(ListenerFilterProvider): | ||
def get_filter(self) -> Q: | ||
return Q(payload__context__tenant='my-tenant') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will all only work for channels with lock_notifications
right (i.e. with stored notifications)? Should we mention that somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point.
else: | ||
scope = 'SESSION' | ||
cursor.execute( | ||
f'SET {scope} pgpubsub.notification_context = %s', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little bit confused here so probably a stupid question. What is actually being set here - where does pgpubsub.notification_context
live in the db?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a custom parameter. They are like custom defined settings that are stored either for a connection (scope='SESSION') or for a transaction (scope='LOCAL').
pgpubsub/models.py
Outdated
@@ -1,4 +1,4 @@ | |||
from typing import Type | |||
from typing import Optional, Type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, removing
@@ -26,6 +30,11 @@ def _build_payload(self, model): | |||
payload := '{{"app": "{model._meta.app_label}", "model": "{model.__name__}"}}'::jsonb; | |||
payload := jsonb_insert(payload, '{{old}}', COALESCE(to_jsonb(OLD), 'null')); | |||
payload := jsonb_insert(payload, '{{new}}', COALESCE(to_jsonb(NEW), 'null')); | |||
SELECT current_setting('pgpubsub.notification_context', True) INTO notification_context_text; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, for some reason I'm confused what pgpubsub.notification_context is here (and also what current_setting is).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
current_setting
is a built in function (see docs) that allows to get the value of the setting. In this case the custom one named pgpubsub.notification_context
.
return [ | ||
('payload', 'JSONB'), | ||
('notification_context', 'JSONB'), | ||
('notification_context_text', 'TEXT'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need both for the backwards compatibility reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's just an oversight from my side. I'll remove unused variable
@romank0 Thanks for another great PR. I've left some comments, mainly me just trying to understand and clarify some things. I'm thinking with an addition of a feature this size and with the help you provide in fixing bugs, it makes sense to list you as a primary author of the library and also to give you more privileges on the repo to help maintain it. If that's something you'd be interested in, please let me know and I'll see what options there are. |
Here's an extract from the docs:
Sometimes it is beneficial to pass some contextual information from the trigger
to the trigger listener along the payload. Examples are:
This can be done by using Payload Context. This feature includes:
context
fields in the listener callbacksNote that this is based on #66 and incorporates that PR.