sqlalchemy-events-lib 0.3.3__tar.gz → 0.3.5__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.3.3 → sqlalchemy_events_lib-0.3.5}/LICENSE +4 -4
  2. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/PKG-INFO +3 -1
  3. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/README.md +187 -185
  4. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/pyproject.toml +45 -45
  5. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/setup.cfg +0 -0
  6. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/__init__.py +14 -14
  7. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/callbacks_strategies/__init__.py +0 -0
  8. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/callbacks_strategies/base.py +7 -7
  9. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/callbacks_strategies/postgres_callback.py +63 -68
  10. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/core.py +162 -158
  11. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/decorators.py +25 -25
  12. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/default_logger.py +24 -24
  13. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/discovery.py +43 -43
  14. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/events.py +34 -34
  15. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/exceptions.py +0 -0
  16. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/handlers.py +49 -49
  17. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/init_triggers_strategies/__init__.py +0 -0
  18. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/init_triggers_strategies/base.py +13 -13
  19. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/init_triggers_strategies/postgres_init_triggers.py +114 -99
  20. sqlalchemy_events_lib-0.3.5/src/sqlalchemy_events/py.typed +0 -0
  21. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/registry.py +14 -14
  22. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/types.py +40 -40
  23. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events/utils.py +35 -35
  24. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events_lib.egg-info/PKG-INFO +3 -1
  25. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events_lib.egg-info/SOURCES.txt +1 -0
  26. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events_lib.egg-info/dependency_links.txt +0 -0
  27. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events_lib.egg-info/requires.txt +0 -0
  28. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/sqlalchemy_events_lib.egg-info/top_level.txt +0 -0
  29. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/tests/conftest.py +55 -55
  30. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/tests/test_autodiscover.py +146 -146
  31. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/tests/test_handlers.py +78 -78
  32. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/tests/test_is_created_triggers.py +48 -48
  33. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/tests/test_models.py +15 -15
  34. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/src/tests/test_sa_events.py +26 -26
  35. {sqlalchemy_events_lib-0.3.3 → sqlalchemy_events_lib-0.3.5}/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
3
+ Version: 0.3.5
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
@@ -133,6 +133,7 @@ The SQLAlchemyEvents class accepts the following parameters:
133
133
  SQLAlchemyEvents(
134
134
  engine,
135
135
  autodiscover_paths,
136
+ schema='',
136
137
  logger=None,
137
138
  verbose=True
138
139
  )
@@ -146,6 +147,7 @@ These modules are automatically imported so that decorators such as
146
147
  **Example:**
147
148
  `autodiscover_paths=["services", "app.handlers"]`
148
149
 
150
+ * **schema** - Custom database schema
149
151
  * **verbose** - Enables detailed logging output. When set to True, the library will log additional informational and warning messages to help with debugging and configuration.
150
152
 
151
153
  ### Important:
@@ -1,186 +1,188 @@
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
+ schema='',
115
+ logger=None,
116
+ verbose=True
117
+ )
118
+ ```
119
+
120
+ Parameters:
121
+ * **engine** - SQLAlchemy Engine or AsyncEngine instance.
122
+ * **autodiscover_paths** - List of Python module paths where event handlers are defined.
123
+ These modules are automatically imported so that decorators such as
124
+ `@sa_insert_handler`, `@sa_update_handler`, `@sa_delete_handler` are executed.
125
+ **Example:**
126
+ `autodiscover_paths=["services", "app.handlers"]`
127
+
128
+ * **schema** - Custom database schema
129
+ * **verbose** - Enables detailed logging output. When set to True, the library will log additional informational and warning messages to help with debugging and configuration.
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
+ import asyncio
149
+ from sqlalchemy_events import SQLAlchemyEvents
150
+ from session import engine
151
+
152
+ logger = logging.getLogger('sqlalchemy_events')
153
+ logger.setLevel(logging.INFO)
154
+
155
+ async def main():
156
+ sa_events = SQLAlchemyEvents(
157
+ engine=engine,
158
+ autodiscover_paths=['services'],
159
+ logger=logger
160
+ )
161
+ await sa_events()
162
+ while True:
163
+ await asyncio.sleep(9999)
164
+
165
+ if __name__ == '__main__':
166
+ asyncio.run(main())
167
+ ```
168
+
169
+ ## How it works
170
+ 1. autodiscover_paths modules are imported at startup
171
+ 2. Decorators register event handlers into a global registry
172
+ 3. Triggers send events via LISTEN/NOTIFY
173
+ 4. The library receives notifications and dispatches them to registered handlers
174
+
175
+ ## Notes
176
+ * Handlers can be both async and regular functions
177
+ * Only models registered with @with_events will emit events
178
+ * Currently supports LISTEN/NOTIFY
179
+ * Ensure handlers are imported via autodiscover_paths, otherwise they will not be registered
180
+
181
+ ## Example flow
182
+ INSERT INTO/UPDATE/DELETE FROM {table} → DATABASE trigger fires →
183
+ NOTIFY → Python listener receives event →
184
+ handler is executed
185
+
186
+ ## License
187
+
186
188
  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.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.5"
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.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.5'
@@ -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