Batch import pilot for xplain via YAML-driven configuration
Project description
xplain-import-pilot
YAML-driven batch import tool for xplain analytics platform
Import database tables into XOE (Xplain Object Explorer) sessions through declarative configuration files. Define your data model once in YAML, then import entire table hierarchies with a single command.
Features
- ๐ฏ Declarative Configuration โ Define database connections and object hierarchies in YAML
- ๐ Multi-Level Relationships โ Support for root objects and nested child relationships
- ๐ฒ Smart Sampling โ Built-in sampling with database-specific random functions
- ๐งฎ SQL Dimensions โ Add computed dimensions using SQL expressions
- โ Validation First โ Comprehensive config validation before any network calls
- ๐ Dry-Run Mode โ Preview import plan without connecting to databases
- ๐๏ธ Multi-Database Support โ PostgreSQL, MySQL, Oracle, DB2, MariaDB, Databricks
- ๐งฌ Attribute Import โ Import lookup/dimension tables as XAttributes with hierarchy levels
- ๐ Notebook Export โ Generate a Jupyter notebook mirroring the import steps
Installation
Prerequisites
- Python โฅ3.12
- uv package manager
Install from Source
cd /Users/peide/xplainrepo/xplain_import_pilot
uv sync
This installs the xplain-import command-line tool.
Quick Start
1. Get Your XOE Session ID
- Open your XOE instance in a browser
- Open DevTools (F12 or right-click โ Inspect)
- Navigate to Application โ Cookies
- Copy the value of
JSESSIONID
2. Create a Configuration File
Create my-import.yaml:
database:
type: POSTGRESQL
host: localhost
port: 5432
user: analytics_user
password: changeme
database: sales_db
schema: public # optional
objects:
# Root object
- name: customers
table: customers
role: root
primary_key: customer_id
# Child of customers
- name: orders
table: orders
role: child
parent: customers
foreign_keys:
- customer_id
3. Run Import
Dry-run (validate config without connecting):
uv run xplain-import --session-id dummy --config my-import.yaml --dry-run
Execute import:
# Using environment variable (recommended)
export XPLAIN_SESSION_ID=<your-session-id>
uv run xplain-import --config my-import.yaml
# Or using CLI flag
uv run xplain-import --session-id <your-session-id> --config my-import.yaml
Configuration Reference
Database Configuration
database:
type: POSTGRESQL | MYSQL | ORACLE | DB2 | MARIADB | DATABRICKS
host: <hostname>
port: <port-number>
user: <username>
password: <password>
database: <database-name>
schema: <schema-name> # optional
driver_properties: # optional JDBC properties
key: value
Object Configuration
Root Object
- name: <object-name>
table: <table-name>
role: root
primary_key: <column-name>
where_clause: "status = 'active'" # optional SQL filter
sampling_rate: 0.8 # optional (0.0-1.0)
sql_dimensions: # optional computed dimensions
- name: <dimension-name>
expression: "EXTRACT(YEAR FROM created_at)"
type: INT | LONG | CATEGORIAL
Child Object
- name: <object-name>
table: <table-name>
role: child
parent: <parent-object-name>
foreign_keys:
- <column-name>
where_clause: "amount > 0" # optional
sampling_rate: 0.5 # optional
sql_dimensions: # optional
- name: <dimension-name>
expression: "EXTRACT(MONTH FROM order_date)"
type: INT
Field Descriptions
| Field | Required | Description |
|---|---|---|
name |
โ | Unique object name in XOE |
table |
โ | Database table name |
role |
โ | root or child |
primary_key |
โ (root) | Primary key column name |
parent |
โ (child) | Parent object name |
foreign_keys |
โ (child) | List of foreign key columns |
where_clause |
โ | SQL WHERE condition (without "WHERE") |
sampling_rate |
โ | Fraction of rows to import (0.0-1.0) |
sql_dimensions |
โ | Computed dimensions via SQL |
Attributes (Dimension Lookup Tables)
Attributes import a lookup/dimension table (e.g. payers, categories) as an XAttribute
with a drill-down hierarchy, rather than as a plain XTable. Add an attributes: section
alongside objects::
attributes:
- name: payers # Unique name for this attribute config
table: payers # Source table
attribute_name: Payer # Display name in XOE
primary_key: id
hierarchy_level_columns: [name, ownership] # leaf first โ coarsest last
hierarchy_level_names: [Payer, Ownership] # optional display names per level
xtable: encounters # which XTable to attach this attribute to in the XView
| Field | Required | Description |
|---|---|---|
name |
โ | Unique attribute config name |
table |
โ | Source database table |
attribute_name |
โ | Display name shown in XOE |
primary_key |
โ | Primary key column |
hierarchy_level_columns |
โ | Columns forming the drill-down hierarchy, leaf first |
hierarchy_level_names |
โ | Display names per hierarchy level (defaults to column names) |
xtable |
โ | XTable to attach the attribute to (default: the root object) |
Usage Examples
Multi-Level Hierarchy
objects:
- name: customers
table: customers
role: root
primary_key: customer_id
- name: orders
table: orders
role: child
parent: customers
primary_key: order_id # required: orders is a parent to order_items below
foreign_keys: [customer_id]
- name: order_items
table: order_items
role: child
parent: orders
foreign_keys: [customer_id, order_id] # full compound key of orders
Filtering and Sampling
objects:
- name: active_customers
table: customers
role: root
primary_key: customer_id
where_clause: "status = 'active' AND created_at > '2023-01-01'"
sampling_rate: 0.1 # Import 10% of matching rows
Computed Dimensions
objects:
- name: orders
table: orders
role: root
primary_key: order_id
sql_dimensions:
- name: order_year
expression: "EXTRACT(YEAR FROM order_date)"
type: INT
- name: order_month
expression: "EXTRACT(MONTH FROM order_date)"
type: INT
- name: order_quarter
expression: "CONCAT('Q', CAST(CEIL(EXTRACT(MONTH FROM order_date)/3.0) AS VARCHAR))"
type: CATEGORIAL
Command-Line Options
xplain-import [OPTIONS]
Options:
--version Show version and exit
--session-id ID XOE JSESSIONID (or set XPLAIN_SESSION_ID env var)
--config PATH Path to YAML configuration file (required)
--url URL XOE server URL (default: http://localhost:8080, or XPLAIN_SERVER_URL env var)
--dry-run Validate config and print plan without connecting
--log-level LEVEL Logging verbosity: DEBUG, INFO, WARNING, ERROR (default: INFO)
--sampling-rate RATE Override sampling rate for all objects (0.0-1.0]
--persist Save configs to XOE for reuse (connection, xtables, xview)
--persist-prefix PREFIX Prefix for saved config names (default: YAML filename)
--persist-ownership LVL Ownership: PUBLIC or PRIVATE (default: PUBLIC)
--export-notebook Export a Jupyter notebook with the xplain method sequence
--notebook-output PATH Output path for notebook (default: <config-name>_import.ipynb)
-h, --help Show help message
Exporting a Notebook
--export-notebook writes a Jupyter notebook that replays the same import steps
(xplain.Xsession, connection setup, per-object import_xtable() calls, xview build)
as executable cells โ useful for handing off an import to someone working in a notebook,
or for tweaking a step interactively after the CLI run:
uv run xplain-import --config sales.yaml --export-notebook
uv run xplain-import --config sales.yaml --export-notebook --notebook-output sales_import.ipynb
Persisting Configurations
Use --persist to save all configurations to XOE for later reuse:
uv run xplain-import --config sales.yaml --persist
This saves:
- Database connection โ
sales_connection.xdbconnection - XTable configs โ
sales_<object>.xtableconfigfor each object - XView โ
sales_view.xview
Customize the prefix or ownership:
uv run xplain-import --config sales.yaml --persist --persist-prefix my_project --persist-ownership PRIVATE
Troubleshooting
Config Validation Errors
Error: object 'orders' references parent 'customers' which is not defined
Solution: Ensure the parent object is defined in the objects list before the child.
Error: object 'customers': root requires 'primary_key'
Solution: Add primary_key: <column-name> to root objects.
Error: validation error for DimensionType
Solution: Ensure type is one of: INT, LONG, CATEGORIAL (note spelling).
Connection Errors
Error: DB connection test failed
Solution:
- Verify database credentials (host, port, user, password)
- Check network connectivity to database
- Ensure database user has SELECT permissions on tables
Error: Import failed: ... session ... not found
Solution:
- Verify XOE session is still active (sessions expire)
- Get a fresh JSESSIONID from browser
- Ensure XOE server URL is correct
Import Errors
Error: xtable import failed for 'orders'
Solution:
- Check that foreign key columns exist in child table
- Verify WHERE clause syntax is valid SQL
- Ensure parent object was imported successfully
Security Best Practices
[!WARNING] Credential Security
- Never commit YAML files with real passwords to version control
- Use environment variables for sensitive data
- Prefer
XPLAIN_SESSION_IDenv var over--session-idCLI flag (CLI args are visible in process lists)
Recommended approach:
-
Create
.envfile (already in.gitignore):XPLAIN_SESSION_ID=your_session_id_here
-
Load environment variables:
source .env # or use direnv, dotenv, etc.
-
Run without exposing secrets:
uv run xplain-import --config my-import.yaml
Development
Setup Development Environment
# Install with dev dependencies
uv sync --extra dev
# Run tests
uv run pytest
# Type checking
uv run mypy xplain_import_pilot/
# Linting
uv run ruff check xplain_import_pilot/
Running Tests
# All tests with coverage
uv run pytest tests/ -v --cov=xplain_import_pilot --cov-report=term-missing
# Specific test file
uv run pytest tests/test_config.py -v
# With debug output
uv run pytest tests/ -v -s
How It Works
The import pipeline follows these steps:
- Connect โ Establish connection to existing XOE session via
http_session_id - Database Setup โ Create and test database connection
- Import XTables โ Import each table in dependency order (parents before children)
- Build XView โ Construct view configuration mirroring object hierarchy
- Load Session โ Load XView into live XOE session
Objects are imported in topological order using stable sorting (preserves YAML order for objects at the same depth).
Compound Keys & FK Dimension Alignment
How Xplain builds compound object keys
Xplain identifies every record in a hierarchy through a compound key โ the ordered concatenation of ancestor key dimensions down to the object itself.
| Level | Object | Own PK column | Compound key dimensions |
|---|---|---|---|
| 1 (root) | patients |
patientid |
[patientid] |
| 2 (child) | admissions |
admissionid |
[patientid, admissionid] |
| 3 (grandchild) | clinical_observations |
observationid |
[patientid, admissionid, observationid] |
A child's foreignKeyDimensions must equal the full compound key of its parent โ not just the immediate FK column. The importer calculates and applies this expansion automatically; you only need to list the direct FK column(s) in the YAML.
FK column names vs. dimension names
After auto-mapping, every column starts with dimensionName == dbColumnName. But the parent's key dimension may have been named differently from the corresponding FK column in the child table:
| Table | DB column | Must become dimension |
|---|---|---|
patients |
patientid |
patientid (no change) |
admissions |
patient_id |
patientid โ renamed |
clinical_observations |
patient_id |
patientid โ renamed |
clinical_observations |
admission_id |
admissionid โ renamed |
foreignKeyDimensions must reference dimension names, not column names. The importer aligns them automatically using normalized name matching: both the column name and the parent dimension name are lowercased and stripped of underscores. If they match, dimensionName is updated to the parent's canonical form. dbColumnName is never changed.
Normalization rule
normalize(name) = name.lower().replace("_", "")
patient_id โ patientid
patientid โ patientid โ same โ dimension is renamed to "patientid"
admission_id โ admissionid
admissionid โ admissionid โ same โ dimension is renamed to "admissionid"
Worked example โ 3-level hierarchy
YAML config:
database:
type: POSTGRESQL
host: localhost
port: 5432
user: postgres
password: secret
database: hospital
objects:
- name: patients
table: patients
role: root
primary_key: patientid
- name: admissions
table: admissions
role: child
parent: patients
primary_key: admissionid
foreign_keys: [patient_id] # direct FK column in the admissions table
- name: clinical_observations
table: clinical_observations
role: child
parent: admissions
primary_key: observationid
foreign_keys: [admission_id] # direct FK column; importer expands to full compound key
Step 1 โ patients (root)
No FK processing needed. Compound key tracked: ["patientid"].
{
"objectName": "patients",
"keyDimension": "patientid",
"foreignKeyDimensions": null,
"dimensionConfigurations": [
{ "dbColumnName": "patientid", "dimensionName": "patientid" },
{ "dbColumnName": "name", "dimensionName": "name" }
]
}
Step 2 โ admissions (child of patients)
Parent compound key: ["patientid"].
The admissions table has a column patient_id. Normalized: patientid == patientid โ dimensionName is renamed from patient_id to patientid. foreignKeyDimensions is set to the full parent compound key ["patientid"].
Compound key tracked: ["patientid", "admissionid"].
{
"objectName": "admissions",
"keyDimension": "admissionid",
"foreignKeyDimensions": ["patientid"],
"dimensionConfigurations": [
{ "dbColumnName": "patient_id", "dimensionName": "patientid" }, โ renamed
{ "dbColumnName": "admissionid", "dimensionName": "admissionid" },
{ "dbColumnName": "admit_date", "dimensionName": "admit_date" }
]
}
Step 3 โ clinical_observations (child of admissions)
Parent compound key: ["patientid", "admissionid"].
Two columns are aligned:
patient_idโ normalize โpatientid==patientidโ renamed topatientidadmission_idโ normalize โadmissionid==admissionidโ renamed toadmissionid
foreignKeyDimensions is set to the full parent compound key ["patientid", "admissionid"] (not just the direct admission_id).
Compound key tracked: ["patientid", "admissionid", "observationid"].
{
"objectName": "clinical_observations",
"keyDimension": "observationid",
"foreignKeyDimensions": ["patientid", "admissionid"],
"dimensionConfigurations": [
{ "dbColumnName": "patient_id", "dimensionName": "patientid" }, โ renamed
{ "dbColumnName": "admission_id", "dimensionName": "admissionid" }, โ renamed
{ "dbColumnName": "observationid", "dimensionName": "observationid"},
{ "dbColumnName": "value", "dimensionName": "value" }
]
}
Tables that do not carry grandparent FK columns
In a normalized (3NF) schema, clinical_observations may only have admission_id โ no patient_id column. In that case:
- The importer still sets
foreignKeyDimensions = ["patientid", "admissionid"] admissioniddimension exists (columnadmission_idwas renamed) โpatientiddimension does not exist โ Xplain will reject the import
Options:
- Add
patient_idto theclinical_observationstable in the database (denormalize the key chain). - Shorten the hierarchy โ import
admissionsonly (without going one level deeper). - Use
admissionsas the root object and importclinical_observationsas its child.
Auto-Pilot Mode
Auto-pilot mode removes the need to write a YAML config by hand. It connects to your database, discovers all tables, infers primary keys and foreign-key relationships from column naming conventions, proposes a root/child hierarchy, writes a YAML file, and โ after a single confirmation โ executes the import.
Quick Start
# Using a DB config file
export XPLAIN_SESSION_ID=<your-session-id>
uv run xplain-import-autopilot --db-config conn.yaml
# Using inline flags
uv run xplain-import-autopilot \
--db-type POSTGRESQL --db-host localhost --db-port 5432 \
--db-user admin --db-password secret --db-name mydb
DB Config File Format
conn.yaml needs only the database: section (same format as a full import config):
database:
type: POSTGRESQL
host: localhost
port: 5432
user: postgres
password: secret
database: mydb
schema: public # optional โ restrict to one schema
What Happens Step by Step
- Connect โ Verifies XOE session and database credentials.
- Explore โ Lists all tables in the schema, fetches a small sample of rows per table for type inference.
- Infer relationships โ Detects foreign keys from column names ending in
_id. A columnfoo_idin tablebaris treated as a FK to tablefoo(or its plural variant). Exact name matches get 90 % confidence; singular/plural matches get 75 %. - Suggest hierarchy โ Tables with no outgoing FK become roots; others become children of their highest-confidence parent. Primary keys are inferred from column names (
id,<table>_id, etc.). - Display โ Prints a schema table, an inferred-relationships table, and a tree view of the suggested hierarchy, with confidence indicators coloured green/yellow/red.
- Write YAML โ Saves the suggested config to
<db-name>_autopilot.yaml(override with--output-yaml). Low-confidence items are flagged as# WARNINGcomments at the top of the file. - Confirm โ Prompts
Proceed with import? [y/N](skip with--yes). - Import โ Runs the same pipeline as
xplain-import.
Command-Line Reference
xplain-import-autopilot [OPTIONS]
Database connection (one of --db-config or all --db-* flags required):
--db-config YAML YAML file with a 'database:' section
--db-type TYPE POSTGRESQL | MYSQL | ORACLE | DB2 | MARIADB | DATABRICKS
--db-host HOST
--db-port PORT
--db-user USER
--db-password PASSWORD
--db-name DBNAME
--db-schema SCHEMA Restrict table discovery to this schema
XOE session:
--session-id ID XOE JSESSIONID (or set XPLAIN_SESSION_ID env var)
--url URL XOE server URL (default: http://localhost:8080, or XPLAIN_SERVER_URL env var)
Autopilot options:
--output-yaml PATH Save suggested config here (default: <db-name>_autopilot.yaml)
--max-tables N Cap table discovery at N tables (default: 50)
--sample-rows N Rows fetched per table for type inference (default: 5)
--yes Skip confirmation prompt
--dry-run Explore and print suggestion only โ do not import
--persist Persist xtable configs and xview to XOE after import
--persist-prefix PREFIX Prefix for saved config names (default: <db-name>)
--log-level LEVEL DEBUG | INFO | WARNING | ERROR (default: INFO)
Common Workflows
Preview without importing:
uv run xplain-import-autopilot --db-config conn.yaml --dry-run
Fully automated (no prompts, save configs to XOE):
uv run xplain-import-autopilot --db-config conn.yaml --yes --persist
Limit to a specific schema and cap table count:
uv run xplain-import-autopilot --db-config conn.yaml --db-schema analytics --max-tables 20
Review, edit, then import manually:
# 1. Generate the YAML (dry-run so nothing is imported yet)
uv run xplain-import-autopilot --db-config conn.yaml --dry-run
# 2. Edit the generated file
vi mydb_autopilot.yaml
# 3. Import using the regular CLI
uv run xplain-import --config mydb_autopilot.yaml
Reading the Output
Confidence colours in the hierarchy tree:
| Colour | Meaning |
|---|---|
| Green | High confidence (โฅ 85 % for FKs, โฅ 70 % for PKs) |
| Yellow | Medium confidence โ review before importing |
| Red | Low confidence โ manual correction recommended |
YAML warnings: Any relationship with confidence below 80 % (FK) or 70 % (PK) appears as a # WARNING comment at the top of the generated file. Review these before running the import.
Limitations
- FK inference relies on column naming conventions (
<table>_id). Columns that use non-standard naming won't be detected โ edit the generated YAML to add them. - Composite foreign keys are not currently inferred; add them manually in the YAML.
- Tables with no columns matching the PK heuristics fall back to the first column โ check the warning and correct if needed.
- The
--max-tableslimit applies to the first N tables returned by the database driver; if the most important tables appear later, increase the limit or use--db-schemato narrow the search.
AI/Claude Skill
This repository includes an AI skill for natural language database imports. The skill allows Claude (or other AI agents) to generate YAML configurations from natural language descriptions.
Skill Location
skill/
โโโ SKILL.md # Main skill instructions
โโโ references/
โ โโโ config-reference.md # YAML field reference
โโโ examples/
โโโ example.yaml # Basic example
โโโ productronica.yaml # Multi-level example
Installing the Skill
For Clawdbot users:
# Copy to your workspace skills directory
cp -r skill/ ~/.clawdbot/workspace/skills/xplain-import/
# Or symlink for development
ln -s $(pwd)/skill ~/.clawdbot/workspace/skills/xplain-import
For Claude Code users:
Add the skill path to your project's .claude/settings.json or reference it in your AGENTS.md.
Using the Skill
Once installed, describe your data structure in natural language:
Example prompts:
- "Import customers and their orders from PostgreSQL into xplain"
- "Create an xplain import for products with categories as a computed dimension"
- "Load boards โ components โ alerts hierarchy from the productronica database"
The AI will:
- Ask clarifying questions about database connection and structure
- Generate a YAML configuration file
- Optionally run the import with
--dry-runfirst - Execute the import with
--persistto save configurations
Skill Triggers
The skill activates on phrases like:
- "import into xplain"
- "xplain import"
- "create xtables"
- "load database into XOE"
- Describing table structures with parent-child relationships
License
See project repository for license information.
Support
For issues or questions:
- Check the Troubleshooting section
- Review
example.yamlfor reference configuration - Examine logs with
--log-level DEBUGfor detailed diagnostics
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xplain_import_pilot-0.1.0.tar.gz.
File metadata
- Download URL: xplain_import_pilot-0.1.0.tar.gz
- Upload date:
- Size: 60.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1eaf76a38fb0d9571f35b8d5f08aad35fff2534329f88096ef5e6e2b0579b1de
|
|
| MD5 |
921cf8d1a59fa397a173ac6f2e3fe6bf
|
|
| BLAKE2b-256 |
4d386221b016ed6ae4473aa5a7a9dd53e6d611ab03b11f5a3236cf2b60ba08c3
|
File details
Details for the file xplain_import_pilot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: xplain_import_pilot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 59.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ad715264d7196fbb6e6d9fee8209d95767439c86bf47125f504d2f3dd53daab
|
|
| MD5 |
67f2aa8c4ffde18918f09529a1acbcfb
|
|
| BLAKE2b-256 |
66ce8cd9fa9d9a73d6ce3e288b801549ce82e8d1ddcd3d90691b9e22f71787be
|