taskflow-meter 1.1.0__tar.gz → 1.3.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.
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/PKG-INFO +16 -9
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/README.md +9 -3
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/pyproject.toml +8 -7
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/_version.py +2 -2
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/sse.py +4 -1
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/listener.py +20 -1
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/base.py +4 -2
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/persistence.py +5 -13
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/__init__.py +2 -1
- taskflow_meter-1.3.0/taskflow_meter/datasource/sqlalchemy/migrations/env.py +115 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/source.py +66 -4
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/events.py +21 -3
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/fold.py +6 -1
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/meter.py +4 -2
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/models.py +20 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/base.py +6 -3
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/test_migrations.py +73 -5
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/integration/test_attach.py +47 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/datasource/sqlalchemy/test_source.py +20 -0
- taskflow_meter-1.1.0/taskflow_meter/datasource/sqlalchemy/migrations/env.py +0 -68
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/.gitignore +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/LICENSE +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/docs/design.md +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/docs/guide.md +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/docs/releasing.md +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/asgi.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/dispatch.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/http.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/router.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/routes.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/serializers.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/service.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/api/wsgi.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/cli.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/attachment.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/pipeline.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/progress.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/conf.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/django.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/fastapi.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/flask.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/paste.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/pecan.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/memory.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/migrations/script.py.mako +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/migrations/versions/0001_initial.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/models.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/diff.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/poller.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/py.typed +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/states.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/amqp.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/http.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/memory.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/oslo_messaging.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/asgi_client.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/conformance/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/conformance/test_hosts.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/conformance/test_parity.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/conftest.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/test_cross_process.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/test_examples.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/test_packaging.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/test_serve.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/functional/test_tooling.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/hosts.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/integration/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/integration/test_collector.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_asgi.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_http.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_router.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_routes.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_serializers.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_service.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_sse.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/api/test_wsgi.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/collect/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_attachment.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_listener.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_pipeline.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_progress.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/contrib/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_django.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_fastapi.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_flask.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_paste.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_pecan.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/datasource/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/datasource/sqlalchemy/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/datasource/test_base.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/datasource/test_memory.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/datasource/test_persistence.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_cli.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_conf.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_diff.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_events.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_meter.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_models.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_poller.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/test_states.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/transports/__init__.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_amqp.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_http.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_memory.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_oslo_messaging.py +0 -0
- {taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/tests/wsgi_client.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: taskflow-meter
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Monitoring interfaces (ASGI, WSGI, datasource) for OpenStack TaskFlow flow execution progress
|
|
5
|
-
Project-URL: Homepage, https://github.com/daipham3213/taskflow
|
|
6
|
-
Project-URL: Source, https://github.com/daipham3213/taskflow
|
|
7
|
-
Project-URL: Issues, https://github.com/daipham3213/taskflow
|
|
8
|
-
Project-URL: Changelog, https://github.com/daipham3213/taskflow
|
|
5
|
+
Project-URL: Homepage, https://github.com/daipham3213/taskflow.meter
|
|
6
|
+
Project-URL: Source, https://github.com/daipham3213/taskflow.meter
|
|
7
|
+
Project-URL: Issues, https://github.com/daipham3213/taskflow.meter/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/daipham3213/taskflow.meter/releases
|
|
9
9
|
Author-email: Pham Le Gia Dai <daipham.3213@gmail.com>
|
|
10
10
|
License-Expression: Apache-2.0
|
|
11
11
|
License-File: LICENSE
|
|
@@ -15,13 +15,14 @@ Classifier: Intended Audience :: Developers
|
|
|
15
15
|
Classifier: Intended Audience :: System Administrators
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
23
|
Classifier: Topic :: System :: Monitoring
|
|
23
24
|
Classifier: Typing :: Typed
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
25
26
|
Requires-Dist: oslo-config>=6.9.0
|
|
26
27
|
Requires-Dist: taskflow>=4.2.0
|
|
27
28
|
Provides-Extra: all
|
|
@@ -40,6 +41,12 @@ Description-Content-Type: text/markdown
|
|
|
40
41
|
|
|
41
42
|
# taskflow-meter
|
|
42
43
|
|
|
44
|
+
[](https://github.com/daipham3213/taskflow.meter/actions/workflows/ci.yml)
|
|
45
|
+
[](https://github.com/daipham3213/taskflow.meter/actions/workflows/conformance.yml)
|
|
46
|
+
[](https://pypi.org/project/taskflow-meter/)
|
|
47
|
+
[](https://pypi.org/project/taskflow-meter/)
|
|
48
|
+
[](LICENSE)
|
|
49
|
+
|
|
43
50
|
Monitoring interfaces — ASGI, WSGI, datasources, transports — for observing
|
|
44
51
|
[OpenStack TaskFlow](https://opendev.org/openstack/taskflow) flow execution
|
|
45
52
|
progress.
|
|
@@ -200,7 +207,7 @@ serving an empty stream that cannot be told apart from silence.
|
|
|
200
207
|
|
|
201
208
|
## Requirements
|
|
202
209
|
|
|
203
|
-
- Python 3.
|
|
210
|
+
- Python 3.10+
|
|
204
211
|
- taskflow 4.2.0+
|
|
205
212
|
- oslo.config 6.9.0+
|
|
206
213
|
|
|
@@ -263,8 +270,8 @@ CI also runs the suite with every declared dependency floor installed exactly,
|
|
|
263
270
|
which is the only job that checks those floors are real. To reproduce it:
|
|
264
271
|
|
|
265
272
|
```bash
|
|
266
|
-
uv lock --python 3.
|
|
267
|
-
uv sync --python 3.
|
|
273
|
+
uv lock --python 3.10 --resolution lowest-direct
|
|
274
|
+
uv sync --python 3.10 --group dev --all-extras --resolution lowest-direct
|
|
268
275
|
uv run --frozen --no-sync pytest
|
|
269
276
|
```
|
|
270
277
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# taskflow-meter
|
|
2
2
|
|
|
3
|
+
[](https://github.com/daipham3213/taskflow.meter/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/daipham3213/taskflow.meter/actions/workflows/conformance.yml)
|
|
5
|
+
[](https://pypi.org/project/taskflow-meter/)
|
|
6
|
+
[](https://pypi.org/project/taskflow-meter/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
3
9
|
Monitoring interfaces — ASGI, WSGI, datasources, transports — for observing
|
|
4
10
|
[OpenStack TaskFlow](https://opendev.org/openstack/taskflow) flow execution
|
|
5
11
|
progress.
|
|
@@ -160,7 +166,7 @@ serving an empty stream that cannot be told apart from silence.
|
|
|
160
166
|
|
|
161
167
|
## Requirements
|
|
162
168
|
|
|
163
|
-
- Python 3.
|
|
169
|
+
- Python 3.10+
|
|
164
170
|
- taskflow 4.2.0+
|
|
165
171
|
- oslo.config 6.9.0+
|
|
166
172
|
|
|
@@ -223,8 +229,8 @@ CI also runs the suite with every declared dependency floor installed exactly,
|
|
|
223
229
|
which is the only job that checks those floors are real. To reproduce it:
|
|
224
230
|
|
|
225
231
|
```bash
|
|
226
|
-
uv lock --python 3.
|
|
227
|
-
uv sync --python 3.
|
|
232
|
+
uv lock --python 3.10 --resolution lowest-direct
|
|
233
|
+
uv sync --python 3.10 --group dev --all-extras --resolution lowest-direct
|
|
228
234
|
uv run --frozen --no-sync pytest
|
|
229
235
|
```
|
|
230
236
|
|
|
@@ -7,7 +7,7 @@ name = "taskflow-meter"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Monitoring interfaces (ASGI, WSGI, datasource) for OpenStack TaskFlow flow execution progress"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [
|
|
@@ -20,6 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Intended Audience :: System Administrators",
|
|
21
21
|
"Operating System :: OS Independent",
|
|
22
22
|
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
23
24
|
"Programming Language :: Python :: 3.11",
|
|
24
25
|
"Programming Language :: Python :: 3.12",
|
|
25
26
|
"Programming Language :: Python :: 3.13",
|
|
@@ -51,10 +52,10 @@ all = [
|
|
|
51
52
|
taskflow-meter = "taskflow_meter.cli:main"
|
|
52
53
|
|
|
53
54
|
[project.urls]
|
|
54
|
-
Homepage = "https://github.com/daipham3213/taskflow
|
|
55
|
-
Source = "https://github.com/daipham3213/taskflow
|
|
56
|
-
Issues = "https://github.com/daipham3213/taskflow
|
|
57
|
-
Changelog = "https://github.com/daipham3213/taskflow
|
|
55
|
+
Homepage = "https://github.com/daipham3213/taskflow.meter"
|
|
56
|
+
Source = "https://github.com/daipham3213/taskflow.meter"
|
|
57
|
+
Issues = "https://github.com/daipham3213/taskflow.meter/issues"
|
|
58
|
+
Changelog = "https://github.com/daipham3213/taskflow.meter/releases"
|
|
58
59
|
|
|
59
60
|
# Plugin entry points are declared as their modules land -- one pointing at a
|
|
60
61
|
# module that does not exist yet breaks discovery for the whole group.
|
|
@@ -139,7 +140,7 @@ include = [
|
|
|
139
140
|
|
|
140
141
|
[tool.ruff]
|
|
141
142
|
line-length = 79
|
|
142
|
-
target-version = "
|
|
143
|
+
target-version = "py310"
|
|
143
144
|
src = ["."]
|
|
144
145
|
|
|
145
146
|
[tool.ruff.lint]
|
|
@@ -174,7 +175,7 @@ force-single-line = true
|
|
|
174
175
|
docstring-code-format = true
|
|
175
176
|
|
|
176
177
|
[tool.mypy]
|
|
177
|
-
python_version = "3.
|
|
178
|
+
python_version = "3.10"
|
|
178
179
|
strict = true
|
|
179
180
|
warn_unreachable = true
|
|
180
181
|
files = ["taskflow_meter", "tests", "tools", "examples"]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -218,5 +218,8 @@ async def aiter_frames(
|
|
|
218
218
|
await asyncio.sleep(interval)
|
|
219
219
|
else:
|
|
220
220
|
# Sleep, but wake immediately if the client leaves.
|
|
221
|
-
|
|
221
|
+
# asyncio.TimeoutError, spelled out: it is the builtin from
|
|
222
|
+
# 3.11 on, but a class of its own before that, so the bare
|
|
223
|
+
# name catches nothing on 3.10 and the sleep raises.
|
|
224
|
+
with contextlib.suppress(asyncio.TimeoutError):
|
|
222
225
|
await asyncio.wait_for(stop.wait(), interval)
|
|
@@ -28,8 +28,11 @@ import time
|
|
|
28
28
|
from collections.abc import Callable
|
|
29
29
|
from typing import Any
|
|
30
30
|
|
|
31
|
+
from taskflow import states
|
|
31
32
|
from taskflow.listeners import base as tf_listeners
|
|
33
|
+
from taskflow.types import failure as tf_failure
|
|
32
34
|
|
|
35
|
+
from taskflow_meter import models
|
|
33
36
|
from taskflow_meter.events import Event
|
|
34
37
|
from taskflow_meter.events import EventKind
|
|
35
38
|
from taskflow_meter.events import SequenceAllocator
|
|
@@ -100,9 +103,25 @@ class MeterListener(tf_listeners.Listener):
|
|
|
100
103
|
) -> None:
|
|
101
104
|
extra: dict[str, Any] = {}
|
|
102
105
|
if "result" in details:
|
|
106
|
+
result = details["result"]
|
|
103
107
|
# Results are arbitrary application objects and are not
|
|
104
108
|
# ours to copy around; their presence is the useful part.
|
|
105
|
-
extra["has_result"] =
|
|
109
|
+
extra["has_result"] = result is not None
|
|
110
|
+
# Except when the result *is* the failure, which is the one
|
|
111
|
+
# thing anybody watching a flow needs the detail of. Rendered
|
|
112
|
+
# in the same shape the persistence datasource reports, so a
|
|
113
|
+
# client cannot tell which producer it is reading.
|
|
114
|
+
if isinstance(result, tf_failure.Failure):
|
|
115
|
+
# Which one it is follows from the state, not from the
|
|
116
|
+
# intention: taskflow does not put an intention on these
|
|
117
|
+
# notifications, and only a revert that itself failed
|
|
118
|
+
# reaches REVERT_FAILURE.
|
|
119
|
+
key = (
|
|
120
|
+
"revert_failure"
|
|
121
|
+
if state == states.REVERT_FAILURE
|
|
122
|
+
else "failure"
|
|
123
|
+
)
|
|
124
|
+
extra[key] = models.failure_dict(result)
|
|
106
125
|
self._safely(
|
|
107
126
|
EventKind.ATOM_STATE,
|
|
108
127
|
state=state,
|
|
@@ -24,12 +24,14 @@ import abc
|
|
|
24
24
|
from collections.abc import Iterable
|
|
25
25
|
from dataclasses import dataclass
|
|
26
26
|
from types import TracebackType
|
|
27
|
-
from typing import
|
|
27
|
+
from typing import TypeVar
|
|
28
28
|
|
|
29
29
|
from taskflow_meter.events import Event
|
|
30
30
|
from taskflow_meter.models import AtomSnapshot
|
|
31
31
|
from taskflow_meter.models import FlowSnapshot
|
|
32
32
|
|
|
33
|
+
_D = TypeVar("_D", bound="DataSource")
|
|
34
|
+
|
|
33
35
|
#: Flows returned by a single unqualified listing.
|
|
34
36
|
DEFAULT_FLOW_LIMIT = 50
|
|
35
37
|
|
|
@@ -96,7 +98,7 @@ class DataSource(abc.ABC):
|
|
|
96
98
|
def stop(self) -> None: # noqa: B027 - optional hook, not abstract
|
|
97
99
|
"""Release it again. Idempotent, and safe to call unstarted."""
|
|
98
100
|
|
|
99
|
-
def __enter__(self) ->
|
|
101
|
+
def __enter__(self: _D) -> _D:
|
|
100
102
|
self.start()
|
|
101
103
|
return self
|
|
102
104
|
|
|
@@ -46,6 +46,7 @@ from taskflow import exceptions as tf_exc
|
|
|
46
46
|
from taskflow.persistence import backends as tf_backends
|
|
47
47
|
from taskflow.persistence import models as tf_models
|
|
48
48
|
|
|
49
|
+
from taskflow_meter import models
|
|
49
50
|
from taskflow_meter.datasource.base import DEFAULT_EVENT_LIMIT
|
|
50
51
|
from taskflow_meter.datasource.base import DEFAULT_FLOW_LIMIT
|
|
51
52
|
from taskflow_meter.datasource.base import DataSource
|
|
@@ -281,8 +282,10 @@ def _atom_snapshot(detail: Any) -> AtomSnapshot:
|
|
|
281
282
|
intention=detail.intention,
|
|
282
283
|
progress=float(meta.get(_META_PROGRESS) or 0.0),
|
|
283
284
|
progress_details=meta.get(_META_PROGRESS_DETAILS),
|
|
284
|
-
failure=
|
|
285
|
-
revert_failure=
|
|
285
|
+
failure=models.failure_dict(detail.failure),
|
|
286
|
+
revert_failure=models.failure_dict(
|
|
287
|
+
getattr(detail, "revert_failure", None)
|
|
288
|
+
),
|
|
286
289
|
# Carrying results would mean copying arbitrary application data
|
|
287
290
|
# into a monitoring path, so only their presence is reported. A
|
|
288
291
|
# task that returns None is indistinguishable from one that
|
|
@@ -292,17 +295,6 @@ def _atom_snapshot(detail: Any) -> AtomSnapshot:
|
|
|
292
295
|
)
|
|
293
296
|
|
|
294
297
|
|
|
295
|
-
def _failure_dict(failure: Any) -> dict[str, Any] | None:
|
|
296
|
-
"""Render a taskflow Failure, tolerating anything that is not one."""
|
|
297
|
-
if failure is None:
|
|
298
|
-
return None
|
|
299
|
-
try:
|
|
300
|
-
return dict(failure.to_dict())
|
|
301
|
-
except Exception:
|
|
302
|
-
LOG.warning("could not serialise a failure", exc_info=True)
|
|
303
|
-
return {"unserialisable": repr(failure)}
|
|
304
|
-
|
|
305
|
-
|
|
306
298
|
def _epoch(when: Any) -> float | None:
|
|
307
299
|
"""Convert a datetime to a float, or give up quietly."""
|
|
308
300
|
try:
|
{taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/__init__.py
RENAMED
|
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
from taskflow_meter.datasource.sqlalchemy.models import metadata
|
|
18
18
|
from taskflow_meter.datasource.sqlalchemy.source import SQLADataSource
|
|
19
|
+
from taskflow_meter.datasource.sqlalchemy.source import alembic_config
|
|
19
20
|
from taskflow_meter.datasource.sqlalchemy.source import upgrade
|
|
20
21
|
|
|
21
|
-
__all__ = ["SQLADataSource", "metadata", "upgrade"]
|
|
22
|
+
__all__ = ["SQLADataSource", "alembic_config", "metadata", "upgrade"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
2
|
+
# not use this file except in compliance with the License. You may obtain
|
|
3
|
+
# a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
# License for the specific language governing permissions and limitations
|
|
11
|
+
# under the License.
|
|
12
|
+
|
|
13
|
+
"""Alembic environment for the meter's own schema.
|
|
14
|
+
|
|
15
|
+
Driven programmatically by :func:`taskflow_meter.datasource.sqlalchemy
|
|
16
|
+
.upgrade`, so the connection arrives through the config rather than from
|
|
17
|
+
an ``alembic.ini`` a deployment would have to carry.
|
|
18
|
+
|
|
19
|
+
Everything variable is read from ``config.attributes``, never from a
|
|
20
|
+
main option: alembic passes main options through configparser, which
|
|
21
|
+
treats ``%`` as an interpolation and rejects a percent-encoded password.
|
|
22
|
+
See :func:`~taskflow_meter.datasource.sqlalchemy.source.alembic_config`.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
import sqlalchemy as sa
|
|
30
|
+
from alembic import context
|
|
31
|
+
from sqlalchemy import pool
|
|
32
|
+
|
|
33
|
+
from taskflow_meter.datasource.sqlalchemy.models import metadata
|
|
34
|
+
|
|
35
|
+
config = context.config
|
|
36
|
+
target_metadata = metadata
|
|
37
|
+
|
|
38
|
+
#: Default name of alembic's own bookkeeping table. Overridable,
|
|
39
|
+
#: because this schema may share a database with a host service that has
|
|
40
|
+
#: an alembic tree of its own -- and two trees writing one
|
|
41
|
+
#: ``alembic_version`` row each mistake the other's revision for a
|
|
42
|
+
#: missing one.
|
|
43
|
+
DEFAULT_VERSION_TABLE = "alembic_version"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _version_table() -> str:
|
|
47
|
+
return config.attributes.get("version_table") or DEFAULT_VERSION_TABLE
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _url() -> str | None:
|
|
51
|
+
"""Where to connect, when no live connection was handed over.
|
|
52
|
+
|
|
53
|
+
Falls back to the main option so that running these migrations from
|
|
54
|
+
a hand-written ``alembic.ini`` still works. Escaping ``%`` is the
|
|
55
|
+
ini author's problem there, and ``%%`` is the documented convention
|
|
56
|
+
for it.
|
|
57
|
+
"""
|
|
58
|
+
url = config.attributes.get("url")
|
|
59
|
+
if url is not None:
|
|
60
|
+
return str(url)
|
|
61
|
+
return config.get_main_option("sqlalchemy.url", None)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def run_migrations_offline() -> None:
|
|
65
|
+
context.configure(
|
|
66
|
+
url=_url(),
|
|
67
|
+
target_metadata=target_metadata,
|
|
68
|
+
literal_binds=True,
|
|
69
|
+
dialect_opts={"paramstyle": "named"},
|
|
70
|
+
version_table=_version_table(),
|
|
71
|
+
)
|
|
72
|
+
with context.begin_transaction():
|
|
73
|
+
context.run_migrations()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_migrations_online() -> None:
|
|
77
|
+
connection = config.attributes.get("connection")
|
|
78
|
+
if connection is not None:
|
|
79
|
+
# The usual path: somebody else owns the connection and the
|
|
80
|
+
# transaction around it.
|
|
81
|
+
_run(connection)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
url = _url()
|
|
85
|
+
if url is None:
|
|
86
|
+
# Reached only by a config built by hand that set neither.
|
|
87
|
+
# Saying so beats whatever SQLAlchemy makes of ``None``.
|
|
88
|
+
msg = (
|
|
89
|
+
"no connection and no url to migrate: build the config with "
|
|
90
|
+
"taskflow_meter.datasource.sqlalchemy.alembic_config()"
|
|
91
|
+
)
|
|
92
|
+
raise RuntimeError(msg)
|
|
93
|
+
|
|
94
|
+
engine = sa.create_engine(url, poolclass=pool.NullPool)
|
|
95
|
+
try:
|
|
96
|
+
with engine.connect() as owned:
|
|
97
|
+
_run(owned)
|
|
98
|
+
finally:
|
|
99
|
+
engine.dispose()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _run(connection: Any) -> None:
|
|
103
|
+
context.configure(
|
|
104
|
+
connection=connection,
|
|
105
|
+
target_metadata=target_metadata,
|
|
106
|
+
version_table=_version_table(),
|
|
107
|
+
)
|
|
108
|
+
with context.begin_transaction():
|
|
109
|
+
context.run_migrations()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if context.is_offline_mode():
|
|
113
|
+
run_migrations_offline()
|
|
114
|
+
else:
|
|
115
|
+
run_migrations_online()
|
{taskflow_meter-1.1.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/source.py
RENAMED
|
@@ -30,10 +30,14 @@ from collections.abc import Iterable
|
|
|
30
30
|
from collections.abc import Iterator
|
|
31
31
|
from dataclasses import asdict
|
|
32
32
|
from pathlib import Path
|
|
33
|
+
from typing import TYPE_CHECKING
|
|
33
34
|
from typing import Any
|
|
34
35
|
|
|
35
36
|
import sqlalchemy as sa
|
|
36
37
|
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from alembic.config import Config
|
|
40
|
+
|
|
37
41
|
from taskflow_meter.datasource.base import DEFAULT_EVENT_LIMIT
|
|
38
42
|
from taskflow_meter.datasource.base import DEFAULT_FLOW_LIMIT
|
|
39
43
|
from taskflow_meter.datasource.base import EventPage
|
|
@@ -352,16 +356,74 @@ def _event_from_row(row: Any) -> Event:
|
|
|
352
356
|
)
|
|
353
357
|
|
|
354
358
|
|
|
355
|
-
def upgrade(
|
|
359
|
+
def upgrade(
|
|
360
|
+
url: str,
|
|
361
|
+
revision: str = "head",
|
|
362
|
+
*,
|
|
363
|
+
version_table: str | None = None,
|
|
364
|
+
) -> None:
|
|
356
365
|
"""Bring a database up to date, the way a deployment should.
|
|
357
366
|
|
|
358
367
|
Imported lazily: alembic is only needed by whoever runs migrations,
|
|
359
368
|
not by every process that reads the results.
|
|
369
|
+
|
|
370
|
+
``version_table`` renames alembic's bookkeeping table, which matters
|
|
371
|
+
when this schema shares a database with a host service that migrates
|
|
372
|
+
its own: both would otherwise keep their revision in
|
|
373
|
+
``alembic_version``, and each would read the other's as a revision it
|
|
374
|
+
has never heard of. Pass the same name on every upgrade of a given
|
|
375
|
+
database -- changing it later looks exactly like an unmigrated one.
|
|
360
376
|
"""
|
|
361
377
|
from alembic import command
|
|
378
|
+
|
|
379
|
+
engine = sa.create_engine(url)
|
|
380
|
+
try:
|
|
381
|
+
# One live connection, handed over in ``attributes``. The URL is
|
|
382
|
+
# parsed by SQLAlchemy, which understands URLs, and never by
|
|
383
|
+
# configparser, which does not.
|
|
384
|
+
with engine.begin() as connection:
|
|
385
|
+
config = alembic_config(
|
|
386
|
+
connection=connection, version_table=version_table
|
|
387
|
+
)
|
|
388
|
+
command.upgrade(config, revision)
|
|
389
|
+
finally:
|
|
390
|
+
engine.dispose()
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def alembic_config(
|
|
394
|
+
*,
|
|
395
|
+
url: str | None = None,
|
|
396
|
+
connection: sa.Connection | None = None,
|
|
397
|
+
version_table: str | None = None,
|
|
398
|
+
) -> Config:
|
|
399
|
+
"""Build the config this package's migrations run under.
|
|
400
|
+
|
|
401
|
+
Public because ``upgrade`` is not the only thing an operator runs:
|
|
402
|
+
``downgrade``, ``stamp`` and ``--sql`` mode all need a config, and
|
|
403
|
+
building one by hand is how the escaping rule below gets forgotten.
|
|
404
|
+
|
|
405
|
+
Everything variable travels in ``config.attributes`` rather than as
|
|
406
|
+
a main option, because alembic hands main options to configparser,
|
|
407
|
+
which reads ``%`` as the start of an interpolation and rejects
|
|
408
|
+
anything that is not one. Escaping it to ``%%`` does work, but
|
|
409
|
+
only for whoever remembers; keeping the value out of configparser
|
|
410
|
+
altogether cannot be forgotten.
|
|
411
|
+
|
|
412
|
+
Pass *connection* for a live migration, or *url* for ``--sql`` mode,
|
|
413
|
+
which has no connection to offer.
|
|
414
|
+
"""
|
|
362
415
|
from alembic.config import Config
|
|
363
416
|
|
|
364
417
|
config = Config()
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
418
|
+
# script_location has to be a main option -- alembic reads it from
|
|
419
|
+
# there to find the scripts. It is a path inside this package
|
|
420
|
+
# rather than anything a caller supplies, but an install prefix
|
|
421
|
+
# containing "%" is unusual rather than impossible, so it gets the
|
|
422
|
+
# escaping that the URL no longer needs.
|
|
423
|
+
config.set_main_option(
|
|
424
|
+
"script_location", str(MIGRATIONS).replace("%", "%%")
|
|
425
|
+
)
|
|
426
|
+
config.attributes["url"] = url
|
|
427
|
+
config.attributes["connection"] = connection
|
|
428
|
+
config.attributes["version_table"] = version_table
|
|
429
|
+
return config
|
|
@@ -19,14 +19,32 @@ datasources, the API and any transport only ever handle one vocabulary.
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
+
import sys
|
|
22
23
|
import threading
|
|
23
24
|
from collections.abc import Mapping
|
|
24
25
|
from dataclasses import asdict
|
|
25
26
|
from dataclasses import dataclass
|
|
26
27
|
from dataclasses import field
|
|
27
|
-
from enum import StrEnum
|
|
28
28
|
from typing import Any
|
|
29
|
-
from typing import
|
|
29
|
+
from typing import TypeVar
|
|
30
|
+
|
|
31
|
+
if sys.version_info >= (3, 11):
|
|
32
|
+
from enum import StrEnum
|
|
33
|
+
else:
|
|
34
|
+
from enum import Enum
|
|
35
|
+
|
|
36
|
+
class StrEnum(str, Enum):
|
|
37
|
+
"""As much of 3.11's ``StrEnum`` as this module relies on.
|
|
38
|
+
|
|
39
|
+
A plain ``str, Enum`` mixin renders as ``EventKind.FLOW_STATE``
|
|
40
|
+
under ``str()``, and the value is what goes on the wire.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
return str.__str__(self)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_E = TypeVar("_E", bound="Event")
|
|
30
48
|
|
|
31
49
|
|
|
32
50
|
class EventKind(StrEnum):
|
|
@@ -77,7 +95,7 @@ class Event:
|
|
|
77
95
|
return data
|
|
78
96
|
|
|
79
97
|
@classmethod
|
|
80
|
-
def from_dict(cls, data: Mapping[str, Any]) ->
|
|
98
|
+
def from_dict(cls: type[_E], data: Mapping[str, Any]) -> _E:
|
|
81
99
|
"""Rebuild from :meth:`to_dict` output.
|
|
82
100
|
|
|
83
101
|
Unknown keys are rejected rather than dropped -- a transport
|
|
@@ -55,7 +55,12 @@ def fold(flow: FlowSnapshot, event: Event) -> FlowSnapshot:
|
|
|
55
55
|
flow = replace(flow, observed_at=max(flow.observed_at, event.ts))
|
|
56
56
|
|
|
57
57
|
if event.kind is EventKind.FLOW_STATE:
|
|
58
|
-
|
|
58
|
+
# The name arrives on these events too, and has to be taken from
|
|
59
|
+
# one of them when the run was seeded by something else: the
|
|
60
|
+
# in-process producer emits the graph before anything runs, and a
|
|
61
|
+
# structure event carries no name to seed from.
|
|
62
|
+
name = _optional_str(event.details.get("flow_name")) or flow.name
|
|
63
|
+
return replace(flow, state=event.state, name=name)
|
|
59
64
|
|
|
60
65
|
if event.kind in (EventKind.ATOM_STATE, EventKind.ATOM_PROGRESS):
|
|
61
66
|
if event.atom_name is None:
|
|
@@ -42,7 +42,7 @@ import logging
|
|
|
42
42
|
import threading
|
|
43
43
|
from collections.abc import AsyncIterator
|
|
44
44
|
from types import TracebackType
|
|
45
|
-
from typing import
|
|
45
|
+
from typing import TypeVar
|
|
46
46
|
|
|
47
47
|
from taskflow_meter.datasource.base import DEFAULT_EVENT_LIMIT
|
|
48
48
|
from taskflow_meter.datasource.base import DEFAULT_FLOW_LIMIT
|
|
@@ -58,6 +58,8 @@ from taskflow_meter.poller import Poller
|
|
|
58
58
|
|
|
59
59
|
LOG = logging.getLogger(__name__)
|
|
60
60
|
|
|
61
|
+
_M = TypeVar("_M", bound="Meter")
|
|
62
|
+
|
|
61
63
|
|
|
62
64
|
class Meter:
|
|
63
65
|
"""Owns a source, a store, a poller, and their lifecycle."""
|
|
@@ -180,7 +182,7 @@ class Meter:
|
|
|
180
182
|
self._holders = 0
|
|
181
183
|
self._stop_locked()
|
|
182
184
|
|
|
183
|
-
def __enter__(self) ->
|
|
185
|
+
def __enter__(self: _M) -> _M:
|
|
184
186
|
self.start()
|
|
185
187
|
return self
|
|
186
188
|
|
|
@@ -20,12 +20,15 @@ producer it is talking to.
|
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
|
+
import logging
|
|
23
24
|
from dataclasses import dataclass
|
|
24
25
|
from dataclasses import field
|
|
25
26
|
from typing import Any
|
|
26
27
|
|
|
27
28
|
from taskflow_meter import states
|
|
28
29
|
|
|
30
|
+
LOG = logging.getLogger(__name__)
|
|
31
|
+
|
|
29
32
|
TASK = "task"
|
|
30
33
|
RETRY = "retry"
|
|
31
34
|
|
|
@@ -141,3 +144,20 @@ class FlowSnapshot:
|
|
|
141
144
|
|
|
142
145
|
def _clamp(value: float, low: float = 0.0, high: float = 1.0) -> float:
|
|
143
146
|
return max(low, min(high, value))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def failure_dict(failure: Any) -> dict[str, Any] | None:
|
|
150
|
+
"""Render a taskflow ``Failure``, tolerating anything that is not one.
|
|
151
|
+
|
|
152
|
+
Lives here because both producers report failures and a client must
|
|
153
|
+
not have to tell which one it is reading: the persistence datasource
|
|
154
|
+
renders what taskflow stored, the listener renders what taskflow
|
|
155
|
+
handed it as it happened, and this is the one shape either way.
|
|
156
|
+
"""
|
|
157
|
+
if failure is None:
|
|
158
|
+
return None
|
|
159
|
+
try:
|
|
160
|
+
return dict(failure.to_dict())
|
|
161
|
+
except Exception:
|
|
162
|
+
LOG.warning("could not serialise a failure", exc_info=True)
|
|
163
|
+
return {"unserialisable": repr(failure)}
|
|
@@ -25,10 +25,13 @@ from collections.abc import Mapping
|
|
|
25
25
|
from collections.abc import Sequence
|
|
26
26
|
from types import TracebackType
|
|
27
27
|
from typing import Any
|
|
28
|
-
from typing import
|
|
28
|
+
from typing import TypeVar
|
|
29
29
|
|
|
30
30
|
from taskflow_meter.events import Event
|
|
31
31
|
|
|
32
|
+
_P = TypeVar("_P", bound="Publisher")
|
|
33
|
+
_S = TypeVar("_S", bound="Subscriber")
|
|
34
|
+
|
|
32
35
|
|
|
33
36
|
def to_payload(events: Sequence[Event]) -> dict[str, Any]:
|
|
34
37
|
"""The envelope every transport that leaves the process sends.
|
|
@@ -64,7 +67,7 @@ class Publisher(abc.ABC):
|
|
|
64
67
|
def publish(self, events: Sequence[Event]) -> None:
|
|
65
68
|
"""Send a batch. Never called with an empty one."""
|
|
66
69
|
|
|
67
|
-
def __enter__(self) ->
|
|
70
|
+
def __enter__(self: _P) -> _P:
|
|
68
71
|
self.start()
|
|
69
72
|
return self
|
|
70
73
|
|
|
@@ -108,7 +111,7 @@ class Subscriber(abc.ABC):
|
|
|
108
111
|
rather than being trapped inside one.
|
|
109
112
|
"""
|
|
110
113
|
|
|
111
|
-
def __enter__(self) ->
|
|
114
|
+
def __enter__(self: _S) -> _S:
|
|
112
115
|
self.start()
|
|
113
116
|
return self
|
|
114
117
|
|