Skip to content

Commit 797cbfe

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 4ce824f + adaa84c commit 797cbfe

File tree

7 files changed

+389
-134
lines changed

7 files changed

+389
-134
lines changed

doc/changes/unreleased.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* #66: Implemented a standard CLI options builder.
66
* #70: Implemented a CLI callback function for the language container deployment.
77
* #69: Added create_bucketfs_location function.
8+
* #75: Added create_bucketfs_location_from_conn_object function.
9+
* #74: Implemented a CLI callback function for creating a bucket-fs connection object.
810

911
# Refactoring
1012

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import exasol.python_extension_common.connections.bucketfs_location as bl
2+
3+
4+
class BucketfsConnObjectCli:
5+
def __init__(self, conn_name_arg: str):
6+
self._conn_name_arg = conn_name_arg
7+
8+
def __call__(self, **kwargs):
9+
conn_name = kwargs.pop(self._conn_name_arg)
10+
bl.create_bucketfs_conn_object(conn_name=conn_name, **kwargs)
Lines changed: 191 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,34 @@
1+
from typing import Any
2+
from enum import Enum, auto
3+
from dataclasses import dataclass
4+
import json
5+
import pyexasol # type: ignore
16
import exasol.bucketfs as bfs # type: ignore
27
from exasol.saas.client.api_access import get_database_id # type: ignore
38

49
from exasol.python_extension_common.cli.std_options import StdParams, check_params
10+
from exasol.python_extension_common.connections.pyexasol_connection import open_pyexasol_connection
511

612

7-
def create_bucketfs_location(**kwargs) -> bfs.path.PathLike:
8-
"""
9-
Creates a BucketFS PathLike object using the data provided in the kwargs. These
10-
can be parameters for the BucketFS either On-Prem or SaaS database. The parameters
11-
should correspond to the CLI options defined in the cli/std_options.py.
13+
class _Backend(Enum):
14+
onprem = auto()
15+
saas = auto()
1216

13-
Raises a ValueError if the provided parameters are insufficient for either
14-
On-Prem or SaaS cases.
15-
"""
1617

17-
path_in_bucket = kwargs.get(StdParams.path_in_bucket.name, '')
18+
def _infer_backend(bfs_params: dict[str, Any]) -> _Backend:
19+
"""
20+
Infers the backend from the provided dictionary of CLI parameters.
21+
Raises a ValueError if the collection of CLI parameters is insufficient to access
22+
the BucketFS on either of the backends.
23+
"""
1824

19-
# Infer where the database is - on-prem or SaaS.
2025
if check_params([StdParams.bucketfs_host, StdParams.bucketfs_port,
2126
StdParams.bucket, StdParams.bucketfs_user, StdParams.bucketfs_password],
22-
kwargs):
23-
net_service = ('https' if kwargs.get(StdParams.bucketfs_use_https.name, True)
24-
else 'http')
25-
url = (f"{net_service}://"
26-
f"{kwargs[StdParams.bucketfs_host.name]}:"
27-
f"{kwargs[StdParams.bucketfs_port.name]}")
28-
return bfs.path.build_path(
29-
backend=bfs.path.StorageBackend.onprem,
30-
url=url,
31-
username=kwargs[StdParams.bucketfs_user.name],
32-
password=kwargs[StdParams.bucketfs_password.name],
33-
service_name=kwargs.get(StdParams.bucketfs_name.name),
34-
bucket_name=kwargs[StdParams.bucket.name],
35-
verify=kwargs.get(StdParams.use_ssl_cert_validation.name, True),
36-
path=path_in_bucket
37-
)
27+
bfs_params):
28+
return _Backend.onprem
3829
elif check_params([StdParams.saas_url, StdParams.saas_account_id, StdParams.saas_token,
39-
[StdParams.saas_database_id, StdParams.saas_database_name]], kwargs):
40-
saas_url = kwargs[StdParams.saas_url.name]
41-
saas_account_id = kwargs[StdParams.saas_account_id.name]
42-
saas_token = kwargs[StdParams.saas_token.name]
43-
saas_database_id = (kwargs.get(StdParams.saas_database_id.name) or
44-
get_database_id(
45-
host=saas_url,
46-
account_id=saas_account_id,
47-
pat=saas_token,
48-
database_name=kwargs[StdParams.saas_database_name.name]
49-
))
50-
return bfs.path.build_path(backend=bfs.path.StorageBackend.saas,
51-
url=saas_url,
52-
account_id=saas_account_id,
53-
database_id=saas_database_id,
54-
pat=saas_token,
55-
path=path_in_bucket)
30+
[StdParams.saas_database_id, StdParams.saas_database_name]], bfs_params):
31+
return _Backend.saas
5632

5733
raise ValueError(
5834
'Incomplete parameter list. Please either provide the parameters ['
@@ -64,3 +40,175 @@ def create_bucketfs_location(**kwargs) -> bfs.path.PathLike:
6440
f'{StdParams.saas_database_name.name}, {StdParams.saas_token.name}] for a '
6541
'SaaS database.'
6642
)
43+
44+
45+
def _convert_onprem_bfs_params(bfs_params: dict[str, Any]) -> dict[str, Any]:
46+
"""
47+
Converts OnPrem BucketFS parameters from the CLI format to the format expected
48+
by the exasol.bucketfs.path.build_path.
49+
"""
50+
51+
net_service = ('https' if bfs_params.get(StdParams.bucketfs_use_https.name, True)
52+
else 'http')
53+
url = (f"{net_service}://"
54+
f"{bfs_params[StdParams.bucketfs_host.name]}:"
55+
f"{bfs_params[StdParams.bucketfs_port.name]}")
56+
return {
57+
'backend': bfs.path.StorageBackend.onprem.name,
58+
'url': url,
59+
'username': bfs_params[StdParams.bucketfs_user.name],
60+
'password': bfs_params[StdParams.bucketfs_password.name],
61+
'service_name': bfs_params.get(StdParams.bucketfs_name.name),
62+
'bucket_name': bfs_params[StdParams.bucket.name],
63+
'verify': bfs_params.get(StdParams.use_ssl_cert_validation.name, True),
64+
'path': bfs_params.get(StdParams.path_in_bucket.name, '')
65+
}
66+
67+
68+
def _convert_saas_bfs_params(bfs_params: dict[str, Any]) -> dict[str, Any]:
69+
"""
70+
Converts SaaS BucketFS parameters from the CLI format to the format expected
71+
by the exasol.bucketfs.path.build_path.
72+
"""
73+
74+
saas_url = bfs_params[StdParams.saas_url.name]
75+
saas_account_id = bfs_params[StdParams.saas_account_id.name]
76+
saas_token = bfs_params[StdParams.saas_token.name]
77+
saas_database_id = (bfs_params.get(StdParams.saas_database_id.name) or
78+
get_database_id(
79+
host=saas_url,
80+
account_id=saas_account_id,
81+
pat=saas_token,
82+
database_name=bfs_params[StdParams.saas_database_name.name]
83+
))
84+
return {
85+
'backend': bfs.path.StorageBackend.saas.name,
86+
'url': saas_url,
87+
'account_id': saas_account_id,
88+
'database_id': saas_database_id,
89+
'pat': saas_token,
90+
'path': bfs_params.get(StdParams.path_in_bucket.name, '')
91+
}
92+
93+
94+
def create_bucketfs_location(**kwargs) -> bfs.path.PathLike:
95+
"""
96+
Creates a BucketFS PathLike object using the data provided in the kwargs. These
97+
can be parameters for the BucketFS at either On-Prem or SaaS database. The input
98+
parameters should correspond to the CLI options defined in the cli/std_options.py.
99+
100+
Raises a ValueError if the provided parameters are insufficient for either
101+
On-Prem or SaaS cases.
102+
"""
103+
104+
db_type = _infer_backend(kwargs)
105+
if db_type == _Backend.onprem:
106+
return bfs.path.build_path(**_convert_onprem_bfs_params(kwargs))
107+
else:
108+
return bfs.path.build_path(**_convert_saas_bfs_params(kwargs))
109+
110+
111+
@dataclass
112+
class ConnectionInfo:
113+
"""
114+
This is not a connection object. It's just a structure to keep together the data
115+
required for creating a BucketFs connection object. Useful for testing.
116+
"""
117+
address: str
118+
user: str
119+
password: str
120+
121+
122+
def _to_json_str(bucketfs_params: dict[str, Any], selected: list[str]) -> str:
123+
filtered_kwargs = {k: v for k, v in bucketfs_params.items()
124+
if (k in selected) and (v is not None)}
125+
return json.dumps(filtered_kwargs)
126+
127+
128+
def write_bucketfs_conn_object(pyexasol_connection: pyexasol.ExaConnection,
129+
conn_name: str,
130+
conn_obj: ConnectionInfo) -> None:
131+
132+
query = (f"CREATE OR REPLACE CONNECTION {conn_name} "
133+
f"TO '{conn_obj.address}' "
134+
f"USER '{conn_obj.user}' "
135+
f"IDENTIFIED BY '{conn_obj.password}'")
136+
pyexasol_connection.execute(query)
137+
138+
139+
def create_bucketfs_conn_object_onprem(pyexasol_connection: pyexasol.ExaConnection,
140+
conn_name: str,
141+
bucketfs_params: dict[str, Any]) -> None:
142+
"""
143+
Creates in the database a connection object encapsulating the BucketFS parameters
144+
for an OnPrem backend.
145+
146+
Parameters:
147+
pyexasol_connection:
148+
DB connection.
149+
conn_name:
150+
Name for the connection object.
151+
bucketfs_params:
152+
OnPrem BucketFS parameters in the format of the exasol.bucketfs.path.build_path.
153+
"""
154+
conn_to = _to_json_str(bucketfs_params, [
155+
'backend', 'url', 'service_name', 'bucket_name', 'path', 'verify'])
156+
conn_user = _to_json_str(bucketfs_params, ['username'])
157+
conn_password = _to_json_str(bucketfs_params, ['password'])
158+
159+
write_bucketfs_conn_object(pyexasol_connection, conn_name,
160+
ConnectionInfo(conn_to, conn_user, conn_password))
161+
162+
163+
def create_bucketfs_conn_object_saas(pyexasol_connection: pyexasol.ExaConnection,
164+
conn_name: str,
165+
bucketfs_params: dict[str, Any]) -> None:
166+
"""
167+
Creates in the database a connection object encapsulating the BucketFS parameters
168+
for a SaaS backend.
169+
170+
Parameters:
171+
pyexasol_connection:
172+
DB connection.
173+
conn_name:
174+
Name for the connection object.
175+
bucketfs_params:
176+
SaaS BucketFS parameters in the format of the exasol.bucketfs.path.build_path.
177+
"""
178+
conn_to = _to_json_str(bucketfs_params, ['backend', 'url', 'path'])
179+
conn_user = _to_json_str(bucketfs_params, ['account_id', 'database_id'])
180+
conn_password = _to_json_str(bucketfs_params, ['pat'])
181+
182+
write_bucketfs_conn_object(pyexasol_connection, conn_name,
183+
ConnectionInfo(conn_to, conn_user, conn_password))
184+
185+
186+
def create_bucketfs_conn_object(conn_name: str, **kwargs) -> None:
187+
"""
188+
Creates in the database a connection object encapsulating the provided BucketFS
189+
parameters. These can be parameters for either On-Prem or SaaS database. They
190+
should correspond to the CLI options defined in the cli/std_options.py.
191+
192+
Raises a ValueError if the provided parameters are insufficient for either
193+
On-Prem or SaaS cases.
194+
"""
195+
with open_pyexasol_connection(**kwargs) as pyexasol_connection:
196+
db_type = _infer_backend(kwargs)
197+
if db_type == _Backend.onprem:
198+
create_bucketfs_conn_object_onprem(pyexasol_connection, conn_name,
199+
_convert_onprem_bfs_params(kwargs))
200+
else:
201+
create_bucketfs_conn_object_saas(pyexasol_connection, conn_name,
202+
_convert_saas_bfs_params(kwargs))
203+
204+
205+
def create_bucketfs_location_from_conn_object(conn_obj) -> bfs.path.PathLike:
206+
"""
207+
Creates a BucketFS PathLike object using data contained in the provided connection
208+
object.
209+
"""
210+
211+
bfs_params = json.loads(conn_obj.address)
212+
bfs_params.update(json.loads(conn_obj.user))
213+
bfs_params.update(json.loads(conn_obj.password))
214+
return bfs.path.build_path(**bfs_params)

test/integration/conftest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from __future__ import annotations
2+
from typing import Any
23
import pytest
34
import click
45
import requests
6+
from urllib.parse import urlparse
57
from contextlib import ExitStack, contextmanager
68
import pyexasol
79
import exasol.bucketfs as bfs
810

11+
from exasol.python_extension_common.cli.std_options import StdParams
912
from exasol.python_extension_common.deployment.language_container_deployer import (
1013
LanguageContainerDeployer,
1114
)
@@ -95,3 +98,53 @@ def create_deployer(create_test_schema: bool = False, open_test_schema: bool = T
9598
open_schema(pyexasol_connection, db_schema)
9699
yield LanguageContainerDeployer(pyexasol_connection, language_alias, bucketfs_path)
97100
return create_deployer
101+
102+
103+
@pytest.fixture(scope='session')
104+
def onprem_db_params(backend_aware_onprem_database,
105+
exasol_config) -> dict[str, Any]:
106+
return {
107+
StdParams.dsn.name: f'{exasol_config.host}:{exasol_config.port}',
108+
StdParams.db_user.name: exasol_config.username,
109+
StdParams.db_password.name: exasol_config.password,
110+
StdParams.use_ssl_cert_validation.name: False
111+
}
112+
113+
114+
@pytest.fixture(scope='session')
115+
def onprem_bfs_params(backend_aware_onprem_database,
116+
bucketfs_config) -> dict[str, Any]:
117+
parsed_url = urlparse(bucketfs_config.url)
118+
host, port = parsed_url.netloc.split(":")
119+
return {
120+
StdParams.bucketfs_host.name: host,
121+
StdParams.bucketfs_port.name: port,
122+
StdParams.bucketfs_use_https.name: parsed_url.scheme.lower() == 'https',
123+
StdParams.bucketfs_user.name: bucketfs_config.username,
124+
StdParams.bucketfs_password.name: bucketfs_config.password,
125+
StdParams.bucketfs_name.name: 'bfsdefault',
126+
StdParams.bucket.name: 'default',
127+
StdParams.use_ssl_cert_validation.name: False
128+
}
129+
130+
131+
@pytest.fixture(scope='session')
132+
def saas_params_id(saas_host,
133+
saas_pat,
134+
saas_account_id,
135+
backend_aware_saas_database_id) -> dict[str, Any]:
136+
return {
137+
StdParams.saas_url.name: saas_host,
138+
StdParams.saas_account_id.name: saas_account_id,
139+
StdParams.saas_database_id.name: backend_aware_saas_database_id,
140+
StdParams.saas_token.name: saas_pat,
141+
}
142+
143+
144+
@pytest.fixture(scope='session')
145+
def saas_params_name(saas_params_id,
146+
database_name) -> dict[str, Any]:
147+
saas_params = dict(saas_params_id)
148+
saas_params.pop(StdParams.saas_database_id.name)
149+
saas_params[StdParams.saas_database_name.name] = database_name
150+
return saas_params

0 commit comments

Comments
 (0)