Skip to content

Commit

Permalink
Add data set management methods (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
burk authored Dec 22, 2021
1 parent 675c654 commit 0707d71
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 15 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.2
2.1.0
37 changes: 37 additions & 0 deletions exabel_data_sdk/client/api/api_client/data_set_api_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from abc import ABC, abstractmethod

from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import (
CreateDataSetRequest,
DataSet,
DeleteDataSetRequest,
GetDataSetRequest,
ListDataSetsRequest,
ListDataSetsResponse,
UpdateDataSetRequest,
)


class DataSetApiClient(ABC):
"""
Superclass for clients that send data set requests to the Exabel Data API.
"""

@abstractmethod
def list_data_sets(self, request: ListDataSetsRequest) -> ListDataSetsResponse:
"""List all data sets."""

@abstractmethod
def get_data_set(self, request: GetDataSetRequest) -> DataSet:
"""Get a data set."""

@abstractmethod
def create_data_set(self, request: CreateDataSetRequest) -> DataSet:
"""Create a data set."""

@abstractmethod
def update_data_set(self, request: UpdateDataSetRequest) -> DataSet:
"""Update a data set."""

@abstractmethod
def delete_data_set(self, request: DeleteDataSetRequest) -> None:
"""Delete a data set."""
44 changes: 44 additions & 0 deletions exabel_data_sdk/client/api/api_client/grpc/data_set_grpc_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from exabel_data_sdk.client.api.api_client.data_set_api_client import DataSetApiClient
from exabel_data_sdk.client.api.api_client.grpc.base_grpc_client import BaseGrpcClient
from exabel_data_sdk.client.api.error_handler import handle_grpc_error
from exabel_data_sdk.client.client_config import ClientConfig
from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import (
CreateDataSetRequest,
DataSet,
DeleteDataSetRequest,
GetDataSetRequest,
ListDataSetsRequest,
ListDataSetsResponse,
UpdateDataSetRequest,
)
from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2_grpc import DataSetServiceStub


class DataSetGrpcClient(DataSetApiClient, BaseGrpcClient):
"""
Client which sends data set requests to the Exabel Data API with gRPC.
"""

def __init__(self, config: ClientConfig):
super().__init__(config)
self.stub = DataSetServiceStub(self.channel)

@handle_grpc_error
def list_data_sets(self, request: ListDataSetsRequest) -> ListDataSetsResponse:
return self.stub.ListDataSets(request, metadata=self.metadata, timeout=self.config.timeout)

@handle_grpc_error
def get_data_set(self, request: GetDataSetRequest) -> DataSet:
return self.stub.GetDataSet(request, metadata=self.metadata, timeout=self.config.timeout)

@handle_grpc_error
def create_data_set(self, request: CreateDataSetRequest) -> DataSet:
return self.stub.CreateDataSet(request, metadata=self.metadata, timeout=self.config.timeout)

@handle_grpc_error
def update_data_set(self, request: UpdateDataSetRequest) -> DataSet:
return self.stub.UpdateDataSet(request, metadata=self.metadata, timeout=self.config.timeout)

@handle_grpc_error
def delete_data_set(self, request: DeleteDataSetRequest) -> None:
return self.stub.DeleteDataSet(request, metadata=self.metadata, timeout=self.config.timeout)
32 changes: 32 additions & 0 deletions exabel_data_sdk/client/api/api_client/http/data_set_http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from exabel_data_sdk.client.api.api_client.data_set_api_client import DataSetApiClient
from exabel_data_sdk.client.api.api_client.http.base_http_client import BaseHttpClient
from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import (
CreateDataSetRequest,
DataSet,
DeleteDataSetRequest,
GetDataSetRequest,
ListDataSetsRequest,
ListDataSetsResponse,
UpdateDataSetRequest,
)


class DataSetHttpClient(DataSetApiClient, BaseHttpClient):
"""
Client which sends data set requests to the Exabel Data API with JSON over gRPC.
"""

def list_data_sets(self, request: ListDataSetsRequest) -> ListDataSetsResponse:
return self._request("GET", "dataSets", ListDataSetsResponse())

def get_data_set(self, request: GetDataSetRequest) -> DataSet:
return self._request("GET", request.name, DataSet())

def create_data_set(self, request: CreateDataSetRequest) -> DataSet:
return self._request("POST", "dataSets", DataSet(), body=request.data_set)

def update_data_set(self, request: UpdateDataSetRequest) -> DataSet:
return self._request("PATCH", "dataSets", DataSet(), body=request.data_set)

def delete_data_set(self, request: DeleteDataSetRequest) -> None:
return self._request("DELETE", request.name, None)
83 changes: 83 additions & 0 deletions exabel_data_sdk/client/api/data_classes/data_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from typing import Sequence

from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import DataSet as ProtoDataSet


class DataSet:
"""
A data set resource in the Data API.
Attributes:
name (str): The resource name of the data set, for example "dataSets/ns.mydata".
display_name (str): The display name of the data set.
description (str): One or more paragraphs of text description.
signals ([str]): Resource names of signals this data set contains.
read_only (bool): Whether this resource is read only.
"""

def __init__(
self,
name: str,
display_name: str,
description: str = "",
signals: Sequence[str] = None,
read_only: bool = False,
):
"""
Create a data set resource in the Data API.
Args:
name (str): The resource name of the data set, for example "dataSets/ns.mydata".
display_name: The display name of the data set.
description: One or more paragraphs of text description.
signals: Resource names of signals this data set contains.
read_only: Whether this resource is read only.
"""
self.name = name
self.display_name = display_name
self.description = description
self.read_only = read_only
self.signals = signals or []

@staticmethod
def from_proto(data_set: ProtoDataSet) -> "DataSet":
"""Create an DataSet from the given protobuf DataSet."""
return DataSet(
name=data_set.name,
display_name=data_set.display_name,
description=data_set.description,
signals=list(data_set.signals),
read_only=data_set.read_only,
)

def to_proto(self) -> ProtoDataSet:
"""Create a protobuf DataSet from this DataSet."""
return ProtoDataSet(
name=self.name,
display_name=self.display_name,
description=self.description,
signals=self.signals if self.signals else None,
)

def __eq__(self, other: object) -> bool:
if not isinstance(other, DataSet):
return False
return (
self.name == other.name
and self.display_name == other.display_name
and self.description == other.description
and self.read_only == other.read_only
and sorted(self.signals) == sorted(other.signals)
)

def __repr__(self) -> str:
return (
f"DataSet(name='{self.name}', display_name='{self.display_name}', "
f"description='{self.description}', signals={self.signals}, "
f"read_only={self.read_only})"
)

def __lt__(self, other: object) -> bool:
if not isinstance(other, DataSet):
raise ValueError(f"Cannot compare DataSet to non-DataSet: {other}")
return self.name < other.name
83 changes: 83 additions & 0 deletions exabel_data_sdk/client/api/data_set_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from typing import Optional, Sequence

from google.protobuf.field_mask_pb2 import FieldMask

from exabel_data_sdk.client.api.api_client.grpc.data_set_grpc_client import DataSetGrpcClient
from exabel_data_sdk.client.api.api_client.http.data_set_http_client import DataSetHttpClient
from exabel_data_sdk.client.api.data_classes.data_set import DataSet
from exabel_data_sdk.client.api.data_classes.request_error import ErrorType, RequestError
from exabel_data_sdk.client.client_config import ClientConfig
from exabel_data_sdk.stubs.exabel.api.data.v1.data_set_service_pb2 import (
CreateDataSetRequest,
DeleteDataSetRequest,
GetDataSetRequest,
ListDataSetsRequest,
UpdateDataSetRequest,
)


class DataSetApi:
"""
API class for data set CRUD operations.
"""

def __init__(self, config: ClientConfig, use_json: bool = False):
self.client = (DataSetHttpClient if use_json else DataSetGrpcClient)(config)

def list_data_sets(self) -> Sequence[DataSet]:
"""
List all data sets.
"""
response = self.client.list_data_sets(ListDataSetsRequest())
return [DataSet.from_proto(d) for d in response.data_sets]

def get_data_set(self, name: str) -> Optional[DataSet]:
"""
Get one data set.
Return None if the data set does not exist.
Args:
name: The resource name of the requested data set, for example
"dataSet/123".
"""
try:
response = self.client.get_data_set(GetDataSetRequest(name=name))
except RequestError as error:
if error.error_type == ErrorType.NOT_FOUND:
return None
raise
return DataSet.from_proto(response)

def create_data_set(self, data_set: DataSet) -> DataSet:
"""
Create a data set.
Args:
data_set: The data set to create.
"""
response = self.client.create_data_set(CreateDataSetRequest(data_set=data_set.to_proto()))
return DataSet.from_proto(response)

def update_data_set(self, data_set: DataSet, update_mask: FieldMask = None) -> DataSet:
"""
Update a data set.
Args:
data_set: The data set to update.
update_mask: The fields to update. If not specified, the update behaves as a
full update, overwriting all existing fields and properties.
"""
response = self.client.update_data_set(
UpdateDataSetRequest(data_set=data_set.to_proto(), update_mask=update_mask)
)
return DataSet.from_proto(response)

def delete_data_set(self, data_set: str) -> None:
"""
Delete a data set.
Args:
data_set: The data set to delete.
"""
self.client.delete_data_set(DeleteDataSetRequest(name=data_set))
2 changes: 2 additions & 0 deletions exabel_data_sdk/client/exabel_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from exabel_data_sdk.client.api.data_set_api import DataSetApi
from exabel_data_sdk.client.api.entity_api import EntityApi
from exabel_data_sdk.client.api.relationship_api import RelationshipApi
from exabel_data_sdk.client.api.signal_api import SignalApi
Expand Down Expand Up @@ -41,3 +42,4 @@ def __init__(
self.signal_api = SignalApi(config, use_json)
self.time_series_api = TimeSeriesApi(config, use_json)
self.relationship_api = RelationshipApi(config, use_json)
self.data_set_api = DataSetApi(config, use_json)
22 changes: 11 additions & 11 deletions exabel_data_sdk/scripts/export_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,6 @@ def __init__(self, argv: Sequence[str]):
def parse_arguments(self) -> argparse.Namespace:
"""Parse the command-line input arguments."""
parser = argparse.ArgumentParser(description="Export data")
parser.add_argument(
"--auth0",
default="auth.exabel.com",
help="The domain of the Auth0 log-in page",
)
parser.add_argument(
"--client-id",
default="6OoAPIEgqz1CQokkBuwtBcYKgNiLKsMF",
help="The Auth0 client id for the Python SDK",
)
parser.add_argument(
"--query",
required=True,
Expand All @@ -50,7 +40,17 @@ def parse_arguments(self) -> argparse.Namespace:
required=False,
type=str,
default="endpoints.exabel.com",
help="The domain of the Exabel back-end API",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--auth0",
default="auth.exabel.com",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--client-id",
default="6OoAPIEgqz1CQokkBuwtBcYKgNiLKsMF",
help=argparse.SUPPRESS,
)
return parser.parse_args(self.argv[1:])

Expand Down
12 changes: 10 additions & 2 deletions exabel_data_sdk/stubs/exabel/api/data/v1/data_set_messages_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ class DataSet(google___protobuf___message___Message):
display_name: typing___Text = ...
description: typing___Text = ...
signals: google___protobuf___internal___containers___RepeatedScalarFieldContainer[typing___Text] = ...
read_only: builtin___bool = ...

def __init__(self,
*,
name : typing___Optional[typing___Text] = None,
display_name : typing___Optional[typing___Text] = None,
description : typing___Optional[typing___Text] = None,
signals : typing___Optional[typing___Iterable[typing___Text]] = None,
read_only : typing___Optional[builtin___bool] = None,
) -> None: ...
def ClearField(self, field_name: typing_extensions___Literal[u"description",b"description",u"display_name",b"display_name",u"name",b"name",u"signals",b"signals"]) -> None: ...
def ClearField(self, field_name: typing_extensions___Literal[u"description",b"description",u"display_name",b"display_name",u"name",b"name",u"read_only",b"read_only",u"signals",b"signals"]) -> None: ...
type___DataSet = DataSet
Loading

0 comments on commit 0707d71

Please sign in to comment.