3tears-datasources 0.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 3tears_datasources-0.14.0/.gitignore +216 -0
- 3tears_datasources-0.14.0/CHANGELOG.md +392 -0
- 3tears_datasources-0.14.0/IMPLEMENTING_DRIVERS.md +397 -0
- 3tears_datasources-0.14.0/LICENSE +21 -0
- 3tears_datasources-0.14.0/PKG-INFO +105 -0
- 3tears_datasources-0.14.0/README.md +70 -0
- 3tears_datasources-0.14.0/pyproject.toml +86 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/__init__.py +119 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/collections.py +1176 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/config.py +662 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/__init__.py +38 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/_sync_bridge.py +176 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/_util.py +232 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/asyncpg_driver.py +738 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/base.py +721 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/bigquery_driver.py +278 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/factory.py +128 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/redshift_driver.py +1605 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/drivers/snowflake_driver.py +241 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/entities.py +275 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/introspection.py +220 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/namespace.py +55 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/py.typed +0 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/schema_priming.py +165 -0
- 3tears_datasources-0.14.0/src/threetears/datasources/secrets.py +30 -0
- 3tears_datasources-0.14.0/tests/__init__.py +0 -0
- 3tears_datasources-0.14.0/tests/enforcement/__init__.py +0 -0
- 3tears_datasources-0.14.0/tests/enforcement/test_no_hardcoded_pool_params.py +130 -0
- 3tears_datasources-0.14.0/tests/enforcement/test_secrets_typed.py +204 -0
- 3tears_datasources-0.14.0/tests/integration/__init__.py +0 -0
- 3tears_datasources-0.14.0/tests/integration/conftest.py +11 -0
- 3tears_datasources-0.14.0/tests/integration/test_asyncpg_driver_live.py +589 -0
- 3tears_datasources-0.14.0/tests/integration/test_redshift_driver_live.py +538 -0
- 3tears_datasources-0.14.0/tests/unit/__init__.py +0 -0
- 3tears_datasources-0.14.0/tests/unit/_helpers/__init__.py +0 -0
- 3tears_datasources-0.14.0/tests/unit/_helpers/cancellation_contract.py +98 -0
- 3tears_datasources-0.14.0/tests/unit/_helpers/fake_driver.py +141 -0
- 3tears_datasources-0.14.0/tests/unit/test_asyncpg_driver.py +632 -0
- 3tears_datasources-0.14.0/tests/unit/test_asyncpg_driver_secret_leak.py +98 -0
- 3tears_datasources-0.14.0/tests/unit/test_bigquery_driver_stub.py +116 -0
- 3tears_datasources-0.14.0/tests/unit/test_collections.py +628 -0
- 3tears_datasources-0.14.0/tests/unit/test_config.py +627 -0
- 3tears_datasources-0.14.0/tests/unit/test_driver_abc.py +235 -0
- 3tears_datasources-0.14.0/tests/unit/test_driver_coverage.py +144 -0
- 3tears_datasources-0.14.0/tests/unit/test_driver_coverage_by_dimension.py +170 -0
- 3tears_datasources-0.14.0/tests/unit/test_entities.py +143 -0
- 3tears_datasources-0.14.0/tests/unit/test_factory.py +323 -0
- 3tears_datasources-0.14.0/tests/unit/test_introspection.py +305 -0
- 3tears_datasources-0.14.0/tests/unit/test_lazy_imports.py +54 -0
- 3tears_datasources-0.14.0/tests/unit/test_namespace.py +51 -0
- 3tears_datasources-0.14.0/tests/unit/test_redshift_driver.py +1177 -0
- 3tears_datasources-0.14.0/tests/unit/test_redshift_driver_concurrency.py +227 -0
- 3tears_datasources-0.14.0/tests/unit/test_redshift_driver_secret_leak.py +103 -0
- 3tears_datasources-0.14.0/tests/unit/test_schema_digest_l1_roundtrip.py +155 -0
- 3tears_datasources-0.14.0/tests/unit/test_schema_priming_integration.py +143 -0
- 3tears_datasources-0.14.0/tests/unit/test_secrets.py +182 -0
- 3tears_datasources-0.14.0/tests/unit/test_snowflake_driver_stub.py +139 -0
- 3tears_datasources-0.14.0/tests/unit/test_sync_bridge.py +231 -0
- 3tears_datasources-0.14.0/tests/unit/test_translate_placeholders.py +139 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
208
|
+
|
|
209
|
+
# Claude Code local state
|
|
210
|
+
.claude/
|
|
211
|
+
|
|
212
|
+
# prawduct session evidence (local governance artifacts, never shipped)
|
|
213
|
+
.prawduct/
|
|
214
|
+
|
|
215
|
+
# macOS folder metadata
|
|
216
|
+
.DS_Store
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `3tears-datasources` are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and the package version moves in **lockstep** with the rest of the
|
|
7
|
+
3tears monorepo (every package tracks the framework git tag; see
|
|
8
|
+
`README.md` "Versioning policy").
|
|
9
|
+
|
|
10
|
+
## [0.13.9]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `RedshiftConnectionConfig.sslmode` (`verify-ca` | `verify-full`, default `verify-ca`)
|
|
15
|
+
— the TLS verification mode passed to `redshift_connector.connect`, now configurable
|
|
16
|
+
per datasource instead of hardcoded to the library default. A Redshift cluster fronted
|
|
17
|
+
by a TLS-terminating proxy (e.g. Satori) can require `verify-full` to complete the
|
|
18
|
+
handshake — `verify-ca` fails there mid-handshake with a broken pipe. The default
|
|
19
|
+
preserves the prior behavior, so existing callers are unaffected.
|
|
20
|
+
|
|
21
|
+
## [0.13.8]
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- `RedshiftDriver` now terminates the **server-side** query on cancel. It captures
|
|
26
|
+
each connection's `pg_backend_pid()` at open and, on cancel, issues
|
|
27
|
+
`pg_terminate_backend(<pid>)` from a fresh short-lived connection before
|
|
28
|
+
closing/evicting the connection. Closing the client socket alone did not stop
|
|
29
|
+
the running Redshift query — a real abandoned query ran for 7.4h, leaking a
|
|
30
|
+
pool slot. Best-effort and non-fatal: the pid read and the terminate never raise
|
|
31
|
+
and never block the existing close + evict path.
|
|
32
|
+
|
|
33
|
+
## [0.13.3]
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `Driver.column_value_coverage_by_dimension(schema, table, dimension_column, columns)` — the
|
|
38
|
+
grouped sibling of `column_value_coverage`: a single `GROUP BY` pass reporting non-null/non-zero
|
|
39
|
+
value coverage per numeric column per dimension value, surfacing the partial-coverage case (a
|
|
40
|
+
column loaded for some dimension values, all-zero for others) the whole-table probe can't see.
|
|
41
|
+
Concrete on the `Driver` ABC (portable SQL routed through `fetch`).
|
|
42
|
+
|
|
43
|
+
## [0.13.2]
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- Cap simultaneously-open Redshift connections with an `asyncio.Semaphore` sized to
|
|
48
|
+
`connection_cache_size`, acquired before opening — so a burst of concurrent `fetch()` can no
|
|
49
|
+
longer open connections past the warehouse user's `CONNECTION LIMIT` (the 0.13.1 cache bound
|
|
50
|
+
alone didn't gate concurrent opens). The executor still bounds concurrent work.
|
|
51
|
+
|
|
52
|
+
## [0.13.1]
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Redshift warm-connection cache sized as a bounded pool. `executor_max_workers`
|
|
57
|
+
default lowered 10 -> 5, and `connection_cache_size` defaults to
|
|
58
|
+
`executor_max_workers` (cache == workers) via a model validator, so concurrent
|
|
59
|
+
queries reuse warm connections instead of opening past the cache every time —
|
|
60
|
+
which overshot a tight per-user Redshift CONNECTION LIMIT ("too many
|
|
61
|
+
connections"). A query past the bound queues on the executor. Set both per
|
|
62
|
+
datasource to the user's connection limit.
|
|
63
|
+
|
|
64
|
+
## [0.13.0]
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- Collection store-tier override methods renamed to the storage-agnostic
|
|
69
|
+
L3-store-seam names (`fetch_from_postgres` -> `fetch_from_store`,
|
|
70
|
+
`save_to_postgres` -> `save_to_store`, `delete_from_postgres` ->
|
|
71
|
+
`delete_from_store`), tracking the core `collections-task-06` rename. The
|
|
72
|
+
schema-digest and column collections implement the new names; behavior is
|
|
73
|
+
unchanged.
|
|
74
|
+
|
|
75
|
+
## [0.12.3]
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- Per-column value-coverage probe: classifies a column as unloaded when every
|
|
80
|
+
value is zero across the table (the `UNLOADED_COLUMN` source the hub mirrors
|
|
81
|
+
into datasource read results), with driver-coverage tests.
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
|
|
85
|
+
- Redshift: re-apply `search_path` on every connection acquisition, so a pooled
|
|
86
|
+
connection never serves a stale path left by a prior caller.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- Centralize JSONB handling through native binding under the codec
|
|
91
|
+
(`collections-task-04`, Option B), simplifying the collection write path; an
|
|
92
|
+
enforcement drift guard prevents a new column bypassing it.
|
|
93
|
+
|
|
94
|
+
## [0.12.2]
|
|
95
|
+
|
|
96
|
+
### Added
|
|
97
|
+
|
|
98
|
+
- `DataSourceSchemaDigest` entity + `DataSourceSchemaDigestCollection` — a
|
|
99
|
+
three-tier collection for the materialized documented-schema digest, one row
|
|
100
|
+
per datasource, addressed by primary key `datasource_id` for a by-pk hot-L1
|
|
101
|
+
read with L2/L3 fallback and cross-pod invalidation. The `tables` projection
|
|
102
|
+
is stored as JSONB.
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- JSONB write double-encode in the digest tables: a pre-`json.dumps`'d string
|
|
107
|
+
bound as `::jsonb` was re-encoded to a scalar by the text-format jsonb codec;
|
|
108
|
+
writes now text-cast (`::text::jsonb`). Covered by a real-L1 round-trip test.
|
|
109
|
+
|
|
110
|
+
## [0.11.0]
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
|
|
114
|
+
- Platform-sharing primitives: a flat datasource primary key, a
|
|
115
|
+
`visibility` field, and `origin_datasource_id` lineage so a customer
|
|
116
|
+
datasource can inherit a platform-shared datasource's schema docs and
|
|
117
|
+
governed knowledge (concepts / entries) rather than re-documenting them.
|
|
118
|
+
|
|
119
|
+
## [0.10.2]
|
|
120
|
+
|
|
121
|
+
### Added
|
|
122
|
+
|
|
123
|
+
- New `allowed_schemas: list[str]` field on
|
|
124
|
+
`RedshiftConnectionConfig`, `PostgresConnectionConfig`, and
|
|
125
|
+
`YugabyteConnectionConfig`. Drivers thread the value onto the
|
|
126
|
+
connection's `search_path` at open time so agents can write
|
|
127
|
+
unqualified table names (`SELECT … FROM report_geofacts_joined_data`)
|
|
128
|
+
instead of having to fully qualify every reference. Empty default
|
|
129
|
+
preserves the backend's default `search_path`.
|
|
130
|
+
- `build_search_path_value` and `build_set_search_path_sql`
|
|
131
|
+
helpers in `threetears.datasources.drivers._util` with
|
|
132
|
+
identifier-quoting suitable for adversarial schema names.
|
|
133
|
+
- Redshift driver issues `SET search_path TO "<schemas>"` via
|
|
134
|
+
`cursor.execute` after the existing `SET statement_timeout`
|
|
135
|
+
block on every connection open. Cached connection lifecycle
|
|
136
|
+
applies the SET once per backend session.
|
|
137
|
+
- asyncpg driver passes `server_settings={"search_path": "..."}`
|
|
138
|
+
through `create_pool`. The value rides the pgwire STARTUP
|
|
139
|
+
packet and is preserved across `DISCARD ALL` (the default
|
|
140
|
+
asyncpg pool-release reset), where an `init=` callback would
|
|
141
|
+
have been wiped. The live testcontainer pass caught the reset
|
|
142
|
+
issue before release.
|
|
143
|
+
- 8 new unit tests (4 Redshift, 4 asyncpg) pin the SQL / kwargs
|
|
144
|
+
both drivers ship to the client library, including the
|
|
145
|
+
identifier-quoting paths. 4 new live integration tests (2
|
|
146
|
+
Redshift against `central-reporting`, 2 asyncpg against the
|
|
147
|
+
testcontainer) prove unqualified-table-name resolution
|
|
148
|
+
end-to-end and confirm empty `allowed_schemas` leaves the
|
|
149
|
+
default `search_path` intact.
|
|
150
|
+
|
|
151
|
+
## [0.10.1]
|
|
152
|
+
|
|
153
|
+
### Fixed
|
|
154
|
+
|
|
155
|
+
- `RedshiftDriver` now runs `ROLLBACK` on a query error before
|
|
156
|
+
returning the connection to its cache. `redshift_connector` uses
|
|
157
|
+
the DB-API default of `autocommit=False`, so a failed statement
|
|
158
|
+
leaves the connection's implicit transaction in `aborted` state;
|
|
159
|
+
without the rollback the next caller to acquire that cached
|
|
160
|
+
connection trips `25P02: current transaction is aborted, commands
|
|
161
|
+
ignored until end of transaction block` on every subsequent
|
|
162
|
+
statement until eviction. The fix lives in `_acquire_and_run`, the
|
|
163
|
+
central wrapper every query method routes through (so `fetch`,
|
|
164
|
+
`execute`, and `fetch_iter` all benefit). If the rollback itself
|
|
165
|
+
raises, the connection is evicted instead of released and a
|
|
166
|
+
WARNING is logged; the ORIGINAL query exception is what propagates
|
|
167
|
+
to the caller. Covered by `TestRollbackOnError` unit tests
|
|
168
|
+
(mocked-cursor positive / failure / two-fetch end-to-end shapes)
|
|
169
|
+
and one live integration test against `central-reporting`.
|
|
170
|
+
|
|
171
|
+
## [0.9.1]
|
|
172
|
+
|
|
173
|
+
### Changed
|
|
174
|
+
|
|
175
|
+
- Credentials are now referenced by a `scheme://locator` string
|
|
176
|
+
(`password_ref` / `credentials_json_ref`) instead of an env-var
|
|
177
|
+
name (`password_env` / `credentials_json_env`). The value is
|
|
178
|
+
resolved at use time by a pluggable backend in
|
|
179
|
+
`threetears.datasources.secrets`. Shipped backends: `env://NAME`
|
|
180
|
+
(process env) and `k8s://rel/path` (projected-Secret file under
|
|
181
|
+
`AIBOTS_DATASOURCE_SECRETS_DIR`). `vault://`, `aws-secretsmanager://`
|
|
182
|
+
and `gcp-sm://` are registered but raise until implemented.
|
|
183
|
+
- Package version realigned to the monorepo lockstep (`0.9.1`); the
|
|
184
|
+
earlier independent-SemVer experiment (`0.1.x`) is retired.
|
|
185
|
+
|
|
186
|
+
## Roadmap
|
|
187
|
+
|
|
188
|
+
Future enhancements after the initial driver migration ships:
|
|
189
|
+
|
|
190
|
+
- Snowflake + BigQuery concrete driver implementations (stubs today
|
|
191
|
+
per shard 12 — the ABC supports both shapes; implementations land
|
|
192
|
+
when a consumer needs them).
|
|
193
|
+
- `Collection.save_entities_batched` to restore the per-batch write
|
|
194
|
+
performance that shard-13's per-row `save_entity` flow gave up
|
|
195
|
+
(preserving the L2-invalidation contract was non-negotiable; the
|
|
196
|
+
batched variant ships as a 3tears-core enhancement when profiling
|
|
197
|
+
data justifies it).
|
|
198
|
+
- `Driver.execute` return-value extension (currently `None`;
|
|
199
|
+
callers that need a row count use `RETURNING *` + the read tool).
|
|
200
|
+
- Lift the Hub-side `introspect_if_changed` orchestrator helpers
|
|
201
|
+
(probe + diff coordination) into `threetears.datasources.introspection`
|
|
202
|
+
when a second 3tears consumer wires up a change-driven introspector.
|
|
203
|
+
|
|
204
|
+
## [0.1.0] — initial release
|
|
205
|
+
|
|
206
|
+
initial release shipping the full datasource driver-abstraction
|
|
207
|
+
migration (shards 07–15 in the `14-eng-ai-bot/docs/datasource-task-*.md`
|
|
208
|
+
series). drops in as the canonical home for datasource primitives
|
|
209
|
+
across every 3tears consumer.
|
|
210
|
+
|
|
211
|
+
### Driver layer (shards 09–12)
|
|
212
|
+
|
|
213
|
+
- `threetears.datasources.drivers.Driver` ABC: the contract every
|
|
214
|
+
concrete driver implements. async-only surface (`fetch`,
|
|
215
|
+
`execute`, `fetch_iter`, `list_tables`, `list_columns`,
|
|
216
|
+
`table_hashes`, `test_connection`, `close`). `fetch_iter` carries
|
|
217
|
+
a working default impl (yields from `fetch`); native streaming
|
|
218
|
+
is the concrete-driver responsibility.
|
|
219
|
+
- `threetears.datasources.drivers.create_driver(config, *, hub_l3_pool=None, datasource_name="unknown")`:
|
|
220
|
+
the factory. dispatches on `config.datasource_type` to the right
|
|
221
|
+
concrete driver class. lazy backend imports — importing the
|
|
222
|
+
`threetears.datasources` package roots does NOT pull
|
|
223
|
+
`redshift_connector` / `snowflake-connector-python` /
|
|
224
|
+
`google-cloud-bigquery`. one runtime audit verifies the contract
|
|
225
|
+
on every test run.
|
|
226
|
+
- Shared helpers (`drivers/base.py`, `_util.py`, `_sync_bridge.py`):
|
|
227
|
+
- `Driver._with_cancellation(coro_fn, cancel_callback=...)` — the
|
|
228
|
+
canonical pattern every concrete driver routes through for
|
|
229
|
+
`CancelledError` propagation to the backend cancel hook.
|
|
230
|
+
- `_translate_placeholders(sql, target_style)` — `$1` ->
|
|
231
|
+
`%s`/`:1`/`@p1`. asyncpg style is a no-op. handles `$10` vs
|
|
232
|
+
`$1`, escaped `$$`, string-literal `'$1'`.
|
|
233
|
+
- `AsyncSyncBridge(max_workers, name)` — bounded
|
|
234
|
+
`ThreadPoolExecutor` + cancel-aware `to_thread_with_cancel`.
|
|
235
|
+
Redshift / Snowflake / BigQuery drivers share one
|
|
236
|
+
implementation rather than three drifting copies.
|
|
237
|
+
- `@_observed(driver_type=...)` decorator — auto-emits OTel
|
|
238
|
+
`datasource.driver.query.duration` (histogram) +
|
|
239
|
+
`datasource.driver.error` (counter) on every wrapped method.
|
|
240
|
+
- `AsyncpgDriver` (shard 10) — covers POSTGRES / YUGABYTE /
|
|
241
|
+
AGENT_INTERNAL. server-side cursor streaming via
|
|
242
|
+
`conn.cursor()` inside `conn.transaction()`. AGENT_INTERNAL
|
|
243
|
+
borrows Hub's L3 pool via `external_pool=`. cancellation uses
|
|
244
|
+
`Connection.cancel()` (NOT `terminate()`) so the connection
|
|
245
|
+
returns to the pool clean.
|
|
246
|
+
- `RedshiftDriver` (shard 11) — covers REDSHIFT via
|
|
247
|
+
`redshift-connector` (AWS's official driver; `asyncpg` against
|
|
248
|
+
Redshift's pg-protocol quirks never completed in production).
|
|
249
|
+
bounded connection cache (`connection_cache_size`) amortises the
|
|
250
|
+
~1-3s TLS+auth handshake. `weakref.finalize` registers GC-time
|
|
251
|
+
cache drain for pod-crash mitigation. cancellation uses
|
|
252
|
+
`Connection.close()` wrapped in `asyncio.wait_for(_, 5.0)` (the
|
|
253
|
+
lib has no `cancel()` API; closing the connection is the only
|
|
254
|
+
in-flight-abort primitive). live integration test against the
|
|
255
|
+
`central-reporting` warehouse is CI-required.
|
|
256
|
+
- `SnowflakeDriver` + `BigQueryDriver` (shard 12) — stub
|
|
257
|
+
implementations. every abstract method raises
|
|
258
|
+
`NotImplementedError` with a roadmap-pointing message; module
|
|
259
|
+
docstrings codify the future implementation shape so the next
|
|
260
|
+
contributor reuses `AsyncSyncBridge` / `_with_cancellation` /
|
|
261
|
+
`_translate_placeholders` instead of reinventing.
|
|
262
|
+
|
|
263
|
+
### Configuration layer (shards 07–08)
|
|
264
|
+
|
|
265
|
+
- `threetears.datasources.config.DatasourceConfig`: agent.yaml-facing
|
|
266
|
+
config. carries `name`, `access_mode`, `schemas`, and a nested
|
|
267
|
+
`connection_config: ConnectionConfig` field.
|
|
268
|
+
- `threetears.datasources.config.ConnectionConfig`: discriminated
|
|
269
|
+
union (Pydantic `Annotated[Union[...], Field(discriminator=
|
|
270
|
+
"datasource_type")]`) routing to per-backend members:
|
|
271
|
+
`PostgresConnectionConfig`, `YugabyteConnectionConfig`,
|
|
272
|
+
`RedshiftConnectionConfig`, `SnowflakeConnectionConfig`,
|
|
273
|
+
`BigQueryConnectionConfig`, `AgentInternalConnectionConfig`.
|
|
274
|
+
- Each member declares pool / executor / timeout knobs as Pydantic
|
|
275
|
+
fields with documented defaults. The enforcement test
|
|
276
|
+
`tests/enforcement/test_no_hardcoded_pool_params.py` fails the
|
|
277
|
+
build on any concrete driver that inlines a banned-kwarg literal
|
|
278
|
+
(`min_size`, `max_size`, `command_timeout`, `connection_cache_size`,
|
|
279
|
+
`executor_max_workers`, ...).
|
|
280
|
+
- Passwords pass as env-var NAMES (`password_env: str`), NEVER
|
|
281
|
+
resolved values. `resolve_password() -> SecretStr` is the only
|
|
282
|
+
way to read the secret; drivers unwrap `.get_secret_value()`
|
|
283
|
+
at the last moment when handing to the backend lib. Exception
|
|
284
|
+
sanitization (`raise X from None`) breaks the cause chain so
|
|
285
|
+
backend errors don't surface the password.
|
|
286
|
+
|
|
287
|
+
### Entity + collection + namespace layer (shard 07)
|
|
288
|
+
|
|
289
|
+
- `threetears.datasources.entities` — `DataSourceEntity`,
|
|
290
|
+
`DataSourceTableEntity`, `DataSourceColumnEntity`,
|
|
291
|
+
`DataSourceRelationEntity`, `TableTemplateEntity` + the
|
|
292
|
+
`DataSourceType`, `DataSourceAccessMode`, `DataSourceStatus`
|
|
293
|
+
enums. All entities subclass `threetears.core.entities.base.BaseEntity`
|
|
294
|
+
and preserve the composite-PK / single-PK shape from their
|
|
295
|
+
Hub origins byte-for-byte.
|
|
296
|
+
- `threetears.datasources.collections` — `DataSourceCollection`
|
|
297
|
+
(`SchemaBackedCollection` subclass), `DataSourceTableCollection`,
|
|
298
|
+
`DataSourceColumnCollection`, `DataSourceRelationCollection`,
|
|
299
|
+
`TableTemplateCollection`. `get_by_natural_key(...)` on the
|
|
300
|
+
table + column collections supports the introspector's "insert
|
|
301
|
+
vs update" decision. `DataSourceTableCollection` carries
|
|
302
|
+
`column_hash` natively (the Tier-2 probe digest).
|
|
303
|
+
- `threetears.datasources.namespace` — `DATASOURCE_NAMESPACE_TYPE`,
|
|
304
|
+
`datasource_namespace_id`, `datasource_namespace_name` helpers.
|
|
305
|
+
|
|
306
|
+
### Introspection helpers (shard 02 + 03, lifted from Hub)
|
|
307
|
+
|
|
308
|
+
- `threetears.datasources.introspection.compute_column_hash(cols) -> str`:
|
|
309
|
+
Python-side cross-language Tier-2 hash. byte-identical to the
|
|
310
|
+
driver-side SQL `MD5(STRING_AGG(column_name || ':' || data_type
|
|
311
|
+
|| ':' || COALESCE(is_nullable, ''), ',' ORDER BY ordinal_position))`.
|
|
312
|
+
- `IntrospectionDiff` (frozen dataclass) — carries work lists
|
|
313
|
+
(`tables_to_introspect`, `tables_to_delete`) + summary counts
|
|
314
|
+
(`tables_checked`, `tables_unchanged`, `tables_changed`,
|
|
315
|
+
`tables_added`, `tables_removed`, `columns_*`, `elapsed_ms`).
|
|
316
|
+
`has_changes` property short-circuits the orchestrator when
|
|
317
|
+
there's no work to do.
|
|
318
|
+
- `compute_introspection_diff(warehouse_hashes, stored_hashes)`:
|
|
319
|
+
pure function. classifies every key into the right work list +
|
|
320
|
+
counter bucket. null stored hashes are forced re-introspect
|
|
321
|
+
(matches the migration-backfill sentinel semantics).
|
|
322
|
+
|
|
323
|
+
### Companion 3tears-core promotion (shard 07)
|
|
324
|
+
|
|
325
|
+
- `threetears.core.utils.pg_pool_kwargs` — promoted from Hub. the
|
|
326
|
+
shared `asyncpg.create_pool` kwargs helper, DSN redactor, and
|
|
327
|
+
startup-timeout wrapper. previously lived in Hub's
|
|
328
|
+
`aibots/hub/common/pg_pool.py`; shard 15 deletes the Hub copy
|
|
329
|
+
entirely. consumers import directly from the core helper.
|
|
330
|
+
|
|
331
|
+
### Out of scope (intentionally, for 0.1.0)
|
|
332
|
+
|
|
333
|
+
- Hub admin API DTOs (`DataSourceCreateRequest`, `DataSourceResponse`,
|
|
334
|
+
etc.) — those are Hub-API contracts, not framework primitives;
|
|
335
|
+
they stay in Hub as `aibots/hub/datasources/hub_api.py`.
|
|
336
|
+
- Per-table ACL via the `datasource_table` namespace — already
|
|
337
|
+
lives in Hub's template routes; separate concern.
|
|
338
|
+
- Snowflake + BigQuery concrete driver implementations (stubs only
|
|
339
|
+
today per shard 12 — see Unreleased section).
|
|
340
|
+
|
|
341
|
+
### Migration notes
|
|
342
|
+
|
|
343
|
+
`3tears-datasources` is a brand-new package; no prior version exists.
|
|
344
|
+
Hub + agent-SDK consumers flip imports from the old Hub paths to
|
|
345
|
+
`threetears.datasources.*` in the same migration PR (shards 07 + 08).
|
|
346
|
+
No backward-compat shims are provided.
|
|
347
|
+
|
|
348
|
+
### Added
|
|
349
|
+
|
|
350
|
+
- `threetears.datasources` package home for the datasource entity +
|
|
351
|
+
collection + namespace + config primitives previously inlined in
|
|
352
|
+
Hub (`aibots/hub/datasources/{entities,collections,schema_collections,namespace,schemas}.py`)
|
|
353
|
+
and the agent SDK (`aibots_agents/devx/schema/agent_config.py:DatasourceConfig`).
|
|
354
|
+
- `threetears.datasources.entities` — `DataSourceEntity`,
|
|
355
|
+
`DataSourceTableEntity`, `DataSourceColumnEntity`,
|
|
356
|
+
`DataSourceRelationEntity`, `TableTemplateEntity` + the
|
|
357
|
+
`DataSourceType`, `DataSourceAccessMode`, `DataSourceStatus` enums.
|
|
358
|
+
All entities subclass `threetears.core.entities.base.BaseEntity` and
|
|
359
|
+
preserve the composite-PK / single-PK shape from their Hub origins
|
|
360
|
+
byte-for-byte.
|
|
361
|
+
- `threetears.datasources.collections` — `DataSourceCollection`
|
|
362
|
+
(`SchemaBackedCollection` subclass), `DataSourceTableCollection`,
|
|
363
|
+
`DataSourceColumnCollection`, `DataSourceRelationCollection`,
|
|
364
|
+
`TableTemplateCollection` (all `BaseCollection` subclasses with the
|
|
365
|
+
same `serialize / deserialize / fetch_from_postgres /
|
|
366
|
+
save_to_postgres / delete_from_postgres` shape).
|
|
367
|
+
- `threetears.datasources.namespace` — `DATASOURCE_NAMESPACE_TYPE`,
|
|
368
|
+
`datasource_namespace_id`, `datasource_namespace_name` helpers.
|
|
369
|
+
- `threetears.datasources.config` — `DatasourceConfig` (agent-yaml
|
|
370
|
+
shape). The discriminated-union `ConnectionConfig` lands in
|
|
371
|
+
`datasource-task-08`.
|
|
372
|
+
- Companion promotion: `threetears.core.utils.pg_pool_kwargs` — the
|
|
373
|
+
shared `asyncpg.create_pool` kwargs helper, DSN redactor, and
|
|
374
|
+
startup-timeout wrapper used by both Hub L3 and the future
|
|
375
|
+
`AsyncpgDriver`. Promoted from Hub's `aibots/hub/common/pg_pool.py`
|
|
376
|
+
so neither owns a divergent copy.
|
|
377
|
+
|
|
378
|
+
### Out of scope (intentionally, for 0.1.0)
|
|
379
|
+
|
|
380
|
+
- Concrete driver implementations (shards 09-12).
|
|
381
|
+
- Hub admin API DTOs (`DataSourceCreateRequest`, `DataSourceResponse`,
|
|
382
|
+
etc.) — those are Hub-API contracts, not framework primitives, and
|
|
383
|
+
stay in Hub as `aibots/hub/datasources/hub_api.py`.
|
|
384
|
+
- Per-table ACL via the `datasource_table` namespace — already lives
|
|
385
|
+
in Hub's template routes; separate concern.
|
|
386
|
+
|
|
387
|
+
### Migration notes
|
|
388
|
+
|
|
389
|
+
`3tears-datasources` is a brand-new package; no prior version exists.
|
|
390
|
+
Hub + agent-SDK consumers flip imports from the old Hub paths to
|
|
391
|
+
`threetears.datasources.*` in the same migration PR (shards 07 + 08).
|
|
392
|
+
No backward-compat shims are provided.
|