irides-core 0.1.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.
- irides_core-0.1.0/PKG-INFO +254 -0
- irides_core-0.1.0/README.md +202 -0
- irides_core-0.1.0/__init__.py +2 -0
- irides_core-0.1.0/basic_test.py +78 -0
- irides_core-0.1.0/check_mysql_connection.py +23 -0
- irides_core-0.1.0/db_connector/__init__.py +12 -0
- irides_core-0.1.0/db_connector/ai_service.py +133 -0
- irides_core-0.1.0/db_connector/cache_manager.py +69 -0
- irides_core-0.1.0/db_connector/config_service.py +116 -0
- irides_core-0.1.0/db_connector/configurations.py +383 -0
- irides_core-0.1.0/db_connector/connectors/__init__.py +19 -0
- irides_core-0.1.0/db_connector/connectors/athena.py +204 -0
- irides_core-0.1.0/db_connector/connectors/duckdb.py +225 -0
- irides_core-0.1.0/db_connector/connectors/dynamodb.py +233 -0
- irides_core-0.1.0/db_connector/connectors/mongodb.py +337 -0
- irides_core-0.1.0/db_connector/connectors/mysql.py +264 -0
- irides_core-0.1.0/db_connector/connectors/postgres.py +341 -0
- irides_core-0.1.0/db_connector/connectors/sqlite.py +178 -0
- irides_core-0.1.0/db_connector/connectors/trino.py +247 -0
- irides_core-0.1.0/db_connector/exporting/__init__.py +4 -0
- irides_core-0.1.0/db_connector/exporting/artifact_store.py +79 -0
- irides_core-0.1.0/db_connector/exporting/markdown.py +94 -0
- irides_core-0.1.0/db_connector/exporting/models.py +20 -0
- irides_core-0.1.0/db_connector/exporting/okf.py +41 -0
- irides_core-0.1.0/db_connector/exporting/preformatters.py +60 -0
- irides_core-0.1.0/db_connector/interface.py +78 -0
- irides_core-0.1.0/db_connector/job_store.py +315 -0
- irides_core-0.1.0/db_connector/manager.py +75 -0
- irides_core-0.1.0/db_connector/models/__init__.py +7 -0
- irides_core-0.1.0/db_connector/models/column.py +10 -0
- irides_core-0.1.0/db_connector/models/instance.py +7 -0
- irides_core-0.1.0/db_connector/models/scan_job.py +37 -0
- irides_core-0.1.0/db_connector/models/schema.py +7 -0
- irides_core-0.1.0/db_connector/models/table.py +8 -0
- irides_core-0.1.0/db_connector/models/table_details.py +51 -0
- irides_core-0.1.0/db_connector/sql_utils.py +28 -0
- irides_core-0.1.0/db_connector/storage.py +325 -0
- irides_core-0.1.0/irides_core.egg-info/PKG-INFO +254 -0
- irides_core-0.1.0/irides_core.egg-info/SOURCES.txt +79 -0
- irides_core-0.1.0/irides_core.egg-info/dependency_links.txt +1 -0
- irides_core-0.1.0/irides_core.egg-info/requires.txt +39 -0
- irides_core-0.1.0/irides_core.egg-info/top_level.txt +1 -0
- irides_core-0.1.0/pyproject.toml +74 -0
- irides_core-0.1.0/setup.cfg +4 -0
- irides_core-0.1.0/setup.py +15 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: irides-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for extracting structured database metadata to provide reliable context to AI systems and data-driven applications.
|
|
5
|
+
Author-email: Gian Andrea Sechi <me@gianandreasechi.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Source Code, https://github.com/GianAndreaSechi/irides/core
|
|
8
|
+
Project-URL: Homepage, https://www.gianandreasechi.com
|
|
9
|
+
Keywords: database,introspection,metadata,irides,schema,redis
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: redis>=4.0
|
|
24
|
+
Requires-Dist: loguru>=0.7.0
|
|
25
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
26
|
+
Requires-Dist: PyYAML>=6.0
|
|
27
|
+
Provides-Extra: postgres
|
|
28
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "postgres"
|
|
29
|
+
Provides-Extra: mysql
|
|
30
|
+
Requires-Dist: mysql-connector-python>=8.0.0; extra == "mysql"
|
|
31
|
+
Provides-Extra: mongo
|
|
32
|
+
Requires-Dist: pymongo>=4.0.0; extra == "mongo"
|
|
33
|
+
Provides-Extra: duckdb
|
|
34
|
+
Requires-Dist: duckdb>=0.9.0; extra == "duckdb"
|
|
35
|
+
Provides-Extra: aws
|
|
36
|
+
Requires-Dist: boto3>=1.26.0; extra == "aws"
|
|
37
|
+
Provides-Extra: trino
|
|
38
|
+
Requires-Dist: trino>=0.320.0; extra == "trino"
|
|
39
|
+
Provides-Extra: ai
|
|
40
|
+
Requires-Dist: litellm>=1.0.0; extra == "ai"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
43
|
+
Requires-Dist: mysql-connector-python>=8.0.0; extra == "all"
|
|
44
|
+
Requires-Dist: pymongo>=4.0.0; extra == "all"
|
|
45
|
+
Requires-Dist: duckdb>=0.9.0; extra == "all"
|
|
46
|
+
Requires-Dist: boto3>=1.26.0; extra == "all"
|
|
47
|
+
Requires-Dist: trino>=0.320.0; extra == "all"
|
|
48
|
+
Requires-Dist: litellm>=1.0.0; extra == "all"
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
51
|
+
Requires-Dist: pytest-asyncio>=0.20.0; extra == "dev"
|
|
52
|
+
|
|
53
|
+
# Core
|
|
54
|
+
|
|
55
|
+
Shared Python library used by both the **API** and the **Worker**. It provides database connector abstractions, Pydantic models, Redis cache management, configuration loading, metadata persistence, and the async job store.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Package Structure
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
core/db_connector/
|
|
63
|
+
├── connectors/ # DB-specific connector implementations
|
|
64
|
+
│ ├── mysql.py
|
|
65
|
+
│ ├── postgres.py
|
|
66
|
+
│ ├── sqlite.py
|
|
67
|
+
│ ├── duckdb.py
|
|
68
|
+
│ ├── dynamodb.py
|
|
69
|
+
│ ├── mongodb.py
|
|
70
|
+
│ ├── athena.py
|
|
71
|
+
│ ├── trino.py
|
|
72
|
+
│ └── presto.py
|
|
73
|
+
├── exporting/ # Multi-format artifact exporting (Markdown, OKF)
|
|
74
|
+
│ ├── __init__.py
|
|
75
|
+
│ ├── models.py # ExportFormat, ExportOptions
|
|
76
|
+
│ ├── preformatters.py # essential_record deterministic view
|
|
77
|
+
│ ├── markdown.py # Markdown table renderer
|
|
78
|
+
│ ├── okf.py # Open Knowledge Format (OKF v0.2) renderer
|
|
79
|
+
│ └── artifact_store.py # FileArtifactStore (atomic write, 0644, bundle index)
|
|
80
|
+
├── models/ # Pydantic data models
|
|
81
|
+
│ ├── instance.py
|
|
82
|
+
│ ├── schema.py
|
|
83
|
+
│ ├── table.py
|
|
84
|
+
│ ├── column.py
|
|
85
|
+
│ ├── table_details.py # TableDescription, PrimaryKey, ForeignKey, Index, Partition
|
|
86
|
+
│ └── scan_job.py # ScanJob, ScanScope, ScanStatus
|
|
87
|
+
├── interface.py # BaseConnector abstract class
|
|
88
|
+
├── manager.py # ConnectorManager (auto-discovers BaseConnector implementations)
|
|
89
|
+
├── cache_manager.py # Redis cache (get/set with prefix + TTL)
|
|
90
|
+
├── config_service.py # ConfigService — resolves configs & instances to connectors
|
|
91
|
+
├── configurations.py # DB configuration loading from environment variables
|
|
92
|
+
├── storage.py # Metadata persistence (BaseMetadataStore + FileMetadataStore)
|
|
93
|
+
└── job_store.py # JobStore — Redis Stream queue + job metadata & result storage
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Key Modules
|
|
99
|
+
|
|
100
|
+
### `exporting/`
|
|
101
|
+
|
|
102
|
+
Provides multi-format artifact exporting decoupled from raw JSON storage.
|
|
103
|
+
|
|
104
|
+
- **`ExportFormat`**: Enum supporting `markdown` and `okf` (Open Knowledge Format v0.2). Both are generated by default.
|
|
105
|
+
- **`ExportOptions`**: Controls derived artifact generation (`formats: list[ExportFormat]`, `preformat: bool = True`). Supports explicit opt-out.
|
|
106
|
+
- **`preformatters.py` (`essential_record`)**: Deterministic view preserving identity, summary, columns, keys, relations, unique non-primary indexes, partitions, owner, tags, and status. Excludes non-unique secondary indexes and verbose internal metadata to optimize token usage.
|
|
107
|
+
- **`markdown.py` (`render_markdown`)**: Generates structured Markdown tables, keys, relationships, indexes, and partitions. Used both for standalone Markdown output and the OKF document body.
|
|
108
|
+
- **`okf.py` (`render_okf`)**: Renders OKF v0.2 documents with YAML frontmatter (`type: Database Table`, title, description, tags, generator metadata, identifiers) followed by the Markdown body.
|
|
109
|
+
- **`artifact_store.py` (`FileArtifactStore`)**: Persists artifacts to `STORAGE_EXPORT_DIR` via atomic writes with readable `0644` file permissions, maintaining separate directory hierarchies for Markdown and OKF catalog bundles with an auto-updated `index.md`:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
storage/
|
|
113
|
+
metadata/
|
|
114
|
+
{config}/{instance}/{schema}/{table}.json
|
|
115
|
+
exports/
|
|
116
|
+
markdown/
|
|
117
|
+
{config}/{instance}/{schema}/{table}.md
|
|
118
|
+
okf/
|
|
119
|
+
catalog/
|
|
120
|
+
index.md
|
|
121
|
+
{config}/{instance}/{schema}/{table}.md
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `storage.py`
|
|
125
|
+
|
|
126
|
+
Provides metadata persistence abstractions.
|
|
127
|
+
|
|
128
|
+
**`BaseMetadataStore`** — abstract interface with these methods:
|
|
129
|
+
|
|
130
|
+
| Method | Description |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `save_table_metadata(...)` | Write or update a table metadata document and derived exports |
|
|
133
|
+
| `get_table_metadata(...)` | Read a stored document by config+instance+schema+table |
|
|
134
|
+
| `list_instances(page, page_size)` | Paginated list of all stored instance names |
|
|
135
|
+
| `list_databases(instance_name, page, page_size)` | Paginated list of databases for an instance |
|
|
136
|
+
| `list_tables_metadata(instance_name, database_name, page, page_size)` | Paginated list of table names |
|
|
137
|
+
| `find_table_metadata(instance_name, database_name, table_name)` | Look up a table across all configs |
|
|
138
|
+
| `update_table_metadata(instance_name, database_name, table_name, payload)` | Merge custom fields into a stored document and regenerate exports |
|
|
139
|
+
|
|
140
|
+
**`FileMetadataStore`** (default) — saves canonical JSON documents under `STORAGE_METADATA_DIR` and derived exports under `STORAGE_EXPORT_DIR`.
|
|
141
|
+
|
|
142
|
+
Key behaviours:
|
|
143
|
+
|
|
144
|
+
- **Decoupled export pipeline**: Derived artifacts (Markdown, OKF) are generated independently from JSON metadata persistence. Setting `save_metadata=False` still generates exports if requested.
|
|
145
|
+
- **Automatic export regeneration**: Calling `update_table_metadata` to merge human annotations automatically regenerates the corresponding Markdown and OKF documents.
|
|
146
|
+
- **Custom field carry-forward**: any key not in `_SYSTEM_KEYS` (`metadata_key`, `config_name`, `instance_name`, `schema_name`, `table_name`, `updated_at`, `schema_description`, `ai_documentation`) is preserved across re-describe calls. Human-added fields such as `owner`, `tags`, and `notes` survive schema refreshes.
|
|
147
|
+
- **`only_if_changed`**: when `True`, `save_table_metadata` skips the JSON write if `schema_description` is identical to the stored version, leaving `updated_at` and human annotations untouched, but exports the current state.
|
|
148
|
+
- **`ai_documentation` preservation**: if `ai_documentation=None` is passed, the existing stored AI doc is kept rather than overwritten.
|
|
149
|
+
- **Protected fields**: `update_table_metadata` silently ignores `metadata_key`, `config_name`, `instance_name`, `schema_name`, `table_name`, and `updated_at` in the payload — these are always managed by the system.
|
|
150
|
+
|
|
151
|
+
Paginated list responses follow this envelope:
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"items": ["name_a", "name_b"],
|
|
155
|
+
"total": 2,
|
|
156
|
+
"page": 1,
|
|
157
|
+
"page_size": 20,
|
|
158
|
+
"pages": 1
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Use `get_metadata_store()` (factory function) to obtain the configured store. Set `METADATA_STORE_TYPE=file` (default) or extend with future backends (`s3`, `athena`).
|
|
163
|
+
|
|
164
|
+
### `ai_service.py`
|
|
165
|
+
**`AIDocumentationService`**: Non-blocking integration with LiteLLM (`LITELLM_MODEL`, default `gpt-4o-mini`). Generates high-level domain summaries and column descriptions. When requested, generated docs are attached to `TableDescription.ai_documentation` with `ai_generation_status`; failures also include `ai_generation_error`. If `litellm` is uninstalled, API keys are missing, or network errors occur, it logs a warning and returns no documentation without throwing exceptions.
|
|
166
|
+
|
|
167
|
+
### `configurations.py`
|
|
168
|
+
Reads database connection parameters from environment variables. `DB_TARGETS` supports any number of named targets for any connector.
|
|
169
|
+
- Supports `DB_CONFIG_FILE` environment variable to explicitly specify the path to a container `.env` file (e.g. `/app/api/.env`), falling back to default `load_dotenv()` discovery when unset.
|
|
170
|
+
- Target names from `DB_TARGETS` become API/MCP `config_name` values. Example: `DB_TARGETS=sales_mysql,analytics_pg` creates `sales_mysql` and `analytics_pg` configurations.
|
|
171
|
+
- Each target uses `DB_TARGET_<TARGET_KEY>_*` variables, where `<TARGET_KEY>` is the uppercased target name with non-alphanumeric characters replaced by underscores.
|
|
172
|
+
- Exact required and optional keys for each connector type are documented in the root README under **DB Configuration & Activation**.
|
|
173
|
+
|
|
174
|
+
### `config_service.py`
|
|
175
|
+
Wraps `ConnectorManager` and `configurations`.
|
|
176
|
+
- **`list_instances(config_name, no_cache)`**: Uniformly lists instances for both multi-host configurations (MySQL/MariaDB) and flat configurations (Athena, DynamoDB, Trino, MongoDB, SQLite).
|
|
177
|
+
- **`resolve_instance_names(config_name, instance_name, no_cache)`**: Returns `[instance_name]` if specified, or all discovered instances if `instance_name` is `None`.
|
|
178
|
+
- **`_get_hosts(config_name)`**: Returns explicitly configured hosts, correctly recognizing both multi-host `hosts` collections and flat `host` parameters.
|
|
179
|
+
- **`configuration_matches_instance(config_name, instance_name, no_cache)`**: Checks whether an instance belongs to a given configuration.
|
|
180
|
+
- **`resolve_configurations_for_instance(instance_name, no_cache)`**: Centralized lookup returning all configuration names matching a target instance.
|
|
181
|
+
- **`_get_connector_for_host(config_name, host)`**: Returns the connector for a specific host, falling back to flat connection parameters when static host definitions are omitted.
|
|
182
|
+
|
|
183
|
+
### `cache_manager.py`
|
|
184
|
+
Redis-backed cache for introspection results. All keys are prefixed with `CACHE_KEY_PREFIX`. Cache can be bypassed per-call with `no_cache=True`.
|
|
185
|
+
|
|
186
|
+
### `job_store.py`
|
|
187
|
+
Manages async scan jobs via Redis:
|
|
188
|
+
- **Stream** (`{prefix}:scan:queue`) — job queue for workers (`scan-workers` consumer group). Stream messages serialize `export_formats` and `export_preformat` parameters.
|
|
189
|
+
- **Hash** (`{prefix}:scan:job:{job_id}`) — job metadata, scope, and status. Supports deserialization of new export options with backwards compatibility for legacy jobs with `save_markdown`.
|
|
190
|
+
- **List** (`{prefix}:scan:results:{job_id}`) — serialized `TableDescription` results with automatic TTL extensions on writes.
|
|
191
|
+
- **Sorted Set** (`{prefix}:scan:jobs`) — job index ordered by creation timestamp, automatically pruned of entries older than `RESULTS_TTL` via `zremrangebyscore` to prevent Redis memory leaks.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Supported Databases
|
|
196
|
+
|
|
197
|
+
| Database | Connector Type | Configuration Style |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| MySQL / MariaDB | `mysql` | Named `DB_TARGETS` |
|
|
200
|
+
| PostgreSQL | `postgres` | Named `DB_TARGETS` |
|
|
201
|
+
| SQLite | `sqlite` | Named `DB_TARGETS` |
|
|
202
|
+
| DuckDB | `duckdb` | Named `DB_TARGETS` |
|
|
203
|
+
| Amazon DynamoDB | `dynamodb` | Named `DB_TARGETS` |
|
|
204
|
+
| Amazon Athena | `athena` | Named `DB_TARGETS` |
|
|
205
|
+
| MongoDB | `mongodb` | Named `DB_TARGETS` |
|
|
206
|
+
| Trino | `trino` | Named `DB_TARGETS` |
|
|
207
|
+
| Presto | `presto` | Named `DB_TARGETS` |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Environment Variables
|
|
212
|
+
|
|
213
|
+
Copy `.env.example` to `.env` and configure as needed.
|
|
214
|
+
|
|
215
|
+
| Variable | Default | Description |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `REDIS_HOST` | `localhost` | Redis host |
|
|
218
|
+
| `REDIS_PORT` | `6379` | Redis port |
|
|
219
|
+
| `REDIS_DB` | `0` | Redis database index |
|
|
220
|
+
| `REDIS_TTL_SECONDS` | `86400` | Introspection cache TTL (1 day) |
|
|
221
|
+
| `CACHE_KEY_PREFIX` | `irides` | Prefix for all Redis keys |
|
|
222
|
+
| `SCAN_RESULTS_TTL_SECONDS` | `604800` | Scan result retention in Redis (7 days) |
|
|
223
|
+
| `DB_CONFIG_FILE` | *(none)* | Explicit path to `.env` configuration file |
|
|
224
|
+
| `DB_TARGETS` | *(none)* | Comma-separated list of named DB targets |
|
|
225
|
+
| `STORAGE_METADATA_DIR` | `storage/metadata` | Metadata JSON output directory |
|
|
226
|
+
| `STORAGE_EXPORT_DIR` | `storage/exports` | Directory for generated Markdown and OKF exports |
|
|
227
|
+
| `METADATA_STORE_TYPE` | `file` | Metadata store backend (`file`; `s3`/`athena` planned) |
|
|
228
|
+
| `LITELLM_MODEL` | `gpt-4o-mini` | LiteLLM model for AI documentation |
|
|
229
|
+
| `LITELLM_API_KEY` | *(none)* | Optional provider API key override |
|
|
230
|
+
| `LITELLM_API_BASE` | *(none)* | Optional custom LiteLLM API base URL |
|
|
231
|
+
|
|
232
|
+
DB activation vars — see [root README](../README.md#db-configuration--activation).
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Installation
|
|
237
|
+
|
|
238
|
+
The core package is installed in editable mode by the API and Worker:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
pip install -e /path/to/core
|
|
242
|
+
# or via requirements.txt:
|
|
243
|
+
pip install -r requirements.txt
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Adding a New Connector
|
|
249
|
+
|
|
250
|
+
1. Create `core/db_connector/connectors/mydb.py` implementing `BaseConnector`.
|
|
251
|
+
2. Export it from `core/db_connector/connectors/__init__.py`.
|
|
252
|
+
3. Add its activation env var and config block to `core/db_connector/configurations.py`.
|
|
253
|
+
|
|
254
|
+
The `ConnectorManager` automatically discovers all classes that extend `BaseConnector`.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Core
|
|
2
|
+
|
|
3
|
+
Shared Python library used by both the **API** and the **Worker**. It provides database connector abstractions, Pydantic models, Redis cache management, configuration loading, metadata persistence, and the async job store.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Package Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
core/db_connector/
|
|
11
|
+
├── connectors/ # DB-specific connector implementations
|
|
12
|
+
│ ├── mysql.py
|
|
13
|
+
│ ├── postgres.py
|
|
14
|
+
│ ├── sqlite.py
|
|
15
|
+
│ ├── duckdb.py
|
|
16
|
+
│ ├── dynamodb.py
|
|
17
|
+
│ ├── mongodb.py
|
|
18
|
+
│ ├── athena.py
|
|
19
|
+
│ ├── trino.py
|
|
20
|
+
│ └── presto.py
|
|
21
|
+
├── exporting/ # Multi-format artifact exporting (Markdown, OKF)
|
|
22
|
+
│ ├── __init__.py
|
|
23
|
+
│ ├── models.py # ExportFormat, ExportOptions
|
|
24
|
+
│ ├── preformatters.py # essential_record deterministic view
|
|
25
|
+
│ ├── markdown.py # Markdown table renderer
|
|
26
|
+
│ ├── okf.py # Open Knowledge Format (OKF v0.2) renderer
|
|
27
|
+
│ └── artifact_store.py # FileArtifactStore (atomic write, 0644, bundle index)
|
|
28
|
+
├── models/ # Pydantic data models
|
|
29
|
+
│ ├── instance.py
|
|
30
|
+
│ ├── schema.py
|
|
31
|
+
│ ├── table.py
|
|
32
|
+
│ ├── column.py
|
|
33
|
+
│ ├── table_details.py # TableDescription, PrimaryKey, ForeignKey, Index, Partition
|
|
34
|
+
│ └── scan_job.py # ScanJob, ScanScope, ScanStatus
|
|
35
|
+
├── interface.py # BaseConnector abstract class
|
|
36
|
+
├── manager.py # ConnectorManager (auto-discovers BaseConnector implementations)
|
|
37
|
+
├── cache_manager.py # Redis cache (get/set with prefix + TTL)
|
|
38
|
+
├── config_service.py # ConfigService — resolves configs & instances to connectors
|
|
39
|
+
├── configurations.py # DB configuration loading from environment variables
|
|
40
|
+
├── storage.py # Metadata persistence (BaseMetadataStore + FileMetadataStore)
|
|
41
|
+
└── job_store.py # JobStore — Redis Stream queue + job metadata & result storage
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Key Modules
|
|
47
|
+
|
|
48
|
+
### `exporting/`
|
|
49
|
+
|
|
50
|
+
Provides multi-format artifact exporting decoupled from raw JSON storage.
|
|
51
|
+
|
|
52
|
+
- **`ExportFormat`**: Enum supporting `markdown` and `okf` (Open Knowledge Format v0.2). Both are generated by default.
|
|
53
|
+
- **`ExportOptions`**: Controls derived artifact generation (`formats: list[ExportFormat]`, `preformat: bool = True`). Supports explicit opt-out.
|
|
54
|
+
- **`preformatters.py` (`essential_record`)**: Deterministic view preserving identity, summary, columns, keys, relations, unique non-primary indexes, partitions, owner, tags, and status. Excludes non-unique secondary indexes and verbose internal metadata to optimize token usage.
|
|
55
|
+
- **`markdown.py` (`render_markdown`)**: Generates structured Markdown tables, keys, relationships, indexes, and partitions. Used both for standalone Markdown output and the OKF document body.
|
|
56
|
+
- **`okf.py` (`render_okf`)**: Renders OKF v0.2 documents with YAML frontmatter (`type: Database Table`, title, description, tags, generator metadata, identifiers) followed by the Markdown body.
|
|
57
|
+
- **`artifact_store.py` (`FileArtifactStore`)**: Persists artifacts to `STORAGE_EXPORT_DIR` via atomic writes with readable `0644` file permissions, maintaining separate directory hierarchies for Markdown and OKF catalog bundles with an auto-updated `index.md`:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
storage/
|
|
61
|
+
metadata/
|
|
62
|
+
{config}/{instance}/{schema}/{table}.json
|
|
63
|
+
exports/
|
|
64
|
+
markdown/
|
|
65
|
+
{config}/{instance}/{schema}/{table}.md
|
|
66
|
+
okf/
|
|
67
|
+
catalog/
|
|
68
|
+
index.md
|
|
69
|
+
{config}/{instance}/{schema}/{table}.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `storage.py`
|
|
73
|
+
|
|
74
|
+
Provides metadata persistence abstractions.
|
|
75
|
+
|
|
76
|
+
**`BaseMetadataStore`** — abstract interface with these methods:
|
|
77
|
+
|
|
78
|
+
| Method | Description |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `save_table_metadata(...)` | Write or update a table metadata document and derived exports |
|
|
81
|
+
| `get_table_metadata(...)` | Read a stored document by config+instance+schema+table |
|
|
82
|
+
| `list_instances(page, page_size)` | Paginated list of all stored instance names |
|
|
83
|
+
| `list_databases(instance_name, page, page_size)` | Paginated list of databases for an instance |
|
|
84
|
+
| `list_tables_metadata(instance_name, database_name, page, page_size)` | Paginated list of table names |
|
|
85
|
+
| `find_table_metadata(instance_name, database_name, table_name)` | Look up a table across all configs |
|
|
86
|
+
| `update_table_metadata(instance_name, database_name, table_name, payload)` | Merge custom fields into a stored document and regenerate exports |
|
|
87
|
+
|
|
88
|
+
**`FileMetadataStore`** (default) — saves canonical JSON documents under `STORAGE_METADATA_DIR` and derived exports under `STORAGE_EXPORT_DIR`.
|
|
89
|
+
|
|
90
|
+
Key behaviours:
|
|
91
|
+
|
|
92
|
+
- **Decoupled export pipeline**: Derived artifacts (Markdown, OKF) are generated independently from JSON metadata persistence. Setting `save_metadata=False` still generates exports if requested.
|
|
93
|
+
- **Automatic export regeneration**: Calling `update_table_metadata` to merge human annotations automatically regenerates the corresponding Markdown and OKF documents.
|
|
94
|
+
- **Custom field carry-forward**: any key not in `_SYSTEM_KEYS` (`metadata_key`, `config_name`, `instance_name`, `schema_name`, `table_name`, `updated_at`, `schema_description`, `ai_documentation`) is preserved across re-describe calls. Human-added fields such as `owner`, `tags`, and `notes` survive schema refreshes.
|
|
95
|
+
- **`only_if_changed`**: when `True`, `save_table_metadata` skips the JSON write if `schema_description` is identical to the stored version, leaving `updated_at` and human annotations untouched, but exports the current state.
|
|
96
|
+
- **`ai_documentation` preservation**: if `ai_documentation=None` is passed, the existing stored AI doc is kept rather than overwritten.
|
|
97
|
+
- **Protected fields**: `update_table_metadata` silently ignores `metadata_key`, `config_name`, `instance_name`, `schema_name`, `table_name`, and `updated_at` in the payload — these are always managed by the system.
|
|
98
|
+
|
|
99
|
+
Paginated list responses follow this envelope:
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"items": ["name_a", "name_b"],
|
|
103
|
+
"total": 2,
|
|
104
|
+
"page": 1,
|
|
105
|
+
"page_size": 20,
|
|
106
|
+
"pages": 1
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Use `get_metadata_store()` (factory function) to obtain the configured store. Set `METADATA_STORE_TYPE=file` (default) or extend with future backends (`s3`, `athena`).
|
|
111
|
+
|
|
112
|
+
### `ai_service.py`
|
|
113
|
+
**`AIDocumentationService`**: Non-blocking integration with LiteLLM (`LITELLM_MODEL`, default `gpt-4o-mini`). Generates high-level domain summaries and column descriptions. When requested, generated docs are attached to `TableDescription.ai_documentation` with `ai_generation_status`; failures also include `ai_generation_error`. If `litellm` is uninstalled, API keys are missing, or network errors occur, it logs a warning and returns no documentation without throwing exceptions.
|
|
114
|
+
|
|
115
|
+
### `configurations.py`
|
|
116
|
+
Reads database connection parameters from environment variables. `DB_TARGETS` supports any number of named targets for any connector.
|
|
117
|
+
- Supports `DB_CONFIG_FILE` environment variable to explicitly specify the path to a container `.env` file (e.g. `/app/api/.env`), falling back to default `load_dotenv()` discovery when unset.
|
|
118
|
+
- Target names from `DB_TARGETS` become API/MCP `config_name` values. Example: `DB_TARGETS=sales_mysql,analytics_pg` creates `sales_mysql` and `analytics_pg` configurations.
|
|
119
|
+
- Each target uses `DB_TARGET_<TARGET_KEY>_*` variables, where `<TARGET_KEY>` is the uppercased target name with non-alphanumeric characters replaced by underscores.
|
|
120
|
+
- Exact required and optional keys for each connector type are documented in the root README under **DB Configuration & Activation**.
|
|
121
|
+
|
|
122
|
+
### `config_service.py`
|
|
123
|
+
Wraps `ConnectorManager` and `configurations`.
|
|
124
|
+
- **`list_instances(config_name, no_cache)`**: Uniformly lists instances for both multi-host configurations (MySQL/MariaDB) and flat configurations (Athena, DynamoDB, Trino, MongoDB, SQLite).
|
|
125
|
+
- **`resolve_instance_names(config_name, instance_name, no_cache)`**: Returns `[instance_name]` if specified, or all discovered instances if `instance_name` is `None`.
|
|
126
|
+
- **`_get_hosts(config_name)`**: Returns explicitly configured hosts, correctly recognizing both multi-host `hosts` collections and flat `host` parameters.
|
|
127
|
+
- **`configuration_matches_instance(config_name, instance_name, no_cache)`**: Checks whether an instance belongs to a given configuration.
|
|
128
|
+
- **`resolve_configurations_for_instance(instance_name, no_cache)`**: Centralized lookup returning all configuration names matching a target instance.
|
|
129
|
+
- **`_get_connector_for_host(config_name, host)`**: Returns the connector for a specific host, falling back to flat connection parameters when static host definitions are omitted.
|
|
130
|
+
|
|
131
|
+
### `cache_manager.py`
|
|
132
|
+
Redis-backed cache for introspection results. All keys are prefixed with `CACHE_KEY_PREFIX`. Cache can be bypassed per-call with `no_cache=True`.
|
|
133
|
+
|
|
134
|
+
### `job_store.py`
|
|
135
|
+
Manages async scan jobs via Redis:
|
|
136
|
+
- **Stream** (`{prefix}:scan:queue`) — job queue for workers (`scan-workers` consumer group). Stream messages serialize `export_formats` and `export_preformat` parameters.
|
|
137
|
+
- **Hash** (`{prefix}:scan:job:{job_id}`) — job metadata, scope, and status. Supports deserialization of new export options with backwards compatibility for legacy jobs with `save_markdown`.
|
|
138
|
+
- **List** (`{prefix}:scan:results:{job_id}`) — serialized `TableDescription` results with automatic TTL extensions on writes.
|
|
139
|
+
- **Sorted Set** (`{prefix}:scan:jobs`) — job index ordered by creation timestamp, automatically pruned of entries older than `RESULTS_TTL` via `zremrangebyscore` to prevent Redis memory leaks.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Supported Databases
|
|
144
|
+
|
|
145
|
+
| Database | Connector Type | Configuration Style |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| MySQL / MariaDB | `mysql` | Named `DB_TARGETS` |
|
|
148
|
+
| PostgreSQL | `postgres` | Named `DB_TARGETS` |
|
|
149
|
+
| SQLite | `sqlite` | Named `DB_TARGETS` |
|
|
150
|
+
| DuckDB | `duckdb` | Named `DB_TARGETS` |
|
|
151
|
+
| Amazon DynamoDB | `dynamodb` | Named `DB_TARGETS` |
|
|
152
|
+
| Amazon Athena | `athena` | Named `DB_TARGETS` |
|
|
153
|
+
| MongoDB | `mongodb` | Named `DB_TARGETS` |
|
|
154
|
+
| Trino | `trino` | Named `DB_TARGETS` |
|
|
155
|
+
| Presto | `presto` | Named `DB_TARGETS` |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Environment Variables
|
|
160
|
+
|
|
161
|
+
Copy `.env.example` to `.env` and configure as needed.
|
|
162
|
+
|
|
163
|
+
| Variable | Default | Description |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `REDIS_HOST` | `localhost` | Redis host |
|
|
166
|
+
| `REDIS_PORT` | `6379` | Redis port |
|
|
167
|
+
| `REDIS_DB` | `0` | Redis database index |
|
|
168
|
+
| `REDIS_TTL_SECONDS` | `86400` | Introspection cache TTL (1 day) |
|
|
169
|
+
| `CACHE_KEY_PREFIX` | `irides` | Prefix for all Redis keys |
|
|
170
|
+
| `SCAN_RESULTS_TTL_SECONDS` | `604800` | Scan result retention in Redis (7 days) |
|
|
171
|
+
| `DB_CONFIG_FILE` | *(none)* | Explicit path to `.env` configuration file |
|
|
172
|
+
| `DB_TARGETS` | *(none)* | Comma-separated list of named DB targets |
|
|
173
|
+
| `STORAGE_METADATA_DIR` | `storage/metadata` | Metadata JSON output directory |
|
|
174
|
+
| `STORAGE_EXPORT_DIR` | `storage/exports` | Directory for generated Markdown and OKF exports |
|
|
175
|
+
| `METADATA_STORE_TYPE` | `file` | Metadata store backend (`file`; `s3`/`athena` planned) |
|
|
176
|
+
| `LITELLM_MODEL` | `gpt-4o-mini` | LiteLLM model for AI documentation |
|
|
177
|
+
| `LITELLM_API_KEY` | *(none)* | Optional provider API key override |
|
|
178
|
+
| `LITELLM_API_BASE` | *(none)* | Optional custom LiteLLM API base URL |
|
|
179
|
+
|
|
180
|
+
DB activation vars — see [root README](../README.md#db-configuration--activation).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Installation
|
|
185
|
+
|
|
186
|
+
The core package is installed in editable mode by the API and Worker:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pip install -e /path/to/core
|
|
190
|
+
# or via requirements.txt:
|
|
191
|
+
pip install -r requirements.txt
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Adding a New Connector
|
|
197
|
+
|
|
198
|
+
1. Create `core/db_connector/connectors/mydb.py` implementing `BaseConnector`.
|
|
199
|
+
2. Export it from `core/db_connector/connectors/__init__.py`.
|
|
200
|
+
3. Add its activation env var and config block to `core/db_connector/configurations.py`.
|
|
201
|
+
|
|
202
|
+
The `ConnectorManager` automatically discovers all classes that extend `BaseConnector`.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from core.db_connector.connectors.mysql import MySQLConnector
|
|
4
|
+
from core.db_connector.cache_manager import CacheManager
|
|
5
|
+
from core.db_connector.models import TableDescription
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
# Configuration for MySQL Test (ADJUST THESE VALUES)
|
|
9
|
+
MYSQL_CONFIG = {
|
|
10
|
+
"host": "<your_mysql_host>", # e.g., "localhost" or "mysql_test_container"
|
|
11
|
+
"user": "<your_mysql_user>", # e.g., "root"
|
|
12
|
+
"password": "<your_mysql_password>", # e.g., "password"
|
|
13
|
+
"port": 3306,
|
|
14
|
+
# "database": "test_db" # Optional, for specific database operations
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def run_basic_mysql_test():
|
|
18
|
+
logger.info("--- Running Basic MySQL Test ---")
|
|
19
|
+
connector = None
|
|
20
|
+
try:
|
|
21
|
+
cache_manager = CacheManager()
|
|
22
|
+
connector = MySQLConnector(connection_params=MYSQL_CONFIG, cache_manager=cache_manager)
|
|
23
|
+
logger.info("MySQL connection successful!")
|
|
24
|
+
|
|
25
|
+
logger.info("\n--- Listing Instances ---")
|
|
26
|
+
instances = connector.list_instances()
|
|
27
|
+
for instance in instances:
|
|
28
|
+
logger.info(f"Instance: {instance.name} (Version: {instance.version})")
|
|
29
|
+
|
|
30
|
+
logger.info(f"\n--- Listing Schemas for Instance: {instance.name} ---")
|
|
31
|
+
schemas = connector.list_schemas(instance_name=instance.name)
|
|
32
|
+
for schema in schemas:
|
|
33
|
+
logger.info(f" Schema: {schema.name}")
|
|
34
|
+
|
|
35
|
+
logger.info(f"\n --- Listing Tables for Schema: {schema.name} ---")
|
|
36
|
+
tables = connector.list_tables(instance_name=instance.name, schema_name=schema.name)
|
|
37
|
+
if not tables:
|
|
38
|
+
logger.info(f" No tables found in schema: {schema.name}")
|
|
39
|
+
for table in tables:
|
|
40
|
+
logger.info(f" Table: {table.name}")
|
|
41
|
+
|
|
42
|
+
logger.info(f"\n --- Describing Table: {table.name} in Schema: {schema.name} ---")
|
|
43
|
+
table_desc: TableDescription = connector.describe_table(instance_name=instance.name, schema_name=schema.name, table_name=table.name)
|
|
44
|
+
|
|
45
|
+
logger.info(f" Columns:")
|
|
46
|
+
if not table_desc.columns:
|
|
47
|
+
logger.info(f" No columns found for table: {table.name}")
|
|
48
|
+
for col in table_desc.columns:
|
|
49
|
+
logger.info(f" - Name: {col.name}, Type: {col.data_type}, Nullable: {col.is_nullable}, Default: {col.default_value}")
|
|
50
|
+
|
|
51
|
+
if table_desc.primary_key:
|
|
52
|
+
logger.info(f" Primary Key: {', '.join(table_desc.primary_key.column_names)}")
|
|
53
|
+
|
|
54
|
+
if table_desc.foreign_keys:
|
|
55
|
+
logger.info(f" Foreign Keys:")
|
|
56
|
+
for fk in table_desc.foreign_keys:
|
|
57
|
+
logger.info(f" - Column: {fk.column_name} -> {fk.referenced_table}.{fk.referenced_column} (Constraint: {fk.constraint_name})")
|
|
58
|
+
|
|
59
|
+
if table_desc.indexes:
|
|
60
|
+
logger.info(f" Indexes:")
|
|
61
|
+
for idx in table_desc.indexes:
|
|
62
|
+
logger.info(f" - Name: {idx.name}, Columns: {', '.join(idx.column_names)}, Unique: {idx.is_unique}, Primary: {idx.is_primary}, Type: {idx.type}")
|
|
63
|
+
|
|
64
|
+
logger.info("-" * 40) # Separator for readability
|
|
65
|
+
|
|
66
|
+
except ConnectionError as e:
|
|
67
|
+
logger.error(f"MySQL connection failed: {e}")
|
|
68
|
+
logger.exception("Connection Error Traceback:")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"An unexpected error occurred: {e}")
|
|
72
|
+
logger.exception("Unexpected Error Traceback:")
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
finally:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
run_basic_mysql_test()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from core.db_connector.connectors.mysql import MySQLConnector
|
|
3
|
+
from core.db_connector.cache_manager import CacheManager
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
MYSQL_TEST_CONFIG = {
|
|
7
|
+
"host": "host.docker.internal",
|
|
8
|
+
"user": "root",
|
|
9
|
+
"password": "",
|
|
10
|
+
"port": 3306,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
logger.info("Attempting MySQL connection check...")
|
|
15
|
+
temp_conn = MySQLConnector(
|
|
16
|
+
connection_params=MYSQL_TEST_CONFIG,
|
|
17
|
+
cache_manager=CacheManager(),
|
|
18
|
+
)
|
|
19
|
+
logger.info("MySQL connection successful!")
|
|
20
|
+
except Exception as e:
|
|
21
|
+
logger.error(f"MySQL connection failed: {e}")
|
|
22
|
+
logger.exception("Connection Error Traceback:")
|
|
23
|
+
sys.exit(1) # Exit with error code if connection fails
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .manager import ConnectorManager
|
|
2
|
+
from .storage import BaseMetadataStore, FileMetadataStore, get_metadata_store
|
|
3
|
+
from .ai_service import AIDocumentationService
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ConnectorManager",
|
|
7
|
+
"BaseMetadataStore",
|
|
8
|
+
"FileMetadataStore",
|
|
9
|
+
"get_metadata_store",
|
|
10
|
+
"AIDocumentationService",
|
|
11
|
+
]
|
|
12
|
+
|