Skip to content

Commit

Permalink
feat: Support for schema in connectors and datasources
Browse files Browse the repository at this point in the history
  • Loading branch information
portellaa committed Jan 26, 2024
1 parent acfcddd commit e5e0d0e
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 41 deletions.
3 changes: 2 additions & 1 deletion src/ydata/sdk/common/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ class BaseModel(PydanticBaseModel):
All datamodel from YData SDK inherits from this class.
"""
class Config:
extra = Extra.ignore
allow_population_by_field_name = True
extra = Extra.ignore
use_enum_values = True
30 changes: 30 additions & 0 deletions src/ydata/sdk/common/pydantic_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Alias generators for converting between different capitalization conventions."""
import re

__all__ = ('to_pascal', 'to_camel', 'to_snake')


def to_pascal(snake: str) -> str:
"""Convert a snake_case string to PascalCase.
Args:
snake: The string to convert.
Returns:
The PascalCase string.
"""
camel = snake.title()
return re.sub('([0-9A-Za-z])_(?=[0-9A-Z])', lambda m: m.group(1), camel)


def to_camel(snake: str) -> str:
"""Convert a snake_case string to camelCase.
Args:
snake: The string to convert.
Returns:
The converted camelCase string.
"""
camel = to_pascal(snake)
return re.sub('(^_*[A-Z])', lambda m: m.group(1).lower(), camel)
18 changes: 18 additions & 0 deletions src/ydata/sdk/connectors/_models/connector_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@

from enum import Enum
from typing import Union

from ydata.sdk.common.exceptions import InvalidConnectorError


class ConnectorType(Enum):
Expand All @@ -19,3 +22,18 @@ class ConnectorType(Enum):
"""BigQuery connector"""
SNOWFLAKE = "snowflake"
"""Snowflake connector"""

@property
def is_rdbms(self) -> bool:
return self in [ConnectorType.AZURE_SQL, ConnectorType.MYSQL, ConnectorType.BIGQUERY, ConnectorType.SNOWFLAKE]

@staticmethod
def _init_connector_type(connector_type: Union["ConnectorType", str]) -> "ConnectorType":
if isinstance(connector_type, str):
try:
connector_type = ConnectorType(connector_type)
except Exception:
c_list = ", ".join([c.value for c in ConnectorType])
raise InvalidConnectorError(
f"ConnectorType '{connector_type}' does not exist.\nValid connector types are: {c_list}.")
return connector_type
10 changes: 10 additions & 0 deletions src/ydata/sdk/connectors/_models/rdbms_connector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Optional

from pydantic import Field
from ydata.sdk.connectors._models.schema import Schema

from .connector import Connector


class RDBMSConnector(Connector):
db_schema: Optional[Schema] = Field(None, alias="schema")
44 changes: 44 additions & 0 deletions src/ydata/sdk/connectors/_models/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import List, Optional

from pydantic import Field
from pydantic.dataclasses import dataclass

from ydata.sdk.common.pydantic_utils import to_camel
from ydata.sdk.common.model import BaseModel


class BaseConfig(BaseModel.Config):
alias_generator = to_camel


class TableColumn(BaseModel):
"""Class to store the information of a Column table."""

name: str
variable_type: str # change this to the datatypes
primary_key: Optional[bool]
is_foreign_key: Optional[bool]
foreign_keys: list
nullable: bool

Config = BaseConfig


class Table(BaseModel):
"""Class to store the table columns information."""

name: str
columns: List[TableColumn]
primary_keys: List[TableColumn]
foreign_keys: List[TableColumn]

Config = BaseConfig


class Schema(BaseModel):
"""Class to store the database schema information."""

name: str
tables: Optional[List[Table]] = Field(None)

Config = BaseConfig
67 changes: 42 additions & 25 deletions src/ydata/sdk/connectors/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
from ydata.sdk.common.client import Client
from ydata.sdk.common.client.utils import init_client
from ydata.sdk.common.config import LOG_LEVEL
from ydata.sdk.common.exceptions import CredentialTypeError, InvalidConnectorError
from ydata.sdk.common.exceptions import CredentialTypeError
from ydata.sdk.common.logger import create_logger
from ydata.sdk.common.types import UID, Project
from ydata.sdk.connectors._models.connector import Connector as mConnector
from ydata.sdk.connectors._models.rdbms_connector import RDBMSConnector as mRDBMSConnector
from ydata.sdk.connectors._models.connector_list import ConnectorsList
from ydata.sdk.connectors._models.connector_type import ConnectorType
from ydata.sdk.connectors._models.credentials.credentials import Credentials
from ydata.sdk.connectors._models.schema import Schema
from ydata.sdk.utils.model_mixin import ModelFactoryMixin


Expand All @@ -33,11 +35,15 @@ class Connector(ModelFactoryMixin):
type (ConnectorType): Type of the connector
"""

_MODEL_CLASS = mConnector

_model: Optional[mConnector]

def __init__(
self, connector_type: Union[ConnectorType, str] = None, credentials: Optional[Dict] = None,
self, connector_type: Union[ConnectorType, str, None] = None, credentials: Optional[Dict] = None,
name: Optional[str] = None, project: Optional[Project] = None, client: Optional[Client] = None):
self._init_common(client=client)
self._model: Optional[mConnector] = self._create_model(
self._model = _connector_type_to_model(ConnectorType._init_connector_type(connector_type))._create_model(
connector_type, credentials, name, client=client)

self._project = project
Expand All @@ -61,7 +67,9 @@ def project(self) -> Project:

@staticmethod
@init_client
def get(uid: UID, project: Optional[Project] = None, client: Optional[Client] = None) -> "Connector":
def get(
uid: UID, project: Optional[Project] = None, client: Optional[Client] = None
) -> Union["Connector", "RDBMSConnector"]:
"""Get an existing connector.
Arguments:
Expand All @@ -74,24 +82,17 @@ def get(uid: UID, project: Optional[Project] = None, client: Optional[Client] =
"""
response = client.get(f'/connector/{uid}', project=project)
data = response.json()
connector = Connector._init_from_model_data(Connector, mConnector(**data))
data_type = data["type"]
connector_class = _connector_type_to_model(ConnectorType._init_connector_type(data_type))
connector = connector_class._init_from_model_data(connector_class._MODEL_CLASS(**data))
connector._project = project

return connector

@staticmethod
def _init_connector_type(connector_type: Union[ConnectorType, str]) -> ConnectorType:
if isinstance(connector_type, str):
try:
connector_type = ConnectorType(connector_type)
except Exception:
c_list = ", ".join([c.value for c in ConnectorType])
raise InvalidConnectorError(
f"ConnectorType '{connector_type}' does not exist.\nValid connector types are: {c_list}.")
return connector_type

@staticmethod
def _init_credentials(connector_type: ConnectorType, credentials: Union[str, Path, Dict, Credentials]) -> Credentials:
def _init_credentials(
connector_type: ConnectorType, credentials: Union[str, Path, Dict, Credentials]
) -> Credentials:
_credentials = None

if isinstance(credentials, str):
Expand All @@ -118,7 +119,7 @@ def _init_credentials(connector_type: ConnectorType, credentials: Union[str, Pat
def create(
connector_type: Union[ConnectorType, str], credentials: Union[str, Path, Dict, Credentials],
name: Optional[str] = None, project: Optional[Project] = None, client: Optional[Client] = None
) -> "Connector":
) -> Union["Connector", "RDBMSConnector"]:
"""Create a new connector.
Arguments:
Expand All @@ -131,20 +132,22 @@ def create(
Returns:
New connector
"""
model = Connector._create_model(
connector_type = ConnectorType._init_connector_type(connector_type)
connector_class = _connector_type_to_model(connector_type)
model = connector_class._create_model(
connector_type=connector_type, credentials=credentials, name=name, project=project, client=client)
connector = ModelFactoryMixin._init_from_model_data(
Connector, model)
connector = connector_class._init_from_model_data(model)
connector._project = project
return connector

@classmethod
@init_client
def _create_model(
cls, connector_type: Union[ConnectorType, str], credentials: Union[str, Path, Dict, Credentials],
name: Optional[str] = None, project: Optional[Project] = None, client: Optional[Client] = None) -> mConnector:
cls, connector_type: Union[ConnectorType, str], credentials: Union[str, Path, Dict, Credentials],
name: Optional[str] = None, project: Optional[Project] = None, client: Optional[Client] = None
) -> Union[mConnector, mRDBMSConnector]:
_name = name if name is not None else str(uuid4())
_connector_type = Connector._init_connector_type(connector_type)
_connector_type = ConnectorType._init_connector_type(connector_type)
_credentials = Connector._init_credentials(_connector_type, credentials)
payload = {
"type": _connector_type.value,
Expand All @@ -154,7 +157,7 @@ def _create_model(
response = client.post('/connector/', project=project, json=payload)
data: list = response.json()

return mConnector(**data)
return cls._MODEL_CLASS(**data)

@staticmethod
@init_client
Expand All @@ -174,3 +177,17 @@ def list(project: Optional[Project] = None, client: Optional[Client] = None) ->

def __repr__(self):
return self._model.__repr__()


class RDBMSConnector(Connector):

_MODEL_CLASS = mRDBMSConnector
_model: Optional[mRDBMSConnector]

@property
def schema(self) -> Optional[Schema]:
return self._model.db_schema


def _connector_type_to_model(type: ConnectorType) -> Union[Connector, RDBMSConnector]:
return RDBMSConnector if type.is_rdbms else Connector
3 changes: 2 additions & 1 deletion src/ydata/sdk/datasources/_models/metadata/metadata.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import List, Optional

from pydantic import BaseModel, Field
from pydantic import Field

from ydata.sdk.common.model import BaseModel
from ydata.sdk.datasources._models.metadata.column import Column
from ydata.sdk.datasources._models.metadata.warnings import MetadataWarning

Expand Down
9 changes: 1 addition & 8 deletions src/ydata/sdk/datasources/_models/metadata/warnings.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
from pydantic import BaseModel

from ydata.sdk.common.model import BaseModel
from ydata.sdk.datasources._models.metadata.warning_types import Level, WarningType


class Details(BaseModel):
level: Level
value: str

class Config:
use_enum_values = True


class MetadataWarning(BaseModel):
column: str
details: Details
type: WarningType

class Config:
use_enum_values = True
2 changes: 1 addition & 1 deletion src/ydata/sdk/datasources/_models/status.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from ydata.sdk.common.model import BaseModel

from ydata.core.enum import StringEnum

Expand Down
3 changes: 2 additions & 1 deletion src/ydata/sdk/synthesizers/_models/status.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Generic, Optional, TypeVar

from pydantic import BaseModel, Field
from pydantic import Field

from ydata.core.enum import StringEnum
from ydata.sdk.common.model import BaseModel

T = TypeVar("T")

Expand Down
3 changes: 2 additions & 1 deletion src/ydata/sdk/synthesizers/_models/synthesizer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Optional

from pydantic import BaseModel, Field
from pydantic import Field
from ydata.sdk.common.model import BaseModel

from .status import Status

Expand Down
6 changes: 3 additions & 3 deletions src/ydata/sdk/utils/model_mixin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional, Type
from typing import Any, Optional

from ydata.sdk.common.client.client import Client
from ydata.sdk.common.model import BaseModel
Expand All @@ -8,8 +8,8 @@ class ModelFactoryMixin:
"""Mixin for class that implements an interface for an internal model.
"""

@staticmethod
def _init_from_model_data(cls: Type, model: BaseModel, client: Optional[Client] = None) -> Any:
@classmethod
def _init_from_model_data(cls, model: BaseModel, client: Optional[Client] = None) -> Any:
o = cls.__new__(cls)
o._model = model
o._init_common(client=client)
Expand Down

0 comments on commit e5e0d0e

Please sign in to comment.