Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Spec For Credential Management and SQLAlchemy Database Connectors #1420

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
33078aa
init commit
sundarshankar89 Jan 15, 2025
e12db4d
Added Base Connector
sundarshankar89 Jan 15, 2025
7fc59ff
Moved the Abstract class to private
sundarshankar89 Jan 15, 2025
978f8bc
Added pyodbc dependency
sundarshankar89 Jan 15, 2025
39cdc3e
fmt fixes
sundarshankar89 Jan 15, 2025
a9d799f
Merge branch 'main' into feature/credential_manager
sundarshankar89 Jan 15, 2025
d1263b5
Added Vault Manager
sundarshankar89 Jan 16, 2025
0c40eca
Added TODO
sundarshankar89 Jan 17, 2025
d7eed08
Adding credential manager for multiple secret
sundarshankar89 Jan 20, 2025
bd0d012
Added reading credentials from env and then falling back to key itself
sundarshankar89 Jan 20, 2025
c7f91f5
fixed case agnostic connection creation.
sundarshankar89 Jan 20, 2025
1dcff15
Added UT
sundarshankar89 Jan 20, 2025
211944e
fmt fixes
sundarshankar89 Jan 20, 2025
a445aba
initial test case setup
sundarshankar89 Jan 20, 2025
3cb9c05
test case setup
sundarshankar89 Jan 21, 2025
8b1c254
Refactored to better
sundarshankar89 Jan 21, 2025
74030d3
Added Integration Test
sundarshankar89 Jan 24, 2025
29b14be
Added Integration Test
sundarshankar89 Jan 24, 2025
0aa457b
fmt fixes
sundarshankar89 Jan 24, 2025
9e1f7fd
added fixture
sundarshankar89 Jan 24, 2025
ee162b0
Merge branch 'main' into feature/credential_manager
sundarshankar89 Jan 27, 2025
79e3a86
add acceptance (#1428)
sundarshankar89 Jan 28, 2025
8e9dea6
Merge branch 'main' into feature/credential_manager
sundarshankar89 Jan 29, 2025
f44a09e
fmt fixes
sundarshankar89 Jan 29, 2025
355b76d
Simplified installation journey
sundarshankar89 Feb 3, 2025
5570790
Merge branch 'main' into feature/simplified_installation
sundarshankar89 Feb 5, 2025
a0eee07
Merge branch 'feature/simplified_installation' into feature/credentia…
sundarshankar89 Feb 5, 2025
329c913
Merge branch 'main' into feature/credential_manager
sundarshankar89 Feb 7, 2025
4d0525c
Merge branch 'main' into feature/credential_manager
sundarshankar89 Feb 7, 2025
7807e94
fmt fixes
sundarshankar89 Feb 7, 2025
750645b
credential manager rewrite
sundarshankar89 Feb 7, 2025
8cdb336
integration tests
sundarshankar89 Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ dependencies = [
"databricks-labs-blueprint[yaml]>=0.2.3",
"databricks-labs-lsql>=0.7.5,<0.14.0", # TODO: Limit the LSQL version until dependencies are correct.
"cryptography>=41.0.3",
"pyodbc",
"SQLAlchemy",
"pygls>=2.0.0a2",

]

[project.urls]
Expand Down
26 changes: 26 additions & 0 deletions src/databricks/labs/remorph/connections/credential_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from pathlib import Path
import yaml
from databricks.labs.blueprint.wheels import ProductInfo


class Credentials:
def __init__(self, product_info: ProductInfo) -> None:
self._product_info = product_info
self._credentials: dict[str, str] = self._load_credentials(self._get_local_version_file_path())

def _get_local_version_file_path(self) -> Path:
user_home = f"{Path(__file__).home()}"
return Path(f"{user_home}/.databricks/labs/{self._product_info.product_name()}/credentials.yml")

def _load_credentials(self, file_path: Path) -> dict[str, str]:
with open(file_path, encoding="utf-8") as f:
return yaml.safe_load(f)

def get(self, source: str) -> dict[str, str]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get what ?

error_msg = f"source system: {source} credentials not found not in file credentials.yml"
if source in self._credentials:
value = self._credentials[source]
if isinstance(value, dict):
return value
raise KeyError(error_msg)
raise KeyError(error_msg)
64 changes: 64 additions & 0 deletions src/databricks/labs/remorph/connections/database_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from abc import ABC, abstractmethod
from typing import Any

from sqlalchemy import create_engine
from sqlalchemy.engine import Engine, Result
from sqlalchemy.orm import sessionmaker
from sqlalchemy import text


class _ISourceSystemConnector(ABC):
@abstractmethod
def _connect(self) -> Engine:
pass

@abstractmethod
def execute_query(self, query: str) -> Result[Any]:
pass


class _BaseConnector(_ISourceSystemConnector):
def __init__(self, config: dict[str, Any]):
self.config = config
self.engine: Engine = self._connect()

def _connect(self) -> Engine:
raise NotImplementedError("Subclasses should implement this method")

def execute_query(self, query: str) -> Result[Any]:
if not self.engine:
raise ConnectionError("Not connected to the database.")
session = sessionmaker(bind=self.engine)
connection = session()
return connection.execute(text(query))


class SnowflakeConnector(_BaseConnector):
def _connect(self) -> Engine:
connection_string = (
f"snowflake://{self.config['user']}:{self.config['password']}@{self.config['account']}/"
f"{self.config['database']}/{self.config['schema']}?warehouse={self.config['warehouse']}&role={self.config['role']}"
)
self.engine = create_engine(connection_string)
return self.engine


class MSSQLConnector(_BaseConnector):
def _connect(self) -> Engine:
connection_string = (
f"mssql+pyodbc://{self.config['user']}:{self.config['password']}@{self.config['server']}/"
f"{self.config['database']}?driver={self.config['driver']}"
)
self.engine = create_engine(connection_string)
return self.engine


class SourceSystemConnectorFactory:
@staticmethod
def create_connector(db_type: str, config: dict[str, str]) -> _ISourceSystemConnector:
if db_type == "snowflake":
sundarshankar89 marked this conversation as resolved.
Show resolved Hide resolved
return SnowflakeConnector(config)
if db_type == "mssql":
return MSSQLConnector(config)

raise ValueError(f"Unsupported database type: {db_type}")
28 changes: 28 additions & 0 deletions src/databricks/labs/remorph/resources/config/credentials.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
snowflake:
account: example_account
connect_retries: 1
connect_timeout: null
host: null
insecure_mode: false
oauth_client_id: null
oauth_client_secret: null
password: null
port: null
private_key: null
private_key_passphrase: null
private_key_path: null
role: null
token: null
user: null
warehouse: null

msssql:
database: example_database
driver: ODBC Driver 18 for SQL Server
server: example_host
port: null
user: null
password: null



Loading