Skip to content

Commit 61a9512

Browse files
Merge pull request #1 from bruin-data/feature/v0.3.0-new-connections
feat: add Fabric, Oracle, DB2, HANA, Spanner, Vertica connections (v0.3.0)
2 parents d315e0e + 0587b8a commit 61a9512

8 files changed

Lines changed: 657 additions & 7 deletions

File tree

.github/workflows/release.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Auto Release
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
fetch-depth: 0
16+
17+
- name: Get version from pyproject.toml
18+
id: version
19+
run: |
20+
VERSION=$(python3 -c "
21+
import re
22+
with open('pyproject.toml') as f:
23+
match = re.search(r'version\s*=\s*\"(.+?)\"', f.read())
24+
print(match.group(1))
25+
")
26+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
27+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
28+
29+
- name: Check if tag exists
30+
id: check
31+
run: |
32+
if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
33+
echo "exists=true" >> "$GITHUB_OUTPUT"
34+
else
35+
echo "exists=false" >> "$GITHUB_OUTPUT"
36+
fi
37+
38+
- name: Create tag and release
39+
if: steps.check.outputs.exists == 'false'
40+
env:
41+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
run: |
43+
git tag "v${{ steps.version.outputs.version }}"
44+
git push origin "v${{ steps.version.outputs.version }}"
45+
gh release create "v${{ steps.version.outputs.version }}" \
46+
--title "v${{ steps.version.outputs.version }}" \
47+
--generate-notes

pyproject.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "bruin-sdk"
7-
version = "0.2.1"
7+
version = "0.3.0"
88
description = "Python SDK for Bruin CLI — query databases, parse context, and access connections with zero boilerplate."
99
readme = "docs/README.md"
1010
license = "Apache-2.0"
@@ -42,6 +42,12 @@ athena = ["pyathena"]
4242
trino = ["trino"]
4343
motherduck = ["duckdb"]
4444
synapse = ["pymssql"]
45+
fabric = ["pymssql"]
46+
oracle = ["oracledb"]
47+
db2 = ["ibm-db"]
48+
hana = ["hdbcli"]
49+
spanner = ["google-cloud-spanner", "google-auth"]
50+
vertica = ["vertica-python"]
4551
all = [
4652
"bruin-sdk[bigquery]",
4753
"bruin-sdk[snowflake]",
@@ -57,6 +63,12 @@ all = [
5763
"bruin-sdk[trino]",
5864
"bruin-sdk[motherduck]",
5965
"bruin-sdk[synapse]",
66+
"bruin-sdk[fabric]",
67+
"bruin-sdk[oracle]",
68+
"bruin-sdk[db2]",
69+
"bruin-sdk[hana]",
70+
"bruin-sdk[spanner]",
71+
"bruin-sdk[vertica]",
6072
]
6173
dev = ["pytest", "pytest-cov"]
6274

src/bruin/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
from bruin._context import context
55
from bruin._query import query
66

7-
__version__ = "0.2.1"
7+
__version__ = "0.3.0"
88
__all__ = ["__version__", "context", "get_connection", "query"]

src/bruin/_connection.py

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,10 @@ def _create_client(conn_type: str, raw):
178178
factories = {
179179
"snowflake": _create_snowflake,
180180
"postgres": _create_postgres,
181-
"redshift": _create_postgres,
181+
"redshift": _create_redshift,
182182
"mssql": _create_mssql,
183183
"synapse": _create_mssql,
184+
"fabric": _create_mssql,
184185
"mysql": _create_mysql,
185186
"duckdb": _create_duckdb,
186187
"databricks": _create_databricks,
@@ -189,6 +190,11 @@ def _create_client(conn_type: str, raw):
189190
"trino": _create_trino,
190191
"sqlite": _create_sqlite,
191192
"motherduck": _create_motherduck,
193+
"oracle": _create_oracle,
194+
"db2": _create_db2,
195+
"hana": _create_hana,
196+
"spanner": _create_spanner,
197+
"vertica": _create_vertica,
192198
}
193199
factory = factories.get(conn_type)
194200
if factory is None:
@@ -249,7 +255,25 @@ def _create_postgres(raw: dict):
249255
dbname=raw.get("database", ""),
250256
user=raw["username"],
251257
password=raw["password"],
252-
sslmode=raw.get("ssl_mode", "disable"),
258+
sslmode=raw.get("ssl_mode", "allow"),
259+
)
260+
261+
262+
def _create_redshift(raw: dict):
263+
try:
264+
import psycopg2
265+
except ImportError:
266+
raise ImportError(
267+
"Install bruin-sdk[redshift] to use Redshift connections: "
268+
"pip install 'bruin-sdk[redshift]'"
269+
)
270+
return psycopg2.connect(
271+
host=raw["host"],
272+
port=raw.get("port", 5439),
273+
dbname=raw.get("database", ""),
274+
user=raw["username"],
275+
password=raw["password"],
276+
sslmode=raw.get("ssl_mode", "allow"),
253277
)
254278

255279

@@ -398,6 +422,107 @@ def _create_motherduck(raw: dict):
398422
return conn
399423

400424

425+
def _create_oracle(raw: dict):
426+
try:
427+
import oracledb
428+
except ImportError:
429+
raise ImportError(
430+
"Install bruin-sdk[oracle] to use Oracle connections: "
431+
"pip install 'bruin-sdk[oracle]'"
432+
)
433+
kwargs = {
434+
"user": raw["username"],
435+
"password": raw["password"],
436+
"host": raw["host"],
437+
"port": int(raw.get("port", 1521)),
438+
}
439+
if raw.get("service_name"):
440+
kwargs["service_name"] = raw["service_name"]
441+
elif raw.get("sid"):
442+
kwargs["sid"] = raw["sid"]
443+
return oracledb.connect(**kwargs)
444+
445+
446+
def _create_db2(raw: dict):
447+
try:
448+
import ibm_db_dbi
449+
except ImportError:
450+
raise ImportError(
451+
"Install bruin-sdk[db2] to use DB2 connections: "
452+
"pip install 'bruin-sdk[db2]'"
453+
)
454+
conn_str = (
455+
f"DATABASE={raw.get('database', '')};"
456+
f"HOSTNAME={raw['host']};"
457+
f"PORT={raw.get('port', 50000)};"
458+
f"PROTOCOL=TCPIP;"
459+
f"UID={raw['username']};"
460+
f"PWD={raw['password']};"
461+
)
462+
return ibm_db_dbi.connect(conn_str, "", "")
463+
464+
465+
def _create_hana(raw: dict):
466+
try:
467+
from hdbcli import dbapi as hana_dbapi
468+
except ImportError:
469+
raise ImportError(
470+
"Install bruin-sdk[hana] to use SAP HANA connections: "
471+
"pip install 'bruin-sdk[hana]'"
472+
)
473+
return hana_dbapi.connect(
474+
address=raw["host"],
475+
port=int(raw.get("port", 30015)),
476+
user=raw["username"],
477+
password=raw["password"],
478+
databaseName=raw.get("database", ""),
479+
)
480+
481+
482+
def _create_spanner(raw: dict):
483+
try:
484+
from google.cloud.spanner_dbapi import connect as spanner_connect
485+
except ImportError:
486+
raise ImportError(
487+
"Install bruin-sdk[spanner] to use Cloud Spanner connections: "
488+
"pip install 'bruin-sdk[spanner]'"
489+
)
490+
kwargs = {
491+
"instance_id": raw.get("instance_id", ""),
492+
"database_id": raw.get("database", ""),
493+
"project": raw.get("project_id", ""),
494+
}
495+
sa_json = raw.get("service_account_json", "")
496+
if sa_json:
497+
try:
498+
from google.oauth2 import service_account
499+
except ImportError:
500+
raise ImportError(
501+
"Install bruin-sdk[spanner] to use Spanner credentials: "
502+
"pip install 'bruin-sdk[spanner]'"
503+
)
504+
sa_info = json.loads(sa_json)
505+
kwargs["credentials"] = service_account.Credentials.from_service_account_info(sa_info)
506+
return spanner_connect(**kwargs)
507+
508+
509+
def _create_vertica(raw: dict):
510+
try:
511+
import vertica_python
512+
except ImportError:
513+
raise ImportError(
514+
"Install bruin-sdk[vertica] to use Vertica connections: "
515+
"pip install 'bruin-sdk[vertica]'"
516+
)
517+
return vertica_python.connect(
518+
host=raw["host"],
519+
port=int(raw.get("port", 5433)),
520+
user=raw["username"],
521+
password=raw["password"],
522+
database=raw.get("database", ""),
523+
)
524+
525+
401526
def get_connection(name: str) -> "Connection | GCPConnection":
402527
"""Look up a Bruin-managed connection by name and return a Connection object.
403528

src/bruin/_query.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
# Connection types that use the generic PEP 249 (DBAPI) cursor/read_sql path.
1818
_DBAPI_TYPES = frozenset((
19-
"postgres", "redshift", "mssql", "synapse", "mysql", "athena", "trino", "sqlite",
19+
"postgres", "redshift", "mssql", "synapse", "fabric", "mysql",
20+
"athena", "trino", "sqlite", "oracle", "db2", "hana", "spanner", "vertica",
2021
))
2122

2223
# Subset of _DBAPI_TYPES that require an explicit commit() for DDL/DML.
2324
_TRANSACTIONAL = frozenset((
24-
"postgres", "redshift", "mssql", "synapse", "mysql", "sqlite",
25+
"postgres", "redshift", "mssql", "synapse", "fabric", "mysql",
26+
"sqlite", "oracle", "db2", "hana", "spanner", "vertica",
2527
))
2628

2729

tests/conftest.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,78 @@ def motherduck_connection_json():
149149
"token": "md_abc123",
150150
"database": "my_db",
151151
}
152+
153+
154+
@pytest.fixture
155+
def fabric_connection_json():
156+
return {
157+
"host": "fabric.example.com",
158+
"port": 1433,
159+
"username": "admin",
160+
"password": "s3cret",
161+
"database": "warehouse",
162+
}
163+
164+
165+
@pytest.fixture
166+
def oracle_connection_json():
167+
return {
168+
"host": "oracle.example.com",
169+
"port": 1521,
170+
"username": "system",
171+
"password": "s3cret",
172+
"service_name": "ORCL",
173+
}
174+
175+
176+
@pytest.fixture
177+
def oracle_sid_connection_json():
178+
return {
179+
"host": "oracle.example.com",
180+
"port": 1521,
181+
"username": "system",
182+
"password": "s3cret",
183+
"sid": "XE",
184+
}
185+
186+
187+
@pytest.fixture
188+
def db2_connection_json():
189+
return {
190+
"host": "db2.example.com",
191+
"port": 50000,
192+
"username": "db2admin",
193+
"password": "s3cret",
194+
"database": "SAMPLE",
195+
}
196+
197+
198+
@pytest.fixture
199+
def hana_connection_json():
200+
return {
201+
"host": "hana.example.com",
202+
"port": 30015,
203+
"username": "SYSTEM",
204+
"password": "s3cret",
205+
"database": "HDB",
206+
}
207+
208+
209+
@pytest.fixture
210+
def spanner_connection_json():
211+
return {
212+
"project_id": "my-gcp-project",
213+
"instance_id": "my-instance",
214+
"database": "my-db",
215+
}
216+
217+
218+
@pytest.fixture
219+
def vertica_connection_json():
220+
return {
221+
"host": "vertica.example.com",
222+
"port": 5433,
223+
"username": "dbadmin",
224+
"password": "s3cret",
225+
"database": "analytics",
226+
}

0 commit comments

Comments
 (0)