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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,3 @@ jobs:
run: hatch test -a
- name: Test build
run: hatch build

3 changes: 3 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ async def main():
facility = (await client.get_facilities())[0] # Get a list of all facilities
print(facility)

# You can see the raw json data corresponding to a datamodel with the .raw attribute.
print(facility.raw)

# Get all components of the facility
example_component = (await facility.get_components())[0]
print(example_component)
Expand Down
11 changes: 10 additions & 1 deletion src/froeling/datamodels/component.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Represents Components and their Parameters."""

from dataclasses import dataclass
from dataclasses import dataclass, field
from http import HTTPStatus
from typing import Any

Expand Down Expand Up @@ -34,6 +34,7 @@ class Component:
time_windows_view (list[TimeWindowDay] | None): Time window data, if fetched.
picture_url (str | None): URL to a representative image of the component.
parameters (list[Parameter]): List of associated parameters.
raw (dict)

"""

Expand All @@ -49,6 +50,8 @@ class Component:

parameters: dict[str, 'Parameter']

raw: dict

def __init__(self, facility_id: int, component_id: str, session: Session):
"""Initialize a Component with minimal identifying information."""
self.facility_id = facility_id
Expand All @@ -58,6 +61,7 @@ def __init__(self, facility_id: int, component_id: str, session: Session):
self.time_windows_view = None
self.picture_url = None
self.parameters = {}
self.raw = {}

@classmethod
def _from_overview_data(cls, facility_id: int, session: Session, obj: dict) -> 'Component | None':
Expand All @@ -72,6 +76,7 @@ def _from_overview_data(cls, facility_id: int, session: Session, obj: dict) -> '
component.standard_name = obj.get('standardName')
component.type = obj.get('type')
component.sub_type = obj.get('subType')
component.raw = obj
return component

def __str__(self) -> str:
Expand All @@ -84,6 +89,7 @@ async def update(self) -> dict[str, 'Parameter']:
'get',
endpoints.COMPONENT.format(self._session.user_id, self.facility_id, self.component_id),
)
self.raw = res
self.component_id = res.get('componentId') # This should not be able to change.
self.display_name = res.get('displayName')
self.display_category = res.get('displayCategory')
Expand Down Expand Up @@ -132,6 +138,8 @@ class Parameter:
max_val: str | None
string_list_key_values: dict[str, str] | None

raw: dict = field(repr=False, default_factory=dict)

@classmethod
def _from_dict(cls, obj: dict, session: Session, facility_id: int) -> 'Parameter':
parameter_id = obj['id']
Expand All @@ -158,6 +166,7 @@ def _from_dict(cls, obj: dict, session: Session, facility_id: int) -> 'Parameter
min_val,
max_val,
string_list_key_values,
obj,
)

@classmethod
Expand Down
4 changes: 3 additions & 1 deletion src/froeling/datamodels/facility.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Dataclasses relating to Facilities."""

from dataclasses import dataclass
from dataclasses import dataclass, field

from froeling import endpoints
from froeling.datamodels.component import Component
Expand Down Expand Up @@ -28,6 +28,7 @@ class Facility:
hours_since_last_maintenance: int | None
operation_hours: int | None
facility_generation: str | None
raw: dict = field(repr=False, default_factory=dict)

@staticmethod
def _from_dict(obj: dict, session: Session) -> 'Facility':
Expand Down Expand Up @@ -84,6 +85,7 @@ def _from_dict(obj: dict, session: Session) -> 'Facility':
hours_since_last_maintenance,
operation_hours,
facility_generation,
obj,
)

@staticmethod
Expand Down
11 changes: 7 additions & 4 deletions src/froeling/datamodels/generics.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Generic datamodels used in multiple places/endpoints."""

from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum


Expand All @@ -20,14 +20,15 @@ class Address:
zip: int | None
city: str | None
country: str | None
raw: dict = field(repr=False, default_factory=dict)

@staticmethod
def _from_dict(obj: dict) -> 'Address':
street = obj.get('street')
zipcode = obj.get('zip')
city = obj.get('city')
country = obj.get('country')
return Address(street, zipcode, city, country)
return Address(street, zipcode, city, country, obj)


class Weekday(Enum):
Expand Down Expand Up @@ -56,14 +57,15 @@ class TimeWindowDay:
id: int
weekday: Weekday
phases: list['TimeWindowPhase']
raw: dict = field(repr=False, default_factory=dict)

@classmethod
def _from_dict(cls, obj: dict) -> 'TimeWindowDay':
_id = obj['id']
weekday = Weekday(obj['weekDay'])
phases = TimeWindowPhase._from_list(obj['phases']) # noqa: SLF001

return cls(_id, weekday, phases)
return cls(_id, weekday, phases, obj)

@classmethod
def _from_list(cls, obj: list) -> list['TimeWindowDay']:
Expand All @@ -86,6 +88,7 @@ class TimeWindowPhase:
start_minute: int
end_hour: int
end_minute: int
raw: dict = field(repr=False, default_factory=dict)

@classmethod
def _from_dict(cls, obj: dict) -> 'TimeWindowPhase':
Expand All @@ -94,7 +97,7 @@ def _from_dict(cls, obj: dict) -> 'TimeWindowPhase':
eh = obj['endHour']
em = obj['endMinute']

return cls(sh, sm, eh, em)
return cls(sh, sm, eh, em, obj)

@classmethod
def _from_list(cls, obj: list) -> list['TimeWindowPhase']:
Expand Down
7 changes: 5 additions & 2 deletions src/froeling/datamodels/notifications.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Datamodels to represent Notifications and related objects."""

import datetime
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -22,6 +22,7 @@ class NotificationOverview:
"""Known Values: "ERROR", "INFO", "WARNING", "ALARM" """
facility_id: int | None
facility_name: str | None
raw: dict

details: 'NotificationDetails'

Expand All @@ -31,7 +32,7 @@ def __init__(self, data: dict, session: 'Session') -> None:
self._set_data(data)

def _set_data(self, data: dict) -> None:
self.data = data
self.raw = data

self.id = data.get('id')
self.subject = data.get('subject')
Expand Down Expand Up @@ -90,6 +91,7 @@ class NotificationSubmissionState:
type: str | None
submitted_to: str | None
submission_result: str | None
raw: dict = field(repr=False, default_factory=dict)

@classmethod
def _from_dict(cls, obj: dict) -> 'NotificationSubmissionState':
Expand All @@ -106,6 +108,7 @@ def _from_dict(cls, obj: dict) -> 'NotificationSubmissionState':
notification_type,
submitted_to,
submission_result,
obj,
)

@classmethod
Expand Down
4 changes: 3 additions & 1 deletion src/froeling/datamodels/userdata.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Datamodels related to the user account."""

from dataclasses import dataclass
from dataclasses import dataclass, field

from froeling.datamodels.generics import Address

Expand All @@ -20,6 +20,7 @@ class UserData:
active: bool | None
picture_url: str | None
facility_count: int | None
raw: dict = field(repr=False, default_factory=dict)

@staticmethod
def _from_dict(obj: dict) -> 'UserData':
Expand Down Expand Up @@ -49,4 +50,5 @@ def _from_dict(obj: dict) -> 'UserData':
active,
picture_url,
facility_count,
obj,
)
9 changes: 6 additions & 3 deletions tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ async def test_facility_get_components(load_json):

async with Froeling(token=token) as api:
f = await api.get_facility(12345)
c = await f.get_components()
assert len(c) == 5
c = c[0]
components = await f.get_components()
assert len(components) == 5
c = components[0]
assert c.raw == component_list_data[0]

assert c.component_id == '1_100'
assert c.display_name == 'some display name'
Expand All @@ -50,7 +51,9 @@ async def test_component_update(load_json):

async with Froeling(token=token) as api:
c = api.get_component(12345, '1_100')
assert c.raw == {}
await c.update()
assert c.raw == component_data
for p in c.parameters.values():
p.display_value
# TODO: Add asserts
Expand Down
17 changes: 16 additions & 1 deletion tests/test_facility.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ async def test_get_facility(load_json):
assert len(f) == 2
f1, f2 = f

assert f1.raw == facility_data[0] # order should be the same, but I wouldn't call it a requirement
assert f2.raw == facility_data[1]

assert f1.facility_id == 12345
assert f1.equipment_number == 100321123
assert f1.status == 'OK'
Expand Down Expand Up @@ -82,6 +85,9 @@ async def test_get_facility_modified(load_json):
f = await api.get_facilities()
assert len(f) == 3
f1, f2, f3 = f
assert f1.raw == facility_data[0]
assert f2.raw == facility_data[1]
assert f3.raw == facility_data[2]

assert f1.facility_id == 12345
assert f1.equipment_number is None
Expand Down Expand Up @@ -218,6 +224,9 @@ async def test_facility_get_components(load_json, component_id, expected):

comp = next(c for c in components if c.component_id == component_id)

api_data = next(d for d in component_list_data if d["componentId"] == component_id)
assert comp.raw == api_data

for field, value in expected.items():
assert getattr(comp, field) == value
assert comp.time_windows_view is None
Expand Down Expand Up @@ -247,6 +256,12 @@ async def test_facility_get_component(load_json):

async with Froeling(token=token) as api:
f = await api.get_facility(12345)
assert f.raw == facility_data[0]
c = f.get_component('1_100')
assert c.raw == {}
c2 = api.get_component(12345, '1_100')
assert c.component_id == c2.component_id
assert c2.raw == {}
await c.update()
assert c.raw == component_data
await c2.update()
assert c2.raw == component_data
5 changes: 4 additions & 1 deletion tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ async def test_login_success(load_json):

async with Froeling(username='joe', password='pwd') as api:
userdata = await api.get_userdata() # should be cached. No new requests
assert userdata.raw == login_data # This includes the token.

assert api.token == token
assert api.user_id == 1234

Expand Down Expand Up @@ -80,6 +82,7 @@ async def test_request_auto_reauth(load_json):
auto_reauth=True,
token_callback=mock_token_callback,
) as api:
await api.get_userdata()
d = await api.get_userdata()
assert d.raw == user_data

mock_token_callback.assert_called_once_with(new_token)
5 changes: 5 additions & 0 deletions tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ async def test_get_notifications(load_json):
notifications = await api.get_notifications()
assert len(notifications) == 3
for i, n in enumerate(notifications):
assert n.raw == notification_list_data[i]

assert n.id == (i + 1) * 10000000 + 123456
assert n.subject == f'Subject {i + 1}'
assert n.unread == (False, True, None)[i]
Expand Down Expand Up @@ -93,6 +95,7 @@ async def test_get_notification_info(load_json):
assert notification_details == notification_details_2

d = notification_details
assert d.raw == notification_data
assert d.id == 10123456
assert d.subject == 'Subject 1'
assert d.body == 'Title\r\ntext'
Expand All @@ -108,13 +111,15 @@ async def test_get_notification_info(load_json):
s1, s2 = d.notification_submission_state_dto

assert isinstance(s1, NotificationSubmissionState)
assert s1.raw == notification_data["notificationSubmissionStateDto"][0]
assert s1.id == 12345678
assert s1.recipient == 'joe@example.com'
assert s1.type == 'EMAIL'
assert s1.submitted_to == 'joe@example.com'
assert s1.submission_result == 'SUCCESS'

assert isinstance(s2, NotificationSubmissionState)
assert s2.raw == notification_data["notificationSubmissionStateDto"][1]
assert s2.id == 12345679
assert s2.recipient == 'sometoken'
assert s2.type == 'TOKEN'
Expand Down