Skip to content

Commit

Permalink
Fixing model constructions for pydantic + formatting/import organizin…
Browse files Browse the repository at this point in the history
…g + flake8 config + bump for next release
  • Loading branch information
ohshazbot committed Apr 25, 2024
1 parent be0ba08 commit 88af936
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 61 deletions.
9 changes: 9 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[flake8]
max-line-length = 99
exclude =
.git,
.tox,
build,
dist,
.mypy_cache,
.pytest_cache,
17 changes: 10 additions & 7 deletions onboard/client/client.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import urllib.parse
from urllib3.util.retry import Retry
from datetime import datetime
from typing import List, Dict, Any, Optional, Tuple, Union, Iterator

import deprecation
from orjson import loads
from typing import List, Dict, Any, Optional, Tuple, Union, Iterator
from .util import divide_chunks, json
from urllib3.util.retry import Retry

from .exceptions import OnboardApiException
from .helpers import ClientBase
from .models import PointSelector, PointDataUpdate, IngestStats, \
TimeseriesQuery, PointData
from .helpers import ClientBase
from .exceptions import OnboardApiException
from .util import divide_chunks, json


class APIClient(ClientBase):
Expand Down Expand Up @@ -82,6 +84,7 @@ def check_data_availability(self,
selector: PointSelector
) -> Tuple[Optional[datetime], Optional[datetime]]:
"""Returns a tuple of data timestamps (most stale, most recent) for selected points"""

@json
def get_as_json():
return self.post('/points/data-availability', json=selector.json())
Expand Down Expand Up @@ -130,8 +133,7 @@ def get_points(url):
return points

# Deprecated
def get_points_by_datasource(self, datasource_hashes: List[str]) \
-> List[Dict[str, str]]:
def get_points_by_datasource(self, datasource_hashes: List[str]) -> List[Dict[str, str]]:
datasource_hashes_chunked = list(divide_chunks(datasource_hashes, 125))

@json
Expand Down Expand Up @@ -193,6 +195,7 @@ def stream_point_timeseries(self, query: TimeseriesQuery) -> Iterator[PointData]
def query_call():
return self.post('/query-v2', json=query.json(), stream=True,
headers={'Accept': 'application/x-ndjson'})

query_call.raw_response = True # type: ignore[attr-defined]

try:
Expand Down
4 changes: 3 additions & 1 deletion onboard/client/dataframes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pandas as pd
from typing import Iterable, Dict, List, Union

import pandas as pd

from onboard.client.models import PointData


Expand Down
4 changes: 3 additions & 1 deletion onboard/client/helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import datetime
from typing import Optional, Union, Any

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from typing import Optional, Union, Any

from .exceptions import OnboardApiException
from .util import json

Expand Down
13 changes: 5 additions & 8 deletions onboard/client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from datetime import datetime, timezone
from typing import List, Optional, Union, Dict

from pydantic import field_validator, BaseModel, ConfigDict
from pydantic.dataclasses import dataclass
from pydantic import field_validator, ConfigDict, BaseModel


class PointDataUpdate(object):
Expand Down Expand Up @@ -62,8 +61,7 @@ def json(self):
}


@dataclass
class PointSelector:
class PointSelector(BaseModel):
"""A flexible interface to allow users to select sets of points"""
# id, name, short_name or name_abbr
orgs: List[Union[int, str]] = field(default_factory=list)
Expand Down Expand Up @@ -106,7 +104,6 @@ def from_json(dict):
return ps


@dataclass
class TimeseriesQuery(BaseModel):
"""Parameters needed to fetch timeseries data.
Expand All @@ -132,7 +129,7 @@ class TimeseriesQuery(BaseModel):
@field_validator('point_ids')
def points_or_selector_required(cls, point_ids, values):
has_points = len(point_ids) > 0
has_selector = values.get('selector') is not None
has_selector = values.data.get('selector') is not None
if has_points == has_selector:
raise ValueError("Exactly one of 'point_ids' or 'selector' is required")
return point_ids
Expand All @@ -153,11 +150,11 @@ def json(self):
}


@dataclass
class PointData(BaseModel):
model_config = ConfigDict(extra='allow')

point_id: int
raw: str
unit: str
columns: List[str]
values: List[List[Union[str, float, int, None]]]
model_config = ConfigDict(extra='allow', )
5 changes: 4 additions & 1 deletion onboard/client/staging.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from urllib3.util.retry import Retry
from typing import List, Dict, Any, Optional

from urllib3.util.retry import Retry

from .helpers import ClientBase
from .util import json

Expand Down Expand Up @@ -39,6 +41,7 @@ def get_equipment_and_points(self, building_id: int) -> Dict:

def get_staged_equipment_csv(self, building_id: int) -> str:
"""Fetch staged equipment and points together in tabular form"""

@json
def get_csv():
return self.get(f'/staging/{building_id}',
Expand Down
6 changes: 5 additions & 1 deletion onboard/client/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import List, Iterable, TypeVar, Callable

import requests

from .exceptions import OnboardApiException, OnboardTemporaryException
from typing import List, Iterable, TypeVar, Callable

T = TypeVar('T')

Expand All @@ -13,6 +15,7 @@ def divide_chunks(input_list: List[T], n: int) -> Iterable[List[T]]:

def json(func: Callable[..., T]) -> Callable[..., T]:
"""Decorator for making sure requests responses are handled consistently"""

# the type annotations on json are a lie to let us type the methods in client
# with approximate descriptions of the JSON they return, even though the methods
# as implemented return requests.Response objects
Expand Down Expand Up @@ -40,4 +43,5 @@ def wrapper(*args, **kwargs):
raise e
except Exception as e:
raise OnboardApiException(e)

return wrapper
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
long_description = f.read()

setup(name='onboard.client',
version='1.10.1',
version='1.10.2',
author='Nathan Merritt, John Vines',
author_email='[email protected]',
description='Onboard API SDK',
Expand Down
70 changes: 29 additions & 41 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,51 @@
# type: ignore

from datetime import datetime, timezone
from typing import Dict, Any

from onboard.client.models import TimeseriesQuery, PointData


def test_timeseries_query():
dict = {
'point_ids': [1],
'start': datetime.utcnow().replace(tzinfo=timezone.utc),
'end': datetime.utcnow().replace(tzinfo=timezone.utc),
}
TimeseriesQuery.model_construct(**dict)


def construct(dict: Dict[str, Any]) -> PointData:
# Pydantic v2
return PointData.model_construct(**dict) # type: ignore[attr-defined]
return TimeseriesQuery(
point_ids=[1],
start=datetime.utcnow().replace(tzinfo=timezone.utc),
end=datetime.utcnow().replace(tzinfo=timezone.utc),
)


def test_point_data():
dict = {
'point_id': 1,
'raw': 'F',
'unit': 'C',
'columns': ['timestamp', 'raw', 'C'],
'values': [
PointData(
point_id=1,
raw='F',
unit='C',
columns=['timestamp', 'raw', 'C'],
values=[
['2020-12-16', 32.0, 0.0],
]
}
construct(dict)
)


def test_point_data_none_value():
dict = {
'point_id': 1,
'raw': 'F',
'unit': 'C',
'columns': ['timestamp', 'raw', 'C'],
'values': [
PointData(
point_id=1,
raw='F',
unit='C',
columns=['timestamp', 'raw', 'C'],
values=[
['2020-12-16', None, 0.0],
]
}
construct(dict)
)


def test_point_data_extra_keys():
dict = {
'point_id': 1,
'raw': 'F',
'unit': 'C',
'columns': ['timestamp', 'raw', 'C'],
'values': [
constructed = PointData(
point_id=1,
raw='F',
unit='C',
columns=['timestamp', 'raw', 'C'],
values=[
['2020-12-16', 32.0, 0.0],
],
'foo': 'bar',
'zip': {'zap': 1},
}
constructed = construct(dict)
assert constructed.foo == 'bar'
foo='bar',
zip={'zap': 1},
)
assert constructed.foo == 'bar' # type: ignore[attr-defined]
assert constructed.point_id == 1

0 comments on commit 88af936

Please sign in to comment.