diff --git a/src/pylibrelinkup/models/config.py b/src/pylibrelinkup/models/config.py index c46e92a..653aa9c 100644 --- a/src/pylibrelinkup/models/config.py +++ b/src/pylibrelinkup/models/config.py @@ -21,8 +21,8 @@ class AlarmRules(BaseModel): f: F l: L nd: Nd - p: int - r: int + p: int = Field(default=0) + r: int = Field(default=0) std: Std @@ -34,5 +34,5 @@ class FixedLowAlarmValues(BaseModel): from_attributes=True, ) - mgdl: int - mmoll: float + mgdl: int = Field(default=0) + mmoll: float = Field(default=0.0) diff --git a/src/pylibrelinkup/models/connection.py b/src/pylibrelinkup/models/connection.py index dcd034d..20835a0 100644 --- a/src/pylibrelinkup/models/connection.py +++ b/src/pylibrelinkup/models/connection.py @@ -23,20 +23,20 @@ class Connection(BaseModel): id: UUID patient_id: UUID - country: str - status: int - first_name: str - last_name: str - target_low: int - target_high: int - uom: int + country: str = Field(default="") + status: int = Field(default=0) + first_name: str = Field(default="") + last_name: str = Field(default="") + target_low: int = Field(default=0) + target_high: int = Field(default=0) + uom: int = Field(default=0) sensor: Sensor alarm_rules: AlarmRules glucose_measurement: GlucoseMeasurement glucose_item: GlucoseMeasurement glucose_alarm: None patient_device: PatientDevice - created: int + created: int = Field(default=0) class Data(BaseModel): @@ -64,9 +64,9 @@ class Ticket(BaseModel): from_attributes=True, ) - token: str - expires: int - duration: int + token: str = Field(default="") + expires: int = Field(default=0) + duration: int = Field(default=0) class GraphResponse(BaseModel): @@ -79,7 +79,7 @@ class GraphResponse(BaseModel): from_attributes=True, ) - status: int + status: int = Field(default=0) data: Data ticket: Ticket diff --git a/src/pylibrelinkup/models/data.py b/src/pylibrelinkup/models/data.py index 87434c5..dfe0b1c 100644 --- a/src/pylibrelinkup/models/data.py +++ b/src/pylibrelinkup/models/data.py @@ -61,11 +61,11 @@ class GlucoseMeasurement(BaseModel): factory_timestamp: datetime = Field(None) timestamp: datetime = Field(None) - type: int - value_in_mg_per_dl: float - measurement_color: int - glucose_units: int - value: float + type: int = Field(default=0) + value_in_mg_per_dl: float = Field(default=0.0) + measurement_color: int = Field(default=0) + glucose_units: int = Field(default=0) + value: float = Field(default=0.0) is_high: bool = Field(alias="isHigh") is_low: bool = Field(alias="isLow") @@ -104,11 +104,11 @@ class F(BaseModel): from_attributes=True, ) - th: int - thmm: float - d: int - tl: int - tlmm: float + th: int = Field(default=0) + thmm: float = Field(default=0.0) + d: int = Field(default=0) + tl: int = Field(default=0) + tlmm: float = Field(default=0.0) class L(BaseModel): @@ -118,11 +118,12 @@ class L(BaseModel): from_attributes=True, ) - th: int - thmm: float - d: int - tl: int - tlmm: float + +th: int = Field(default=0) +thmm: float = Field(default=0.0) +d: int = Field(default=0) +tl: int = Field(default=0) +tlmm: float = Field(default=0.0) class H(BaseModel): @@ -132,10 +133,11 @@ class H(BaseModel): from_attributes=True, ) - th: int - thmm: float - d: int - f: float + +th: int = Field(default=0) +thmm: float = Field(default=0.0) +d: int = Field(default=0) +f: float = Field(default=0.0) class Nd(BaseModel): @@ -145,9 +147,10 @@ class Nd(BaseModel): from_attributes=True, ) - i: int - r: int - l: int + +i: int = Field(default=0) +r: int = Field(default=0) +l: int = Field(default=0) class Std(BaseModel): @@ -157,4 +160,4 @@ class Std(BaseModel): model_config = ConfigDict( from_attributes=True, ) - sd: bool | None = None + sd: bool | None = Field(default=None) diff --git a/src/pylibrelinkup/models/hardware.py b/src/pylibrelinkup/models/hardware.py index 4c7d4a7..029e7fa 100644 --- a/src/pylibrelinkup/models/hardware.py +++ b/src/pylibrelinkup/models/hardware.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from pydantic.alias_generators import to_camel from .config import FixedLowAlarmValues @@ -16,11 +16,12 @@ class Sensor(BaseModel): from_attributes=True, ) - device_id: str - sn: str - a: int - w: int - pt: int + +device_id: str = Field(default="") +sn: str = Field(default="") +a: int = Field(default=0) +w: int = Field(default=0) +pt: int = Field(default=0) class PatientDevice(BaseModel): @@ -33,14 +34,15 @@ class PatientDevice(BaseModel): from_attributes=True, ) - did: str - dtid: int - v: str - ll: int - hl: int - u: int - fixed_low_alarm_values: FixedLowAlarmValues - alarms: bool + +did: str = Field(default="") +dtid: int = Field(default=0) +v: str = Field(default="") +ll: int = Field(default=0) +hl: int = Field(default=0) +u: int = Field(default=0) +fixed_low_alarm_values: FixedLowAlarmValues +alarms: bool = Field(default=False) class ActiveSensor(BaseModel): @@ -53,5 +55,6 @@ class ActiveSensor(BaseModel): from_attributes=True, ) - sensor: Sensor - device: PatientDevice + +sensor: Sensor +device: PatientDevice diff --git a/src/pylibrelinkup/models/login.py b/src/pylibrelinkup/models/login.py index 241d966..175a191 100644 --- a/src/pylibrelinkup/models/login.py +++ b/src/pylibrelinkup/models/login.py @@ -1,6 +1,6 @@ from typing import List -from pydantic import BaseModel +from pydantic import BaseModel, Field __all__ = [ "Llu", @@ -22,8 +22,8 @@ class Llu(BaseModel): - policyAccept: int - touAccept: int + policyAccept: int = Field(default=0) + touAccept: int = Field(default=0) class Consents(BaseModel): @@ -31,12 +31,12 @@ class Consents(BaseModel): class SystemMessages(BaseModel): - firstUsePhoenix: int - firstUsePhoenixReportsDataMerged: int - lluGettingStartedBanner: int - lluNewFeatureModal: int - lluOnboarding: int - lvWebPostRelease: str + firstUsePhoenix: int = Field(default=0) + firstUsePhoenixReportsDataMerged: int = Field(default=0) + lluGettingStartedBanner: int = Field(default=0) + lluNewFeatureModal: int = Field(default=0) + lluOnboarding: int = Field(default=0) + lvWebPostRelease: str = Field(default="") class System(BaseModel): @@ -44,17 +44,17 @@ class System(BaseModel): class User(BaseModel): - id: str - firstName: str - lastName: str - email: str - country: str - uiLanguage: str - communicationLanguage: str - accountType: str - uom: str - dateFormat: str - timeFormat: str + id: str = Field(default="") + firstName: str = Field(default="") + lastName: str = Field(default="") + email: str = Field(default="") + country: str = Field(default="") + uiLanguage: str = Field(default="") + communicationLanguage: str = Field(default="") + accountType: str = Field(default="") + uom: str = Field(default="") + dateFormat: str = Field(default="") + timeFormat: str = Field(default="") emailDay: List[int] system: System details: dict @@ -68,17 +68,17 @@ class User(BaseModel): class Notifications(BaseModel): - unresolved: int + unresolved: int = Field(default=0) class DataMessages(BaseModel): - unread: int + unread: int = Field(default=0) class AuthTicket(BaseModel): - token: str - expires: int - duration: int + token: str = Field(default="") + expires: int = Field(default=0) + duration: int = Field(default=0) class Data(BaseModel): @@ -90,29 +90,29 @@ class Data(BaseModel): class LoginResponse(BaseModel): - status: int + status: int = Field(default=0) data: Data class ErrorMessage(BaseModel): - message: str + message: str = Field(default="") class LoginResponseUnauthenticated(BaseModel): - status: int + status: int = Field(default=0) error: ErrorMessage class LoginRedirectData(BaseModel): - redirect: bool - region: str + redirect: bool = Field(default=False) + region: str = Field(default="") class LoginRedirectResponse(BaseModel): - status: int + status: int = Field(default=0) data: LoginRedirectData class LoginArgs(BaseModel): - email: str = "" - password: str = "" + email: str = Field(default="") + password: str = Field(default="") diff --git a/tests/conftest.py b/tests/conftest.py index 5093297..3c371a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,6 +38,11 @@ def graph_response_no_alarm_rules_c_json(get_response_json): return get_response_json("graph_response_no_alarm_rules_c.json") +@pytest.fixture +def graph_response_no_u_json(get_response_json): + return get_response_json("graph_response_no_u.json") + + @dataclass class PyLibreLinkUpClientFixture: client: PyLibreLinkUp diff --git a/tests/data/graph_response_no_u.json b/tests/data/graph_response_no_u.json new file mode 100644 index 0000000..36486c6 --- /dev/null +++ b/tests/data/graph_response_no_u.json @@ -0,0 +1,256 @@ +{ + "status": 0, + "data": { + "connection": { + "id": "53a2a8d5-3864-4519-af47-496becf9a17a", + "patientId": "9dfd0d64-4863-4775-9438-712fa68787df", + "country": "DE", + "status": 2, + "firstName": "John", + "lastName": "Doe", + "targetLow": 70, + "targetHigh": 130, + "uom": 1, + "sensor": { + "deviceId": "", + "sn": "XXXXXXXXXX", + "a": 1652400270, + "w": 60, + "pt": 4 + }, + "alarmRules": { + "c": true, + "h": { + "on": true, + "th": 130, + "thmm": 7.2, + "d": 1440, + "f": 0.1 + }, + "f": { + "th": 55, + "thmm": 3, + "d": 30, + "tl": 10, + "tlmm": 0.6 + }, + "l": { + "on": true, + "th": 70, + "thmm": 3.9, + "d": 1440, + "tl": 10, + "tlmm": 0.6 + }, + "nd": { + "i": 20, + "r": 5, + "l": 6 + }, + "p": 5, + "r": 5, + "std": {} + }, + "glucoseMeasurement": { + "FactoryTimestamp": "5/21/2022 1:38:50 PM", + "Timestamp": "5/21/2022 3:38:50 PM", + "type": 1, + "ValueInMgPerDl": 91, + "TrendArrow": 3, + "TrendMessage": null, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 91, + "isHigh": false, + "isLow": false + }, + "glucoseItem": { + "FactoryTimestamp": "5/21/2022 1:38:50 PM", + "Timestamp": "5/21/2022 3:38:50 PM", + "type": 1, + "ValueInMgPerDl": 91, + "TrendArrow": 3, + "TrendMessage": null, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 91, + "isHigh": false, + "isLow": false + }, + "glucoseAlarm": null, + "patientDevice": { + "did": "xxxxxxx", + "dtid": 40068, + "v": "3.3.1", + "ll": 65, + "hl": 130, + "fixedLowAlarmValues": { + "mgdl": 60, + "mmoll": 3.3 + }, + "alarms": false + }, + "created": 1652399545 + }, + "activeSensors": [ + { + "sensor": { + "deviceId": "xxxxxx", + "sn": "xxxxx", + "a": 1652400270, + "w": 60, + "pt": 4 + }, + "device": { + "did": "xxxxxx", + "dtid": 40068, + "v": "3.3.1", + "ll": 65, + "hl": 130, + "fixedLowAlarmValues": { + "mgdl": 60, + "mmoll": 3.3 + }, + "alarms": false + } + }, + { + "sensor": { + "deviceId": "xxxxx", + "sn": "xxxxxx", + "a": 1652399154, + "w": 60, + "pt": 4 + }, + "device": { + "did": "xxxxxxxxx", + "dtid": 40068, + "v": "3.3.1", + "ll": 70, + "hl": 250, + "fixedLowAlarmValues": { + "mgdl": 60, + "mmoll": 3.3 + }, + "alarms": false + } + }, + { + "sensor": { + "deviceId": "xxxxx", + "sn": "xxxxx", + "a": 1652391830, + "w": 60, + "pt": 4 + }, + "device": { + "did": "xxxxx", + "dtid": 40068, + "v": "3.3.1", + "ll": 70, + "hl": 250, + "fixedLowAlarmValues": { + "mgdl": 60, + "mmoll": 3.3 + }, + "alarms": false + } + } + ], + "graphData": [ + { + "FactoryTimestamp": "5/21/2022 1:39:50 AM", + "Timestamp": "5/21/2022 3:39:50 AM", + "type": 0, + "ValueInMgPerDl": 117, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 117, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 1:44:51 AM", + "Timestamp": "5/21/2022 3:44:51 AM", + "type": 0, + "ValueInMgPerDl": 115, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 115, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 1:49:50 AM", + "Timestamp": "5/21/2022 3:49:50 AM", + "type": 0, + "ValueInMgPerDl": 115, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 115, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 1:54:51 AM", + "Timestamp": "5/21/2022 3:54:51 AM", + "type": 0, + "ValueInMgPerDl": 116, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 116, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 1:59:50 AM", + "Timestamp": "5/21/2022 3:59:50 AM", + "type": 0, + "ValueInMgPerDl": 116, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 116, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 2:04:50 AM", + "Timestamp": "5/21/2022 4:04:50 AM", + "type": 0, + "ValueInMgPerDl": 118, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 118, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 2:09:50 AM", + "Timestamp": "5/21/2022 4:09:50 AM", + "type": 0, + "ValueInMgPerDl": 118, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 118, + "isHigh": false, + "isLow": false + }, + { + "FactoryTimestamp": "5/21/2022 2:14:51 AM", + "Timestamp": "5/21/2022 4:14:51 AM", + "type": 0, + "ValueInMgPerDl": 115, + "MeasurementColor": 1, + "GlucoseUnits": 1, + "Value": 115, + "isHigh": false, + "isLow": false + } + ] + }, + "ticket": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjZlZGFjNDk2LWQyNGUtMTFlYy04ZTVkLTAyNDJhYzExMDAwMiIsImZpcnN0TmFtZSI6IkhhbGltZSBTZWxjdWsiLCJsYXN0TmFtZSI6Iktla2VjIiwiY291bnRyeSI6IkRFIiwicmVnaW9uIjoiZXUiLCJyb2xlIjoicGF0aWVudCIsInVuaXRzIjoxLCJwcmFjdGljZXMiOltdLCJjIjoxLCJzIjoibGx1LmFuZHJvaWQiLCJleHAiOjE2Njg2OTIzNTl9.LK8Ejr2IDKGM7oiObVYMHC8HV2bPcv6obt7UiEFXXXX", + "expires": 1668692359, + "duration": 15552000000 + } +} \ No newline at end of file diff --git a/tests/test_client_graph.py b/tests/test_client_graph.py index 83415e4..5bdf6cd 100644 --- a/tests/test_client_graph.py +++ b/tests/test_client_graph.py @@ -3,7 +3,7 @@ import pytest import responses -from pylibrelinkup import AuthenticationError, GraphResponse +from pylibrelinkup import AuthenticationError from pylibrelinkup.models.data import GlucoseMeasurement from tests.conftest import graph_response_json from tests.factories import PatientFactory @@ -163,3 +163,28 @@ def test_graph_response_no_alarm_rules_c_returns_graph_response( "ValueInMgPerDl" ] ) + + +def test_graph_response_no_u_returns_graph_response( + mocked_responses, graph_response_no_u_json, pylibrelinkup_client +): + """Test that the read method returns GraphResponse when no alarm_rules.c key is present in llu api response data.""" + patient_id = UUID("12345678-1234-5678-1234-567812345678") + + mocked_responses.add( + responses.GET, + f"{pylibrelinkup_client.api_url.value}/llu/connections/{patient_id}/graph", + json=graph_response_no_u_json, + status=200, + ) + + pylibrelinkup_client.client.token = "not_a_token" + + result = pylibrelinkup_client.client.graph(patient_id) + + assert isinstance(result, list) + assert all([isinstance(measurement, GlucoseMeasurement) for measurement in result]) + assert ( + result[0].value_in_mg_per_dl + == graph_response_no_u_json["data"]["graphData"][0]["ValueInMgPerDl"] + )