Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5

with:
python-version-file: '.python-version'
- name: Install dependencies and build package
run: |
pip install pipenv wheel
Expand All @@ -29,7 +30,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
fail-fast: true

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
fail-fast: true

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_installability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
fail-fast: true

steps:
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9.20
3.10.19
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ wheel = "*"
[dev-packages]

[requires]
python_version = "3.9"
python_version = "3.10"
1,473 changes: 760 additions & 713 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pip install exabel-data-sdk

or download from [PyPI](https://pypi.org/project/exabel-data-sdk/).

The SDK requires Python 3.9 or later.
The SDK requires Python 3.10 or later.

### Installation with SQL data source support

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.3.0
7.0.0
37 changes: 37 additions & 0 deletions exabel_data_sdk/client/api/api_client/calendar_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.calendar_service_pb2 import (
BatchCreateFiscalPeriodsRequest,
BatchCreateFiscalPeriodsResponse,
DeleteFiscalPeriodRequest,
ListCompaniesWithFiscalPeriodsRequest,
ListCompaniesWithFiscalPeriodsResponse,
ListFiscalPeriodsRequest,
ListFiscalPeriodsResponse,
)


class CalendarApiClient(ABC):
"""Superclass for clients that send calendar requests to the Exabel Data API."""

@abstractmethod
def batch_create_fiscal_periods(
self, request: BatchCreateFiscalPeriodsRequest
) -> BatchCreateFiscalPeriodsResponse:
"""Add fiscal periods for a company."""

@abstractmethod
def list_company_fiscal_periods(
self, request: ListFiscalPeriodsRequest
) -> ListFiscalPeriodsResponse:
"""List the fiscal periods for a company."""

@abstractmethod
def delete_fiscal_periods(self, request: DeleteFiscalPeriodRequest) -> None:
"""Delete fiscal periods for a company."""

@abstractmethod
def list_companies_with_fiscal_periods(
self, request: ListCompaniesWithFiscalPeriodsRequest
) -> ListCompaniesWithFiscalPeriodsResponse:
"""List the companies for which there are uploaded fiscal periods."""
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ def __init__(self, config: ClientConfig, api_group: ExabelApiGroup):
# Use an insecure channel. This can be used for local testing.
self.channel = grpc.insecure_channel(**common_kwargs)
else:
assert config.api_key is not None
self.metadata.append(("x-api-key", config.api_key))
if config.access_token is not None:
self.metadata.append(("authorization", "Bearer " + config.access_token))
else:
assert config.api_key is not None
self.metadata.append(("x-api-key", config.api_key))
self.channel = grpc.secure_channel(
credentials=grpc.ssl_channel_credentials(
root_certificates=config.root_certificates
Expand Down
48 changes: 48 additions & 0 deletions exabel_data_sdk/client/api/api_client/grpc/calendar_grpc_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from exabel_data_sdk.client.api.api_client.calendar_api_client import CalendarApiClient
from exabel_data_sdk.client.api.api_client.exabel_api_group import ExabelApiGroup
from exabel_data_sdk.client.api.api_client.grpc.base_grpc_client import BaseGrpcClient
from exabel_data_sdk.client.client_config import ClientConfig
from exabel_data_sdk.stubs.exabel.api.data.v1.calendar_service_pb2 import (
BatchCreateFiscalPeriodsRequest,
BatchCreateFiscalPeriodsResponse,
DeleteFiscalPeriodRequest,
ListCompaniesWithFiscalPeriodsRequest,
ListCompaniesWithFiscalPeriodsResponse,
ListFiscalPeriodsRequest,
ListFiscalPeriodsResponse,
)
from exabel_data_sdk.stubs.exabel.api.data.v1.calendar_service_pb2_grpc import CalendarServiceStub


class CalendarGrpcClient(CalendarApiClient, BaseGrpcClient):
"""
Client which sends calendar requests to the Exabel Data API with gRPC.
"""

def __init__(self, config: ClientConfig):
super().__init__(config, ExabelApiGroup.DATA_API)
self.stub = CalendarServiceStub(self.channel)

def batch_create_fiscal_periods(
self, request: BatchCreateFiscalPeriodsRequest
) -> BatchCreateFiscalPeriodsResponse:
return self.stub.BatchCreateFiscalPeriods(
request, metadata=self.metadata, timeout=self.config.timeout
)

def list_company_fiscal_periods(
self, request: ListFiscalPeriodsRequest
) -> ListFiscalPeriodsResponse:
return self.stub.ListFiscalPeriods(
request, metadata=self.metadata, timeout=self.config.timeout
)

def delete_fiscal_periods(self, request: DeleteFiscalPeriodRequest) -> None:
self.stub.DeleteFiscalPeriod(request, metadata=self.metadata, timeout=self.config.timeout)

def list_companies_with_fiscal_periods(
self, request: ListCompaniesWithFiscalPeriodsRequest
) -> ListCompaniesWithFiscalPeriodsResponse:
return self.stub.ListCompaniesWithFiscalPeriods(
request, metadata=self.metadata, timeout=self.config.timeout
)
13 changes: 13 additions & 0 deletions exabel_data_sdk/client/api/api_client/grpc/signal_grpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from exabel_data_sdk.stubs.exabel.api.data.v1.all_pb2 import (
CreateSignalRequest,
DeleteSignalRequest,
FilterDerivedSignalsRequest,
FilterDerivedSignalsResponse,
GetSignalRequest,
ListSignalsRequest,
ListSignalsResponse,
Expand Down Expand Up @@ -63,3 +65,14 @@ def delete_signal(self, request: DeleteSignalRequest) -> None:
metadata=self.metadata,
timeout=self.config.timeout,
)

@handle_grpc_error
def filter_derived_signals(
self, request: FilterDerivedSignalsRequest
) -> FilterDerivedSignalsResponse:
"""Filter derived signals by entity names."""
return self.stub.FilterDerivedSignals(
request,
metadata=self.metadata,
timeout=self.config.timeout,
)
5 changes: 1 addition & 4 deletions exabel_data_sdk/client/api/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,7 @@ def _bulk_import(
results,
resource_batch,
import_func,
# Python 3.9 added support for the shutdown argument 'cancel_futures'.
# We should set this argument to True once we have moved to this python
# version.
lambda: executor.shutdown(wait=False),
lambda: executor.shutdown(wait=False, cancel_futures=True),
)
if results.abort:
raise BulkInsertFailedError()
5 changes: 1 addition & 4 deletions exabel_data_sdk/client/api/bulk_insert.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,7 @@ def _bulk_insert(
results,
resource,
insert_func,
# Python 3.9 added support for the shutdown argument 'cancel_futures'.
# We should set this argument to True once we have moved to this python
# version.
lambda: executor.shutdown(wait=False),
lambda: executor.shutdown(wait=False, cancel_futures=True),
)
if results.abort:
raise BulkInsertFailedError()
Expand Down
109 changes: 109 additions & 0 deletions exabel_data_sdk/client/api/calendar_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from collections.abc import Sequence
from typing import Optional

from exabel_data_sdk.client.api.api_client.grpc.calendar_grpc_client import CalendarGrpcClient
from exabel_data_sdk.client.client_config import ClientConfig
from exabel_data_sdk.stubs.exabel.api.data.v1.calendar_messages_pb2 import FiscalPeriod, Frequency
from exabel_data_sdk.stubs.exabel.api.data.v1.calendar_service_pb2 import (
BatchCreateFiscalPeriodsRequest,
DeleteFiscalPeriodRequest,
ListCompaniesWithFiscalPeriodsRequest,
ListFiscalPeriodsRequest,
)
from exabel_data_sdk.stubs.exabel.api.time.date_pb2 import Date


class CalendarApi:
"""API class for CRUD operations on companies’ fiscal periods."""

def __init__(self, config: ClientConfig):
self.client = CalendarGrpcClient(config)

def create_fiscal_periods(
self,
company: str,
*,
quarterly: Sequence[Date] = (),
semiannual: Sequence[Date] = (),
annual: Sequence[Date] = (),
) -> None:
"""
Create fiscal periods for a company.

Args:
company: The resource name of the company.
quarterly: The end dates of the quarterly fiscal periods.
semiannual: The end dates of the semi-annual fiscal periods.
annual: The end dates of the annual fiscal periods.
"""
periods = self._to_fiscal_periods(quarterly=quarterly, semiannual=semiannual, annual=annual)
if not periods:
return
request = BatchCreateFiscalPeriodsRequest(parent=company, periods=periods)
self.client.batch_create_fiscal_periods(request)

def list_fiscal_periods(
self, company: str, frequency: Optional[Frequency.ValueType] = None
) -> Sequence[FiscalPeriod]:
"""
List the fiscal periods for a company.

Args:
company: The resource name of the company.
frequency: The frequency of the returned fiscal periods. If not provided, the fiscal
periods for all the frequencies are returned.
"""
request = ListFiscalPeriodsRequest(parent=company, frequency=frequency)
return self.client.list_company_fiscal_periods(request).periods

def delete_fiscal_period(
self,
company: str,
date: Date,
frequency: Frequency.ValueType,
) -> None:
"""
Delete a previously added fiscal period for a company.

Args:
company: The resource name of the company.
date: The end date of the fiscal period which should be deleted.
frequency: The frequency of the fiscal period which should be deleted.
"""
request = DeleteFiscalPeriodRequest(
parent=company, period=FiscalPeriod(frequency=frequency, end_date=date)
)
self.client.delete_fiscal_periods(request)

def delete_fiscal_periods(self, company: str) -> None:
"""
Deletes all previously added fiscal periods for a company.

Args:
company: The resource name of the company.
"""
request = DeleteFiscalPeriodRequest(parent=company)
self.client.delete_fiscal_periods(request)

def _to_fiscal_periods(
self, *, quarterly: Sequence[Date], semiannual: Sequence[Date], annual: Sequence[Date]
) -> Sequence[FiscalPeriod]:
periods = []
for dates, frequency in (
(quarterly, Frequency.QUARTERLY),
(semiannual, Frequency.SEMIANNUAL),
(annual, Frequency.ANNUAL),
):
periods.extend([FiscalPeriod(frequency=frequency, end_date=d) for d in dates])
return periods

def list_companies_with_fiscal_periods(self) -> Sequence[str]:
"""
Return a list of companies which have fiscal periods.

Returns:
A list containing the resource names of all the companies for which the customer has
uploaded fiscal periods
"""
request = ListCompaniesWithFiscalPeriodsRequest()
return self.client.list_companies_with_fiscal_periods(request).companies
Loading