cdata-connect 0.0.1__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.
- cdata_connect-0.0.1/.github/workflows/release.yml +146 -0
- cdata_connect-0.0.1/.github/workflows/test.yml +57 -0
- cdata_connect-0.0.1/.gitignore +51 -0
- cdata_connect-0.0.1/CLAUDE.md +51 -0
- cdata_connect-0.0.1/EXAMPLES.md +315 -0
- cdata_connect-0.0.1/LICENSE +21 -0
- cdata_connect-0.0.1/MANIFEST.in +8 -0
- cdata_connect-0.0.1/PKG-INFO +287 -0
- cdata_connect-0.0.1/README.md +253 -0
- cdata_connect-0.0.1/cdata_connect/LICENSE +21 -0
- cdata_connect-0.0.1/cdata_connect/README.md +132 -0
- cdata_connect-0.0.1/cdata_connect/__init__.py +82 -0
- cdata_connect-0.0.1/cdata_connect/_version.py +34 -0
- cdata_connect-0.0.1/cdata_connect/connection.py +122 -0
- cdata_connect-0.0.1/cdata_connect/cursor.py +368 -0
- cdata_connect-0.0.1/cdata_connect/exceptions.py +42 -0
- cdata_connect-0.0.1/cdata_connect/log.py +7 -0
- cdata_connect-0.0.1/cdata_connect/util/__init__.py +0 -0
- cdata_connect-0.0.1/cdata_connect/util/types.py +214 -0
- cdata_connect-0.0.1/cdata_connect/version.py +13 -0
- cdata_connect-0.0.1/cdata_connect.egg-info/PKG-INFO +287 -0
- cdata_connect-0.0.1/cdata_connect.egg-info/SOURCES.txt +61 -0
- cdata_connect-0.0.1/cdata_connect.egg-info/dependency_links.txt +1 -0
- cdata_connect-0.0.1/cdata_connect.egg-info/requires.txt +11 -0
- cdata_connect-0.0.1/cdata_connect.egg-info/top_level.txt +2 -0
- cdata_connect-0.0.1/pyproject.toml +85 -0
- cdata_connect-0.0.1/requirements.txt +3 -0
- cdata_connect-0.0.1/setup.cfg +4 -0
- cdata_connect-0.0.1/tests/README.md +112 -0
- cdata_connect-0.0.1/tests/__init__.py +0 -0
- cdata_connect-0.0.1/tests/conftest.py +148 -0
- cdata_connect-0.0.1/tests/integration/__init__.py +0 -0
- cdata_connect-0.0.1/tests/integration/conftest.py +201 -0
- cdata_connect-0.0.1/tests/integration/test_dbapi20_compliance.py +691 -0
- cdata_connect-0.0.1/tests/integration/test_error_scenarios.py +67 -0
- cdata_connect-0.0.1/tests/integration/test_query_operations.py +73 -0
- cdata_connect-0.0.1/tests/integration/test_stored_procedures.py +27 -0
- cdata_connect-0.0.1/tests/integration/test_streaming.py +72 -0
- cdata_connect-0.0.1/tests/integration/test_timeout_and_delays.py +251 -0
- cdata_connect-0.0.1/tests/integration_live/__init__.py +0 -0
- cdata_connect-0.0.1/tests/integration_live/conftest.py +88 -0
- cdata_connect-0.0.1/tests/integration_live/helpers.py +82 -0
- cdata_connect-0.0.1/tests/integration_live/test_delete.py +49 -0
- cdata_connect-0.0.1/tests/integration_live/test_insert.py +67 -0
- cdata_connect-0.0.1/tests/integration_live/test_select.py +63 -0
- cdata_connect-0.0.1/tests/integration_live/test_stored_procedures.py +199 -0
- cdata_connect-0.0.1/tests/integration_live/test_update.py +62 -0
- cdata_connect-0.0.1/tests/performance/README.md +206 -0
- cdata_connect-0.0.1/tests/performance/__init__.py +0 -0
- cdata_connect-0.0.1/tests/performance/conftest.py +134 -0
- cdata_connect-0.0.1/tests/performance/test_concurrency.py +194 -0
- cdata_connect-0.0.1/tests/performance/test_soak.py +505 -0
- cdata_connect-0.0.1/tests/performance/test_streaming.py +273 -0
- cdata_connect-0.0.1/tests/performance/test_throughput.py +202 -0
- cdata_connect-0.0.1/tests/performance/test_timeout_behavior.py +192 -0
- cdata_connect-0.0.1/tests/unit/__init__.py +0 -0
- cdata_connect-0.0.1/tests/unit/conftest.py +33 -0
- cdata_connect-0.0.1/tests/unit/test_connection.py +259 -0
- cdata_connect-0.0.1/tests/unit/test_cursor.py +118 -0
- cdata_connect-0.0.1/tests/unit/test_exceptions.py +68 -0
- cdata_connect-0.0.1/tests/unit/test_module.py +66 -0
- cdata_connect-0.0.1/tests/unit/test_types.py +342 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
# Triggered when a GitHub Release is published (which also creates the git tag).
|
|
4
|
+
# Flow: test → build → publish PyPI → verify
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
# ──────────────────────────────────────────────────────────────────
|
|
11
|
+
# 1. Run unit tests before building
|
|
12
|
+
# ──────────────────────────────────────────────────────────────────
|
|
13
|
+
test:
|
|
14
|
+
name: Test (Python 3.11)
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
defaults:
|
|
17
|
+
run:
|
|
18
|
+
working-directory: connector
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0 # setuptools-scm needs full history
|
|
23
|
+
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.11"
|
|
27
|
+
|
|
28
|
+
- name: Install connector + test deps
|
|
29
|
+
run: |
|
|
30
|
+
pip install --upgrade pip
|
|
31
|
+
pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Run non-integration tests
|
|
34
|
+
run: |
|
|
35
|
+
pytest tests/ \
|
|
36
|
+
-k "test_apilevel or test_threadsafety or test_paramstyle or test_Exceptions or test_Date or test_Time or test_Timestamp or test_Binary or test_STRING or test_BINARY or test_NUMBER or test_DATETIME or test_ROWID" \
|
|
37
|
+
-v --tb=short
|
|
38
|
+
env:
|
|
39
|
+
CDATA_BASE_URL: http://localhost:8080/api
|
|
40
|
+
SKIP_LIVE_TESTS: "1"
|
|
41
|
+
|
|
42
|
+
# ──────────────────────────────────────────────────────────────────
|
|
43
|
+
# 2. Build wheel + sdist
|
|
44
|
+
# ──────────────────────────────────────────────────────────────────
|
|
45
|
+
build:
|
|
46
|
+
name: Build Distribution
|
|
47
|
+
needs: test
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
defaults:
|
|
50
|
+
run:
|
|
51
|
+
working-directory: connector
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
with:
|
|
55
|
+
fetch-depth: 0 # setuptools-scm needs full history to derive version
|
|
56
|
+
|
|
57
|
+
- uses: actions/setup-python@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: "3.11"
|
|
60
|
+
|
|
61
|
+
- name: Install build tools
|
|
62
|
+
run: pip install build twine
|
|
63
|
+
|
|
64
|
+
- name: Build wheel and sdist
|
|
65
|
+
run: python -m build
|
|
66
|
+
|
|
67
|
+
- name: Validate distribution
|
|
68
|
+
run: twine check dist/*
|
|
69
|
+
|
|
70
|
+
- name: Smoke-test the wheel in a clean venv
|
|
71
|
+
run: |
|
|
72
|
+
python -m venv /tmp/wheel_test_env
|
|
73
|
+
/tmp/wheel_test_env/bin/pip install dist/*.whl
|
|
74
|
+
/tmp/wheel_test_env/bin/python -c "
|
|
75
|
+
import cdata_connect
|
|
76
|
+
assert cdata_connect.apilevel == '2.0'
|
|
77
|
+
assert cdata_connect.threadsafety == 1
|
|
78
|
+
assert cdata_connect.paramstyle == 'pyformat'
|
|
79
|
+
from cdata_connect import Connection, Error, STRING, BINARY, NUMBER, DATETIME, ROWID
|
|
80
|
+
from cdata_connect.util.types import FIELD_TYPES
|
|
81
|
+
print('Wheel smoke test passed. Version:', cdata_connect.__version__)
|
|
82
|
+
"
|
|
83
|
+
|
|
84
|
+
- name: Upload distribution artifacts
|
|
85
|
+
uses: actions/upload-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: dist-packages
|
|
88
|
+
path: connector/dist/
|
|
89
|
+
retention-days: 7
|
|
90
|
+
|
|
91
|
+
# ──────────────────────────────────────────────────────────────────
|
|
92
|
+
# 3. Publish to production PyPI
|
|
93
|
+
# Gated by the 'pypi' GitHub Environment — add required reviewers in
|
|
94
|
+
# Settings > Environments > pypi > Required reviewers.
|
|
95
|
+
# Uses OIDC Trusted Publishing — no API token required.
|
|
96
|
+
# ──────────────────────────────────────────────────────────────────
|
|
97
|
+
publish:
|
|
98
|
+
name: Publish to PyPI
|
|
99
|
+
needs: build
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
environment:
|
|
102
|
+
name: pypi
|
|
103
|
+
url: https://pypi.org/project/cdata-connect/
|
|
104
|
+
permissions:
|
|
105
|
+
id-token: write # Required for Trusted Publishing
|
|
106
|
+
|
|
107
|
+
steps:
|
|
108
|
+
- name: Download distribution artifacts
|
|
109
|
+
uses: actions/download-artifact@v4
|
|
110
|
+
with:
|
|
111
|
+
name: dist-packages
|
|
112
|
+
path: dist/
|
|
113
|
+
|
|
114
|
+
- name: Publish to PyPI
|
|
115
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
116
|
+
with:
|
|
117
|
+
print-hash: true
|
|
118
|
+
|
|
119
|
+
# ──────────────────────────────────────────────────────────────────
|
|
120
|
+
# 4. Post-release verification
|
|
121
|
+
# ──────────────────────────────────────────────────────────────────
|
|
122
|
+
verify:
|
|
123
|
+
name: Verify PyPI Publication
|
|
124
|
+
needs: publish
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
steps:
|
|
127
|
+
- name: Determine release version from tag
|
|
128
|
+
id: version
|
|
129
|
+
run: |
|
|
130
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
131
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
132
|
+
|
|
133
|
+
- name: Wait for PyPI propagation
|
|
134
|
+
run: sleep 60
|
|
135
|
+
|
|
136
|
+
- name: Install from PyPI and verify
|
|
137
|
+
run: |
|
|
138
|
+
pip install "cdata-connect==${{ steps.version.outputs.version }}"
|
|
139
|
+
python -c "
|
|
140
|
+
import cdata_connect
|
|
141
|
+
print('Installed version:', cdata_connect.__version__)
|
|
142
|
+
assert cdata_connect.apilevel == '2.0'
|
|
143
|
+
assert cdata_connect.threadsafety == 1
|
|
144
|
+
assert cdata_connect.paramstyle == 'pyformat'
|
|
145
|
+
print('Post-release verification passed.')
|
|
146
|
+
"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main", "develop"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Python ${{ matrix.python-version }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install connector
|
|
30
|
+
working-directory: connector
|
|
31
|
+
run: |
|
|
32
|
+
pip install --upgrade pip
|
|
33
|
+
pip install -e .
|
|
34
|
+
pip install pytest pytest-cov requests
|
|
35
|
+
|
|
36
|
+
- name: Install mock server dependencies
|
|
37
|
+
run: |
|
|
38
|
+
pip install -r ../connect-ai-mock/requirements.txt
|
|
39
|
+
working-directory: connector
|
|
40
|
+
# conftest.py auto-discovers the mock server at ../connect-ai-mock relative to connector/
|
|
41
|
+
|
|
42
|
+
- name: Run tests against mock API
|
|
43
|
+
run: pytest tests/ -v --tb=short
|
|
44
|
+
env:
|
|
45
|
+
CDATA_BASE_URL: http://localhost:8080/api
|
|
46
|
+
CDATA_USERNAME: test@example.com
|
|
47
|
+
CDATA_PASSWORD: any_token
|
|
48
|
+
SKIP_LIVE_TESTS: "1"
|
|
49
|
+
# conftest.py auto-starts the mock server on localhost:8080
|
|
50
|
+
|
|
51
|
+
- name: Upload coverage
|
|
52
|
+
if: matrix.python-version == '3.11'
|
|
53
|
+
uses: actions/upload-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: coverage-report
|
|
56
|
+
path: build/coverage/
|
|
57
|
+
retention-days: 7
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# setuptools-scm generated version file (written at build time)
|
|
2
|
+
cdata_connect/_version.py
|
|
3
|
+
|
|
4
|
+
# Python bytecode
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.pyo
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging (produced by `python -m build`)
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
*.egg
|
|
14
|
+
MANIFEST
|
|
15
|
+
|
|
16
|
+
# Wheel / sdist archives
|
|
17
|
+
*.whl
|
|
18
|
+
*.tar.gz
|
|
19
|
+
|
|
20
|
+
# Virtual environments
|
|
21
|
+
.venv/
|
|
22
|
+
venv/
|
|
23
|
+
env/
|
|
24
|
+
ENV/
|
|
25
|
+
|
|
26
|
+
# Pytest / coverage
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
.coverage.*
|
|
30
|
+
htmlcov/
|
|
31
|
+
coverage.xml
|
|
32
|
+
|
|
33
|
+
# Type checkers
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.pytype/
|
|
36
|
+
.pyre/
|
|
37
|
+
|
|
38
|
+
# Local pyhocon config (users may create this for local testing)
|
|
39
|
+
config.conf
|
|
40
|
+
*.conf
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Editor / IDE
|
|
44
|
+
.vscode/
|
|
45
|
+
.idea/
|
|
46
|
+
*.swp
|
|
47
|
+
*.swo
|
|
48
|
+
|
|
49
|
+
# OS noise
|
|
50
|
+
.DS_Store
|
|
51
|
+
Thumbs.db
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Connector — AI Context
|
|
2
|
+
|
|
3
|
+
This is the installable `cdata-connect` Python package — a DB-API 2.0 (PEP 249) connector for CData Connect AI.
|
|
4
|
+
|
|
5
|
+
## Source Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
cdata_connect/
|
|
9
|
+
├── __init__.py # Module exports, connect() factory, apilevel/threadsafety/paramstyle
|
|
10
|
+
├── connection.py # Connection class — auth, config, lifecycle, cursor creation
|
|
11
|
+
├── cursor.py # Cursor class — execute, fetch, streaming (ijson), retry logic
|
|
12
|
+
├── exceptions.py # PEP 249 exception hierarchy + ConfigurationError
|
|
13
|
+
├── util/types.py # Type mapping (Connect API ↔ Python), Date/Time/Timestamp/Binary
|
|
14
|
+
├── log.py # Logger (NullHandler)
|
|
15
|
+
└── version.py # __version__
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
1. `connect()` → creates a `Connection` (validates params or reads PyHOCON config)
|
|
21
|
+
2. `Connection.cursor()` → creates a `Cursor` (holds reference to Connection for auth/base_url)
|
|
22
|
+
3. `Cursor.execute(query)` → `_execute_request()` → HTTP POST to `/api/query` with retry
|
|
23
|
+
4. Response is streamed: `ijson.parse()` extracts schema, then `ijson.items()` yields rows lazily
|
|
24
|
+
5. `fetchone()`/`fetchall()`/`fetchmany()` consume from the row generator
|
|
25
|
+
|
|
26
|
+
## Key Patterns
|
|
27
|
+
|
|
28
|
+
- **Retry logic** lives in `Cursor._execute_request()` — exponential backoff on 5xx/ConnectionError
|
|
29
|
+
- **Type conversion**: `util/types.py:convert_to_python_type()` maps API type names → Python types
|
|
30
|
+
- **Thread safety**: Connection uses `threading.local()`, Cursor uses `threading.Lock()`
|
|
31
|
+
- **No-ops**: `commit()`, `rollback()`, `setinputsizes()`, `setoutputsize()` are intentional no-ops
|
|
32
|
+
- **Exception hierarchy**: Must match PEP 249 exactly. `ConfigurationError` extends `Error` (custom addition)
|
|
33
|
+
|
|
34
|
+
## Tests
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e ".[dev]"
|
|
38
|
+
pytest tests/unit/ -v # 140 tests, no server, ~0.1s
|
|
39
|
+
pytest tests/integration/ -v # 49 tests, mock server auto-starts, ~1s
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Unit**: `tests/unit/` — fixtures `mock_connection`/`mock_cursor` (mocked HTTP). Never hit network.
|
|
43
|
+
- **Integration**: `tests/integration/` — fixtures `con`/`cursor` (real HTTP to mock server).
|
|
44
|
+
- **Mock scenarios**: PAT-based. `any_token`=default, `error_pat`=401, `large_pat`=100+ rows. See `../connect-ai-mock/src/data/static/scenarios.json`.
|
|
45
|
+
|
|
46
|
+
## Build
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install build && python -m build
|
|
50
|
+
# Produces dist/cdata_connect-1.0.0-py3-none-any.whl
|
|
51
|
+
```
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# cdata-connect Usage Examples
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install cdata-connect
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Basic Connection
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import cdata_connect
|
|
13
|
+
|
|
14
|
+
conn = cdata_connect.connect(
|
|
15
|
+
base_url="https://cloud.cdata.com/api",
|
|
16
|
+
username="you@example.com",
|
|
17
|
+
password="<your_personal_access_token>",
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Using Context Managers (Recommended)
|
|
22
|
+
|
|
23
|
+
Connections and cursors support `with` statements for automatic cleanup:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import cdata_connect
|
|
27
|
+
|
|
28
|
+
with cdata_connect.connect(
|
|
29
|
+
base_url="https://cloud.cdata.com/api",
|
|
30
|
+
username="you@example.com",
|
|
31
|
+
password="<your_pat>",
|
|
32
|
+
) as conn:
|
|
33
|
+
with conn.cursor() as cur:
|
|
34
|
+
cur.execute("SELECT * FROM [Salesforce1].[Salesforce].[Account]")
|
|
35
|
+
for row in cur:
|
|
36
|
+
print(row)
|
|
37
|
+
# Connection and cursor are automatically closed here
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Querying Data
|
|
41
|
+
|
|
42
|
+
### Fetch All Rows
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
with conn.cursor() as cur:
|
|
46
|
+
cur.execute("SELECT Id, Name, Industry FROM [Salesforce1].[Salesforce].[Account]")
|
|
47
|
+
rows = cur.fetchall()
|
|
48
|
+
for row in rows:
|
|
49
|
+
print(f"Id={row[0]}, Name={row[1]}, Industry={row[2]}")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Fetch One Row at a Time
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
with conn.cursor() as cur:
|
|
56
|
+
cur.execute("SELECT * FROM [Salesforce1].[Salesforce].[Contact]")
|
|
57
|
+
while True:
|
|
58
|
+
row = cur.fetchone()
|
|
59
|
+
if row is None:
|
|
60
|
+
break
|
|
61
|
+
print(row)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Fetch in Batches
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
with conn.cursor() as cur:
|
|
68
|
+
cur.execute("SELECT * FROM [Salesforce1].[Salesforce].[Contact]")
|
|
69
|
+
while True:
|
|
70
|
+
batch = cur.fetchmany(100) # 100 rows at a time
|
|
71
|
+
if not batch:
|
|
72
|
+
break
|
|
73
|
+
for row in batch:
|
|
74
|
+
process(row)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Iterate Directly (PEP 249 Iterator Protocol)
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
with conn.cursor() as cur:
|
|
81
|
+
cur.execute("SELECT Name, Email FROM [Salesforce1].[Salesforce].[Contact]")
|
|
82
|
+
for row in cur:
|
|
83
|
+
print(f"{row[0]} — {row[1]}")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Column Metadata
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
with conn.cursor() as cur:
|
|
90
|
+
cur.execute("SELECT Id, Name, AnnualRevenue FROM [Salesforce1].[Salesforce].[Account]")
|
|
91
|
+
|
|
92
|
+
# cursor.description is a list of 7-tuples per PEP 249
|
|
93
|
+
for col in cur.description:
|
|
94
|
+
name, type_code, display_size, length, precision, scale, nullable = col
|
|
95
|
+
print(f"Column: {name}, Type: {type_code}")
|
|
96
|
+
|
|
97
|
+
rows = cur.fetchall()
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Parameterized Queries
|
|
101
|
+
|
|
102
|
+
The driver uses `pyformat` parameter style (`%(name)s` placeholders):
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
with conn.cursor() as cur:
|
|
106
|
+
cur.execute(
|
|
107
|
+
"SELECT * FROM [DB].[public].[users] WHERE city = %(city)s AND age > %(min_age)s",
|
|
108
|
+
{"city": "New York", "min_age": 25},
|
|
109
|
+
)
|
|
110
|
+
rows = cur.fetchall()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Insert, Update, Delete
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
with conn.cursor() as cur:
|
|
117
|
+
# Insert
|
|
118
|
+
cur.execute("INSERT INTO [DB].[public].[users] (name, email) VALUES ('Alice', 'alice@example.com')")
|
|
119
|
+
|
|
120
|
+
# Update
|
|
121
|
+
cur.execute("UPDATE [DB].[public].[users] SET email = 'alice@new.com' WHERE name = 'Alice'")
|
|
122
|
+
|
|
123
|
+
# Delete
|
|
124
|
+
cur.execute("DELETE FROM [DB].[public].[users] WHERE name = 'Alice'")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Batch Insert (executemany)
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
with conn.cursor() as cur:
|
|
131
|
+
cur.executemany(
|
|
132
|
+
"INSERT INTO [DB].[public].[cities] (city, id) VALUES (@city, @id)",
|
|
133
|
+
[
|
|
134
|
+
{"@city": {"dataType": 5, "value": "New York"}, "@id": {"dataType": 8, "value": 1}},
|
|
135
|
+
{"@city": {"dataType": 5, "value": "London"}, "@id": {"dataType": 8, "value": 2}},
|
|
136
|
+
{"@city": {"dataType": 5, "value": "Tokyo"}, "@id": {"dataType": 8, "value": 3}},
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Stored Procedures
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
with conn.cursor() as cur:
|
|
145
|
+
result_args = cur.callproc("[DB].[public].[my_procedure]", ("arg1", "arg2"))
|
|
146
|
+
rows = cur.fetchall()
|
|
147
|
+
print(f"Input args returned: {result_args}")
|
|
148
|
+
print(f"Result rows: {rows}")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Configuration File
|
|
152
|
+
|
|
153
|
+
Keep credentials out of code using a [PyHOCON](https://github.com/chimpler/pyhocon) config file:
|
|
154
|
+
|
|
155
|
+
```hocon
|
|
156
|
+
# config.conf
|
|
157
|
+
cdata_api_db {
|
|
158
|
+
base_url = "https://cloud.cdata.com/api"
|
|
159
|
+
username = "you@example.com"
|
|
160
|
+
password = "<your_personal_access_token>"
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
conn = cdata_connect.connect(config_path="config.conf")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Workspaces
|
|
169
|
+
|
|
170
|
+
Access a specific CData Connect AI workspace:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
conn = cdata_connect.connect(
|
|
174
|
+
base_url="https://cloud.cdata.com/api",
|
|
175
|
+
username="you@example.com",
|
|
176
|
+
password="<your_pat>",
|
|
177
|
+
workspace="production",
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Connection Options
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
conn = cdata_connect.connect(
|
|
185
|
+
base_url="https://cloud.cdata.com/api",
|
|
186
|
+
username="you@example.com",
|
|
187
|
+
password="<your_pat>",
|
|
188
|
+
timeout=60, # HTTP timeout in seconds (default: 30)
|
|
189
|
+
max_retries=5, # Retries on 5xx errors (default: 3)
|
|
190
|
+
retry_delay=2.0, # Base delay between retries in seconds (default: 1.0)
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Error Handling
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
import cdata_connect
|
|
198
|
+
from cdata_connect.exceptions import (
|
|
199
|
+
InterfaceError,
|
|
200
|
+
OperationalError,
|
|
201
|
+
ProgrammingError,
|
|
202
|
+
DatabaseError,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
conn = cdata_connect.connect(
|
|
207
|
+
base_url="https://cloud.cdata.com/api",
|
|
208
|
+
username="you@example.com",
|
|
209
|
+
password="<your_pat>",
|
|
210
|
+
)
|
|
211
|
+
with conn.cursor() as cur:
|
|
212
|
+
cur.execute("SELECT * FROM [Salesforce1].[Salesforce].[Account]")
|
|
213
|
+
rows = cur.fetchall()
|
|
214
|
+
|
|
215
|
+
except InterfaceError as e:
|
|
216
|
+
print(f"Connection/interface problem: {e}")
|
|
217
|
+
|
|
218
|
+
except OperationalError as e:
|
|
219
|
+
print(f"Server error, timeout, or connectivity issue: {e}")
|
|
220
|
+
|
|
221
|
+
except ProgrammingError as e:
|
|
222
|
+
print(f"Query error or fetch before execute: {e}")
|
|
223
|
+
|
|
224
|
+
except DatabaseError as e:
|
|
225
|
+
print(f"General database error: {e}")
|
|
226
|
+
|
|
227
|
+
finally:
|
|
228
|
+
if conn.is_open:
|
|
229
|
+
conn.close()
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Using with pandas
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
pip install "cdata-connect[full]"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
import pandas as pd
|
|
240
|
+
import cdata_connect
|
|
241
|
+
|
|
242
|
+
conn = cdata_connect.connect(
|
|
243
|
+
base_url="https://cloud.cdata.com/api",
|
|
244
|
+
username="you@example.com",
|
|
245
|
+
password="<your_pat>",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
with conn.cursor() as cur:
|
|
249
|
+
cur.execute("SELECT Id, Name, Industry, AnnualRevenue FROM [Salesforce1].[Salesforce].[Account]")
|
|
250
|
+
columns = [desc[0] for desc in cur.description]
|
|
251
|
+
rows = cur.fetchall()
|
|
252
|
+
|
|
253
|
+
df = pd.DataFrame(rows, columns=columns)
|
|
254
|
+
print(df.head())
|
|
255
|
+
print(df.describe())
|
|
256
|
+
|
|
257
|
+
conn.close()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Type Objects
|
|
261
|
+
|
|
262
|
+
PEP 249 type objects for use with `cursor.description`:
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import cdata_connect
|
|
266
|
+
|
|
267
|
+
# Check column types from cursor.description
|
|
268
|
+
with conn.cursor() as cur:
|
|
269
|
+
cur.execute("SELECT Id, Name, AnnualRevenue, CreatedDate FROM [Salesforce1].[Salesforce].[Account]")
|
|
270
|
+
for col in cur.description:
|
|
271
|
+
name, type_code = col[0], col[1]
|
|
272
|
+
if type_code == cdata_connect.STRING:
|
|
273
|
+
print(f"{name} is a string")
|
|
274
|
+
elif type_code == cdata_connect.NUMBER:
|
|
275
|
+
print(f"{name} is a number")
|
|
276
|
+
elif type_code == cdata_connect.DATETIME:
|
|
277
|
+
print(f"{name} is a datetime")
|
|
278
|
+
elif type_code == cdata_connect.BINARY:
|
|
279
|
+
print(f"{name} is binary")
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Date/Time Constructors
|
|
283
|
+
|
|
284
|
+
PEP 249 date/time helper constructors:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
import cdata_connect
|
|
288
|
+
|
|
289
|
+
d = cdata_connect.Date(2025, 3, 15)
|
|
290
|
+
t = cdata_connect.Time(14, 30, 0)
|
|
291
|
+
ts = cdata_connect.Timestamp(2025, 3, 15, 14, 30, 0)
|
|
292
|
+
b = cdata_connect.Binary(b"raw bytes")
|
|
293
|
+
|
|
294
|
+
# From Unix timestamps
|
|
295
|
+
import time
|
|
296
|
+
d2 = cdata_connect.DateFromTicks(time.time())
|
|
297
|
+
t2 = cdata_connect.TimeFromTicks(time.time())
|
|
298
|
+
ts2 = cdata_connect.TimestampFromTicks(time.time())
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Exception Hierarchy
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
cdata_connect.Warning — Important warnings (subclass of Exception)
|
|
305
|
+
cdata_connect.Error — Base error class
|
|
306
|
+
├── InterfaceError — Connection/interface issues
|
|
307
|
+
├── ConfigurationError — Bad config file or missing parameters
|
|
308
|
+
└── DatabaseError — Database-related errors
|
|
309
|
+
├── DataError — Data processing errors (bad JSON, etc.)
|
|
310
|
+
├── OperationalError — Server errors, timeouts, connectivity
|
|
311
|
+
├── IntegrityError — Constraint violations
|
|
312
|
+
├── InternalError — Internal database errors
|
|
313
|
+
├── ProgrammingError — Query errors, fetch before execute
|
|
314
|
+
└── NotSupportedError — Unsupported operations
|
|
315
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CData Software, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|