taskflow-meter 1.2.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.2.0 → taskflow_meter-1.3.0}/PKG-INFO +1 -1
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/_version.py +2 -2
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/__init__.py +2 -1
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/migrations/env.py +46 -15
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/source.py +53 -5
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/test_migrations.py +73 -5
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/.gitignore +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/LICENSE +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/README.md +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/docs/design.md +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/docs/guide.md +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/docs/releasing.md +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/pyproject.toml +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/asgi.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/dispatch.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/http.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/router.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/routes.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/serializers.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/service.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/sse.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/api/wsgi.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/cli.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/attachment.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/listener.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/pipeline.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/collect/progress.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/conf.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/django.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/fastapi.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/flask.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/paste.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/contrib/pecan.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/base.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/memory.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/persistence.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/migrations/script.py.mako +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/migrations/versions/0001_initial.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/models.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/diff.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/events.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/fold.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/meter.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/models.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/poller.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/py.typed +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/states.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/amqp.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/base.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/http.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/memory.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/transports/oslo_messaging.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/asgi_client.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/conformance/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/conformance/test_hosts.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/conformance/test_parity.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/conftest.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/test_cross_process.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/test_examples.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/test_packaging.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/test_serve.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/functional/test_tooling.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/hosts.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/integration/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/integration/test_attach.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/integration/test_collector.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_asgi.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_http.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_router.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_routes.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_serializers.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_service.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_sse.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/api/test_wsgi.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/collect/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_attachment.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_listener.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_pipeline.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/collect/test_progress.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/contrib/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_django.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_fastapi.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_flask.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_paste.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/contrib/test_pecan.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/sqlalchemy/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/sqlalchemy/test_source.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/test_base.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/test_memory.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/test_persistence.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_cli.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_conf.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_diff.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_events.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_meter.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_models.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_poller.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/test_states.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/transports/__init__.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_amqp.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_http.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_memory.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/transports/test_oslo_messaging.py +0 -0
- {taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/wsgi_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
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
5
|
Project-URL: Homepage, https://github.com/daipham3213/taskflow.meter
|
|
6
6
|
Project-URL: Source, https://github.com/daipham3213/taskflow.meter
|
|
@@ -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
|
{taskflow_meter-1.2.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"]
|
{taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/migrations/env.py
RENAMED
|
@@ -13,16 +13,21 @@
|
|
|
13
13
|
"""Alembic environment for the meter's own schema.
|
|
14
14
|
|
|
15
15
|
Driven programmatically by :func:`taskflow_meter.datasource.sqlalchemy
|
|
16
|
-
.upgrade`, so the
|
|
17
|
-
``alembic.ini`` a deployment would have to carry.
|
|
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`.
|
|
18
23
|
"""
|
|
19
24
|
|
|
20
25
|
from __future__ import annotations
|
|
21
26
|
|
|
22
27
|
from typing import Any
|
|
23
28
|
|
|
29
|
+
import sqlalchemy as sa
|
|
24
30
|
from alembic import context
|
|
25
|
-
from sqlalchemy import engine_from_config
|
|
26
31
|
from sqlalchemy import pool
|
|
27
32
|
|
|
28
33
|
from taskflow_meter.datasource.sqlalchemy.models import metadata
|
|
@@ -39,12 +44,26 @@ DEFAULT_VERSION_TABLE = "alembic_version"
|
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
def _version_table() -> str:
|
|
42
|
-
return config.
|
|
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)
|
|
43
62
|
|
|
44
63
|
|
|
45
64
|
def run_migrations_offline() -> None:
|
|
46
65
|
context.configure(
|
|
47
|
-
url=
|
|
66
|
+
url=_url(),
|
|
48
67
|
target_metadata=target_metadata,
|
|
49
68
|
literal_binds=True,
|
|
50
69
|
dialect_opts={"paramstyle": "named"},
|
|
@@ -55,17 +74,29 @@ def run_migrations_offline() -> None:
|
|
|
55
74
|
|
|
56
75
|
|
|
57
76
|
def run_migrations_online() -> None:
|
|
58
|
-
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
poolclass=pool.NullPool,
|
|
64
|
-
)
|
|
65
|
-
with connectable.connect() as connection:
|
|
66
|
-
_run(connection)
|
|
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)
|
|
67
82
|
return
|
|
68
|
-
|
|
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()
|
|
69
100
|
|
|
70
101
|
|
|
71
102
|
def _run(connection: Any) -> None:
|
{taskflow_meter-1.2.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
|
|
@@ -371,11 +375,55 @@ def upgrade(
|
|
|
371
375
|
database -- changing it later looks exactly like an unmigrated one.
|
|
372
376
|
"""
|
|
373
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
|
+
"""
|
|
374
415
|
from alembic.config import Config
|
|
375
416
|
|
|
376
417
|
config = Config()
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
|
@@ -25,18 +25,20 @@ import sqlalchemy as sa
|
|
|
25
25
|
from alembic import command
|
|
26
26
|
from alembic.config import Config
|
|
27
27
|
|
|
28
|
-
from taskflow_meter.datasource.sqlalchemy.source import MIGRATIONS
|
|
29
28
|
from taskflow_meter.datasource.sqlalchemy.source import SQLADataSource
|
|
29
|
+
from taskflow_meter.datasource.sqlalchemy.source import alembic_config
|
|
30
30
|
from taskflow_meter.datasource.sqlalchemy.source import upgrade
|
|
31
31
|
from taskflow_meter.events import Event
|
|
32
32
|
from taskflow_meter.events import EventKind
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def config_for(url: str) -> Config:
|
|
36
|
-
config
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
"""What an operator building their own config should call.
|
|
37
|
+
|
|
38
|
+
A second, hand-rolled config here is how the tests would stop
|
|
39
|
+
covering the one that ships.
|
|
40
|
+
"""
|
|
41
|
+
return alembic_config(url=url)
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
def tables_of(url: str) -> set[str]:
|
|
@@ -61,6 +63,20 @@ def test_upgrading_creates_the_tables(url: str) -> None:
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
def test_a_percent_encoded_url_is_not_read_as_interpolation(
|
|
67
|
+
tmp_path: Path,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""``Fci%40123%21`` is a password, not a configparser substitution."""
|
|
70
|
+
url = f"sqlite:///{tmp_path / 'me%40ter.db'}"
|
|
71
|
+
|
|
72
|
+
upgrade(url)
|
|
73
|
+
|
|
74
|
+
assert tables_of(url) == {
|
|
75
|
+
"taskflow_meter_flows",
|
|
76
|
+
"taskflow_meter_events",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
64
80
|
def test_downgrading_removes_them_again(url: str) -> None:
|
|
65
81
|
# A migration whose downgrade is wrong is worse than one with none:
|
|
66
82
|
# it fails halfway through somebody's rollback.
|
|
@@ -99,3 +115,55 @@ def test_the_revision_is_recorded(url: str) -> None:
|
|
|
99
115
|
sa.text("SELECT version_num FROM alembic_version")
|
|
100
116
|
).scalar()
|
|
101
117
|
assert version == "0001"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_offline_sql_also_survives_a_percent_encoded_url(
|
|
121
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
122
|
+
) -> None:
|
|
123
|
+
"""The half the old ``%%`` escape did not reach.
|
|
124
|
+
|
|
125
|
+
``upgrade`` escaped the URL itself, so it coped; anyone building a
|
|
126
|
+
config for ``--sql`` got a ValueError out of configparser instead.
|
|
127
|
+
"""
|
|
128
|
+
url = f"sqlite:///{tmp_path / 'me%40ter.db'}"
|
|
129
|
+
|
|
130
|
+
command.upgrade(alembic_config(url=url), "head", sql=True)
|
|
131
|
+
|
|
132
|
+
assert "CREATE TABLE taskflow_meter_flows" in capsys.readouterr().out
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_the_url_never_reaches_configparser() -> None:
|
|
136
|
+
"""The structural reason the percent tests pass.
|
|
137
|
+
|
|
138
|
+
Not an implementation detail: the moment a URL goes back into a
|
|
139
|
+
main option, every percent-encoded password breaks again.
|
|
140
|
+
"""
|
|
141
|
+
url = "mysql+pymysql://u:abc%40123%21%40%23@127.0.0.1:3306/grimoire"
|
|
142
|
+
|
|
143
|
+
config = alembic_config(url=url)
|
|
144
|
+
|
|
145
|
+
assert config.attributes["url"] == url
|
|
146
|
+
assert config.get_main_option("sqlalchemy.url", None) is None
|
|
147
|
+
# And nothing in the ini section carries it either.
|
|
148
|
+
section = dict(config.get_section(config.config_ini_section, {}))
|
|
149
|
+
assert not any(url in str(value) for value in section.values())
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_a_percent_encoded_url_needs_no_escaping_by_the_caller() -> None:
|
|
153
|
+
"""The whole point: hand it over exactly as it arrived."""
|
|
154
|
+
url = "mysql+pymysql://u:abc%40123%21%40%23@db.internal:3306/grimoire"
|
|
155
|
+
assert alembic_config(url=url).attributes["url"] == url
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_a_borrowed_connection_is_used_rather_than_a_new_one(
|
|
159
|
+
url: str,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""``upgrade`` hands alembic a live connection, not a URL to open."""
|
|
162
|
+
upgrade(url)
|
|
163
|
+
|
|
164
|
+
engine = sa.create_engine(url)
|
|
165
|
+
with engine.begin() as connection:
|
|
166
|
+
command.downgrade(alembic_config(connection=connection), "base")
|
|
167
|
+
engine.dispose()
|
|
168
|
+
|
|
169
|
+
assert tables_of(url) == set()
|
|
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
|
|
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
|
{taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/taskflow_meter/datasource/sqlalchemy/models.py
RENAMED
|
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
|
|
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
|
{taskflow_meter-1.2.0 → taskflow_meter-1.3.0}/tests/unit/datasource/sqlalchemy/test_source.py
RENAMED
|
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
|