Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement create/get/delete/list table metadata methods #28

Merged
merged 7 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
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
mure marked this conversation as resolved.
Show resolved Hide resolved
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
mure marked this conversation as resolved.
Show resolved Hide resolved
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
mure marked this conversation as resolved.
Show resolved Hide resolved
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,
mure marked this conversation as resolved.
Show resolved Hide resolved
order_by: Optional[models.OrderBy] = None,
order_by_descending: Optional[bool] = None,
continuation_token: Optional[str] = None,
workspace: Optional[str] = None,
mure marked this conversation as resolved.
Show resolved Hide resolved
) -> 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


OrderBy = Literal[
mure marked this conversation as resolved.
Show resolved Hide resolved
"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