sqlnotify 0.1.0__tar.gz → 0.1.1__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.
- {sqlnotify-0.1.0/src/sqlnotify.egg-info → sqlnotify-0.1.1}/PKG-INFO +3 -3
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/README.md +2 -2
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/pyproject.toml +4 -5
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/constants.py +2 -2
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/notifiers/notifier.py +10 -2
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/utils.py +71 -3
- {sqlnotify-0.1.0 → sqlnotify-0.1.1/src/sqlnotify.egg-info}/PKG-INFO +3 -3
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/LICENSE +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/setup.cfg +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/__init__.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/adapters/__init__.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/adapters/asgi.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/dialects/__init__.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/dialects/base.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/dialects/postgresql.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/dialects/sqlite.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/dialects/utils.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/exceptions.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/logger.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/notifiers/__init__.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/notifiers/base.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/types.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify/watcher.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify.egg-info/SOURCES.txt +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify.egg-info/dependency_links.txt +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify.egg-info/entry_points.txt +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify.egg-info/requires.txt +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/src/sqlnotify.egg-info/top_level.txt +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_postgres_dialect.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_postgres_notifier.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_postgres_notifier_asgi_adapter.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_sqlite_dialect.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_sqlite_notifier.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_sqlite_notifier_asgi_adapter.py +0 -0
- {sqlnotify-0.1.0 → sqlnotify-0.1.1}/tests/test_watcher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlnotify
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: A near real-time SQL notification library for database changes, supporting PostgreSQL and SQLite.
|
|
5
5
|
Author-email: Daniel Brai <danielbrai.dev@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -359,7 +359,7 @@ await notifier.anotify(
|
|
|
359
359
|
# notifier.notify(User, Operation.UPDATE, payload={"id": 123})
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
-
**Note**: The `notify`/`anotify` methods validate payload size and can use overflow tables for large payloads. If `use_overflow_table=True`, payloads exceeding
|
|
362
|
+
**Note**: The `notify`/`anotify` methods validate payload size and can use overflow tables for large payloads. If `use_overflow_table=True`, payloads exceeding SQLNotify limit are automatically stored in the overflow table, and only an overflow ID is sent through the notification channel.
|
|
363
363
|
|
|
364
364
|
### Model Change Detection
|
|
365
365
|
|
|
@@ -584,7 +584,7 @@ notifier.start() # Synchronous start
|
|
|
584
584
|
2. **Trigger Creation** - The dialect creates database-specific triggers on your tables
|
|
585
585
|
3. **Change Detection** - When data changes, triggers fire and call notification functions
|
|
586
586
|
4. **NOTIFY** - Functions use database-native notification mechanisms (e.g., PostgreSQL's NOTIFY)
|
|
587
|
-
5. **LISTEN** - SQLNotify maintains a dedicated connection listening for notifications
|
|
587
|
+
5. **LISTEN** - SQLNotify maintains a dedicated connection listening for notifications or polls for changes (SQLite)
|
|
588
588
|
6. **Event Distribution** - Incoming notifications are routed to subscribed callbacks
|
|
589
589
|
7. **Callback Execution** - Your subscriber functions are called with change events
|
|
590
590
|
|
|
@@ -323,7 +323,7 @@ await notifier.anotify(
|
|
|
323
323
|
# notifier.notify(User, Operation.UPDATE, payload={"id": 123})
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
-
**Note**: The `notify`/`anotify` methods validate payload size and can use overflow tables for large payloads. If `use_overflow_table=True`, payloads exceeding
|
|
326
|
+
**Note**: The `notify`/`anotify` methods validate payload size and can use overflow tables for large payloads. If `use_overflow_table=True`, payloads exceeding SQLNotify limit are automatically stored in the overflow table, and only an overflow ID is sent through the notification channel.
|
|
327
327
|
|
|
328
328
|
### Model Change Detection
|
|
329
329
|
|
|
@@ -548,7 +548,7 @@ notifier.start() # Synchronous start
|
|
|
548
548
|
2. **Trigger Creation** - The dialect creates database-specific triggers on your tables
|
|
549
549
|
3. **Change Detection** - When data changes, triggers fire and call notification functions
|
|
550
550
|
4. **NOTIFY** - Functions use database-native notification mechanisms (e.g., PostgreSQL's NOTIFY)
|
|
551
|
-
5. **LISTEN** - SQLNotify maintains a dedicated connection listening for notifications
|
|
551
|
+
5. **LISTEN** - SQLNotify maintains a dedicated connection listening for notifications or polls for changes (SQLite)
|
|
552
552
|
6. **Event Distribution** - Incoming notifications are routed to subscribed callbacks
|
|
553
553
|
7. **Callback Execution** - Your subscriber functions are called with change events
|
|
554
554
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlnotify"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
description = "A near real-time SQL notification library for database changes, supporting PostgreSQL and SQLite."
|
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
6
|
license = "MIT"
|
|
@@ -44,14 +44,13 @@ dependencies = ["sqlalchemy>=2.0.0", "asyncpg>=0.31.0"]
|
|
|
44
44
|
[dependency-groups]
|
|
45
45
|
test = [
|
|
46
46
|
"pre-commit>=4.5.1",
|
|
47
|
-
"coverage>=7.13.
|
|
47
|
+
"coverage>=7.13.4",
|
|
48
48
|
"pytest>=9.0.2",
|
|
49
49
|
"pytest-cov>=7.0.0",
|
|
50
50
|
"pytest-xdist>=3.8.0",
|
|
51
51
|
"pytest-asyncio>=1.3.0",
|
|
52
|
-
"psycopg[binary]
|
|
53
|
-
"sqlmodel
|
|
54
|
-
"httpx>=0.28.0",
|
|
52
|
+
"psycopg[binary]==3.3.2",
|
|
53
|
+
"sqlmodel==0.0.33",
|
|
55
54
|
]
|
|
56
55
|
|
|
57
56
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PACKAGE_NAME = "sqlnotify"
|
|
2
2
|
|
|
3
|
-
MAX_SQLNOTIFY_PAYLOAD_BYTES = 7999 #
|
|
3
|
+
MAX_SQLNOTIFY_PAYLOAD_BYTES = 7999 # SQLNotify payload limit (8000 - 1 for terminator)
|
|
4
4
|
|
|
5
|
-
MAX_SQLNOTIFY_IDENTIFER_BYTES = 63 #
|
|
5
|
+
MAX_SQLNOTIFY_IDENTIFER_BYTES = 63 # SQLNotify identifier limit (63 bytes)
|
|
6
6
|
|
|
7
7
|
MAX_SQLNOTIFY_EXTRA_COLUMNS = 5 # Limit extra columns to help stay within payload size limit, but this is not a hard limit since column data size can vary greatly
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ from ..dialects import BaseDialect, get_dialect_for_engine
|
|
|
10
10
|
from ..exceptions import SQLNotifyConfigurationError
|
|
11
11
|
from ..logger import get_logger
|
|
12
12
|
from ..types import ChangeEvent, Operation
|
|
13
|
-
from ..utils import extract_database_url, strip_database_query_params
|
|
13
|
+
from ..utils import extract_database_url, strip_database_query_params, wrap_unhandled_error
|
|
14
14
|
from ..watcher import Watcher
|
|
15
15
|
from .base import BaseNotifier
|
|
16
16
|
|
|
@@ -135,6 +135,7 @@ class Notifier(BaseNotifier):
|
|
|
135
135
|
def is_running(self) -> bool:
|
|
136
136
|
return self._running
|
|
137
137
|
|
|
138
|
+
@wrap_unhandled_error(lambda self: self._logger)
|
|
138
139
|
def start(self) -> None:
|
|
139
140
|
"""
|
|
140
141
|
Start watching for database changes synchronously.
|
|
@@ -151,6 +152,7 @@ class Notifier(BaseNotifier):
|
|
|
151
152
|
|
|
152
153
|
self._start_sync()
|
|
153
154
|
|
|
155
|
+
@wrap_unhandled_error(lambda self: self._logger)
|
|
154
156
|
async def astart(self) -> None:
|
|
155
157
|
"""
|
|
156
158
|
Start watching for database changes asynchronously.
|
|
@@ -203,6 +205,7 @@ class Notifier(BaseNotifier):
|
|
|
203
205
|
if self._logger:
|
|
204
206
|
self._logger.info(f"SQLNotify Notifier started (sync mode, dialect: {self.dialect_name})")
|
|
205
207
|
|
|
208
|
+
@wrap_unhandled_error(lambda self: self._logger)
|
|
206
209
|
def stop(self) -> None:
|
|
207
210
|
"""
|
|
208
211
|
Stop watching for database changes synchronously
|
|
@@ -219,6 +222,7 @@ class Notifier(BaseNotifier):
|
|
|
219
222
|
|
|
220
223
|
self._stop_sync()
|
|
221
224
|
|
|
225
|
+
@wrap_unhandled_error(lambda self: self._logger)
|
|
222
226
|
async def astop(self) -> None:
|
|
223
227
|
"""
|
|
224
228
|
Stop watching for database changes asynchronously.
|
|
@@ -261,6 +265,7 @@ class Notifier(BaseNotifier):
|
|
|
261
265
|
if self._logger:
|
|
262
266
|
self._logger.info("SQLNotify Notifier stopped (sync mode)")
|
|
263
267
|
|
|
268
|
+
@wrap_unhandled_error(lambda self: self._logger, reraise=False)
|
|
264
269
|
def cleanup(self) -> None:
|
|
265
270
|
"""
|
|
266
271
|
Remove all triggers and functions from the database synchronously
|
|
@@ -277,6 +282,7 @@ class Notifier(BaseNotifier):
|
|
|
277
282
|
|
|
278
283
|
self._cleanup_sync()
|
|
279
284
|
|
|
285
|
+
@wrap_unhandled_error(lambda self: self._logger, reraise=False)
|
|
280
286
|
async def acleanup(self) -> None:
|
|
281
287
|
"""
|
|
282
288
|
Remove all triggers and functions from the database asynchronously.
|
|
@@ -311,6 +317,7 @@ class Notifier(BaseNotifier):
|
|
|
311
317
|
if self._logger:
|
|
312
318
|
self._logger.info("SQLNotify Notifier cleaned up all triggers and functions (sync mode)")
|
|
313
319
|
|
|
320
|
+
@wrap_unhandled_error(lambda self: self._logger)
|
|
314
321
|
def notify(
|
|
315
322
|
self,
|
|
316
323
|
model: type | str,
|
|
@@ -345,6 +352,7 @@ class Notifier(BaseNotifier):
|
|
|
345
352
|
|
|
346
353
|
self._notify_sync(watcher, payload, use_overflow_table)
|
|
347
354
|
|
|
355
|
+
@wrap_unhandled_error(lambda self: self._logger)
|
|
348
356
|
async def anotify(
|
|
349
357
|
self,
|
|
350
358
|
model: type | str,
|
|
@@ -580,7 +588,7 @@ class Notifier(BaseNotifier):
|
|
|
580
588
|
|
|
581
589
|
Args:
|
|
582
590
|
watcher (Watcher): The watcher that triggered
|
|
583
|
-
payload (dict): The notification payload
|
|
591
|
+
payload (dict[str, Any]): The notification payload
|
|
584
592
|
"""
|
|
585
593
|
|
|
586
594
|
if len(watcher.primary_keys) == 1:
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
1
7
|
from sqlalchemy.engine import Engine
|
|
2
8
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
3
9
|
|
|
4
10
|
from .constants import MAX_SQLNOTIFY_IDENTIFER_BYTES, MAX_SQLNOTIFY_PAYLOAD_BYTES
|
|
5
|
-
from .exceptions import SQLNotifyIdentifierSizeError, SQLNotifyPayloadSizeError
|
|
11
|
+
from .exceptions import SQLNotifyException, SQLNotifyIdentifierSizeError, SQLNotifyPayloadSizeError
|
|
6
12
|
|
|
7
13
|
|
|
8
14
|
def extract_database_url(engine: AsyncEngine | Engine) -> str:
|
|
9
15
|
"""
|
|
10
16
|
Extract the database URL from an engine for use with asyncpg LISTEN.
|
|
11
17
|
|
|
12
|
-
Converts SQLAlchemy URL format to
|
|
18
|
+
Converts SQLAlchemy URL format to raw database DSN.
|
|
13
19
|
|
|
14
20
|
Args:
|
|
15
21
|
engine (Union[AsyncEngine, Engine]): SQLAlchemy Engine or AsyncEngine
|
|
16
22
|
|
|
17
23
|
Returns:
|
|
18
|
-
str:
|
|
24
|
+
str: Database connection URL
|
|
19
25
|
"""
|
|
20
26
|
|
|
21
27
|
url = engine.url.render_as_string(hide_password=False)
|
|
@@ -163,3 +169,65 @@ def validate_identifier_size(
|
|
|
163
169
|
raise SQLNotifyIdentifierSizeError(combined)
|
|
164
170
|
|
|
165
171
|
return True
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def wrap_unhandled_error(
|
|
175
|
+
logger_getter: Callable[..., logging.Logger] | None = None,
|
|
176
|
+
reraise=True,
|
|
177
|
+
):
|
|
178
|
+
"""
|
|
179
|
+
Decorator factory that wraps unhandled exceptions and converts them to SQLNotifyException
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def _get_logger_from_call(*args, **kwargs):
|
|
183
|
+
if callable(logger_getter):
|
|
184
|
+
try:
|
|
185
|
+
return logger_getter(*args, **kwargs)
|
|
186
|
+
except Exception:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
return logger_getter
|
|
190
|
+
|
|
191
|
+
def decorator(func: Callable[..., Any]):
|
|
192
|
+
|
|
193
|
+
if inspect.iscoroutinefunction(func):
|
|
194
|
+
|
|
195
|
+
@wraps(func)
|
|
196
|
+
async def async_wrapper(*args, **kwargs):
|
|
197
|
+
try:
|
|
198
|
+
return await func(*args, **kwargs)
|
|
199
|
+
except SQLNotifyException:
|
|
200
|
+
raise
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger = _get_logger_from_call(*args, **kwargs)
|
|
203
|
+
if logger:
|
|
204
|
+
logger.exception(f"Unhandled exception in {func.__name__}")
|
|
205
|
+
|
|
206
|
+
if reraise:
|
|
207
|
+
raise SQLNotifyException(f"SQLNotify unhandled exception caught. Error: {str(e)}") from e
|
|
208
|
+
else:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
return async_wrapper
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
|
|
215
|
+
@wraps(func)
|
|
216
|
+
def sync_wrapper(*args, **kwargs):
|
|
217
|
+
try:
|
|
218
|
+
return func(*args, **kwargs)
|
|
219
|
+
except SQLNotifyException:
|
|
220
|
+
raise
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger = _get_logger_from_call(*args, **kwargs)
|
|
223
|
+
if logger:
|
|
224
|
+
logger.exception(f"Unhandled exception in {func.__name__}")
|
|
225
|
+
|
|
226
|
+
if reraise:
|
|
227
|
+
raise SQLNotifyException(f"SQLNotify unhandled exception caught. Error: {str(e)}") from e
|
|
228
|
+
else:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
return sync_wrapper
|
|
232
|
+
|
|
233
|
+
return decorator
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlnotify
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: A near real-time SQL notification library for database changes, supporting PostgreSQL and SQLite.
|
|
5
5
|
Author-email: Daniel Brai <danielbrai.dev@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -359,7 +359,7 @@ await notifier.anotify(
|
|
|
359
359
|
# notifier.notify(User, Operation.UPDATE, payload={"id": 123})
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
-
**Note**: The `notify`/`anotify` methods validate payload size and can use overflow tables for large payloads. If `use_overflow_table=True`, payloads exceeding
|
|
362
|
+
**Note**: The `notify`/`anotify` methods validate payload size and can use overflow tables for large payloads. If `use_overflow_table=True`, payloads exceeding SQLNotify limit are automatically stored in the overflow table, and only an overflow ID is sent through the notification channel.
|
|
363
363
|
|
|
364
364
|
### Model Change Detection
|
|
365
365
|
|
|
@@ -584,7 +584,7 @@ notifier.start() # Synchronous start
|
|
|
584
584
|
2. **Trigger Creation** - The dialect creates database-specific triggers on your tables
|
|
585
585
|
3. **Change Detection** - When data changes, triggers fire and call notification functions
|
|
586
586
|
4. **NOTIFY** - Functions use database-native notification mechanisms (e.g., PostgreSQL's NOTIFY)
|
|
587
|
-
5. **LISTEN** - SQLNotify maintains a dedicated connection listening for notifications
|
|
587
|
+
5. **LISTEN** - SQLNotify maintains a dedicated connection listening for notifications or polls for changes (SQLite)
|
|
588
588
|
6. **Event Distribution** - Incoming notifications are routed to subscribed callbacks
|
|
589
589
|
7. **Callback Execution** - Your subscriber functions are called with change events
|
|
590
590
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|