Skip to content

Commit 62d2339

Browse files
Fir 8531 allow passing engine name rathe (#27)
* migrate to python3.7 * fix mypy issues * implement connect function * improve examples.ipynb * fix tests, implement unit tests for connect * fix merge issues * add integration tests for connect * address comments * fix mypy
1 parent 6b54081 commit 62d2339

File tree

9 files changed

+354
-32
lines changed

9 files changed

+354
-32
lines changed

src/firebolt/db/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Timestamp,
1414
TimestampFromTicks,
1515
)
16-
from firebolt.db.connection import Connection
16+
from firebolt.db.connection import Connection, connect
1717
from firebolt.db.cursor import Cursor
1818

1919
apilevel = "2.0"

src/firebolt/db/connection.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,97 @@
22

33
from inspect import cleandoc
44
from types import TracebackType
5-
from typing import List
5+
from typing import List, Optional
66

77
from httpx import Timeout
88
from readerwriterlock.rwlock import RWLockWrite
99

1010
from firebolt.client import DEFAULT_API_URL, Client
11-
from firebolt.common.exception import ConnectionClosedError
11+
from firebolt.common.exception import ConnectionClosedError, InterfaceError
12+
from firebolt.common.settings import Settings
1213
from firebolt.db.cursor import Cursor
14+
from firebolt.service.manager import ResourceManager
1315

1416
DEFAULT_TIMEOUT_SECONDS: int = 5
1517

1618

19+
def connect(
20+
database: str = None,
21+
username: str = None,
22+
password: str = None,
23+
engine_name: Optional[str] = None,
24+
engine_url: Optional[str] = None,
25+
api_endpoint: str = DEFAULT_API_URL,
26+
) -> Connection:
27+
cleandoc(
28+
"""
29+
Connect to Firebolt database.
30+
31+
Connection parameters:
32+
database - name of the database to connect
33+
username - user name to use for authentication
34+
password - password to use for authentication
35+
engine_name - name of the engine to connect to
36+
engine_url - engine endpoint to use
37+
note: either engine_name or engine_url should be provided, but not both
38+
"""
39+
)
40+
if engine_name and engine_url:
41+
raise InterfaceError(
42+
"Both engine_name and engine_url are provided. Provide only one to connect."
43+
)
44+
if not engine_name and not engine_url:
45+
raise InterfaceError(
46+
"Neither engine_name nor engine_url are provided. Provide one to connect."
47+
)
48+
# This parameters are optional in function signature, but are required to connect.
49+
# It's recomended to make them kwargs by PEP 249
50+
for param, name in (
51+
(database, "database"),
52+
(username, "username"),
53+
(password, "password"),
54+
):
55+
if not param:
56+
raise InterfaceError(f"{name} is required to connect.")
57+
58+
if engine_name is not None:
59+
rm = ResourceManager(
60+
Settings(
61+
user=username, password=password, server=api_endpoint, default_region=""
62+
)
63+
)
64+
endpoint = rm.engines.get_by_name(engine_name).endpoint
65+
if endpoint is None:
66+
raise InterfaceError("unable to retrieve engine endpoint.")
67+
else:
68+
engine_url = endpoint
69+
70+
# Mypy checks, this should never happen
71+
assert engine_url is not None
72+
assert database is not None
73+
assert username is not None
74+
assert password is not None
75+
76+
engine_url = (
77+
engine_url if engine_url.startswith("http") else f"https://{engine_url}"
78+
)
79+
return Connection(engine_url, database, username, password, api_endpoint)
80+
81+
1782
class Connection:
1883
cleandoc(
1984
"""
2085
Firebolt database connection class. Implements PEP-249.
2186
2287
Parameters:
88+
engine_url - Firebolt database engine REST API url
89+
database - Firebolt database name
2390
username - Firebolt account username
2491
password - Firebolt account password
25-
engine_url - Firebolt database engine REST API url
2692
api_endpoint(optional) - Firebolt API endpoint. Used for authentication
2793
2894
Methods:
29-
cursor - created new Cursor object
95+
cursor - create new Cursor object
3096
close - close the Connection and all it's cursors
3197
3298
Firebolt currenly doesn't support transactions so commit and rollback methods
@@ -43,9 +109,6 @@ def __init__(
43109
password: str,
44110
api_endpoint: str = DEFAULT_API_URL,
45111
):
46-
engine_url = (
47-
engine_url if engine_url.startswith("http") else f"https://{engine_url}"
48-
)
49112
self._client = Client(
50113
auth=(username, password),
51114
base_url=engine_url,

src/firebolt/db/examples.ipynb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"metadata": {},
1616
"outputs": [],
1717
"source": [
18-
"from firebolt.db import Connection\n",
18+
"from firebolt.db import connect\n",
1919
"from firebolt.client import DEFAULT_API_URL"
2020
]
2121
},
@@ -34,8 +34,14 @@
3434
"metadata": {},
3535
"outputs": [],
3636
"source": [
37-
"database_name = \"\"\n",
37+
"# Only one of these two parameters should be specified\n",
3838
"engine_url = \"\"\n",
39+
"engine_name = \"\"\n",
40+
"assert bool(engine_url) != bool(\n",
41+
" engine_name\n",
42+
"), \"Specify only one of engine_name and engine_url\"\n",
43+
"\n",
44+
"database_name = \"\"\n",
3945
"username = \"\"\n",
4046
"password = \"\"\n",
4147
"api_endpoint = DEFAULT_API_URL"
@@ -57,8 +63,13 @@
5763
"outputs": [],
5864
"source": [
5965
"# create a connection based on provided credentials\n",
60-
"connection = Connection(\n",
61-
" engine_url, database_name, username, password, api_endpoint=api_endpoint\n",
66+
"connection = connect(\n",
67+
" engine_url=engine_url,\n",
68+
" engine_name=engine_name,\n",
69+
" database=database_name,\n",
70+
" username=username,\n",
71+
" password=password,\n",
72+
" api_endpoint=api_endpoint,\n",
6273
")\n",
6374
"\n",
6475
"# create a cursor for connection\n",

tests/integration/dbapi/conftest.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
from pytest import fixture
77

8-
from firebolt.db import ARRAY, Connection
8+
from firebolt.db import ARRAY, Connection, connect
99
from firebolt.db._types import ColType
1010
from firebolt.db.cursor import Column
1111

1212
LOGGER = getLogger(__name__)
1313

1414
ENGINE_URL_ENV = "ENGINE_URL"
15+
ENGINE_NAME_ENV = "ENGINE_NAME"
1516
DATABASE_NAME_ENV = "DATABASE_NAME"
1617
USERNAME_ENV = "USERNAME"
1718
PASSWORD_ENV = "PASSWORD"
@@ -29,6 +30,11 @@ def engine_url() -> str:
2930
return must_env(ENGINE_URL_ENV)
3031

3132

33+
@fixture(scope="session")
34+
def engine_name() -> str:
35+
return must_env(ENGINE_NAME_ENV)
36+
37+
3238
@fixture(scope="session")
3339
def database_name() -> str:
3440
return must_env(DATABASE_NAME_ENV)
@@ -53,8 +59,29 @@ def api_endpoint() -> str:
5359
def connection(
5460
engine_url: str, database_name: str, username: str, password: str, api_endpoint: str
5561
) -> Connection:
56-
return Connection(
57-
engine_url, database_name, username, password, api_endpoint=api_endpoint
62+
return connect(
63+
engine_url=engine_url,
64+
database=database_name,
65+
username=username,
66+
password=password,
67+
api_endpoint=api_endpoint,
68+
)
69+
70+
71+
@fixture
72+
def connection_engine_name(
73+
engine_name: str,
74+
database_name: str,
75+
username: str,
76+
password: str,
77+
api_endpoint: str,
78+
) -> Connection:
79+
return connect(
80+
engine_name=engine_name,
81+
database=database_name,
82+
username=username,
83+
password=password,
84+
api_endpoint=api_endpoint,
5885
)
5986

6087

tests/integration/dbapi/test_errors.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@
66
OperationalError,
77
ProgrammingError,
88
)
9-
from firebolt.db import Connection
9+
from firebolt.db import Connection, connect
1010

1111

1212
def test_invalid_credentials(
1313
engine_url: str, database_name: str, username: str, password: str, api_endpoint: str
1414
) -> None:
1515
"""Connection properly reacts to invalid credentials error"""
16-
connection = Connection(
17-
engine_url, database_name, username + "_", password + "_", api_endpoint
16+
connection = connect(
17+
engine_url=engine_url,
18+
database=database_name,
19+
username=username + "_",
20+
password=password + "_",
21+
api_endpoint=api_endpoint,
1822
)
1923
with raises(AuthenticationError) as exc_info:
2024
connection.cursor().execute("show tables")
@@ -24,12 +28,35 @@ def test_invalid_credentials(
2428
), "Invalid authentication error message"
2529

2630

27-
def test_engine_not_exists(
31+
def test_engine_url_not_exists(
2832
engine_url: str, database_name: str, username: str, password: str, api_endpoint: str
2933
) -> None:
3034
"""Connection properly reacts to invalid engine url error"""
31-
connection = Connection(
32-
engine_url + "_", database_name, username, password, api_endpoint
35+
connection = connect(
36+
engine_url=engine_url + "_",
37+
database=database_name,
38+
username=username,
39+
password=password,
40+
api_endpoint=api_endpoint,
41+
)
42+
with raises(ConnectError):
43+
connection.cursor().execute("show tables")
44+
45+
46+
def test_engine_name_not_exists(
47+
engine_name: str,
48+
database_name: str,
49+
username: str,
50+
password: str,
51+
api_endpoint: str,
52+
) -> None:
53+
"""Connection properly reacts to invalid engine name error"""
54+
connection = connect(
55+
engine_url=engine_name + "_________",
56+
database=database_name,
57+
username=username,
58+
password=password,
59+
api_endpoint=api_endpoint,
3360
)
3461
with raises(ConnectError):
3562
connection.cursor().execute("show tables")
@@ -40,7 +67,13 @@ def test_database_not_exists(
4067
) -> None:
4168
"""Connection properly reacts to invalid database error"""
4269
new_db_name = database_name + "_"
43-
connection = Connection(engine_url, new_db_name, username, password, api_endpoint)
70+
connection = connect(
71+
engine_url=engine_url,
72+
database=new_db_name,
73+
username=username,
74+
password=password,
75+
api_endpoint=api_endpoint,
76+
)
4477
with raises(ProgrammingError) as exc_info:
4578
connection.cursor().execute("show tables")
4679

@@ -50,6 +83,7 @@ def test_database_not_exists(
5083

5184

5285
def test_sql_error(connection: Connection) -> None:
86+
"""Connection properly reacts to sql execution error"""
5387
with connection.cursor() as c:
5488
with raises(OperationalError) as exc_info:
5589
c.execute("select ]")

tests/integration/dbapi/test_queries.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ def assert_deep_eq(got: Any, expected: Any, msg: str) -> bool:
1414
), f"{msg}: {got}(got) != {expected}(expected)"
1515

1616

17+
def test_connect_engine_name(
18+
connection_engine_name: Connection,
19+
all_types_query: str,
20+
all_types_query_description: List[Column],
21+
all_types_query_response: List[ColType],
22+
) -> None:
23+
"""Connecting with engine name is handled properly."""
24+
test_select(
25+
connection_engine_name,
26+
all_types_query,
27+
all_types_query_description,
28+
all_types_query_response,
29+
)
30+
31+
1732
def test_select(
1833
connection: Connection,
1934
all_types_query: str,

0 commit comments

Comments
 (0)