sqlalchemy-events-lib 0.3.2__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/LICENSE +4 -4
  2. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/PKG-INFO +1 -1
  3. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/README.md +185 -185
  4. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/pyproject.toml +45 -45
  5. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/setup.cfg +0 -0
  6. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/__init__.py +14 -14
  7. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/callbacks_strategies/__init__.py +0 -0
  8. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/callbacks_strategies/base.py +7 -7
  9. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/callbacks_strategies/postgres_callback.py +68 -68
  10. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/core.py +162 -158
  11. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/decorators.py +25 -25
  12. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/default_logger.py +24 -24
  13. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/discovery.py +43 -43
  14. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/events.py +34 -34
  15. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/exceptions.py +0 -0
  16. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/handlers.py +49 -49
  17. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/init_triggers_strategies/__init__.py +0 -0
  18. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/init_triggers_strategies/base.py +13 -13
  19. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/init_triggers_strategies/postgres_init_triggers.py +114 -102
  20. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/registry.py +14 -14
  21. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/types.py +40 -40
  22. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/utils.py +35 -35
  23. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/PKG-INFO +1 -1
  24. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/SOURCES.txt +0 -0
  25. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/dependency_links.txt +0 -0
  26. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/requires.txt +0 -0
  27. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/top_level.txt +0 -0
  28. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/conftest.py +55 -55
  29. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_autodiscover.py +146 -146
  30. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_handlers.py +78 -78
  31. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_is_created_triggers.py +48 -41
  32. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_models.py +15 -15
  33. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_sa_events.py +26 -26
  34. {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_with_events_deco.py +44 -44
@@ -1,5 +1,5 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Alexey Kostarev
4
-
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexey Kostarev
4
+
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlalchemy-events-lib
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Event-driven extension for SQLAlchemy that enables listening to database CUD events. This library allows you to react to database changes in real time using a clean, declarative API.
5
5
  Author-email: Alexey Kostarev <normjkeeewm@gmail.com>
6
6
  License: MIT
@@ -1,186 +1,186 @@
1
- # SQLAlchemy Events
2
-
3
- ## About
4
- Event-driven extension for SQLAlchemy that enables listening to database CUD events.
5
- This library allows you to react to database changes in real time using a clean, declarative API.
6
- * **Currently supports PostgreSQL only**
7
-
8
-
9
- ## Installation
10
-
11
- ```bash
12
- $ pip install sqlalchemy-events-lib
13
- ```
14
- ## Quick start
15
-
16
-
17
- ### Define models with enabled event tracking using **`@with_events`**
18
- models.py
19
- ```python
20
- import uuid
21
-
22
- from sqlalchemy import UUID
23
- from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
24
-
25
- from sqlalchemy_events import with_events, SaEvent
26
-
27
-
28
- class Base(DeclarativeBase):
29
- id: Mapped[uuid.UUID] = mapped_column(
30
- UUID, nullable=False, primary_key=True, default=uuid.uuid4
31
- )
32
-
33
- @with_events([SaEvent.INSERT, SaEvent.UPDATE, SaEvent.DELETE])
34
- class UserModel(Base):
35
- __tablename__ = 'users'
36
-
37
- name: Mapped[str] = mapped_column()
38
- ```
39
- ___
40
-
41
-
42
- ### Define event handlers using decorators `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler`
43
- services/handlers.py
44
- ```python
45
- from models import UserModel
46
- from sqlalchemy_events import sa_insert_handler
47
-
48
- @sa_insert_handler(UserModel)
49
- async def handle_user_insert():
50
- print('User inserted!')
51
- ```
52
-
53
- **Optional Argument**: `rows`
54
- Handlers can optionally accept a rows argument.
55
-
56
- If the parameter is declared in the function signature, it will automatically receive a list of affected `row` IDs.
57
-
58
- ```python
59
- from models import UserModel
60
- from sqlalchemy_events import sa_insert_handler
61
-
62
- @sa_insert_handler(UserModel)
63
- async def handle_user_insert(rows: list[DB_ID]):
64
- print('Users inserted!', rows)
65
- ```
66
- **Output:**
67
- ```
68
- Users inserted! ['3310aa38-f555-4c05-a57f-5acae32a0a7b', '6aeba1b0-8e80-4369-8adf-cf967ab7ba7a']
69
- ```
70
-
71
- ___
72
-
73
- ### Configure SQLAlchemy async engine
74
- session.py
75
- ```python
76
- from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
77
- from config import DATABASE_URL
78
-
79
- engine = create_async_engine(DATABASE_URL)
80
- async_session = async_sessionmaker(engine, expire_on_commit=False, autoflush=False)
81
-
82
- ```
83
- ___
84
-
85
- ### Initialize event system and start listening
86
- main.py
87
- ```python
88
- import asyncio
89
- from sqlalchemy_events import SQLAlchemyEvents
90
- from session import engine
91
-
92
-
93
- async def main():
94
- sa_events = SQLAlchemyEvents(
95
- engine=engine,
96
- autodiscover_paths=['services']
97
- )
98
- await sa_events()
99
- while True:
100
- await asyncio.sleep(9999)
101
-
102
- if __name__ == '__main__':
103
- asyncio.run(main())
104
- ```
105
-
106
- ## Configuration
107
- ### SQLAlchemyEvents
108
-
109
- The SQLAlchemyEvents class accepts the following parameters:
110
- ```python
111
- SQLAlchemyEvents(
112
- engine,
113
- autodiscover_paths,
114
- logger=None,
115
- verbose=True
116
- )
117
- ```
118
-
119
- Parameters:
120
- * **engine** - SQLAlchemy Engine or AsyncEngine instance.
121
- * **autodiscover_paths** - List of Python module paths where event handlers are defined.
122
- These modules are automatically imported so that decorators such as
123
- `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler` are executed.
124
- **Example:**
125
- `autodiscover_paths=["services", "app.handlers"]`
126
-
127
- * **verbose** - Enables detailed logging output. When set to True, the library will log additional informational and warning messages to help with debugging and configuration.
128
-
129
- ### Important:
130
-
131
- All modules containing event handlers must be imported through autodiscover
132
- This ensures decorator registration is executed at startup
133
-
134
- ### logger (optional)
135
- A standard Python logging.Logger instance.
136
-
137
- If provided, the library will log internal lifecycle events such as:
138
-
139
- * successful initialization
140
- * listener startup
141
- * trigger setup
142
-
143
- **Example:**
144
- ```python
145
- import logging
146
- import asyncio
147
- from sqlalchemy_events import SQLAlchemyEvents
148
- from session import engine
149
-
150
- logger = logging.getLogger('sqlalchemy_events')
151
- logger.setLevel(logging.INFO)
152
-
153
- async def main():
154
- sa_events = SQLAlchemyEvents(
155
- engine=engine,
156
- autodiscover_paths=['services'],
157
- logger=logger
158
- )
159
- await sa_events()
160
- while True:
161
- await asyncio.sleep(9999)
162
-
163
- if __name__ == '__main__':
164
- asyncio.run(main())
165
- ```
166
-
167
- ## How it works
168
- 1. autodiscover_paths modules are imported at startup
169
- 2. Decorators register event handlers into a global registry
170
- 3. Triggers send events via LISTEN/NOTIFY
171
- 4. The library receives notifications and dispatches them to registered handlers
172
-
173
- ## Notes
174
- * Handlers can be both async and regular functions
175
- * Only models registered with @with_events will emit events
176
- * Currently supports LISTEN/NOTIFY
177
- * Ensure handlers are imported via autodiscover_paths, otherwise they will not be registered
178
-
179
- ## Example flow
180
- INSERT INTO/UPDATE/DELETE FROM {table} → DATABASE trigger fires →
181
- NOTIFY → Python listener receives event →
182
- handler is executed
183
-
184
- ## License
185
-
1
+ # SQLAlchemy Events
2
+
3
+ ## About
4
+ Event-driven extension for SQLAlchemy that enables listening to database CUD events.
5
+ This library allows you to react to database changes in real time using a clean, declarative API.
6
+ * **Currently supports PostgreSQL only**
7
+
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ $ pip install sqlalchemy-events-lib
13
+ ```
14
+ ## Quick start
15
+
16
+
17
+ ### Define models with enabled event tracking using **`@with_events`**
18
+ models.py
19
+ ```python
20
+ import uuid
21
+
22
+ from sqlalchemy import UUID
23
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
24
+
25
+ from sqlalchemy_events import with_events, SaEvent
26
+
27
+
28
+ class Base(DeclarativeBase):
29
+ id: Mapped[uuid.UUID] = mapped_column(
30
+ UUID, nullable=False, primary_key=True, default=uuid.uuid4
31
+ )
32
+
33
+ @with_events([SaEvent.INSERT, SaEvent.UPDATE, SaEvent.DELETE])
34
+ class UserModel(Base):
35
+ __tablename__ = 'users'
36
+
37
+ name: Mapped[str] = mapped_column()
38
+ ```
39
+ ___
40
+
41
+
42
+ ### Define event handlers using decorators `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler`
43
+ services/handlers.py
44
+ ```python
45
+ from models import UserModel
46
+ from sqlalchemy_events import sa_insert_handler
47
+
48
+ @sa_insert_handler(UserModel)
49
+ async def handle_user_insert():
50
+ print('User inserted!')
51
+ ```
52
+
53
+ **Optional Argument**: `rows`
54
+ Handlers can optionally accept a rows argument.
55
+
56
+ If the parameter is declared in the function signature, it will automatically receive a list of affected `row` IDs.
57
+
58
+ ```python
59
+ from models import UserModel
60
+ from sqlalchemy_events import sa_insert_handler
61
+
62
+ @sa_insert_handler(UserModel)
63
+ async def handle_user_insert(rows: list[DB_ID]):
64
+ print('Users inserted!', rows)
65
+ ```
66
+ **Output:**
67
+ ```
68
+ Users inserted! ['3310aa38-f555-4c05-a57f-5acae32a0a7b', '6aeba1b0-8e80-4369-8adf-cf967ab7ba7a']
69
+ ```
70
+
71
+ ___
72
+
73
+ ### Configure SQLAlchemy async engine
74
+ session.py
75
+ ```python
76
+ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
77
+ from config import DATABASE_URL
78
+
79
+ engine = create_async_engine(DATABASE_URL)
80
+ async_session = async_sessionmaker(engine, expire_on_commit=False, autoflush=False)
81
+
82
+ ```
83
+ ___
84
+
85
+ ### Initialize event system and start listening
86
+ main.py
87
+ ```python
88
+ import asyncio
89
+ from sqlalchemy_events import SQLAlchemyEvents
90
+ from session import engine
91
+
92
+
93
+ async def main():
94
+ sa_events = SQLAlchemyEvents(
95
+ engine=engine,
96
+ autodiscover_paths=['services']
97
+ )
98
+ await sa_events()
99
+ while True:
100
+ await asyncio.sleep(9999)
101
+
102
+ if __name__ == '__main__':
103
+ asyncio.run(main())
104
+ ```
105
+
106
+ ## Configuration
107
+ ### SQLAlchemyEvents
108
+
109
+ The SQLAlchemyEvents class accepts the following parameters:
110
+ ```python
111
+ SQLAlchemyEvents(
112
+ engine,
113
+ autodiscover_paths,
114
+ logger=None,
115
+ verbose=True
116
+ )
117
+ ```
118
+
119
+ Parameters:
120
+ * **engine** - SQLAlchemy Engine or AsyncEngine instance.
121
+ * **autodiscover_paths** - List of Python module paths where event handlers are defined.
122
+ These modules are automatically imported so that decorators such as
123
+ `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler` are executed.
124
+ **Example:**
125
+ `autodiscover_paths=["services", "app.handlers"]`
126
+
127
+ * **verbose** - Enables detailed logging output. When set to True, the library will log additional informational and warning messages to help with debugging and configuration.
128
+
129
+ ### Important:
130
+
131
+ All modules containing event handlers must be imported through autodiscover
132
+ This ensures decorator registration is executed at startup
133
+
134
+ ### logger (optional)
135
+ A standard Python logging.Logger instance.
136
+
137
+ If provided, the library will log internal lifecycle events such as:
138
+
139
+ * successful initialization
140
+ * listener startup
141
+ * trigger setup
142
+
143
+ **Example:**
144
+ ```python
145
+ import logging
146
+ import asyncio
147
+ from sqlalchemy_events import SQLAlchemyEvents
148
+ from session import engine
149
+
150
+ logger = logging.getLogger('sqlalchemy_events')
151
+ logger.setLevel(logging.INFO)
152
+
153
+ async def main():
154
+ sa_events = SQLAlchemyEvents(
155
+ engine=engine,
156
+ autodiscover_paths=['services'],
157
+ logger=logger
158
+ )
159
+ await sa_events()
160
+ while True:
161
+ await asyncio.sleep(9999)
162
+
163
+ if __name__ == '__main__':
164
+ asyncio.run(main())
165
+ ```
166
+
167
+ ## How it works
168
+ 1. autodiscover_paths modules are imported at startup
169
+ 2. Decorators register event handlers into a global registry
170
+ 3. Triggers send events via LISTEN/NOTIFY
171
+ 4. The library receives notifications and dispatches them to registered handlers
172
+
173
+ ## Notes
174
+ * Handlers can be both async and regular functions
175
+ * Only models registered with @with_events will emit events
176
+ * Currently supports LISTEN/NOTIFY
177
+ * Ensure handlers are imported via autodiscover_paths, otherwise they will not be registered
178
+
179
+ ## Example flow
180
+ INSERT INTO/UPDATE/DELETE FROM {table} → DATABASE trigger fires →
181
+ NOTIFY → Python listener receives event →
182
+ handler is executed
183
+
184
+ ## License
185
+
186
186
  MIT
@@ -1,45 +1,45 @@
1
- [build-system]
2
- requires = ["setuptools>=68", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [tool.setuptools]
6
- package-dir = {"" = "src"}
7
-
8
- [tool.setuptools.packages.find]
9
- where = ["src"]
10
-
11
- [project]
12
- name = "sqlalchemy-events-lib"
13
- version = "0.3.2"
14
- description = "Event-driven extension for SQLAlchemy that enables listening to database CUD events. This library allows you to react to database changes in real time using a clean, declarative API."
15
- requires-python = ">=3.12"
16
- authors = [
17
- {name = "Alexey Kostarev", email = "normjkeeewm@gmail.com"}
18
- ]
19
- dependencies = [
20
- "sqlalchemy>=2.0.49",
21
- ]
22
- readme = {file = "README.md", content-type = "text/markdown"}
23
- license = {text = "MIT"}
24
- classifiers = [
25
- "Programming Language :: Python :: 3",
26
- "Programming Language :: Python :: 3.12",
27
- "License :: OSI Approved :: MIT License",
28
- "Operating System :: OS Independent",
29
- ]
30
- keywords = []
31
- [project.optional-dependencies]
32
- test = [
33
- "pytest",
34
- "pytest-asyncio",
35
- ]
36
-
37
- [project.urls]
38
- Homepage = "https://github.com/NormanwOw/SQLAlchemy-Events"
39
- Repository = "https://github.com/NormanwOw/SQLAlchemy-Events"
40
- Issues = "https://github.com/NormanwOw/SQLAlchemy-Events/issues"
41
-
42
- [tool.pytest.ini_options]
43
- asyncio_mode = "auto"
44
- testpaths = ["src/tests"]
45
- pythonpath = ["src"]
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools]
6
+ package-dir = {"" = "src"}
7
+
8
+ [tool.setuptools.packages.find]
9
+ where = ["src"]
10
+
11
+ [project]
12
+ name = "sqlalchemy-events-lib"
13
+ version = "0.3.4"
14
+ description = "Event-driven extension for SQLAlchemy that enables listening to database CUD events. This library allows you to react to database changes in real time using a clean, declarative API."
15
+ requires-python = ">=3.12"
16
+ authors = [
17
+ {name = "Alexey Kostarev", email = "normjkeeewm@gmail.com"}
18
+ ]
19
+ dependencies = [
20
+ "sqlalchemy>=2.0.49",
21
+ ]
22
+ readme = {file = "README.md", content-type = "text/markdown"}
23
+ license = {text = "MIT"}
24
+ classifiers = [
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.12",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ ]
30
+ keywords = []
31
+ [project.optional-dependencies]
32
+ test = [
33
+ "pytest",
34
+ "pytest-asyncio",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/NormanwOw/SQLAlchemy-Events"
39
+ Repository = "https://github.com/NormanwOw/SQLAlchemy-Events"
40
+ Issues = "https://github.com/NormanwOw/SQLAlchemy-Events/issues"
41
+
42
+ [tool.pytest.ini_options]
43
+ asyncio_mode = "auto"
44
+ testpaths = ["src/tests"]
45
+ pythonpath = ["src"]
@@ -1,14 +1,14 @@
1
- from .core import SQLAlchemyEvents
2
- from .decorators import with_events
3
- from .handlers import sa_insert_handler, sa_update_handler, sa_delete_handler
4
- from .types import SaEvent
5
-
6
- __all__ = [
7
- 'SQLAlchemyEvents',
8
- 'sa_insert_handler',
9
- 'sa_update_handler',
10
- 'sa_delete_handler',
11
- 'with_events',
12
- 'SaEvent'
13
- ]
14
- __version__ = '0.3.2'
1
+ from .core import SQLAlchemyEvents
2
+ from .decorators import with_events
3
+ from .handlers import sa_insert_handler, sa_update_handler, sa_delete_handler
4
+ from .types import SaEvent
5
+
6
+ __all__ = [
7
+ 'SQLAlchemyEvents',
8
+ 'sa_insert_handler',
9
+ 'sa_update_handler',
10
+ 'sa_delete_handler',
11
+ 'with_events',
12
+ 'SaEvent'
13
+ ]
14
+ __version__ = '0.3.4'
@@ -1,8 +1,8 @@
1
- from abc import ABC, abstractmethod
2
-
3
-
4
- class SaEventsCallbacksStrategy(ABC):
5
-
6
- @abstractmethod
7
- async def handle(self, *args, **kwargs):
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class SaEventsCallbacksStrategy(ABC):
5
+
6
+ @abstractmethod
7
+ async def handle(self, *args, **kwargs):
8
8
  raise NotImplementedError
@@ -1,69 +1,69 @@
1
- import asyncio
2
- import json
3
- import inspect
4
- import signal
5
- import sys
6
-
7
- from .base import SaEventsCallbacksStrategy
8
- from ..registry import get_event_handlers
9
- from ..types import Handler
10
- from ..utils import _process_pool
11
-
12
-
13
- signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0))
14
-
15
-
16
- class PostgresCallback(SaEventsCallbacksStrategy):
17
-
18
- def __init__(self) -> None:
19
- self.__handlers = get_event_handlers()
20
-
21
- async def handle(self, *args, **kwargs):
22
- data = json.loads(args[3] or '{}')
23
- trigger = data['op']
24
- rows = data['rows']
25
- trig_name = f'sa_{data['table']}_{trigger}_notify'
26
- handlers: list[Handler] = self.__handlers.get(trig_name, [])
27
- if not handlers:
28
- return
29
-
30
- loop = asyncio.get_running_loop()
31
- tasks = []
32
-
33
- for handler in handlers:
34
- func = handler.func
35
- sig = inspect.signature(func)
36
-
37
- params = list(sig.parameters.values())
38
- rows_index = None
39
-
40
- for i, p in enumerate(params):
41
- if p.name == 'rows':
42
- rows_index = i
43
- break
44
-
45
- def build_args():
46
- args = []
47
- kwargs = {}
48
-
49
- if rows_index is not None:
50
- if not any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params):
51
- args.insert(rows_index, rows)
52
- else:
53
- kwargs['rows'] = rows
54
- return args, kwargs
55
-
56
- args, kwargs = build_args()
57
-
58
- if asyncio.iscoroutinefunction(func):
59
- tasks.append(func(*args, **kwargs))
60
- else:
61
- tasks.append(
62
- loop.run_in_executor(
63
- _process_pool,
64
- func,
65
- *args,
66
- )
67
- )
68
-
1
+ import asyncio
2
+ import json
3
+ import inspect
4
+ import signal
5
+ import sys
6
+
7
+ from .base import SaEventsCallbacksStrategy
8
+ from ..registry import get_event_handlers
9
+ from ..types import Handler
10
+ from ..utils import _process_pool
11
+
12
+
13
+ signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0))
14
+
15
+
16
+ class PostgresCallback(SaEventsCallbacksStrategy):
17
+
18
+ def __init__(self) -> None:
19
+ self.__handlers = get_event_handlers()
20
+
21
+ async def handle(self, *args, **kwargs):
22
+ data = json.loads(args[3] or '{}')
23
+ trigger = data['op']
24
+ rows = data['rows']
25
+ trig_name = f'sa_{data['table']}_{trigger}_notify'
26
+ handlers: list[Handler] = self.__handlers.get(trig_name, [])
27
+ if not handlers:
28
+ return
29
+
30
+ loop = asyncio.get_running_loop()
31
+ tasks = []
32
+
33
+ for handler in handlers:
34
+ func = handler.func
35
+ sig = inspect.signature(func)
36
+
37
+ params = list(sig.parameters.values())
38
+ rows_index = None
39
+
40
+ for i, p in enumerate(params):
41
+ if p.name == 'rows':
42
+ rows_index = i
43
+ break
44
+
45
+ def build_args():
46
+ args = []
47
+ kwargs = {}
48
+
49
+ if rows_index is not None:
50
+ if not any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params):
51
+ args.insert(rows_index, rows)
52
+ else:
53
+ kwargs['rows'] = rows
54
+ return args, kwargs
55
+
56
+ args, kwargs = build_args()
57
+
58
+ if asyncio.iscoroutinefunction(func):
59
+ tasks.append(func(*args, **kwargs))
60
+ else:
61
+ tasks.append(
62
+ loop.run_in_executor(
63
+ _process_pool,
64
+ func,
65
+ *args,
66
+ )
67
+ )
68
+
69
69
  return await asyncio.gather(*tasks)