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.
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/LICENSE +4 -4
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/PKG-INFO +1 -1
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/README.md +185 -185
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/pyproject.toml +45 -45
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/setup.cfg +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/__init__.py +14 -14
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/callbacks_strategies/__init__.py +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/callbacks_strategies/base.py +7 -7
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/callbacks_strategies/postgres_callback.py +68 -68
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/core.py +162 -158
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/decorators.py +25 -25
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/default_logger.py +24 -24
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/discovery.py +43 -43
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/events.py +34 -34
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/exceptions.py +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/handlers.py +49 -49
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/init_triggers_strategies/__init__.py +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/init_triggers_strategies/base.py +13 -13
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/init_triggers_strategies/postgres_init_triggers.py +114 -102
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/registry.py +14 -14
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/types.py +40 -40
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/utils.py +35 -35
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/PKG-INFO +1 -1
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/SOURCES.txt +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/dependency_links.txt +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/requires.txt +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events_lib.egg-info/top_level.txt +0 -0
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/conftest.py +55 -55
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_autodiscover.py +146 -146
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_handlers.py +78 -78
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_is_created_triggers.py +48 -41
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_models.py +15 -15
- {sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/tests/test_sa_events.py +26 -26
- {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.
|
|
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.
|
|
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"]
|
|
File without changes
|
{sqlalchemy_events_lib-0.3.2 → sqlalchemy_events_lib-0.3.4}/src/sqlalchemy_events/__init__.py
RENAMED
|
@@ -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.
|
|
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'
|
|
File without changes
|
|
@@ -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)
|