diff --git a/exabel_data_sdk/client/api/entity_api.py b/exabel_data_sdk/client/api/entity_api.py index 665d6b7..4021687 100644 --- a/exabel_data_sdk/client/api/entity_api.py +++ b/exabel_data_sdk/client/api/entity_api.py @@ -1,4 +1,4 @@ -from typing import Sequence +from typing import Optional, Sequence from exabel_data_sdk.client.api.api_client.grpc.entity_grpc_client import EntityGrpcClient from exabel_data_sdk.client.api.api_client.http.entity_http_client import EntityHttpClient @@ -48,15 +48,22 @@ def list_entity_types( total_size=response.total_size, ) - def get_entity_type(self, name: str) -> EntityType: + def get_entity_type(self, name: str) -> Optional[EntityType]: """ Get one entity type. + Return None if the entity type does not exist. + Args: name: The resource name of the requested entity type, for example "entityTypes/ns.type1". """ - response = self.client.get_entity_type(GetEntityTypeRequest(name=name)) + try: + response = self.client.get_entity_type(GetEntityTypeRequest(name=name)) + except RequestError as error: + if error.error_type == ErrorType.NOT_FOUND: + return None + raise return EntityType.from_proto(response) def list_entities( @@ -81,15 +88,22 @@ def list_entities( total_size=response.total_size, ) - def get_entity(self, name: str) -> Entity: + def get_entity(self, name: str) -> Optional[Entity]: """ Get one entity. + Return None if the entity does not exist. + Args: name: The resource name of the requested entity, for example "entityTypes/ns.type1/entities/ns.entity1". """ - response = self.client.get_entity(GetEntityRequest(name=name)) + try: + response = self.client.get_entity(GetEntityRequest(name=name)) + except RequestError as error: + if error.error_type == ErrorType.NOT_FOUND: + return None + raise return Entity.from_proto(response) def create_entity(self, entity: Entity, entity_type: str) -> Entity: @@ -141,10 +155,4 @@ def entity_exists(self, name: str) -> bool: name: The resource name of the entity, for example "entityTypes/ns.type1/entities/ns.entity1". """ - try: - self.get_entity(name) - return True - except RequestError as error: - if error.error_type is ErrorType.NOT_FOUND: - return False - raise + return self.get_entity(name) is not None diff --git a/exabel_data_sdk/client/api/relationship_api.py b/exabel_data_sdk/client/api/relationship_api.py index a7a3f94..29a98ff 100644 --- a/exabel_data_sdk/client/api/relationship_api.py +++ b/exabel_data_sdk/client/api/relationship_api.py @@ -1,3 +1,5 @@ +from typing import Optional + from exabel_data_sdk.client.api.api_client.grpc.relationship_grpc_client import ( RelationshipGrpcClient, ) @@ -49,15 +51,22 @@ def list_relationship_types( total_size=response.total_size, ) - def get_relationship_type(self, name: str) -> RelationshipType: + def get_relationship_type(self, name: str) -> Optional[RelationshipType]: """ Get one relationship type. + Return None if the relationship type does not exist. + Args: name: The resource name of the requested relationship type, for example "relationshipTypes/ns.type1" """ - response = self.client.get_relationship_type(GetRelationshipTypeRequest(name=name)) + try: + response = self.client.get_relationship_type(GetRelationshipTypeRequest(name=name)) + except RequestError as error: + if error.error_type == ErrorType.NOT_FOUND: + return None + raise return RelationshipType.from_proto(response) def create_relationship_type(self, relationship_type: RelationshipType) -> RelationshipType: @@ -153,10 +162,12 @@ def get_relationships_to_entity( def get_relationship( self, relationship_type: str, from_entity: str, to_entity: str - ) -> Relationship: + ) -> Optional[Relationship]: """ Get one relationship. + Return None if the relationship does not exist. + Args: relationship_type: The type of the relationship, for example "relationshipTypes/ns.type1". @@ -165,11 +176,16 @@ def get_relationship( to_entity: The resource name of the end point of the relationship, for example "entityTypes/ns.type2/entities/ns.entity2". """ - response = self.client.get_relationship( - GetRelationshipRequest( - parent=relationship_type, from_entity=from_entity, to_entity=to_entity + try: + response = self.client.get_relationship( + GetRelationshipRequest( + parent=relationship_type, from_entity=from_entity, to_entity=to_entity + ) ) - ) + except RequestError as error: + if error.error_type == ErrorType.NOT_FOUND: + return None + raise return Relationship.from_proto(response) def create_relationship(self, relationship: Relationship) -> Relationship: @@ -212,10 +228,4 @@ def relationship_exists(self, relationship_type: str, from_entity: str, to_entit to_entity: The resource name of the end point of the relationship, for example "entityTypes/ns.type2/entities/ns.entity2". """ - try: - self.get_relationship(relationship_type, from_entity, to_entity) - return True - except RequestError as error: - if error.error_type is ErrorType.NOT_FOUND: - return False - raise + return self.get_relationship(relationship_type, from_entity, to_entity) is not None diff --git a/exabel_data_sdk/client/api/signal_api.py b/exabel_data_sdk/client/api/signal_api.py index 07e67a3..5852895 100644 --- a/exabel_data_sdk/client/api/signal_api.py +++ b/exabel_data_sdk/client/api/signal_api.py @@ -1,6 +1,9 @@ +from typing import Optional + from exabel_data_sdk.client.api.api_client.grpc.signal_grpc_client import SignalGrpcClient from exabel_data_sdk.client.api.api_client.http.signal_http_client import SignalHttpClient from exabel_data_sdk.client.api.data_classes.paging_result import PagingResult +from exabel_data_sdk.client.api.data_classes.request_error import ErrorType, RequestError from exabel_data_sdk.client.api.data_classes.signal import Signal from exabel_data_sdk.client.client_config import ClientConfig from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import ( @@ -37,16 +40,23 @@ def list_signals(self, page_size: int = 1000, page_token: str = None) -> PagingR total_size=response.total_size, ) - def get_signal(self, name: str) -> Signal: + def get_signal(self, name: str) -> Optional[Signal]: """ Get one signal. + Return None if the signal does not exist. + Args: name: The resource name of the requested signal, for example "signals/ns.signal1". """ - response = self.client.get_signal( - GetSignalRequest(name=name), - ) + try: + response = self.client.get_signal( + GetSignalRequest(name=name), + ) + except RequestError as error: + if error.error_type == ErrorType.NOT_FOUND: + return None + raise return Signal.from_proto(response) def create_signal(self, signal: Signal) -> Signal: diff --git a/exabel_data_sdk/client/api/time_series_api.py b/exabel_data_sdk/client/api/time_series_api.py index f956608..afac763 100644 --- a/exabel_data_sdk/client/api/time_series_api.py +++ b/exabel_data_sdk/client/api/time_series_api.py @@ -79,13 +79,15 @@ def get_entity_time_series( def get_time_series( self, name: str, start: pd.Timestamp = None, end: pd.Timestamp = None - ) -> pd.Series: + ) -> Optional[pd.Series]: """ Get one time series. If start and end are not specified, all data points will be returned. If start or end is specified, both must be specified. + If time series does not exist, None is returned. + Args: name: The resource name of the requested time series, for example "entityTypes/ns.type1/entities/ns.entity1/signals/ns.signal1" or @@ -96,9 +98,16 @@ def get_time_series( if bool(start) != bool(end): raise ValueError("Either specify both 'start' and 'end' or none of them.") time_range = self._get_time_range(start, end) - time_series = self.client.get_time_series( - GetTimeSeriesRequest(name=name, view=TimeSeriesView(time_range=time_range)), - ) + + try: + time_series = self.client.get_time_series( + GetTimeSeriesRequest(name=name, view=TimeSeriesView(time_range=time_range)), + ) + except RequestError as error: + if error.error_type == ErrorType.NOT_FOUND: + return None + raise + return self._time_series_points_to_series(time_series.points, time_series.name) def create_time_series(self, name: str, series: pd.Series) -> None: @@ -205,13 +214,7 @@ def time_series_exists(self, name: str) -> bool: name: The resource name of the time series, for example "entityTypes/ns.type1/entities/ns.entity1/signals/ns.signal1". """ - try: - self.get_time_series(name) - return True - except RequestError as error: - if error.error_type is ErrorType.NOT_FOUND: - return False - raise + return self.get_time_series(name) is not None @staticmethod def _series_to_time_series_points(series: pd.Series) -> Sequence[TimeSeriesPoint]: diff --git a/exabel_data_sdk/examples/create_time_series_example.py b/exabel_data_sdk/examples/create_time_series_example.py index 4535e2c..9cd9d3d 100644 --- a/exabel_data_sdk/examples/create_time_series_example.py +++ b/exabel_data_sdk/examples/create_time_series_example.py @@ -46,6 +46,7 @@ def create_time_series() -> None: # Add a relationship between a company entity and the brand entity. company_entity = client.entity_api.get_entity("entityTypes/company/entities/F_000Q07-E") + assert company_entity relationship = client.relationship_api.create_relationship( Relationship( description="Relationship between a company and a brand.", diff --git a/exabel_data_sdk/examples/get_company_example.py b/exabel_data_sdk/examples/get_company_example.py index 17db48a..b2325b4 100644 --- a/exabel_data_sdk/examples/get_company_example.py +++ b/exabel_data_sdk/examples/get_company_example.py @@ -12,6 +12,7 @@ def get_company() -> None: # Fetch the company. microsoft = client.entity_api.get_entity("entityTypes/company/entities/F_000Q07-E") + assert microsoft # Retrieve location of company. located_in_relationships = client.relationship_api.get_relationships_from_entity( @@ -34,6 +35,7 @@ def get_company() -> None: primary_security = client.entity_api.get_entity( primary_security_relationships.results[0].to_entity ) + assert primary_security # Retrieve primary regional for company. primary_regional_relationships = client.relationship_api.get_relationships_from_entity( @@ -43,6 +45,7 @@ def get_company() -> None: primary_regional = client.entity_api.get_entity( primary_regional_relationships.results[0].to_entity ) + assert primary_regional # Retrieve primary listing for company. primary_listing_relationships = client.relationship_api.get_relationships_from_entity( @@ -61,6 +64,7 @@ def get_company() -> None: # Retrieve all regionals. regionals = [] for security in securities: + assert security regional_relationships = client.relationship_api.get_relationships_from_entity( relationship_type="relationshipTypes/HAS_REGIONAL", from_entity=security.name ) @@ -71,6 +75,7 @@ def get_company() -> None: # Retrieve all listings. listings = [] for regional in regionals: + assert regional listing_relationships = client.relationship_api.get_relationships_from_entity( relationship_type="relationshipTypes/HAS_LISTING", from_entity=regional.name ) diff --git a/exabel_data_sdk/scripts/delete_entity.py b/exabel_data_sdk/scripts/delete_entity.py index 190dbcb..fb1b7f2 100644 --- a/exabel_data_sdk/scripts/delete_entity.py +++ b/exabel_data_sdk/scripts/delete_entity.py @@ -32,7 +32,9 @@ def run_script(self, client: ExabelClient, args: argparse.Namespace) -> None: print("Running dry-run...") entity = client.entity_api.get_entity(args.entity_name) - if entity.read_only: + if entity is None: + print(f"Entity does not exist: {entity}") + elif entity.read_only: print(f"ERROR: Entity is read-only: {entity}") else: if args.dry_run: diff --git a/setup.py b/setup.py index 46eacef..340f91c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="exabel-data-sdk", - version="0.0.14", + version="0.0.15", author="Exabel", author_email="support@exabel.com", description="Python SDK for the Exabel Data API",