sqlalchemy-events-lib 0.1.0__tar.gz → 0.2.0__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 (35) hide show
  1. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/LICENSE +0 -0
  2. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/PKG-INFO +200 -179
  3. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/README.md +35 -17
  4. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/pyproject.toml +45 -35
  5. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/setup.cfg +4 -4
  6. sqlalchemy_events_lib-0.2.0/src/sqlalchemy_events/__init__.py +14 -0
  7. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/callbacks_strategies/__init__.py +0 -0
  8. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/callbacks_strategies/base.py +0 -0
  9. sqlalchemy_events_lib-0.2.0/src/sqlalchemy_events/callbacks_strategies/postgres_callback.py +69 -0
  10. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/core.py +44 -22
  11. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/decorators.py +2 -2
  12. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/discovery.py +0 -0
  13. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/events.py +1 -1
  14. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/exceptions.py +0 -0
  15. sqlalchemy_events_lib-0.2.0/src/sqlalchemy_events/handlers.py +50 -0
  16. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/init_triggers_strategies/__init__.py +0 -0
  17. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/init_triggers_strategies/base.py +0 -0
  18. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/init_triggers_strategies/postgres_init_triggers.py +30 -16
  19. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/registry.py +0 -0
  20. sqlalchemy_events_lib-0.2.0/src/sqlalchemy_events/types.py +26 -0
  21. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events/utils.py +12 -2
  22. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events_lib.egg-info/PKG-INFO +200 -179
  23. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events_lib.egg-info/SOURCES.txt +7 -3
  24. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events_lib.egg-info/dependency_links.txt +0 -0
  25. sqlalchemy_events_lib-0.2.0/src/sqlalchemy_events_lib.egg-info/requires.txt +5 -0
  26. {sqlalchemy_events_lib-0.1.0 → sqlalchemy_events_lib-0.2.0}/src/sqlalchemy_events_lib.egg-info/top_level.txt +1 -0
  27. sqlalchemy_events_lib-0.2.0/src/tests/conftest.py +50 -0
  28. sqlalchemy_events_lib-0.2.0/src/tests/test_autodiscover.py +129 -0
  29. sqlalchemy_events_lib-0.2.0/src/tests/test_is_created_triggers.py +42 -0
  30. sqlalchemy_events_lib-0.2.0/src/tests/test_models.py +16 -0
  31. sqlalchemy_events_lib-0.1.0/src/sqlalchemy_events/__init__.py +0 -8
  32. sqlalchemy_events_lib-0.1.0/src/sqlalchemy_events/callbacks_strategies/postgres_callback.py +0 -28
  33. sqlalchemy_events_lib-0.1.0/src/sqlalchemy_events/enums.py +0 -15
  34. sqlalchemy_events_lib-0.1.0/src/sqlalchemy_events/handler.py +0 -38
  35. sqlalchemy_events_lib-0.1.0/src/sqlalchemy_events_lib.egg-info/requires.txt +0 -1
@@ -1,179 +1,200 @@
1
- Metadata-Version: 2.4
2
- Name: sqlalchemy-events-lib
3
- Version: 0.1.0
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
- Author-email: Alexey Kostarev <normjkeeewm@gmail.com>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/NormanwOw/SQLAlchemy-Events
8
- Project-URL: Repository, https://github.com/NormanwOw/SQLAlchemy-Events
9
- Project-URL: Issues, https://github.com/NormanwOw/SQLAlchemy-Events/issues
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.12
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: sqlalchemy>=2.0.49
18
- Dynamic: license-file
19
-
20
- # SQLAlchemy Events
21
-
22
- ## About
23
- Event-driven extension for SQLAlchemy that enables listening to database CUD events.
24
- This library allows you to react to database changes in real time using a clean, declarative API.
25
- * **Currently supports PostgreSQL only**
26
-
27
-
28
- ## Installation
29
-
30
- ```bash
31
- $ pip install sqlalchemy-events-lib
32
- ```
33
- ## Quick start
34
-
35
-
36
- ### Define models with enabled event tracking using **`@with_events`**
37
- models.py
38
- ```python
39
- import uuid
40
-
41
- from sqlalchemy import UUID
42
- from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
43
-
44
- from sqlalchemy_events import with_events, SaEvent
45
-
46
-
47
- class Base(DeclarativeBase):
48
- id: Mapped[uuid.UUID] = mapped_column(
49
- UUID, nullable=False, primary_key=True, default=uuid.uuid4
50
- )
51
-
52
- @with_events([SaEvent.INSERT, SaEvent.UPDATE, SaEvent.DELETE])
53
- class UserModel(Base):
54
- __tablename__ = 'users'
55
-
56
- name: Mapped[str] = mapped_column()
57
- ```
58
- ___
59
-
60
-
61
- ### Define event handlers using decorator `@sa_event_handler`
62
- services/handlers.py
63
- ```python
64
- from models import UserModel
65
- from sqlalchemy_events import sa_event_handler
66
-
67
- @sa_event_handler.on_insert(UserModel)
68
- async def handle_user_insert():
69
- print("User inserted!")
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
-
78
- from config import DATABASE_URL
79
-
80
- engine = create_async_engine(DATABASE_URL)
81
- async_session = async_sessionmaker(engine, expire_on_commit=False, autoflush=False)
82
-
83
- ```
84
- ___
85
-
86
- ### Initialize event system and start listening
87
- main.py
88
- ```python
89
- import asyncio
90
- from sqlalchemy_events import SQLAlchemyEvents
91
- from models import Base
92
- from session import engine
93
-
94
-
95
- async def main():
96
- asyncio.create_task(
97
- SQLAlchemyEvents(
98
- base=Base,
99
- engine=engine,
100
- autodiscover_paths=['services']
101
- ).init()
102
- )
103
- try:
104
- await asyncio.sleep(9999)
105
- except:
106
- pass
107
-
108
- if __name__ == '__main__':
109
- asyncio.run(main())
110
- ```
111
-
112
- ## Configuration
113
- ### SQLAlchemyEvents
114
-
115
- The SQLAlchemyEvents class accepts the following parameters:
116
- ```python
117
- SQLAlchemyEvents(
118
- base,
119
- engine,
120
- autodiscover_paths,
121
- logger=None
122
- )
123
- ```
124
-
125
- Parameters:
126
- * **base** - SQLAlchemy declarative base class used to discover mapped models.
127
- * **engine** - SQLAlchemy Engine or AsyncEngine instance.
128
- * **autodiscover_paths** - List of Python module paths where event handlers are defined.
129
- These modules are automatically imported so that decorators such as `@sa_event_handler` are executed. Example:`autodiscover_paths=["services", "app.handlers"]`
130
-
131
- ### Important:
132
-
133
- All modules containing event handlers must be imported through autodiscover
134
- This ensures decorator registration is executed at startup
135
-
136
- ### logger (optional)
137
- A standard Python logging.Logger instance.
138
-
139
- If provided, the library will log internal lifecycle events such as:
140
-
141
- * successful initialization
142
- * listener startup
143
- * trigger setup
144
-
145
- **Example:**
146
- ```python
147
- import logging
148
-
149
- logger = logging.getLogger("sqlalchemy_events")
150
- logger.setLevel(logging.INFO)
151
-
152
- SQLAlchemyEvents(
153
- base=Base,
154
- engine=engine,
155
- autodiscover_paths=["services"],
156
- logger=logger
157
- )
158
- ```
159
-
160
- ## How it works
161
- 1. autodiscover_paths modules are imported at startup
162
- 2. Decorators register event handlers into a global registry
163
- 3. Triggers send events via LISTEN/NOTIFY
164
- 4. The library receives notifications and dispatches them to registered handlers
165
-
166
- ## Notes
167
- * Handlers can be both async and regular functions
168
- * Only models registered with @with_events will emit events
169
- * Currently supports LISTEN/NOTIFY
170
- * Ensure handlers are imported via autodiscover_paths, otherwise they will not be registered
171
-
172
- ## Example flow
173
- INSERT INTO {table} → DATABASE trigger fires →
174
- NOTIFY → Python listener receives event →
175
- handler is executed
176
-
177
- ## License
178
-
179
- MIT
1
+ Metadata-Version: 2.4
2
+ Name: sqlalchemy-events-lib
3
+ Version: 0.2.0
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
+ Author-email: Alexey Kostarev <normjkeeewm@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/NormanwOw/SQLAlchemy-Events
8
+ Project-URL: Repository, https://github.com/NormanwOw/SQLAlchemy-Events
9
+ Project-URL: Issues, https://github.com/NormanwOw/SQLAlchemy-Events/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: sqlalchemy>=2.0.49
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest; extra == "test"
20
+ Requires-Dist: pytest-asyncio; extra == "test"
21
+ Dynamic: license-file
22
+
23
+ # SQLAlchemy Events
24
+
25
+ ## About
26
+ Event-driven extension for SQLAlchemy that enables listening to database CUD events.
27
+ This library allows you to react to database changes in real time using a clean, declarative API.
28
+ * **Currently supports PostgreSQL only**
29
+
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ $ pip install sqlalchemy-events-lib
35
+ ```
36
+ ## Quick start
37
+
38
+
39
+ ### Define models with enabled event tracking using **`@with_events`**
40
+ models.py
41
+ ```python
42
+ import uuid
43
+
44
+ from sqlalchemy import UUID
45
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
46
+
47
+ from sqlalchemy_events import with_events, SaEvent
48
+
49
+
50
+ class Base(DeclarativeBase):
51
+ id: Mapped[uuid.UUID] = mapped_column(
52
+ UUID, nullable=False, primary_key=True, default=uuid.uuid4
53
+ )
54
+
55
+ @with_events([SaEvent.INSERT, SaEvent.UPDATE, SaEvent.DELETE])
56
+ class UserModel(Base):
57
+ __tablename__ = 'users'
58
+
59
+ name: Mapped[str] = mapped_column()
60
+ ```
61
+ ___
62
+
63
+
64
+ ### Define event handlers using decorators `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler`
65
+ services/handlers.py
66
+ ```python
67
+ from models import UserModel
68
+ from sqlalchemy_events import sa_insert_handler
69
+
70
+ @sa_insert_handler(UserModel)
71
+ async def handle_user_insert():
72
+ print('User inserted!')
73
+ ```
74
+
75
+ **Optional Argument**: `rows`
76
+ Handlers can optionally accept a rows argument.
77
+
78
+ If the parameter is declared in the function signature, it will automatically receive a list of affected `row` IDs.
79
+
80
+ ```python
81
+ from models import UserModel
82
+ from sqlalchemy_events import sa_insert_handler
83
+
84
+ @sa_insert_handler(UserModel)
85
+ async def handle_user_insert(rows: list[DB_ID]):
86
+ print('Users inserted!', rows)
87
+ ```
88
+ **Output:**
89
+ ```
90
+ Users inserted! ['3310aa38-f555-4c05-a57f-5acae32a0a7b', '6aeba1b0-8e80-4369-8adf-cf967ab7ba7a']
91
+ ```
92
+
93
+ ___
94
+
95
+ ### Configure SQLAlchemy async engine
96
+ session.py
97
+ ```python
98
+ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
99
+
100
+ from config import DATABASE_URL
101
+
102
+ engine = create_async_engine(DATABASE_URL)
103
+ async_session = async_sessionmaker(engine, expire_on_commit=False, autoflush=False)
104
+
105
+ ```
106
+ ___
107
+
108
+ ### Initialize event system and start listening
109
+ main.py
110
+ ```python
111
+ import asyncio
112
+ from sqlalchemy_events import SQLAlchemyEvents
113
+ from models import Base
114
+ from session import engine
115
+
116
+
117
+ async def main():
118
+ SQLAlchemyEvents(
119
+ base=Base,
120
+ engine=engine,
121
+ autodiscover_paths=['services']
122
+ )
123
+ while True:
124
+ await asyncio.sleep(9999)
125
+
126
+ if __name__ == '__main__':
127
+ asyncio.run(main())
128
+ ```
129
+
130
+ ## Configuration
131
+ ### SQLAlchemyEvents
132
+
133
+ The SQLAlchemyEvents class accepts the following parameters:
134
+ ```python
135
+ SQLAlchemyEvents(
136
+ base,
137
+ engine,
138
+ autodiscover_paths,
139
+ logger=None
140
+ )
141
+ ```
142
+
143
+ Parameters:
144
+ * **base** - SQLAlchemy declarative base class used to discover mapped models.
145
+ * **engine** - SQLAlchemy Engine or AsyncEngine instance.
146
+ * **autodiscover_paths** - List of Python module paths where event handlers are defined.
147
+ These modules are automatically imported so that decorators such as
148
+ `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler` are executed.
149
+ **Example:**
150
+ `autodiscover_paths=["services", "app.handlers"]`
151
+
152
+ ### Important:
153
+
154
+ All modules containing event handlers must be imported through autodiscover
155
+ This ensures decorator registration is executed at startup
156
+
157
+ ### logger (optional)
158
+ A standard Python logging.Logger instance.
159
+
160
+ If provided, the library will log internal lifecycle events such as:
161
+
162
+ * successful initialization
163
+ * listener startup
164
+ * trigger setup
165
+
166
+ **Example:**
167
+ ```python
168
+ import logging
169
+
170
+ logger = logging.getLogger('sqlalchemy_events')
171
+ logger.setLevel(logging.INFO)
172
+
173
+ SQLAlchemyEvents(
174
+ base=Base,
175
+ engine=engine,
176
+ autodiscover_paths=['services'],
177
+ logger=logger
178
+ )
179
+ ```
180
+
181
+ ## How it works
182
+ 1. autodiscover_paths modules are imported at startup
183
+ 2. Decorators register event handlers into a global registry
184
+ 3. Triggers send events via LISTEN/NOTIFY
185
+ 4. The library receives notifications and dispatches them to registered handlers
186
+
187
+ ## Notes
188
+ * Handlers can be both async and regular functions
189
+ * Only models registered with @with_events will emit events
190
+ * Currently supports LISTEN/NOTIFY
191
+ * Ensure handlers are imported via autodiscover_paths, otherwise they will not be registered
192
+
193
+ ## Example flow
194
+ INSERT INTO/UPDATE/DELETE FROM {table} → DATABASE trigger fires →
195
+ NOTIFY → Python listener receives event →
196
+ handler is executed
197
+
198
+ ## License
199
+
200
+ MIT
@@ -39,16 +39,35 @@ class UserModel(Base):
39
39
  ___
40
40
 
41
41
 
42
- ### Define event handlers using decorator `@sa_event_handler`
42
+ ### Define event handlers using decorators `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler`
43
43
  services/handlers.py
44
44
  ```python
45
45
  from models import UserModel
46
- from sqlalchemy_events import sa_event_handler
46
+ from sqlalchemy_events import sa_insert_handler
47
47
 
48
- @sa_event_handler.on_insert(UserModel)
48
+ @sa_insert_handler(UserModel)
49
49
  async def handle_user_insert():
50
- print("User inserted!")
50
+ print('User inserted!')
51
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
+
52
71
  ___
53
72
 
54
73
  ### Configure SQLAlchemy async engine
@@ -74,17 +93,13 @@ from session import engine
74
93
 
75
94
 
76
95
  async def main():
77
- asyncio.create_task(
78
- SQLAlchemyEvents(
79
- base=Base,
80
- engine=engine,
81
- autodiscover_paths=['services']
82
- ).init()
96
+ SQLAlchemyEvents(
97
+ base=Base,
98
+ engine=engine,
99
+ autodiscover_paths=['services']
83
100
  )
84
- try:
101
+ while True:
85
102
  await asyncio.sleep(9999)
86
- except:
87
- pass
88
103
 
89
104
  if __name__ == '__main__':
90
105
  asyncio.run(main())
@@ -107,7 +122,10 @@ Parameters:
107
122
  * **base** - SQLAlchemy declarative base class used to discover mapped models.
108
123
  * **engine** - SQLAlchemy Engine or AsyncEngine instance.
109
124
  * **autodiscover_paths** - List of Python module paths where event handlers are defined.
110
- These modules are automatically imported so that decorators such as `@sa_event_handler` are executed. Example:`autodiscover_paths=["services", "app.handlers"]`
125
+ These modules are automatically imported so that decorators such as
126
+ `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler` are executed.
127
+ **Example:**
128
+ `autodiscover_paths=["services", "app.handlers"]`
111
129
 
112
130
  ### Important:
113
131
 
@@ -127,13 +145,13 @@ If provided, the library will log internal lifecycle events such as:
127
145
  ```python
128
146
  import logging
129
147
 
130
- logger = logging.getLogger("sqlalchemy_events")
148
+ logger = logging.getLogger('sqlalchemy_events')
131
149
  logger.setLevel(logging.INFO)
132
150
 
133
151
  SQLAlchemyEvents(
134
152
  base=Base,
135
153
  engine=engine,
136
- autodiscover_paths=["services"],
154
+ autodiscover_paths=['services'],
137
155
  logger=logger
138
156
  )
139
157
  ```
@@ -151,7 +169,7 @@ SQLAlchemyEvents(
151
169
  * Ensure handlers are imported via autodiscover_paths, otherwise they will not be registered
152
170
 
153
171
  ## Example flow
154
- INSERT INTO {table} → DATABASE trigger fires →
172
+ INSERT INTO/UPDATE/DELETE FROM {table} → DATABASE trigger fires →
155
173
  NOTIFY → Python listener receives event →
156
174
  handler is executed
157
175
 
@@ -1,35 +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.1.0"
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
-
32
- [project.urls]
33
- Homepage = "https://github.com/NormanwOw/SQLAlchemy-Events"
34
- Repository = "https://github.com/NormanwOw/SQLAlchemy-Events"
35
- Issues = "https://github.com/NormanwOw/SQLAlchemy-Events/issues"
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.2.0"
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,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +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.2.0'
@@ -0,0 +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
+
69
+ await asyncio.gather(*tasks)