dbctl 0.7.1__tar.gz → 0.7.2__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.
- {dbctl-0.7.1 → dbctl-0.7.2}/CHANGELOG.md +19 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/PKG-INFO +7 -1
- {dbctl-0.7.1 → dbctl-0.7.2}/README.md +6 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/db.py +8 -0
- dbctl-0.7.2/dbctl/refs.py +173 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/tunnels/base.py +7 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/connections.md +55 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/operations.md +13 -13
- {dbctl-0.7.1 → dbctl-0.7.2}/pyproject.toml +1 -1
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_copy_features.py +2 -2
- dbctl-0.7.2/tests/test_refs.py +292 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/uv.lock +214 -1
- {dbctl-0.7.1 → dbctl-0.7.2}/.dbctl/connections.yaml +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/.dbctl/operations.yaml +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/.github/workflows/ci.yml +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/.github-local/ci.yml +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/.gitignore +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/Makefile +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/__init__.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/__main__.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/audit.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/cli.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/config.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/connections.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/execute.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/init.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/multi.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/operations.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/reports.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/runtime.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/tunnels/__init__.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/tunnels/direct.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/tunnels/k8s.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/tunnels/ssh.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/dbctl/tunnels/ssm.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docker-compose.yml +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/ACTION_OUTPUT.md +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/DESIGN.md +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/SESSION_STATE.md +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/logo.png +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/logo_small.png +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/docs/tutorial.md +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/seed/mssql.sql +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/seed/mysql.sql +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/seed/postgres.sql +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_bastion_tags.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_connections_loader.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_issue_1.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_k8s_tunnel.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_regressions.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_smoke.py +0 -0
- {dbctl-0.7.1 → dbctl-0.7.2}/tests/test_sso_cache.py +0 -0
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.2] — 2026-08-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`{{ssm:...}}` connection references** — any string field on a
|
|
13
|
+
connection (`password`, `username`, `database`, `driver`, `url`, or a
|
|
14
|
+
nested tunnel field like `ssm.remote_host` / `direct.host`) can now be
|
|
15
|
+
a placeholder — `{{ssm:<parameter-name>[;property:<json-key>][;profile:<aws-profile>]}}`
|
|
16
|
+
— resolved against AWS SSM Parameter Store instead of a literal value.
|
|
17
|
+
`SecureString` parameters are fetched with decryption; `property`
|
|
18
|
+
extracts one key from a JSON-object parameter value (so several
|
|
19
|
+
credentials can share one parameter). Resolution is lazy — it runs via
|
|
20
|
+
`dbctl.refs.resolve_connection` right before a connection is actually
|
|
21
|
+
used to open a tunnel or build an engine, never at `connections.yaml`
|
|
22
|
+
load time and never from `dbctl connections list` / `show` — so
|
|
23
|
+
browsing the registry never makes an AWS call or risks echoing a
|
|
24
|
+
decrypted secret, and one unreachable profile can't break unrelated
|
|
25
|
+
connections. See `docs/connections.md` for the full reference.
|
|
26
|
+
|
|
8
27
|
## [0.7.1] — 2026-08-03
|
|
9
28
|
|
|
10
29
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbctl
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Generic CLI to monitor, control, and administer multiple databases via SSM, SSH, or direct connection.
|
|
5
5
|
Author: dbctl contributors
|
|
6
6
|
License: MIT
|
|
@@ -339,6 +339,12 @@ DB password sources are mutually exclusive — pick **one** per connection:
|
|
|
339
339
|
| `windows_sso: true` | mssql+pyodbc only: Windows Integrated Security (`Trusted_Connection=yes`). No username/password needed. |
|
|
340
340
|
| `url: "…"` | full SQLAlchemy URL (overrides all the above). Use for Azure AD, ODBC-specific kwargs, or any non-standard connection string. |
|
|
341
341
|
|
|
342
|
+
Any of these — plus `username`, `database`, `driver`, or a tunnel field like
|
|
343
|
+
`direct.host` — can instead be a `{{ssm:/param/path;property:key;profile:name}}`
|
|
344
|
+
placeholder resolved against AWS SSM Parameter Store at connection time. See
|
|
345
|
+
[`docs/connections.md`](docs/connections.md#ssm-references) for the full
|
|
346
|
+
reference.
|
|
347
|
+
|
|
342
348
|
Secret-typed **operation** parameters (`type: secret`) are redacted in the
|
|
343
349
|
audit log regardless of which DB password source the connection uses.
|
|
344
350
|
|
|
@@ -305,6 +305,12 @@ DB password sources are mutually exclusive — pick **one** per connection:
|
|
|
305
305
|
| `windows_sso: true` | mssql+pyodbc only: Windows Integrated Security (`Trusted_Connection=yes`). No username/password needed. |
|
|
306
306
|
| `url: "…"` | full SQLAlchemy URL (overrides all the above). Use for Azure AD, ODBC-specific kwargs, or any non-standard connection string. |
|
|
307
307
|
|
|
308
|
+
Any of these — plus `username`, `database`, `driver`, or a tunnel field like
|
|
309
|
+
`direct.host` — can instead be a `{{ssm:/param/path;property:key;profile:name}}`
|
|
310
|
+
placeholder resolved against AWS SSM Parameter Store at connection time. See
|
|
311
|
+
[`docs/connections.md`](docs/connections.md#ssm-references) for the full
|
|
312
|
+
reference.
|
|
313
|
+
|
|
308
314
|
Secret-typed **operation** parameters (`type: secret`) are redacted in the
|
|
309
315
|
audit log regardless of which DB password source the connection uses.
|
|
310
316
|
|
|
@@ -102,7 +102,15 @@ def build_engine(conn: Connection, tunnel: Tunnel, *, echo: bool = False) -> Eng
|
|
|
102
102
|
password injected — the URL is just ``driver:///path/to/file``. The
|
|
103
103
|
tunnel's local bind is irrelevant for file-based DBs (the file is
|
|
104
104
|
local), and injecting `host:port` makes SQLAlchemy reject the URL.
|
|
105
|
+
|
|
106
|
+
Resolves any ``{{ssm:...}}`` placeholders on ``conn`` first (see
|
|
107
|
+
``dbctl.refs``) — this is the point a connection is actually used, so
|
|
108
|
+
it's the right place for that lazy resolution to happen.
|
|
105
109
|
"""
|
|
110
|
+
from dbctl.refs import resolve_connection
|
|
111
|
+
|
|
112
|
+
conn = resolve_connection(conn)
|
|
113
|
+
|
|
106
114
|
driver = _driver_name(conn)
|
|
107
115
|
_check_driver_available(driver)
|
|
108
116
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Resolve ``{{ssm:...}}`` placeholders in connection values against AWS SSM
|
|
2
|
+
Parameter Store, so secrets never need to live in plaintext in
|
|
3
|
+
``connections.yaml``.
|
|
4
|
+
|
|
5
|
+
Reference format::
|
|
6
|
+
|
|
7
|
+
{{ssm:<parameter-name>[;property:<json-key>][;profile:<aws-profile>]}}
|
|
8
|
+
|
|
9
|
+
Resolution shells out to the ``aws`` CLI — the same convention already used
|
|
10
|
+
by :mod:`dbctl.tunnels.ssm` for bastion/session lookups — so no extra AWS SDK
|
|
11
|
+
dependency is required.
|
|
12
|
+
|
|
13
|
+
Resolution is deliberately **lazy**: nothing in this module runs at
|
|
14
|
+
``connections.yaml`` load time. It only runs when :func:`resolve_connection`
|
|
15
|
+
is called, which happens right before a connection is actually used (see
|
|
16
|
+
``build_tunnel`` / ``build_engine``). A connection nobody is using — or one
|
|
17
|
+
just being displayed via ``dbctl connections show`` — never triggers an AWS
|
|
18
|
+
call, so one unreachable profile can't break unrelated connections.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import re
|
|
25
|
+
import subprocess
|
|
26
|
+
from typing import TYPE_CHECKING, Any
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from dbctl.config import Connection
|
|
30
|
+
|
|
31
|
+
_REF_RE = re.compile(r"\{\{ssm:([^}]+)\}\}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RefResolutionError(RuntimeError):
|
|
35
|
+
"""Raised when a ``{{ssm:...}}`` reference cannot be resolved.
|
|
36
|
+
|
|
37
|
+
Messages here must only ever name the *reference* (parameter name,
|
|
38
|
+
property, profile) — never the resolved value — so this exception is
|
|
39
|
+
always safe to print to logs/stderr/console.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parse_ref(spec: str) -> tuple[str, str | None, str | None]:
|
|
44
|
+
"""Split ``<name>[;property:<key>][;profile:<profile>]`` into its parts."""
|
|
45
|
+
parts = spec.split(";")
|
|
46
|
+
name = parts[0].strip()
|
|
47
|
+
if not name:
|
|
48
|
+
raise RefResolutionError(f"ssm reference {{{{ssm:{spec}}}}} is missing a parameter name")
|
|
49
|
+
|
|
50
|
+
property_: str | None = None
|
|
51
|
+
profile: str | None = None
|
|
52
|
+
for raw_part in parts[1:]:
|
|
53
|
+
part = raw_part.strip()
|
|
54
|
+
if not part:
|
|
55
|
+
continue
|
|
56
|
+
key, sep, value = part.partition(":")
|
|
57
|
+
if not sep:
|
|
58
|
+
raise RefResolutionError(
|
|
59
|
+
f"ssm reference {{{{ssm:{spec}}}}}: malformed option {part!r} (expected key:value)"
|
|
60
|
+
)
|
|
61
|
+
key = key.strip()
|
|
62
|
+
value = value.strip()
|
|
63
|
+
if key == "property":
|
|
64
|
+
property_ = value
|
|
65
|
+
elif key == "profile":
|
|
66
|
+
profile = value
|
|
67
|
+
else:
|
|
68
|
+
raise RefResolutionError(f"ssm reference {{{{ssm:{spec}}}}}: unknown option {key!r}")
|
|
69
|
+
return name, property_, profile
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _fetch_parameter(name: str, profile: str | None) -> str:
|
|
73
|
+
"""Fetch a parameter's raw value via ``aws ssm get-parameter``.
|
|
74
|
+
|
|
75
|
+
Always passes ``--with-decryption`` — a no-op for plain ``String``
|
|
76
|
+
parameters, required for ``SecureString`` ones.
|
|
77
|
+
"""
|
|
78
|
+
cmd = ["aws", "ssm", "get-parameter", "--name", name, "--with-decryption", "--output", "json"]
|
|
79
|
+
if profile:
|
|
80
|
+
cmd[1:1] = ["--profile", profile]
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
result = subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=15)
|
|
84
|
+
except FileNotFoundError as e:
|
|
85
|
+
raise RefResolutionError(
|
|
86
|
+
"the `aws` CLI was not found on PATH — install it before resolving ssm: references"
|
|
87
|
+
) from e
|
|
88
|
+
except subprocess.CalledProcessError as e:
|
|
89
|
+
stderr = (e.stderr or "").strip()
|
|
90
|
+
raise RefResolutionError(
|
|
91
|
+
f"failed to resolve ssm parameter {name!r}: {stderr[:300] or 'unknown error'}"
|
|
92
|
+
) from e
|
|
93
|
+
except subprocess.TimeoutExpired as e:
|
|
94
|
+
raise RefResolutionError(f"aws ssm get-parameter timed out resolving {name!r}") from e
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
payload: Any = json.loads(result.stdout)
|
|
98
|
+
value = payload["Parameter"]["Value"]
|
|
99
|
+
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
100
|
+
raise RefResolutionError(f"unexpected response resolving ssm parameter {name!r}") from e
|
|
101
|
+
if not isinstance(value, str):
|
|
102
|
+
raise RefResolutionError(f"unexpected response resolving ssm parameter {name!r}")
|
|
103
|
+
return value
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _extract_property(raw_value: str, property_name: str, param_name: str) -> str:
|
|
107
|
+
try:
|
|
108
|
+
parsed = json.loads(raw_value)
|
|
109
|
+
except json.JSONDecodeError as e:
|
|
110
|
+
raise RefResolutionError(
|
|
111
|
+
f"ssm parameter {param_name!r} is not valid JSON; cannot extract property {property_name!r}"
|
|
112
|
+
) from e
|
|
113
|
+
if not isinstance(parsed, dict) or property_name not in parsed:
|
|
114
|
+
raise RefResolutionError(f"ssm parameter {param_name!r} has no property {property_name!r}")
|
|
115
|
+
return str(parsed[property_name])
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def resolve_ssm_ref(spec: str, *, cache: dict[str, str]) -> str:
|
|
119
|
+
"""Resolve one ``<name>[;property:...][;profile:...]`` reference body
|
|
120
|
+
(the part inside ``{{ssm: }}``), consulting/populating ``cache`` so the
|
|
121
|
+
same reference is never fetched twice within one resolution pass."""
|
|
122
|
+
if spec in cache:
|
|
123
|
+
return cache[spec]
|
|
124
|
+
name, property_, profile = _parse_ref(spec)
|
|
125
|
+
raw = _fetch_parameter(name, profile)
|
|
126
|
+
value = _extract_property(raw, property_, name) if property_ else raw
|
|
127
|
+
cache[spec] = value
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def resolve_string(value: str, *, cache: dict[str, str]) -> str:
|
|
132
|
+
"""Replace every ``{{ssm:...}}`` occurrence in ``value`` with its
|
|
133
|
+
resolved value. Strings with no reference are returned unchanged
|
|
134
|
+
(and never trigger an AWS call)."""
|
|
135
|
+
if "{{ssm:" not in value:
|
|
136
|
+
return value
|
|
137
|
+
return _REF_RE.sub(lambda m: resolve_ssm_ref(m.group(1), cache=cache), value)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def resolve_value(value: Any, *, cache: dict[str, str]) -> Any:
|
|
141
|
+
"""Recursively resolve ``{{ssm:...}}`` references nested in dicts/lists."""
|
|
142
|
+
if isinstance(value, str):
|
|
143
|
+
return resolve_string(value, cache=cache)
|
|
144
|
+
if isinstance(value, dict):
|
|
145
|
+
return {k: resolve_value(v, cache=cache) for k, v in value.items()}
|
|
146
|
+
if isinstance(value, list):
|
|
147
|
+
return [resolve_value(v, cache=cache) for v in value]
|
|
148
|
+
return value
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def resolve_connection(conn: Connection) -> Connection:
|
|
152
|
+
"""Return a copy of ``conn`` with every ``{{ssm:...}}`` placeholder in
|
|
153
|
+
its string fields (including nested tunnel blocks) resolved.
|
|
154
|
+
|
|
155
|
+
Call this right before a connection is actually used (opening a tunnel,
|
|
156
|
+
building an engine) — never at ``connections.yaml`` load time or from a
|
|
157
|
+
listing/display command, so browsing the registry never makes an AWS
|
|
158
|
+
call or risks echoing a decrypted secret to the screen.
|
|
159
|
+
"""
|
|
160
|
+
from dbctl.config import Connection
|
|
161
|
+
|
|
162
|
+
raw = conn.model_dump()
|
|
163
|
+
resolved = resolve_value(raw, cache={})
|
|
164
|
+
return Connection.model_validate(resolved)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = [
|
|
168
|
+
"RefResolutionError",
|
|
169
|
+
"resolve_connection",
|
|
170
|
+
"resolve_ssm_ref",
|
|
171
|
+
"resolve_string",
|
|
172
|
+
"resolve_value",
|
|
173
|
+
]
|
|
@@ -56,12 +56,19 @@ def build_tunnel(conn: Connection, *, override_port: int | None = None) -> Tunne
|
|
|
56
56
|
the local bind port even when the config says ``local_port: 0`` (auto).
|
|
57
57
|
For ``direct`` tunnels the override replaces the upstream port (useful
|
|
58
58
|
for pointing at a different port than the config declares).
|
|
59
|
+
|
|
60
|
+
Resolves any ``{{ssm:...}}`` placeholders on ``conn`` first (see
|
|
61
|
+
``dbctl.refs``) — this is the point a connection is actually used, so
|
|
62
|
+
it's the right place for that lazy resolution to happen.
|
|
59
63
|
"""
|
|
64
|
+
from dbctl.refs import resolve_connection
|
|
60
65
|
from dbctl.tunnels.direct import DirectTunnel as _Direct
|
|
61
66
|
from dbctl.tunnels.k8s import K8sTunnel as _K8k
|
|
62
67
|
from dbctl.tunnels.ssh import SshTunnel as _Ssh
|
|
63
68
|
from dbctl.tunnels.ssm import SsmTunnel as _Ssm
|
|
64
69
|
|
|
70
|
+
conn = resolve_connection(conn)
|
|
71
|
+
|
|
65
72
|
match conn.type.value:
|
|
66
73
|
case "ssm":
|
|
67
74
|
assert conn.ssm
|
|
@@ -97,6 +97,61 @@ connections.yaml: 1 invalid connection skipped:
|
|
|
97
97
|
The offending connection is simply absent from `dbctl connections list` until
|
|
98
98
|
you fix it — the CLI never refuses to start because of one bad entry.
|
|
99
99
|
|
|
100
|
+
## `{{ssm:...}}` references
|
|
101
|
+
|
|
102
|
+
Any string field on a connection — `password`, `username`, `database`,
|
|
103
|
+
`driver`, `url`, or a nested tunnel field like `ssm.remote_host` or
|
|
104
|
+
`direct.host` — can be a placeholder that resolves against AWS SSM
|
|
105
|
+
Parameter Store instead of a literal value:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
{{ssm:<parameter-name>[;property:<json-key>][;profile:<aws-profile>]}}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| part | required | meaning |
|
|
112
|
+
|------|----------|---------|
|
|
113
|
+
| `<parameter-name>` | yes | full SSM parameter name/path, e.g. `/prod/db/password`. |
|
|
114
|
+
| `property` | no | if the parameter value is a JSON object, extract only this key. Omit to use the whole raw value. |
|
|
115
|
+
| `profile` | no | AWS CLI profile for the lookup. Omit to use the default credential chain (env vars, default profile, instance role). |
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
connections:
|
|
119
|
+
prod-pg:
|
|
120
|
+
type: direct
|
|
121
|
+
driver: postgresql+psycopg
|
|
122
|
+
database: app
|
|
123
|
+
username: app_admin
|
|
124
|
+
password: "{{ssm:/prod/db/credentials;property:password;profile:prod-admin}}"
|
|
125
|
+
direct:
|
|
126
|
+
host: "{{ssm:/prod/db/credentials;property:host;profile:prod-admin}}"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Given an SSM parameter `/prod/db/credentials` holding
|
|
130
|
+
`{"username": "app_admin", "password": "s3cret", "host": "prod.rds.internal"}`,
|
|
131
|
+
`{{ssm:/prod/db/credentials;property:password}}` resolves to `s3cret`.
|
|
132
|
+
|
|
133
|
+
**When resolution happens.** References are resolved lazily, right before a
|
|
134
|
+
connection is actually used to open a tunnel or build an engine (`dbctl
|
|
135
|
+
<conn> health`, `dbctl tunnel open`, an operation, …) — never at load time.
|
|
136
|
+
`dbctl connections list` / `dbctl connections show` never touch AWS and
|
|
137
|
+
never print a resolved secret; they show the raw `{{ssm:...}}` placeholder
|
|
138
|
+
as written in the YAML. Because resolution is per-connection and on-demand,
|
|
139
|
+
an unreachable profile on one connection never breaks loading or using any
|
|
140
|
+
other connection.
|
|
141
|
+
|
|
142
|
+
**Resolution details:**
|
|
143
|
+
- Parameters are always fetched `--with-decryption`, so `SecureString`
|
|
144
|
+
parameters work transparently; plain `String` parameters are unaffected.
|
|
145
|
+
- If `property` is set but the parameter's value isn't valid JSON, or the
|
|
146
|
+
key is missing, resolution fails with an error naming the parameter and
|
|
147
|
+
property — never the (partial) value.
|
|
148
|
+
- If the parameter doesn't exist or access is denied, the error names the
|
|
149
|
+
parameter but never attempts to print a value.
|
|
150
|
+
- Resolution shells out to the `aws` CLI (same mechanism `ssm` tunnels
|
|
151
|
+
already use for bastion lookups), so no extra AWS SDK dependency is
|
|
152
|
+
required — just a working `aws` CLI on `PATH` and credentials for the
|
|
153
|
+
chosen profile.
|
|
154
|
+
|
|
100
155
|
## `ssm` block
|
|
101
156
|
|
|
102
157
|
AWS SSM port-forwarding to a remote host (typically RDS) through an EC2
|
|
@@ -282,7 +282,7 @@ cross-engine migration needs, without writing a custom script:
|
|
|
282
282
|
|
|
283
283
|
`rstrip` exists specifically for the SQL Server → PostgreSQL case: SQL
|
|
284
284
|
Server's default collation treats `'abc'` and `'abc '` as equal, so a
|
|
285
|
-
column like `
|
|
285
|
+
column like `notes` can accumulate trailing padding that never
|
|
286
286
|
breaks a lookup on the source — until it's copied byte-exact into a
|
|
287
287
|
target whose collation *is* whitespace-sensitive, at which point every
|
|
288
288
|
padded row silently stops matching.
|
|
@@ -290,17 +290,17 @@ cross-engine migration needs, without writing a custom script:
|
|
|
290
290
|
```yaml
|
|
291
291
|
operations:
|
|
292
292
|
copy-lookup-tables:
|
|
293
|
-
description: "Migrate
|
|
293
|
+
description: "Migrate legacy lookup tables (SQL Server) -> Postgres"
|
|
294
294
|
scope: multi
|
|
295
295
|
mode: copy
|
|
296
296
|
roles: [src, trg]
|
|
297
297
|
copy_spec:
|
|
298
298
|
batch_size: 10000
|
|
299
|
-
tables: [
|
|
299
|
+
tables: [orders, order_items]
|
|
300
300
|
exclude_columns: [Id] # source IDENTITY column; trg generates its own
|
|
301
301
|
transforms:
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
notes: rstrip # trailing-space padding from src collation
|
|
303
|
+
category: rstrip
|
|
304
304
|
```
|
|
305
305
|
|
|
306
306
|
```bash
|
|
@@ -329,12 +329,12 @@ dbctl copy-lookup-tables mssql pg --validate-data
|
|
|
329
329
|
|
|
330
330
|
```text
|
|
331
331
|
pre-flight: scanning src rows against trg's column constraints; nothing will be written yet
|
|
332
|
-
|
|
333
|
-
┃ table
|
|
334
|
-
|
|
335
|
-
│
|
|
336
|
-
│
|
|
337
|
-
|
|
332
|
+
┏━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
333
|
+
┃ table ┃ column ┃ kind ┃ row ┃ detail ┃
|
|
334
|
+
┡━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
335
|
+
│ order_items │ region │ not_null │ 482 │ NULL value for a NOT NULL │
|
|
336
|
+
│ │ │ │ │ column │
|
|
337
|
+
└─────────────┴────────┴──────────┴─────┴─────────────────────────────┘
|
|
338
338
|
1 violation(s) found — copy aborted, nothing was written
|
|
339
339
|
```
|
|
340
340
|
|
|
@@ -354,8 +354,8 @@ row range that reproduces the failure, and reports the driver's
|
|
|
354
354
|
root-cause error instead of the full wrapped SQLAlchemy exception:
|
|
355
355
|
|
|
356
356
|
```text
|
|
357
|
-
Error: table '
|
|
358
|
-
duplicate key value violates unique constraint "
|
|
357
|
+
Error: table 'orders': insert failed at row 2847 of this batch:
|
|
358
|
+
duplicate key value violates unique constraint "orders_pkey"
|
|
359
359
|
```
|
|
360
360
|
|
|
361
361
|
Disable it with `--no-diagnose-failures` (or `diagnose_failures: false` in
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dbctl"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
description = "Generic CLI to monitor, control, and administer multiple databases via SSM, SSH, or direct connection."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -237,12 +237,12 @@ def test_copy_spec_accepts_exclude_columns_and_transforms():
|
|
|
237
237
|
"copy_spec": {
|
|
238
238
|
"tables": ["t"],
|
|
239
239
|
"exclude_columns": ["Id"],
|
|
240
|
-
"transforms": {"
|
|
240
|
+
"transforms": {"notes": "rstrip"},
|
|
241
241
|
},
|
|
242
242
|
}
|
|
243
243
|
)
|
|
244
244
|
assert op.copy_spec.exclude_columns == ["Id"]
|
|
245
|
-
assert op.copy_spec.transforms == {"
|
|
245
|
+
assert op.copy_spec.transforms == {"notes": ColumnTransform.rstrip_}
|
|
246
246
|
assert op.copy_spec.diagnose_failures is True # default on
|
|
247
247
|
|
|
248
248
|
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Tests for `dbctl.refs` — `{{ssm:...}}` placeholder resolution against AWS
|
|
2
|
+
SSM Parameter Store.
|
|
3
|
+
|
|
4
|
+
`aws` CLI calls are mocked throughout (via `_fetch_parameter` or
|
|
5
|
+
`subprocess.run`) — no AWS credentials are needed to run this file. The
|
|
6
|
+
`resolve_connection` integration tests confirm the whole pipeline (raw YAML
|
|
7
|
+
-> validated `Connection` with placeholders intact -> resolved `Connection`
|
|
8
|
+
with real values) end to end, using a fake SSM backend.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import subprocess
|
|
15
|
+
from unittest.mock import patch
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from dbctl.refs import (
|
|
20
|
+
RefResolutionError,
|
|
21
|
+
_extract_property,
|
|
22
|
+
_fetch_parameter,
|
|
23
|
+
_parse_ref,
|
|
24
|
+
resolve_connection,
|
|
25
|
+
resolve_ssm_ref,
|
|
26
|
+
resolve_string,
|
|
27
|
+
resolve_value,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# --------------------------------------------------------------------------- #
|
|
32
|
+
# _parse_ref
|
|
33
|
+
# --------------------------------------------------------------------------- #
|
|
34
|
+
def test_parse_ref_name_only():
|
|
35
|
+
assert _parse_ref("/prod/db/password") == ("/prod/db/password", None, None)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_parse_ref_with_property():
|
|
39
|
+
assert _parse_ref("/prod/db/creds;property:password") == ("/prod/db/creds", "password", None)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_parse_ref_with_property_and_profile():
|
|
43
|
+
assert _parse_ref("/prod/db/creds;property:password;profile:prod-admin") == (
|
|
44
|
+
"/prod/db/creds",
|
|
45
|
+
"password",
|
|
46
|
+
"prod-admin",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_parse_ref_profile_only():
|
|
51
|
+
assert _parse_ref("/dev/db/password;profile:developer") == ("/dev/db/password", None, "developer")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_parse_ref_ignores_blank_segments():
|
|
55
|
+
# Trailing `;` (or accidental double `;;`) shouldn't blow up parsing.
|
|
56
|
+
assert _parse_ref("/dev/db/password;profile:developer;") == ("/dev/db/password", None, "developer")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_parse_ref_empty_name_rejected():
|
|
60
|
+
with pytest.raises(RefResolutionError, match="missing a parameter name"):
|
|
61
|
+
_parse_ref(";property:password")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_parse_ref_malformed_option_rejected():
|
|
65
|
+
with pytest.raises(RefResolutionError, match="malformed option"):
|
|
66
|
+
_parse_ref("/prod/db/password;profileprod-admin")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_parse_ref_unknown_option_rejected():
|
|
70
|
+
with pytest.raises(RefResolutionError, match="unknown option"):
|
|
71
|
+
_parse_ref("/prod/db/password;region:eu-west-1")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# --------------------------------------------------------------------------- #
|
|
75
|
+
# _extract_property
|
|
76
|
+
# --------------------------------------------------------------------------- #
|
|
77
|
+
def test_extract_property_happy_path():
|
|
78
|
+
raw = json.dumps({"username": "app_user", "password": "s3cret"})
|
|
79
|
+
assert _extract_property(raw, "password", "/prod/db/creds") == "s3cret"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_extract_property_not_json_raises_without_leaking_value():
|
|
83
|
+
with pytest.raises(RefResolutionError) as exc_info:
|
|
84
|
+
_extract_property("not-json-at-all", "password", "/prod/db/creds")
|
|
85
|
+
msg = str(exc_info.value)
|
|
86
|
+
assert "/prod/db/creds" in msg
|
|
87
|
+
assert "not-json-at-all" not in msg
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_extract_property_missing_key_raises_without_leaking_value():
|
|
91
|
+
raw = json.dumps({"username": "app_user", "password": "s3cret"})
|
|
92
|
+
with pytest.raises(RefResolutionError) as exc_info:
|
|
93
|
+
_extract_property(raw, "host", "/prod/db/creds")
|
|
94
|
+
msg = str(exc_info.value)
|
|
95
|
+
assert "host" in msg
|
|
96
|
+
assert "s3cret" not in msg
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# --------------------------------------------------------------------------- #
|
|
100
|
+
# _fetch_parameter (aws CLI mocked)
|
|
101
|
+
# --------------------------------------------------------------------------- #
|
|
102
|
+
def _fake_run(value: str, *, secure: bool = True):
|
|
103
|
+
def _run(cmd, **kwargs):
|
|
104
|
+
assert cmd[0] == "aws"
|
|
105
|
+
assert "ssm" in cmd and "get-parameter" in cmd
|
|
106
|
+
assert "--with-decryption" in cmd
|
|
107
|
+
stdout = json.dumps({"Parameter": {"Value": value, "Type": "SecureString" if secure else "String"}})
|
|
108
|
+
return subprocess.CompletedProcess(cmd, 0, stdout=stdout, stderr="")
|
|
109
|
+
|
|
110
|
+
return _run
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_fetch_parameter_happy_path():
|
|
114
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run("s3cret")):
|
|
115
|
+
assert _fetch_parameter("/prod/db/password", None) == "s3cret"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_fetch_parameter_uses_profile_flag():
|
|
119
|
+
captured_cmd = {}
|
|
120
|
+
|
|
121
|
+
def _run(cmd, **kwargs):
|
|
122
|
+
captured_cmd["cmd"] = cmd
|
|
123
|
+
return subprocess.CompletedProcess(
|
|
124
|
+
cmd, 0, stdout=json.dumps({"Parameter": {"Value": "s3cret"}}), stderr=""
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_run):
|
|
128
|
+
_fetch_parameter("/prod/db/password", "prod-admin")
|
|
129
|
+
assert "--profile" in captured_cmd["cmd"]
|
|
130
|
+
assert "prod-admin" in captured_cmd["cmd"]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_fetch_parameter_aws_cli_missing():
|
|
134
|
+
with (
|
|
135
|
+
patch("dbctl.refs.subprocess.run", side_effect=FileNotFoundError),
|
|
136
|
+
pytest.raises(RefResolutionError, match="not found on PATH"),
|
|
137
|
+
):
|
|
138
|
+
_fetch_parameter("/prod/db/password", None)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_fetch_parameter_denied_reports_param_name_not_value():
|
|
142
|
+
err = subprocess.CalledProcessError(
|
|
143
|
+
254, ["aws"], stderr="An error occurred (AccessDeniedException): secret-looking-stderr-text"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
with patch("dbctl.refs.subprocess.run", side_effect=err), pytest.raises(RefResolutionError) as exc_info:
|
|
147
|
+
_fetch_parameter("/prod/db/password", None)
|
|
148
|
+
msg = str(exc_info.value)
|
|
149
|
+
assert "/prod/db/password" in msg
|
|
150
|
+
assert "AccessDeniedException" in msg
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_fetch_parameter_timeout():
|
|
154
|
+
with (
|
|
155
|
+
patch(
|
|
156
|
+
"dbctl.refs.subprocess.run",
|
|
157
|
+
side_effect=subprocess.TimeoutExpired(cmd=["aws"], timeout=15),
|
|
158
|
+
),
|
|
159
|
+
pytest.raises(RefResolutionError, match="timed out"),
|
|
160
|
+
):
|
|
161
|
+
_fetch_parameter("/prod/db/password", None)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_fetch_parameter_bad_json_response():
|
|
165
|
+
def _run(cmd, **kwargs):
|
|
166
|
+
return subprocess.CompletedProcess(cmd, 0, stdout="not json", stderr="")
|
|
167
|
+
|
|
168
|
+
with (
|
|
169
|
+
patch("dbctl.refs.subprocess.run", side_effect=_run),
|
|
170
|
+
pytest.raises(RefResolutionError, match="unexpected response"),
|
|
171
|
+
):
|
|
172
|
+
_fetch_parameter("/prod/db/password", None)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# --------------------------------------------------------------------------- #
|
|
176
|
+
# resolve_ssm_ref / resolve_string / resolve_value (caching + composition)
|
|
177
|
+
# --------------------------------------------------------------------------- #
|
|
178
|
+
def test_resolve_ssm_ref_caches_same_spec():
|
|
179
|
+
calls = []
|
|
180
|
+
|
|
181
|
+
def _run(cmd, **kwargs):
|
|
182
|
+
calls.append(cmd)
|
|
183
|
+
stdout = json.dumps({"Parameter": {"Value": "s3cret"}})
|
|
184
|
+
return subprocess.CompletedProcess(cmd, 0, stdout=stdout, stderr="")
|
|
185
|
+
|
|
186
|
+
cache: dict[str, str] = {}
|
|
187
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_run):
|
|
188
|
+
assert resolve_ssm_ref("/prod/db/password", cache=cache) == "s3cret"
|
|
189
|
+
assert resolve_ssm_ref("/prod/db/password", cache=cache) == "s3cret"
|
|
190
|
+
assert len(calls) == 1 # second call served from cache, no second `aws` invocation
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_resolve_string_no_placeholder_is_noop_and_never_calls_aws():
|
|
194
|
+
with patch("dbctl.refs.subprocess.run") as mock_run:
|
|
195
|
+
assert resolve_string("plain-value", cache={}) == "plain-value"
|
|
196
|
+
mock_run.assert_not_called()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_resolve_string_single_placeholder():
|
|
200
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run("s3cret")):
|
|
201
|
+
assert resolve_string("{{ssm:/prod/db/password}}", cache={}) == "s3cret"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_resolve_string_with_property_and_profile():
|
|
205
|
+
raw = json.dumps({"host": "prod.rds.internal", "password": "s3cret"})
|
|
206
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run(raw)):
|
|
207
|
+
result = resolve_string("{{ssm:/prod/db/creds;property:host;profile:prod-admin}}", cache={})
|
|
208
|
+
assert result == "prod.rds.internal"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_resolve_string_embedded_in_larger_string():
|
|
212
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run("prod-host")):
|
|
213
|
+
result = resolve_string("jdbc://{{ssm:/prod/db/host}}:5432/app", cache={})
|
|
214
|
+
assert result == "jdbc://prod-host:5432/app"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_resolve_value_recurses_dict_and_list():
|
|
218
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run("resolved")):
|
|
219
|
+
result = resolve_value(
|
|
220
|
+
{"a": "{{ssm:/x}}", "b": ["plain", "{{ssm:/x}}"], "c": {"d": "{{ssm:/x}}"}, "e": 5, "f": None},
|
|
221
|
+
cache={},
|
|
222
|
+
)
|
|
223
|
+
assert result == {
|
|
224
|
+
"a": "resolved",
|
|
225
|
+
"b": ["plain", "resolved"],
|
|
226
|
+
"c": {"d": "resolved"},
|
|
227
|
+
"e": 5,
|
|
228
|
+
"f": None,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# --------------------------------------------------------------------------- #
|
|
233
|
+
# resolve_connection — full Connection round-trip
|
|
234
|
+
# --------------------------------------------------------------------------- #
|
|
235
|
+
def _connection(**overrides):
|
|
236
|
+
from dbctl.config import Connection
|
|
237
|
+
|
|
238
|
+
base = {
|
|
239
|
+
"type": "direct",
|
|
240
|
+
"driver": "postgresql+psycopg",
|
|
241
|
+
"database": "app",
|
|
242
|
+
"username": "app_admin",
|
|
243
|
+
"password": "plain-password",
|
|
244
|
+
"direct": {"host": "127.0.0.1", "port": 5432},
|
|
245
|
+
}
|
|
246
|
+
base.update(overrides)
|
|
247
|
+
return Connection.model_validate(base)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_resolve_connection_resolves_password_placeholder():
|
|
251
|
+
conn = _connection(password="{{ssm:/prod/db/password}}")
|
|
252
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run("s3cret")):
|
|
253
|
+
resolved = resolve_connection(conn)
|
|
254
|
+
assert resolved.password == "s3cret"
|
|
255
|
+
# Original connection object (and its placeholder) is untouched.
|
|
256
|
+
assert conn.password == "{{ssm:/prod/db/password}}"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_resolve_connection_leaves_plain_values_untouched_and_skips_aws():
|
|
260
|
+
conn = _connection()
|
|
261
|
+
with patch("dbctl.refs.subprocess.run") as mock_run:
|
|
262
|
+
resolved = resolve_connection(conn)
|
|
263
|
+
mock_run.assert_not_called()
|
|
264
|
+
assert resolved.password == "plain-password"
|
|
265
|
+
assert resolved.username == "app_admin"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_resolve_connection_resolves_nested_tunnel_field():
|
|
269
|
+
conn = _connection(direct={"host": "{{ssm:/prod/db/host}}", "port": 5432})
|
|
270
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run("prod.rds.internal")):
|
|
271
|
+
resolved = resolve_connection(conn)
|
|
272
|
+
assert resolved.direct.host == "prod.rds.internal"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_resolve_connection_property_extraction_from_shared_parameter():
|
|
276
|
+
raw = json.dumps({"username": "app_admin", "password": "s3cret", "host": "prod.rds.internal"})
|
|
277
|
+
conn = _connection(
|
|
278
|
+
password="{{ssm:/prod/db/creds;property:password}}",
|
|
279
|
+
direct={"host": "{{ssm:/prod/db/creds;property:host}}", "port": 5432},
|
|
280
|
+
)
|
|
281
|
+
with patch("dbctl.refs.subprocess.run", side_effect=_fake_run(raw)):
|
|
282
|
+
resolved = resolve_connection(conn)
|
|
283
|
+
assert resolved.password == "s3cret"
|
|
284
|
+
assert resolved.direct.host == "prod.rds.internal"
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def test_resolve_connection_missing_parameter_error_names_param_not_value():
|
|
288
|
+
conn = _connection(password="{{ssm:/prod/db/password}}")
|
|
289
|
+
err = subprocess.CalledProcessError(254, ["aws"], stderr="ParameterNotFound: /prod/db/password")
|
|
290
|
+
with patch("dbctl.refs.subprocess.run", side_effect=err), pytest.raises(RefResolutionError) as exc_info:
|
|
291
|
+
resolve_connection(conn)
|
|
292
|
+
assert "/prod/db/password" in str(exc_info.value)
|
|
@@ -56,6 +56,91 @@ wheels = [
|
|
|
56
56
|
{ url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" },
|
|
57
57
|
]
|
|
58
58
|
|
|
59
|
+
[[package]]
|
|
60
|
+
name = "cffi"
|
|
61
|
+
version = "2.1.1"
|
|
62
|
+
source = { registry = "https://pypi.org/simple" }
|
|
63
|
+
dependencies = [
|
|
64
|
+
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
|
65
|
+
]
|
|
66
|
+
sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" }
|
|
67
|
+
wheels = [
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" },
|
|
69
|
+
{ url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" },
|
|
70
|
+
{ url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" },
|
|
71
|
+
{ url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" },
|
|
72
|
+
{ url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" },
|
|
73
|
+
{ url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" },
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" },
|
|
75
|
+
{ url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" },
|
|
76
|
+
{ url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" },
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" },
|
|
78
|
+
{ url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" },
|
|
79
|
+
{ url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" },
|
|
80
|
+
{ url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" },
|
|
81
|
+
{ url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" },
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" },
|
|
83
|
+
{ url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" },
|
|
84
|
+
{ url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" },
|
|
85
|
+
{ url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" },
|
|
86
|
+
{ url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" },
|
|
87
|
+
{ url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" },
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" },
|
|
89
|
+
{ url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" },
|
|
90
|
+
{ url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" },
|
|
91
|
+
{ url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" },
|
|
92
|
+
{ url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" },
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" },
|
|
94
|
+
{ url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" },
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" },
|
|
100
|
+
{ url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" },
|
|
101
|
+
{ url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" },
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" },
|
|
103
|
+
{ url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" },
|
|
104
|
+
{ url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" },
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" },
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" },
|
|
107
|
+
{ url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" },
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" },
|
|
109
|
+
{ url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" },
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" },
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" },
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" },
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" },
|
|
131
|
+
{ url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" },
|
|
132
|
+
{ url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" },
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" },
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" },
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" },
|
|
136
|
+
{ url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" },
|
|
137
|
+
{ url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" },
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" },
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" },
|
|
140
|
+
{ url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" },
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" },
|
|
142
|
+
]
|
|
143
|
+
|
|
59
144
|
[[package]]
|
|
60
145
|
name = "click"
|
|
61
146
|
version = "8.4.2"
|
|
@@ -146,12 +231,64 @@ wheels = [
|
|
|
146
231
|
{ url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" },
|
|
147
232
|
]
|
|
148
233
|
|
|
234
|
+
[[package]]
|
|
235
|
+
name = "cryptography"
|
|
236
|
+
version = "50.0.0"
|
|
237
|
+
source = { registry = "https://pypi.org/simple" }
|
|
238
|
+
dependencies = [
|
|
239
|
+
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
|
240
|
+
]
|
|
241
|
+
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
|
|
242
|
+
wheels = [
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" },
|
|
257
|
+
{ url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" },
|
|
258
|
+
{ url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" },
|
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" },
|
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" },
|
|
261
|
+
{ url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" },
|
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" },
|
|
263
|
+
{ url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" },
|
|
264
|
+
{ url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" },
|
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" },
|
|
266
|
+
{ url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" },
|
|
267
|
+
{ url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" },
|
|
268
|
+
{ url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" },
|
|
269
|
+
{ url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" },
|
|
270
|
+
{ url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" },
|
|
271
|
+
{ url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" },
|
|
272
|
+
{ url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" },
|
|
273
|
+
{ url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" },
|
|
274
|
+
{ url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" },
|
|
275
|
+
{ url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" },
|
|
276
|
+
{ url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" },
|
|
277
|
+
{ url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" },
|
|
278
|
+
{ url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" },
|
|
279
|
+
{ url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" },
|
|
280
|
+
{ url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" },
|
|
281
|
+
{ url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" },
|
|
282
|
+
]
|
|
283
|
+
|
|
149
284
|
[[package]]
|
|
150
285
|
name = "dbctl"
|
|
151
|
-
version = "0.
|
|
286
|
+
version = "0.7.2"
|
|
152
287
|
source = { editable = "." }
|
|
153
288
|
dependencies = [
|
|
154
289
|
{ name = "click" },
|
|
290
|
+
{ name = "duckdb" },
|
|
291
|
+
{ name = "oracledb" },
|
|
155
292
|
{ name = "psycopg", extra = ["binary"] },
|
|
156
293
|
{ name = "pydantic" },
|
|
157
294
|
{ name = "pymysql" },
|
|
@@ -172,7 +309,9 @@ dev = [
|
|
|
172
309
|
[package.metadata]
|
|
173
310
|
requires-dist = [
|
|
174
311
|
{ name = "click", specifier = ">=8.1" },
|
|
312
|
+
{ name = "duckdb", specifier = ">=1" },
|
|
175
313
|
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" },
|
|
314
|
+
{ name = "oracledb", specifier = ">=2" },
|
|
176
315
|
{ name = "psycopg", extras = ["binary"], specifier = ">=3.1" },
|
|
177
316
|
{ name = "pydantic", specifier = ">=2" },
|
|
178
317
|
{ name = "pymysql", specifier = ">=1.1" },
|
|
@@ -186,6 +325,35 @@ requires-dist = [
|
|
|
186
325
|
]
|
|
187
326
|
provides-extras = ["dev"]
|
|
188
327
|
|
|
328
|
+
[[package]]
|
|
329
|
+
name = "duckdb"
|
|
330
|
+
version = "1.5.5"
|
|
331
|
+
source = { registry = "https://pypi.org/simple" }
|
|
332
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/19/e57151753576373c6696a12022648546cca6038e8833fda2908ee2342d9b/duckdb-1.5.5.tar.gz", hash = "sha256:72f33ee57ca7595b23957671a2cc7f7fe2be0ecc2d68f63abedcfcaa3a5c1238", size = 18066741, upload-time = "2026-07-22T10:55:17.819Z" }
|
|
333
|
+
wheels = [
|
|
334
|
+
{ url = "https://files.pythonhosted.org/packages/d6/40/2e05d324400fdaa5656c9f48d6298da421cb034d85e509fa0e6e325cf04b/duckdb-1.5.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d4dd65f8941a604b947e0b9b4b4f7165988e29a23ec0b69b4038520956d9933e", size = 32753858, upload-time = "2026-07-22T10:54:05.514Z" },
|
|
335
|
+
{ url = "https://files.pythonhosted.org/packages/79/15/5ceb58ffb5bb8a62b3fd7abb39c41467cdf94850ece02e6d88664dfc75ce/duckdb-1.5.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33db46679b071f108d57139493dee2d37e1f5efcf5c5c039c2969eed11a6c8a7", size = 17368293, upload-time = "2026-07-22T10:54:09.139Z" },
|
|
336
|
+
{ url = "https://files.pythonhosted.org/packages/bf/5c/bf02da0b354fe83cca4f95a4fbf762181af466f7d551ab2a093f7698882a/duckdb-1.5.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f0b88535a5d86fdd63dba6ea02ab68c003dfb9e4892b11256ef24c4da208baae", size = 15509131, upload-time = "2026-07-22T10:54:12.228Z" },
|
|
337
|
+
{ url = "https://files.pythonhosted.org/packages/ea/a9/5f1f09da421d8e930e0b063d11c1b3f90363f40ede74438cd188afdd13a2/duckdb-1.5.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f316eae2323d9a851883fdf2dee91c1f9efe251ab33e14a2272f82a913422ed6", size = 19391959, upload-time = "2026-07-22T10:54:15.551Z" },
|
|
338
|
+
{ url = "https://files.pythonhosted.org/packages/4f/98/6549769f158126fa64fd6c1ac2eb59a18282146c939867a3eb31b7c1db07/duckdb-1.5.5-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a6d2d11859d82a936ebdcb30ce3d8a1cbb3e990bff05c12abb9b54c44fa7bd1", size = 21510909, upload-time = "2026-07-22T10:54:19.681Z" },
|
|
339
|
+
{ url = "https://files.pythonhosted.org/packages/af/b7/5753b41d3124838f868f9f523362812d9fc45409e9e4dd70dcbb0a25826e/duckdb-1.5.5-cp312-cp312-win_amd64.whl", hash = "sha256:ddfbdb096c11d51ee22492397d342c90a82e62c5d09961477895934d0a25372f", size = 13168544, upload-time = "2026-07-22T10:54:22.789Z" },
|
|
340
|
+
{ url = "https://files.pythonhosted.org/packages/5c/28/44b679c7d46245f8398feae7edac959d1b83d4eb143e25b3fce0630b78bd/duckdb-1.5.5-cp312-cp312-win_arm64.whl", hash = "sha256:2725d2b9ace3a4e75d72fc5a239f6a44b502c580edadb8fb2676db772c5f9282", size = 13988684, upload-time = "2026-07-22T10:54:26.003Z" },
|
|
341
|
+
{ url = "https://files.pythonhosted.org/packages/47/37/4a38116e7700720fd152c666292214fd3abdf916496991296d8d1f66efbf/duckdb-1.5.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd98829b67788609017e65c761bd42a5dd0f9129441bed8bda4d6881ccf819f0", size = 32754294, upload-time = "2026-07-22T10:54:29.822Z" },
|
|
342
|
+
{ url = "https://files.pythonhosted.org/packages/66/42/7d392f1ba1eee0eaf4ab4c8c7a604bfe3536cd63f979cf5c98798664f807/duckdb-1.5.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feead93c56679b79592d437c62975d39cb67adedffa7592c763baf8160ac7366", size = 17368211, upload-time = "2026-07-22T10:54:33.359Z" },
|
|
343
|
+
{ url = "https://files.pythonhosted.org/packages/9f/a5/0a6f4fa60562faa615e55e15bd1953a2f2b17a8edd8105e5cda215e43457/duckdb-1.5.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:49c963d9469373d7aba8d750d9ea565ab823e94166efed953f184dd9b169b98c", size = 15509136, upload-time = "2026-07-22T10:54:36.369Z" },
|
|
344
|
+
{ url = "https://files.pythonhosted.org/packages/e4/cb/023c89f51978545b9fab318581bba0c457a58e7530d2d933e54ae7d8647c/duckdb-1.5.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a736217825461732b5442d05a220f3da2e23a0dae114efbf08c9bf171b53098a", size = 19392147, upload-time = "2026-07-22T10:54:39.551Z" },
|
|
345
|
+
{ url = "https://files.pythonhosted.org/packages/3e/c5/41bef391fb8b23dbc133c9f2ba016e7a7a8124513d2cc1b430f1897d87e4/duckdb-1.5.5-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:078e6a60dd8eedde5832f45422ca5c4a6b8c837aeabd8a56ca0b7d933f588053", size = 21511060, upload-time = "2026-07-22T10:54:42.788Z" },
|
|
346
|
+
{ url = "https://files.pythonhosted.org/packages/07/9f/c44dfc1f924ac29b3252dc1b91393c01d009dbfe9f8ed33f10b986151bd1/duckdb-1.5.5-cp313-cp313-win_amd64.whl", hash = "sha256:6826504277dba513c0c5d71d828456c94d729c9d2482f94b2e289f90a9167e28", size = 13168028, upload-time = "2026-07-22T10:54:46.127Z" },
|
|
347
|
+
{ url = "https://files.pythonhosted.org/packages/ca/88/591384b2cd59abddd6f5dc175e60374f9abae6064429f0c4402854c10f44/duckdb-1.5.5-cp313-cp313-win_arm64.whl", hash = "sha256:baa9c5702002fabb559ded2a39008f9f421fcbc7237d388b8213eff1e08858de", size = 13989955, upload-time = "2026-07-22T10:54:49.262Z" },
|
|
348
|
+
{ url = "https://files.pythonhosted.org/packages/3e/56/12c65bfa2d2605b81981b264788891bcf11ec72227889554cead5d8d13b9/duckdb-1.5.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8e6413dd40facb7b8ab21bd844450cd8f549b29e138635be9cf090ef4d2049e2", size = 32761946, upload-time = "2026-07-22T10:54:53.412Z" },
|
|
349
|
+
{ url = "https://files.pythonhosted.org/packages/b9/46/682ce155f17e0d2822d4f13ee3db9ca4b5b7c2da61b841b2629035e1f4bc/duckdb-1.5.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:64078acfd16541132ac6e191eb81b2845554444a0305cc1aa581ba107e514aa8", size = 17375069, upload-time = "2026-07-22T10:54:57.269Z" },
|
|
350
|
+
{ url = "https://files.pythonhosted.org/packages/39/ce/a24bcbd3289c8f305a430759c5fc12242740b4af3e17f7593f3a34e333d2/duckdb-1.5.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c11775cc99a447618d5f1840126db17f2652f3eae05529df4f81f40e2df7151", size = 15519791, upload-time = "2026-07-22T10:55:00.681Z" },
|
|
351
|
+
{ url = "https://files.pythonhosted.org/packages/d9/76/3a01afbc615c1d418c0de58a6b68ac5ce2a8563232c0464bfbc2ce552398/duckdb-1.5.5-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77bbc1e6ba12e1e06f9020117bdf848627ecfdf36f907550e62e008e6109dece", size = 19398251, upload-time = "2026-07-22T10:55:04.168Z" },
|
|
352
|
+
{ url = "https://files.pythonhosted.org/packages/a1/43/3a5e81d1728f4d234c79bfe385808ee7c04834f7c37a4b5c257459c25614/duckdb-1.5.5-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbf0f2d48b43c6c304d00463b463c27ead6c4b01c3c1816b750f728decf71afe", size = 21513851, upload-time = "2026-07-22T10:55:07.864Z" },
|
|
353
|
+
{ url = "https://files.pythonhosted.org/packages/91/41/fc7c829172c60ca22485251eab285f4f1a0d87b486a024c726f21471d86e/duckdb-1.5.5-cp314-cp314-win_amd64.whl", hash = "sha256:9dc826c4b50e64f6c4e4d07a3a9cb075ef70ba3899dc43ec5493dc3d7b04b353", size = 13691858, upload-time = "2026-07-22T10:55:11.181Z" },
|
|
354
|
+
{ url = "https://files.pythonhosted.org/packages/e1/2c/95d9216b79e9273689d7ebce125a54503ed0c9bd7da931f0265888e99779/duckdb-1.5.5-cp314-cp314-win_arm64.whl", hash = "sha256:63e48d4b74b15aeacd688976432a7225163df8c226eddeb8536bba2d4d4ff433", size = 14470180, upload-time = "2026-07-22T10:55:14.445Z" },
|
|
355
|
+
]
|
|
356
|
+
|
|
189
357
|
[[package]]
|
|
190
358
|
name = "greenlet"
|
|
191
359
|
version = "3.5.4"
|
|
@@ -387,6 +555,42 @@ wheels = [
|
|
|
387
555
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
|
388
556
|
]
|
|
389
557
|
|
|
558
|
+
[[package]]
|
|
559
|
+
name = "oracledb"
|
|
560
|
+
version = "4.0.2"
|
|
561
|
+
source = { registry = "https://pypi.org/simple" }
|
|
562
|
+
dependencies = [
|
|
563
|
+
{ name = "cryptography" },
|
|
564
|
+
{ name = "typing-extensions" },
|
|
565
|
+
]
|
|
566
|
+
sdist = { url = "https://files.pythonhosted.org/packages/87/ae/4576e5df7b8eadec51bb7d981a2bcc0d8387d7d6998a51d146c0886a523e/oracledb-4.0.2.tar.gz", hash = "sha256:0a380ab72853487ea2764c5df772f35026b4219868fc3eba68e193c9aea230ac", size = 881658, upload-time = "2026-07-14T17:21:28.876Z" }
|
|
567
|
+
wheels = [
|
|
568
|
+
{ url = "https://files.pythonhosted.org/packages/a5/89/2568c2d32afb3c0a66cef2e74dac7769f69e117a14402301b106970475dd/oracledb-4.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cbe3a75b463a334da9b3d76026062bcfb24ec563b7df291f700c9f7eefcbeefe", size = 4366002, upload-time = "2026-07-14T17:21:58.951Z" },
|
|
569
|
+
{ url = "https://files.pythonhosted.org/packages/9c/c8/1825d240aa68b255eb78867c62964d2a69c6ae6197ab3543fe220539bf69/oracledb-4.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d11723018b6aeae035f4e1feae4150b7cca1161023c2d68d957d7310fe0dd56", size = 2286174, upload-time = "2026-07-14T17:22:00.649Z" },
|
|
570
|
+
{ url = "https://files.pythonhosted.org/packages/10/31/5b9d6fa28942ff38e2d76dcef3184e7569d4b0006d0425f92a3d4a764dc4/oracledb-4.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:579f2c568433523a990cde5bea73c980d144754dc54d3ab2cd37efd670dc31d6", size = 2490198, upload-time = "2026-07-14T17:22:02.322Z" },
|
|
571
|
+
{ url = "https://files.pythonhosted.org/packages/28/4e/77b21ec50c786270a7471abd6f48ec3c2e629ce304ec792eeeb6f03ba4d0/oracledb-4.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82d3d83bdc6246e53f8ad8b598d2508e9f5d983e352ce2e8a71a020200ba2d6", size = 2330058, upload-time = "2026-07-14T17:23:04.213Z" },
|
|
572
|
+
{ url = "https://files.pythonhosted.org/packages/24/83/834e07805b8b3aaab7e87b818ef44ab3cb5394a73149a580ecf73cd92d4d/oracledb-4.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b4f2b51837248d4acfb323a64c48d89b62467b0f73d1211c99db9b80df0cdfbc", size = 2510815, upload-time = "2026-07-14T17:23:05.838Z" },
|
|
573
|
+
{ url = "https://files.pythonhosted.org/packages/49/10/f05a3348a50ecab07b86317424751a4f63891e31c228a6d95f92b9538afd/oracledb-4.0.2-cp312-cp312-win32.whl", hash = "sha256:b5d203426f0d191842b4cfd87c223163ccc57f7e7875ec7ed073f163de2229af", size = 1488879, upload-time = "2026-07-14T17:23:07.53Z" },
|
|
574
|
+
{ url = "https://files.pythonhosted.org/packages/85/e2/99fe3fa29466533df10fdfed90faee133aa1e7147b9edfc13b839e06f738/oracledb-4.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:5fe6e07ed29f84a6656e0580b4e662109d3bb90fc13d8f98ae3a56a67c75b80e", size = 1865771, upload-time = "2026-07-14T17:23:09.374Z" },
|
|
575
|
+
{ url = "https://files.pythonhosted.org/packages/87/cb/009980df826442900419a7bc317e35dc85a031bc2d3806256d469de7d670/oracledb-4.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:a1f46b01a089e0e0dd44c4ac4c5c79903760976346c74a698955c1fb76d68bf5", size = 1519969, upload-time = "2026-07-14T17:23:11.531Z" },
|
|
576
|
+
{ url = "https://files.pythonhosted.org/packages/2b/cb/e9ad24c2fa20ff977ca52b5a68a2010a054f724d420681be75e7e3d92b57/oracledb-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:af4121112f0b68e8d61ce46a086c6aec8256fa2ca32d2e6467195a6c575c207b", size = 4352202, upload-time = "2026-07-14T17:23:13.202Z" },
|
|
577
|
+
{ url = "https://files.pythonhosted.org/packages/f6/ad/583d85906b5c4b344600be2bf30077f3a6c2bf04ee01b4fee9856d1da269/oracledb-4.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a814307ca8a6bf0649d8bf0a650a72f23030d9af052a2d4a481b94b6ab50d5a4", size = 2274847, upload-time = "2026-07-14T17:23:14.967Z" },
|
|
578
|
+
{ url = "https://files.pythonhosted.org/packages/cf/53/badcc7e29ba9e9f2158f2e18214197d63c53d99c4720d436d8fc0b6b175f/oracledb-4.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b89c02670bafc9b1fcc0d15175f097bdc1968bc36ee4d66aa63aee09bfbbe30", size = 2474482, upload-time = "2026-07-14T17:23:16.605Z" },
|
|
579
|
+
{ url = "https://files.pythonhosted.org/packages/b9/76/0bc64bdfc7ca794f4576c757da89617922be402f9fcf434bf5de7e44235a/oracledb-4.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:db0d76199b6dffd721bda48dad4eeea6603942ce2e246f3b9301d85af6bda0ee", size = 2327237, upload-time = "2026-07-14T17:23:18.602Z" },
|
|
580
|
+
{ url = "https://files.pythonhosted.org/packages/15/f5/2fc4e30b24a60e40fa6419dbadd8f869878a3ac49d28b7d16bca59dd2519/oracledb-4.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f5ed684ab419603d53981e0f32b51da673e21a42d7dce1bf3f215a9301c7c108", size = 2504800, upload-time = "2026-07-14T17:23:20.025Z" },
|
|
581
|
+
{ url = "https://files.pythonhosted.org/packages/2e/03/0a15a43a88addfb7b4f8d8343f0c47df68173510a27e2196b6b7347efb06/oracledb-4.0.2-cp313-cp313-win32.whl", hash = "sha256:a45a13e33509db5bd3622a05cb2e4a6cb41d56d1bfd2a32caeacd26f4ce8b988", size = 1493052, upload-time = "2026-07-14T17:23:21.603Z" },
|
|
582
|
+
{ url = "https://files.pythonhosted.org/packages/bb/4a/9895cb5a1fffa68f2d9fb1cfb43de88628c049d313734de7ecef17099460/oracledb-4.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:6444be4991f33754cd98f624cecef3eb98db3e87f8ac14e9b65bd3591c9dd252", size = 1864126, upload-time = "2026-07-14T17:23:22.985Z" },
|
|
583
|
+
{ url = "https://files.pythonhosted.org/packages/0b/14/5c7c44a8f441783d461b9657994e03f8798e28bebe67395951c53c86c270/oracledb-4.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:087fdf5bc36b03dc3c55f7abc944a163ba8edc9c802bc8baf91698a52041ee13", size = 1519906, upload-time = "2026-07-14T17:23:24.338Z" },
|
|
584
|
+
{ url = "https://files.pythonhosted.org/packages/d8/96/30cdbc8399f8edd5cd8bc28bae89fba0eaa40c529168360e9aa84fa21e97/oracledb-4.0.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:58e71a3c1e294a99e39b086adf42074d48287548bf8edfa720c9507106186bbb", size = 4389290, upload-time = "2026-07-14T17:23:25.769Z" },
|
|
585
|
+
{ url = "https://files.pythonhosted.org/packages/1f/35/eec834f01d84f6974ff9b397b83cc80b67064410476049aaf9f88269590d/oracledb-4.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d4a1cc1482b5927a9962c7766aeeae616a85eedbe49af8b797148ac1c1ac32", size = 2315809, upload-time = "2026-07-14T17:23:27.265Z" },
|
|
586
|
+
{ url = "https://files.pythonhosted.org/packages/fd/09/5185ea1270987fa3514ff6bf99540e23befefdb511759137f57848e1ab72/oracledb-4.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14124b5e8e4ba087f105ee389ee43e407840220a0ba2504de68eed903f51a711", size = 2490090, upload-time = "2026-07-14T17:23:28.995Z" },
|
|
587
|
+
{ url = "https://files.pythonhosted.org/packages/c2/63/2547b13274f95d2eb073af2c936a37d7da3025f910937dc2e411b7160784/oracledb-4.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:36e72ffaaf589041dac72ddf589d694c0a9ac4576662a04628966d4ea089d6ea", size = 2365819, upload-time = "2026-07-14T17:23:30.691Z" },
|
|
588
|
+
{ url = "https://files.pythonhosted.org/packages/23/bd/f52b69a5d86d62cf9cb3992a7cafd1d46c418ae64eaa8a256a3d1eee9e51/oracledb-4.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0101b88c15ae628af556506840ae876e12bdb75506ef342e4990ac1758bad1eb", size = 2518689, upload-time = "2026-07-14T17:23:32.566Z" },
|
|
589
|
+
{ url = "https://files.pythonhosted.org/packages/2e/c5/6d15fedcbd67fabd6c9c345554e184ba6617e389bfea348358f8aacc0e5f/oracledb-4.0.2-cp314-cp314-win32.whl", hash = "sha256:b14d52c09b3c7a8273b3640ba06675acec5000dab0356ef288587c46c30bbbb4", size = 1514946, upload-time = "2026-07-14T17:23:33.981Z" },
|
|
590
|
+
{ url = "https://files.pythonhosted.org/packages/2d/b9/d6e2c3ee3d9fb3585f04910ecf6ff91dbfd84e8dafc06084717f6a134bca/oracledb-4.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:5c1f13ba535d9f0fc61e1987720988612cf9424667d491fc344ba681f3336a17", size = 1915171, upload-time = "2026-07-14T17:23:35.574Z" },
|
|
591
|
+
{ url = "https://files.pythonhosted.org/packages/0d/6b/47f5c76223798ebfb5b6d41e9c3a451d296d6d90123584d85159e4236ff5/oracledb-4.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:93b4d2cd17a93224711fadd28f77e956e5c6575cd923f24bfe0b1a2a581beb79", size = 1574652, upload-time = "2026-07-14T17:23:36.982Z" },
|
|
592
|
+
]
|
|
593
|
+
|
|
390
594
|
[[package]]
|
|
391
595
|
name = "packaging"
|
|
392
596
|
version = "26.2"
|
|
@@ -472,6 +676,15 @@ wheels = [
|
|
|
472
676
|
{ url = "https://files.pythonhosted.org/packages/eb/e6/5fff07a70d1f945ed90ae131c3bd76cab32beff7c58c6db15ad5820b6d1f/psycopg_binary-3.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:c37e024c07308cd06cf3ec51bfd0e7f6157585a4d84d1bce4a7f5f7913719bf8", size = 3666849, upload-time = "2026-05-01T23:31:51.165Z" },
|
|
473
677
|
]
|
|
474
678
|
|
|
679
|
+
[[package]]
|
|
680
|
+
name = "pycparser"
|
|
681
|
+
version = "3.0"
|
|
682
|
+
source = { registry = "https://pypi.org/simple" }
|
|
683
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
|
|
684
|
+
wheels = [
|
|
685
|
+
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
|
686
|
+
]
|
|
687
|
+
|
|
475
688
|
[[package]]
|
|
476
689
|
name = "pydantic"
|
|
477
690
|
version = "2.13.4"
|
|
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
|