Skip to content

Commit

Permalink
feat: Implement create/get/delete/list table metadata methods (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
mure committed Nov 18, 2022
1 parent 17a08ea commit 7cbf7e8
Show file tree
Hide file tree
Showing 15 changed files with 412 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@

# Configuration for https://autodoc-pydantic.readthedocs.io
autodoc_pydantic_model_show_config_summary = False
autodoc_pydantic_field_show_alias = False
1 change: 0 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ strict_equality=True

[mypy-tests.*]
disallow_untyped_calls=True
disallow_incomplete_defs=True
disallow_untyped_decorators=True

strict_equality=True
Expand Down
18 changes: 15 additions & 3 deletions nisystemlink/clients/core/_uplink/_base_client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# mypy: disable-error-code = misc

from typing import Optional
from typing import Dict, Optional, Type

from nisystemlink.clients import core
from requests import JSONDecodeError, Response
from uplink import Consumer, response_handler
from uplink import Consumer, dumps, response_handler

from ._json_model import JsonModel


@response_handler
Expand Down Expand Up @@ -36,6 +38,12 @@ def _handle_http_status(response: Response) -> Optional[Response]:
raise core.ApiException(msg, http_status_code=response.status_code)


@dumps.to_json(JsonModel)
def _deserialize_model(model_cls: Type[JsonModel], model_instance: JsonModel) -> Dict:
"""Turns a :class:`.JsonModel` instance into a dictionary for serialization."""
return model_instance.dict(by_alias=True, exclude_unset=True)


class BaseClient(Consumer):
"""Base class for SystemLink clients, built on top of `Uplink <https://github.com/prkumar/uplink>`_."""

Expand All @@ -45,6 +53,10 @@ def __init__(self, configuration: core.HttpConfiguration):
Args:
configuration: Defines the web server to connect to and information about how to connect.
"""
super().__init__(base_url=configuration.server_uri, hooks=[_handle_http_status])
super().__init__(
base_url=configuration.server_uri,
converter=_deserialize_model,
hooks=[_handle_http_status],
)
if configuration.api_keys:
self.session.headers.update(configuration.api_keys)
15 changes: 15 additions & 0 deletions nisystemlink/clients/core/_uplink/_paged_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Optional

from ._json_model import JsonModel


class PagedResult(JsonModel):
continuation_token: Optional[str]
"""A token which allows the user to resume a query at the next item in the matching results.
When querying, a token will be returned if a query may be
continued. To obtain the next page of results, pass the token to the service
on a subsequent request. The service will respond with a new continuation
token. To paginate results, continue sending requests with the newest
continuation token provided by the service, until this value is null.
"""
77 changes: 73 additions & 4 deletions nisystemlink/clients/dataframe/_data_frame_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

"""Implementation of DataFrameClient."""

from typing import Optional
from typing import List, Optional

from nisystemlink.clients import core
from nisystemlink.clients.core._uplink._base_client import BaseClient
from uplink import get, returns
from uplink import Body, delete, get, json, post, Query, returns

from . import models

Expand All @@ -28,8 +28,77 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None):

super().__init__(configuration)

@returns.json()
@get(_BASE_PATH)
def api_info(self) -> models.ApiInfo:
"""Returns information about available API operations."""
pass
...

@get(
_BASE_PATH + "/tables",
args=(
Query("take"),
Query("id"),
Query("orderBy"),
Query("orderByDescending"),
Query("continuationToken"),
Query("workspace"),
),
)
def list_tables(
self,
take: Optional[int] = None,
id: Optional[List[str]] = None,
order_by: Optional[models.OrderBy] = None,
order_by_descending: Optional[bool] = None,
continuation_token: Optional[str] = None,
workspace: Optional[List[str]] = None,
) -> models.PagedTables:
"""Lists available tables on the SystemLink DataFrame service.
Args:
take: Limits the returned list to the specified number of results. Defaults to 1000.
id: List of table IDs to filter by.
order_by: The sort order of the returned list of tables.
order_by_descending: Whether to sort descending instead of ascending. Defaults to false.
continuation_token: The token used to paginate results.
workspace: List of workspace IDs to filter by.
Returns:
models.PagedTables: The list of tables with a continuation token.
"""
...

@json
@returns.json(key="id")
@post(_BASE_PATH + "/tables", args=(Body,))
def create_table(self, table: models.CreateTableRequest) -> str:
"""Create a new table with the provided metadata and column definitions.
Args:
table: The request create the table.
Returns:
The ID of the newly created table.
"""
...

@get(_BASE_PATH + "/tables/{id}")
def get_table_metadata(self, id: str) -> models.TableMetadata:
"""Retrieves the metadata and column information for a single table identified by its ID.
Args:
id (str): Unique ID of a DataFrame table.
Returns:
models.TableMetadata: The metadata for the table.
"""
...

@delete(_BASE_PATH + "/tables/{id}")
def delete_table(self, id: str) -> None:
"""Deletes a table.
Args:
id (str): Unique ID of a DataFrame table.
"""
...
7 changes: 7 additions & 0 deletions nisystemlink/clients/dataframe/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from ._api_info import ApiInfo, Operation, OperationsV1
from ._create_table_request import CreateTableRequest
from ._column import Column
from ._column_type import ColumnType
from ._data_type import DataType
from ._order_by import OrderBy
from ._paged_tables import PagedTables
from ._table_metadata import TableMetadata

# flake8: noqa
30 changes: 22 additions & 8 deletions nisystemlink/clients/dataframe/models/_api_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,33 @@
class Operation(JsonModel):
"""Represents an operation that can be performed on a data frame."""

available: bool #: Whether or not the operation is available to the caller (e.g. due to permissions).
version: int #: The version of the available operation.
available: bool
"""Whether or not the operation is available to the caller (e.g. due to permissions)."""

version: int
"""The version of the available operation."""


class OperationsV1(JsonModel):
"""The operations available in the routes provided by the v1 HTTP API."""

create_tables: Operation #: The ability to create new DataFrame tables.
delete_tables: Operation #: The ability to delete tables and all of their data.
modify_metadata: Operation #: The ability to modify metadata for tables.
list_tables: Operation #: The ability to locate and read metadata for tables.
read_data: Operation #: The ability to query and read data from tables.
write_data: Operation #: The ability to append rows of data to tables.
create_tables: Operation
"""The ability to create new DataFrame tables."""

delete_tables: Operation
"""The ability to delete tables and all of their data."""

modify_metadata: Operation
"""The ability to modify metadata for tables."""

list_tables: Operation
"""The ability to locate and read metadata for tables."""

read_data: Operation
"""The ability to query and read data from tables."""

write_data: Operation
"""The ability to append rows of data to tables."""


class ApiInfo(JsonModel):
Expand Down
22 changes: 22 additions & 0 deletions nisystemlink/clients/dataframe/models/_column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Dict, Optional

from nisystemlink.clients.core._uplink._json_model import JsonModel

from ._column_type import ColumnType
from ._data_type import DataType


class Column(JsonModel):
"""Defines a single column in a table."""

name: str
"""The column name, which must be unique across all columns in the table."""

data_type: DataType
"""The data type of the column."""

column_type: ColumnType = ColumnType.Normal
"""The column type. Defaults to ColumnType.Normal."""

properties: Optional[Dict[str, str]] = None
"""User-defined properties associated with the column."""
18 changes: 18 additions & 0 deletions nisystemlink/clients/dataframe/models/_column_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from enum import Enum


class ColumnType(str, Enum):
"""Represents the different column types for a table column."""

Normal = "NORMAL"
"""The column has no special properties. This is the default."""

Index = "INDEX"
"""The column provides a unique value per row. Each table must provide
exactly one INDEX column. The column's :class:`.DataType` must be INT32,
INT64, or TIMESTAMP."""

Nullable = "NULLABLE"
"""Rows may contain null values for this column. When appending rows,
NULLABLE columns may be left out entirely, in which case all rows being
appended will use null values for that column."""
22 changes: 22 additions & 0 deletions nisystemlink/clients/dataframe/models/_create_table_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Dict, List, Optional

from nisystemlink.clients.core._uplink._json_model import JsonModel

from ._column import Column


class CreateTableRequest(JsonModel):
"""Contains information needed to create a table, including its properties and column definitions."""

columns: List[Column]
"""The list of columns in the table. Exactly one column must have a :class:`.ColumnType` of INDEX."""

name: Optional[str] = None
"""The name to associate with the table. When not specified, a name will be
assigned from the table's ID."""

properties: Optional[Dict[str, str]] = None
"""User-defined properties to associate with the table."""

workspace: Optional[str] = None
"""The workspace to create the table in. Uses the default workspace when not specified."""
26 changes: 26 additions & 0 deletions nisystemlink/clients/dataframe/models/_data_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from enum import Enum


class DataType(str, Enum):
"""Represents the different data types for a table column."""

Bool = "BOOL"
"""32-bit IEEE 754 floating-point number."""

Float32 = "FLOAT32"
"""32-bit IEEE 754 floating-point number."""

Float64 = "FLOAT64"
"""64-bit IEEE 754 floating-point number."""

Int32 = "INT32"
"""32-bit signed integers."""

Int64 = "INT64"
"""64-bit signed integers."""

String = "STRING"
"""Arbitrary string data."""

Timestamp = "TIMESTAMP"
"""Date and time represented in UTC with millisecond precision."""
14 changes: 14 additions & 0 deletions nisystemlink/clients/dataframe/models/_order_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import Literal

# TODO: Migrate to Enum when this change is released: https://github.com/prkumar/uplink/pull/282
OrderBy = Literal[
"CREATED_AT", "METADATA_MODIFIED_AT", "NAME", "NUMBER_OF_ROWS", "ROWS_MODIFIED_AT"
]
"""Possible options for sorting when querying tables.
* ``CREATED_AT``: The date and time the table was created.
* ``METADATA_MODIFIED_AT``: The date and time the table's metadata properties were modified.
* ``NAME``: The name of the table.
* ``NUMBER_OF_ROWS``: The number of rows of data in the table.
* ``ROWS_MODIFIED_AT``: Date and time rows were most recently appended to the table.
"""
12 changes: 12 additions & 0 deletions nisystemlink/clients/dataframe/models/_paged_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import List

from nisystemlink.clients.core._uplink._paged_result import PagedResult

from ._table_metadata import TableMetadata


class PagedTables(PagedResult):
"""The response for a table query containing the matched tables."""

tables: List[TableMetadata]
"""The list of tables returned by the query."""
43 changes: 43 additions & 0 deletions nisystemlink/clients/dataframe/models/_table_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from datetime import datetime
from typing import Dict, List

from nisystemlink.clients.core._uplink._json_model import JsonModel

from ._column import Column


class TableMetadata(JsonModel):
"""Contains information about a table, including its properties and column definitions."""

columns: List[Column]
"""The list of columns in the table."""

created_at: datetime
"""The date and time the table was created."""

id: str
"""The table's unique identifier."""

metadata_modified_at: datetime
"""The date and time the table's metadata was last modified."""

metadata_revision: int
"""The table's metadata revision number, incremented each time the metadata is modified."""

name: str
"""The name associated with the table."""

properties: Dict[str, str]
"""User-defined properties associated with the table."""

row_count: int
"""The number of rows in the table."""

rows_modified_at: datetime
"""The date and time the table's data was last modified."""

supports_append: bool
"""Whether the table supports appending additional rows of data."""

workspace: str
"""The workspace the table belongs to."""
Loading

0 comments on commit 7cbf7e8

Please sign in to comment.