Skip to content

Commit

Permalink
refactor!: improve docs & interface for Client
Browse files Browse the repository at this point in the history
  • Loading branch information
rick-jennings committed Sep 11, 2024
1 parent 92bdd26 commit bc52193
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 204 deletions.
6 changes: 2 additions & 4 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

::: phable.client.Client

::: phable.client.UnknownRecError
::: phable.client.CallError

::: phable.client.ErrorGridError

::: phable.client.IncompleteDataError
::: phable.client.UnknownRecError
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ plugins:
separate_signature: true
show_root_heading: true
show_root_full_path: false
show_docstring_raises: true
# show_docstring_description: true
# show_signature_annotations: true
7 changes: 1 addition & 6 deletions phable/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# flake8: noqa

from phable.client import (
Client,
ErrorGridResponseError,
HaystackHisWriteOpParametersError,
UnknownRecError,
)
from phable.client import CallError, Client, UnknownRecError
from phable.hx_client import HxClient
from phable.kinds import (
NA,
Expand Down
170 changes: 29 additions & 141 deletions phable/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,7 @@


@dataclass
class HaystackHisWriteOpParametersError(Exception):
help_msg: str


@dataclass
class UnknownRecError(Exception):
"""Error raised by `Client` when server's `Grid` response does not include data for
one more more recs being requested.
Parameters:
help_msg: A display to help with troubleshooting.
"""

help_msg: str


@dataclass
class ErrorGridError(Exception):
class CallError(Exception):
"""Error raised by `Client` when server's `Grid` response meta has an `err` marker
tag.
Expand All @@ -46,17 +29,15 @@ class ErrorGridError(Exception):


@dataclass
class IncompleteDataError(Exception):
"""Error raised by `Client` when server's `Grid` response meta has an `incomplete`
tag.
class UnknownRecError(Exception):
"""Error raised by `Client` when server's `Grid` response does not include data for
one or more recs being requested.
Parameters:
response:
`Grid` that has `incomplete` tag in meta described
[here](https://project-haystack.org/doc/docHaystack/HttpApi#incompleteData).
help_msg: A display to help with troubleshooting.
"""

response: Grid
help_msg: str


class Client:
Expand All @@ -69,6 +50,8 @@ class Client:
from phable import Client
```
Methods may raise a `CallError`.
## Context Manager
An optional context manager may be used to automatically open and close the session
Expand Down Expand Up @@ -116,10 +99,6 @@ def __init__(
self._auth_token: str
self._context = ssl_context

# -------------------------------------------------------------------------
# open the connection with the server
# -------------------------------------------------------------------------

def open(self) -> None:
"""Initiates and executes the SCRAM authentication exchange with the server.
Expand All @@ -134,21 +113,13 @@ def open(self) -> None:
self._auth_token = scram.get_auth_token()
del scram

# -------------------------------------------------------------------------
# define an optional context manager
# -------------------------------------------------------------------------

def __enter__(self):
self.open()
return self

def __exit__(self, exc_type, exc_value, traceback): # type: ignore
self.close()

# -------------------------------------------------------------------------
# standard Haystack ops
# -------------------------------------------------------------------------

def about(self) -> dict[str, Any]:
"""Query basic information about the server.
Expand All @@ -173,10 +144,8 @@ def read(self, filter: str, checked: bool = True) -> Grid:
"""Read from the database the first record which matches the
[filter](https://project-haystack.org/doc/docHaystack/Filters).
**Errors**
See `checked` parameter details. Also, this method might raise an
`ErrorGridError` or `IncompleteDataError`.
Raises:
UnknownRecError: Server's response does not include requested rec.
Parameters:
filter:
Expand Down Expand Up @@ -206,10 +175,6 @@ def read_all(self, filter: str, limit: int | None = None) -> Grid:
"""Read all records from the database which match the
[filter](https://project-haystack.org/doc/docHaystack/Filters).
**Errors**
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Parameters:
filter:
Project Haystack defined
Expand All @@ -233,10 +198,8 @@ def read_all(self, filter: str, limit: int | None = None) -> Grid:
def read_by_id(self, id: Ref, checked: bool = True) -> Grid:
"""Read an entity record using its unique identifier.
**Errors**
See `checked` parameter details. Also, this method might raise an
`ErrorGridError` or `IncompleteDataError`.
Raises:
UnknownRecError: Server's response does not include requested recs.
Parameters:
id: Unique identifier for the record being read.
Expand Down Expand Up @@ -266,10 +229,8 @@ def read_by_ids(self, ids: list[Ref]) -> Grid:
not be supported by some servers. If your server does not support the batch
read feature, then try using the `Client.read_by_id()` method instead.
**Errors**
Raises an `UnknownRecError` if any of the records cannot be found. Also, this
method might raise an `ErrorGridError` or `IncompleteDataError`.
Raises:
UnknownRecError: Server's response does not include requested recs.
Parameters:
ids: Unique identifiers for the records being read.
Expand All @@ -291,10 +252,9 @@ def read_by_ids(self, ids: list[Ref]) -> Grid:

return response

# TODO: raise exceptions if there are no valid pt ids on HisRead ops?
def his_read(
self,
pt_data: Grid,
pt_recs: Grid,
range: date | DateRange | DateTimeRange,
) -> Grid:
"""Reads history data associated with `ids` within `pt_data` for the given
Expand All @@ -310,12 +270,8 @@ def his_read(
Project Haystack servers may not support reading history data for more than one
point record at a time.
**Errors**
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Parameters:
pt_data:
pt_recs:
A `Grid` that contains a unique point record `id` on each row.
Additional data for the point record is appended as column metadata in
the returned `Grid`.
Expand All @@ -326,17 +282,15 @@ def his_read(
the day after the defined date.
Returns:
`Grid` with history data associated with the `ids` described in `pt_data`
for the given `range`. The return `Grid` contains column metadata defined
in `pt_data`.
History data for the `ids`. Includes metadata from `pt_recs`.
"""

pt_ids = [pt_row["id"] for pt_row in pt_data.rows]
pt_ids = [pt_row["id"] for pt_row in pt_recs.rows]
data = _create_his_read_req_data(pt_ids, range)
response = self._call("hisRead", data)

meta = response.meta | pt_data.meta
cols = merge_pt_data_to_his_grid_cols(response, pt_data)
meta = response.meta | pt_recs.meta
cols = merge_pt_data_to_his_grid_cols(response, pt_recs)
rows = response.rows

return Grid(meta, cols, rows)
Expand All @@ -352,10 +306,6 @@ def his_read_by_id(
considering to use the `Client.his_read()` method to store available
metadata within the returned `Grid`.
**Errors**
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Parameters:
id:
Unique identifier for the point record associated with the requested
Expand Down Expand Up @@ -390,10 +340,6 @@ def his_read_by_ids(
Project Haystack servers may not support reading history data for more than one
point record at a time.
**Errors**
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Parameters:
ids:
Unique identifiers for the point records associated with the requested
Expand Down Expand Up @@ -423,7 +369,7 @@ def his_write_by_id(
History row key names must be `ts` or `val`. Values in the column named `val`
are for the `Ref` described by the `id` parameter.
**Example `his_rows`:
**Example `his_rows`:**
```python
from datetime import datetime, timedelta
Expand All @@ -443,24 +389,13 @@ def his_write_by_id(
]
```
**Errors**
A `HaystackHisWriteOpParametersError` is raised if invalid column names are
used for the `his_rows` parameter.
Also, this method might raise an `ErrorGridError` or `IncompleteDataError`.
**Additional requirements which are not validated by this method**
**Additional requirements**
1. Timestamp and value kind of `his_row` data must match the entity's (Ref)
configured timezone and kind
2. Numeric data must match the entity's (Ref) configured unit or status of
being unitless
**Note:** We are considering to add another method `Client.his_write()` in the
future that would validate these requirements. It would require `pt_data`
similar to `Client.his_read()`.
**Recommendations for enhanced performance**
1. Avoid posting out-of-order or duplicate data
Expand All @@ -473,7 +408,6 @@ def his_write_by_id(
An empty `Grid`.
"""

_validate_his_write_parameters(id, his_rows)
meta = {"id": id}
his_grid = Grid.to_grid(his_rows, meta)
return self._call("hisWrite", his_grid)
Expand Down Expand Up @@ -520,24 +454,13 @@ def his_write_by_ids(
- Column named `v1` corresponds to index 1 of ids, or `Ref("foo1")`
- Column named `v2` corresponds to index 2 of ids, or `Ref("foo2")`
**Errors**
A `HaystackHisWriteOpParametersError` is raised if invalid column names are
used for the `his_rows` parameter.
Also, this method might raise an `ErrorGridError` or `IncompleteDataError`.
**Additional requirements which are not validated by this method**
**Additional requirements**
1. Timestamp and value kind of `his_row` data must match the entity's (Ref)
configured timezone and kind
2. Numeric data must match the entity's (Ref) configured unit or status of
being unitless
**Note:** We are considering to add another method `Client.his_write()` in the
future that would validate these requirements. It would require `pt_data`
similar to `Client.his_read()`.
**Recommendations for enhanced performance**
1. Avoid posting out-of-order or duplicate data
Expand All @@ -557,8 +480,6 @@ def his_write_by_ids(
An empty `Grid`.
"""

_validate_his_write_parameters(ids, his_rows)

meta = {"ver": "3.0"}
cols = [{"name": "ts"}]

Expand All @@ -579,10 +500,6 @@ def point_write(
) -> Grid:
"""Writes to a given level of a writable point's priority array.
**Errors**
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Parameters:
id: Unique identifier of the writable point.
level: Integer from 1 - 17 (17 is default).
Expand Down Expand Up @@ -610,10 +527,6 @@ def point_write(
def point_write_array(self, id: Ref) -> Grid:
"""Reads the current status of a writable point's priority array.
**Errors**
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Parameters:
id: Unique identifier for the record.
Expand All @@ -631,7 +544,11 @@ def _call(
"""Sends a POST request based on given parameters, receives a HTTP
response, and returns JSON data.
This method might raise an `ErrorGridError` or `IncompleteDataError`.
Raises:
CallError:
Error raised by `Client` when server's `Grid` response meta has an
`err` marker tag described
[here](https://project-haystack.org/doc/docHaystack/HttpApi#errorGrid).
"""

headers = {
Expand All @@ -650,39 +567,10 @@ def _call(
return response


# -----------------------------------------------------------------------------
# Misc support functions for Client()
# -----------------------------------------------------------------------------


def _validate_his_write_parameters(
ids: list[Ref] | Ref,
his_rows: list[dict[str, datetime | bool | Number | str]],
):
if isinstance(ids, list):
# order does not matter here
expected_col_names = [f"v{i}" for i in range(len(ids))]
expected_col_names.append("ts")
elif isinstance(ids, Ref):
expected_col_names = ["ts", "val"]

for his_row in his_rows:
for key in his_row.keys():
if key not in expected_col_names:
raise HaystackHisWriteOpParametersError(
f'There is an invalid column name "{key}" in one of the history '
"rows."
)


def _validate_response_meta(response: Grid):

meta = response.meta
if "err" in meta.keys():
raise ErrorGridError(response)

if "incomplete" in meta.keys():
raise IncompleteDataError(response)
raise CallError(response)


def _create_his_read_req_data(
Expand Down
Loading

0 comments on commit bc52193

Please sign in to comment.