Skip to content

Commit fced7b4

Browse files
Minor refactor to OR client to properly support realm users
1 parent a3c95dc commit fced7b4

File tree

7 files changed

+121
-131
lines changed

7 files changed

+121
-131
lines changed

frontend/src/services/api-service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function getBaseUrl(realm: string): string {
2323
return ML_SERVICE_URL + '/api/' + realm;
2424
}
2525

26-
function getOpenRemoteBaseUrl(realm: string): string {
26+
function getOpenRemoteBaseUrl(realm: string = AuthService.realm): string {
2727
return ML_SERVICE_URL + '/proxy/openremote/' + realm;
2828
}
2929

@@ -125,6 +125,7 @@ export const APIService = {
125125
* @returns The realm config
126126
*/
127127
async getOpenRemoteRealmConfig(realm: string): Promise<RealmConfig> {
128+
// Explicitly pass in the realm
128129
const response = await fetch(getOpenRemoteBaseUrl(realm) + '/realm/config', {
129130
method: 'GET',
130131
headers: buildHeaders()
@@ -140,7 +141,7 @@ export const APIService = {
140141
* @returns The list of accessible realms
141142
*/
142143
async getAccessibleRealms(): Promise<BasicRealm[]> {
143-
const response = await fetch(getOpenRemoteBaseUrl(AuthService.realm) + '/realm/accessible', {
144+
const response = await fetch(getOpenRemoteBaseUrl() + '/realm/accessible', {
144145
method: 'GET',
145146
headers: buildHeaders()
146147
});
@@ -156,7 +157,7 @@ export const APIService = {
156157
* @returns The list of assets
157158
*/
158159
async getOpenRemoteAssets(realm: string): Promise<BasicAsset[]> {
159-
const response = await fetch(getOpenRemoteBaseUrl(realm) + '/assets', {
160+
const response = await fetch(getOpenRemoteBaseUrl() + '/assets?realm_query=' + realm, {
160161
method: 'GET',
161162
headers: buildHeaders()
162163
});
@@ -173,7 +174,7 @@ export const APIService = {
173174
* @returns The list of assets
174175
*/
175176
async getOpenRemoteAssetsById(realm: string, ids: string[]): Promise<BasicAsset[]> {
176-
const response = await fetch(getOpenRemoteBaseUrl(realm) + '/assets/ids?ids=' + ids.join(','), {
177+
const response = await fetch(getOpenRemoteBaseUrl() + '/assets/ids?realm_query=' + realm + '&ids=' + ids.join(','), {
177178
method: 'GET',
178179
headers: buildHeaders()
179180
});

src/service_ml_forecast/api/openremote_proxy_route.py

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,33 +27,38 @@
2727
from fastapi import APIRouter, Depends, HTTPException, Query
2828

2929
from service_ml_forecast.clients.openremote.models import Asset, BasicRealm, RealmConfig
30-
from service_ml_forecast.clients.openremote_proxy_client import OpenRemoteProxyClient
30+
from service_ml_forecast.clients.openremote.openremote_proxy_client import OpenRemoteProxyClient
3131
from service_ml_forecast.config import ENV
3232
from service_ml_forecast.dependencies import oauth2_scheme
33-
from service_ml_forecast.services.openremote_service import OpenRemoteService
3433

3534
router = APIRouter(prefix="/proxy/openremote/{realm}", tags=["OpenRemote Proxy API"])
3635

3736

3837
@router.get(
3938
"/assets",
40-
summary="Retrieve assets from an OpenRemote realm that store datapoints",
39+
summary="Retrieve assets that store datapoints",
4140
responses={
4241
HTTPStatus.OK: {"description": "Assets have been retrieved"},
4342
HTTPStatus.UNAUTHORIZED: {"description": "Unauthorized"},
4443
},
4544
)
46-
async def get_assets(
45+
async def get_assets_with_historical_data(
4746
token: Annotated[str, Depends(oauth2_scheme)],
4847
realm: str,
48+
realm_query: str = Query(..., alias="realm_query", description="The realm used for the asset query"),
4949
) -> list[Asset]:
50-
or_proxy_service = _build_proxy_service(token)
51-
return or_proxy_service.get_assets_with_historical_datapoints(realm)
50+
proxy_client = _build_proxy_client(token)
51+
assets = proxy_client.get_assets_with_historical_data(realm_query, realm)
52+
53+
if assets is None:
54+
return []
55+
56+
return assets
5257

5358

5459
@router.get(
5560
"/assets/ids",
56-
summary="Retrieve assets from an OpenRemote realm by a comma-separated list of Asset IDs",
61+
summary="Retrieve assets by a comma-separated list of Asset IDs",
5762
responses={
5863
HTTPStatus.OK: {"description": "Assets have been retrieved"},
5964
HTTPStatus.UNAUTHORIZED: {"description": "Unauthorized"},
@@ -62,17 +67,23 @@ async def get_assets(
6267
async def get_assets_by_ids(
6368
token: Annotated[str, Depends(oauth2_scheme)],
6469
realm: str,
70+
realm_query: str = Query(..., alias="realm_query", description="The realm used for the asset query"),
6571
ids_str: str = Query(..., alias="ids", description="Comma-separated list of asset IDs"),
6672
) -> list[Asset]:
6773
ids_list = [asset_id.strip() for asset_id in ids_str.split(",") if asset_id.strip()]
6874

69-
or_proxy_service = _build_proxy_service(token)
70-
return or_proxy_service.get_assets_by_ids(realm, ids_list)
75+
proxy_client = _build_proxy_client(token)
76+
assets = proxy_client.get_assets_by_ids(ids_list, realm_query, realm)
77+
78+
if assets is None:
79+
return []
80+
81+
return assets
7182

7283

7384
@router.get(
7485
"/realm/config",
75-
summary="Retrieve the realm configuration of an OpenRemote realm",
86+
summary="Retrieve realm configuration",
7687
responses={
7788
HTTPStatus.OK: {"description": "Realm configuration has been retrieved"},
7889
HTTPStatus.NOT_FOUND: {"description": "Realm configuration not found"},
@@ -83,18 +94,23 @@ async def get_realm_config(
8394
token: Annotated[str, Depends(oauth2_scheme)],
8495
realm: str,
8596
) -> RealmConfig:
86-
or_proxy_service = _build_proxy_service(token)
87-
config = or_proxy_service.get_realm_config(realm)
97+
proxy_client = _build_proxy_client(token)
98+
config = proxy_client.get_manager_config(realm)
99+
100+
if config is None or config.realms is None:
101+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Configuration not found")
102+
103+
realm_config = config.realms[realm]
88104

89-
if config is None:
105+
if realm_config is None:
90106
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Realm configuration not found")
91107

92-
return config
108+
return realm_config
93109

94110

95111
@router.get(
96112
"/realm/accessible",
97-
summary="Retrieve accessible realms",
113+
summary="Retrieve accessible realms for the current user",
98114
responses={
99115
HTTPStatus.OK: {"description": "Accessible realms have been retrieved"},
100116
HTTPStatus.UNAUTHORIZED: {"description": "Unauthorized"},
@@ -105,23 +121,22 @@ async def get_accessible_realms(
105121
token: Annotated[str, Depends(oauth2_scheme)],
106122
realm: str,
107123
) -> list[BasicRealm]:
108-
or_proxy_service = _build_proxy_service(token)
109-
realms = or_proxy_service.get_accessible_realms(realm)
124+
proxy_client = _build_proxy_client(token)
125+
realms = proxy_client.get_accessible_realms(realm)
110126

111127
if realms is None:
112128
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Unable to retrieve realms")
113129

114130
return realms
115131

116132

117-
def _build_proxy_service(token: str) -> OpenRemoteService:
118-
"""Build the OpenRemote service that proxies the request with the given token.
133+
def _build_proxy_client(token: str) -> OpenRemoteProxyClient:
134+
"""Build the OpenRemote proxy client with the given token.
119135
120136
Args:
121137
token: The token to use for the OpenRemote service.
122138
123139
Returns:
124140
The OpenRemote service with the proxy client.
125141
"""
126-
proxy_client = OpenRemoteProxyClient(openremote_url=ENV.ML_OR_URL, token=token)
127-
return OpenRemoteService(client=proxy_client)
142+
return OpenRemoteProxyClient(openremote_url=ENV.ML_OR_URL, token=token)

src/service_ml_forecast/clients/openremote/openremote_client.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
Realm,
3434
)
3535

36+
MASTER_REALM = "master"
37+
3638

3739
class OAuthTokenResponse(BaseModel):
3840
"""Response model for OpenRemote OAuth token."""
@@ -148,7 +150,9 @@ def health_check(self) -> bool:
148150
self.logger.error(f"OpenRemote API is not healthy: {e}")
149151
return False
150152

151-
def retrieve_asset_datapoint_period(self, asset_id: str, attribute_name: str) -> AssetDatapointPeriod | None:
153+
def get_asset_datapoint_period(
154+
self, asset_id: str, attribute_name: str, realm: str = MASTER_REALM
155+
) -> AssetDatapointPeriod | None:
152156
"""Retrieve the datapoints timestamp period of a given asset attribute.
153157
154158
Args:
@@ -160,7 +164,7 @@ def retrieve_asset_datapoint_period(self, asset_id: str, attribute_name: str) ->
160164
"""
161165

162166
query = f"?assetId={asset_id}&attributeName={attribute_name}"
163-
url = f"{self.openremote_url}/api/master/asset/datapoint/periods{query}"
167+
url = f"{self.openremote_url}/api/{realm}/asset/datapoint/periods{query}"
164168

165169
request = self.__build_request("GET", url)
166170

@@ -173,12 +177,13 @@ def retrieve_asset_datapoint_period(self, asset_id: str, attribute_name: str) ->
173177
self.logger.error(f"Error retrieving asset datapoint period: {e}")
174178
return None
175179

176-
def retrieve_historical_datapoints(
180+
def get_historical_datapoints(
177181
self,
178182
asset_id: str,
179183
attribute_name: str,
180184
from_timestamp: int,
181185
to_timestamp: int,
186+
realm: str = MASTER_REALM,
182187
) -> list[AssetDatapoint] | None:
183188
"""Retrieve the historical data points of a given asset attribute.
184189
@@ -187,13 +192,13 @@ def retrieve_historical_datapoints(
187192
attribute_name: The name of the attribute.
188193
from_timestamp: Epoch timestamp in milliseconds.
189194
to_timestamp: Epoch timestamp in milliseconds.
190-
195+
realm: The realm to retrieve assets from defaulting to MASTER_REALM.
191196
Returns:
192197
list[AssetDatapoint] | None: List of historical data points or None
193198
"""
194199

195200
params = f"{asset_id}/{attribute_name}"
196-
url = f"{self.openremote_url}/api/master/asset/datapoint/{params}"
201+
url = f"{self.openremote_url}/api/{realm}/asset/datapoint/{params}"
197202

198203
request_body = AssetDatapointQuery(
199204
fromTimestamp=from_timestamp,
@@ -212,20 +217,22 @@ def retrieve_historical_datapoints(
212217
self.logger.error(f"Error retrieving historical datapoints: {e}")
213218
return None
214219

215-
def write_predicted_datapoints(self, asset_id: str, attribute_name: str, datapoints: list[AssetDatapoint]) -> bool:
220+
def write_predicted_datapoints(
221+
self, asset_id: str, attribute_name: str, datapoints: list[AssetDatapoint], realm: str = MASTER_REALM
222+
) -> bool:
216223
"""Write the predicted data points of a given asset attribute.
217224
218225
Args:
219226
asset_id: The ID of the asset.
220227
attribute_name: The name of the attribute.
221228
datapoints: The data points to write.
222-
229+
realm: The realm to write the data points to defaulting to MASTER_REALM.
223230
Returns:
224231
bool: True if successful
225232
"""
226233

227234
params = f"{asset_id}/{attribute_name}"
228-
url = f"{self.openremote_url}/api/master/asset/predicted/{params}"
235+
url = f"{self.openremote_url}/api/{realm}/asset/predicted/{params}"
229236

230237
datapoints_json = [datapoint.model_dump() for datapoint in datapoints]
231238

@@ -240,12 +247,13 @@ def write_predicted_datapoints(self, asset_id: str, attribute_name: str, datapoi
240247
self.logger.error(f"Error writing predicted datapoints: {e}")
241248
return False
242249

243-
def retrieve_predicted_datapoints(
250+
def get_predicted_datapoints(
244251
self,
245252
asset_id: str,
246253
attribute_name: str,
247254
from_timestamp: int,
248255
to_timestamp: int,
256+
realm: str = MASTER_REALM,
249257
) -> list[AssetDatapoint] | None:
250258
"""Retrieve the predicted data points of a given asset attribute.
251259
@@ -254,13 +262,13 @@ def retrieve_predicted_datapoints(
254262
attribute_name: The name of the attribute.
255263
from_timestamp: Epoch timestamp in milliseconds.
256264
to_timestamp: Epoch timestamp in milliseconds.
257-
265+
realm: The realm to retrieve assets from defaulting to MASTER_REALM.
258266
Returns:
259267
list[AssetDatapoint] | None: List of predicted data points or None
260268
"""
261269

262270
params = f"{asset_id}/{attribute_name}"
263-
url = f"{self.openremote_url}/api/master/asset/predicted/{params}"
271+
url = f"{self.openremote_url}/api/{realm}/asset/predicted/{params}"
264272

265273
request_body = AssetDatapointQuery(
266274
fromTimestamp=from_timestamp,
@@ -279,21 +287,22 @@ def retrieve_predicted_datapoints(
279287
self.logger.error(f"Error retrieving predicted datapoints: {e}")
280288
return None
281289

282-
def retrieve_assets_with_historical_datapoints(self, realm: str) -> list[Asset] | None:
290+
def get_assets_with_historical_data(self, query_realm: str, realm: str = MASTER_REALM) -> list[Asset] | None:
283291
"""Retrieve all assets for a given realm that store historical datapoints.
284292
285293
Args:
286-
realm: The realm to retrieve assets from.
294+
query_realm: The realm for the asset query.
295+
realm: The realm to retrieve assets from defaulting to MASTER_REALM.
287296
288297
Returns:
289298
list[Asset] | None: List of assets or None
290299
"""
291300

292-
url = f"{self.openremote_url}/api/master/asset/query"
301+
url = f"{self.openremote_url}/api/{realm}/asset/query"
293302

294303
# OR Asset Query to retrieve only assets that have attributes with "meta": {"storeDataPoints": true}
295304
asset_query = {
296-
"realm": {"name": realm},
305+
"realm": {"name": query_realm},
297306
"attributes": {
298307
"operator": "AND",
299308
"items": [
@@ -342,19 +351,22 @@ def _filter_asset_attributes(asset_obj: Asset) -> Asset:
342351
self.logger.error(f"Error retrieving assets with storeDataPoints: {e}")
343352
return None
344353

345-
def retrieve_assets_by_ids(self, asset_ids: list[str], realm: str) -> list[Asset] | None:
354+
def get_assets_by_ids(
355+
self, asset_ids: list[str], query_realm: str, realm: str = MASTER_REALM
356+
) -> list[Asset] | None:
346357
"""Retrieve assets by their IDs.
347358
348359
Args:
349360
asset_ids: The IDs of the assets to retrieve.
350-
realm: The realm to retrieve assets from.
361+
query_realm: The realm for the asset query.
362+
realm: The realm to retrieve assets from defaulting to MASTER_REALM.
351363
352364
Returns:
353365
list[Asset] | None: List of assets or None
354366
"""
355367

356-
url = f"{self.openremote_url}/api/master/asset/query"
357-
asset_query = {"recursive": False, "realm": {"name": realm}, "ids": asset_ids}
368+
url = f"{self.openremote_url}/api/{realm}/asset/query"
369+
asset_query = {"recursive": False, "realm": {"name": query_realm}, "ids": asset_ids}
358370

359371
request = self.__build_request("POST", url, data=asset_query)
360372

@@ -368,13 +380,16 @@ def retrieve_assets_by_ids(self, asset_ids: list[str], realm: str) -> list[Asset
368380
self.logger.error(f"Error retrieving assets: {e}")
369381
return None
370382

371-
def retrieve_manager_config(self) -> ManagerConfig | None:
383+
def get_manager_config(self, realm: str = MASTER_REALM) -> ManagerConfig | None:
372384
"""Retrieve the manager configuration.
373385
386+
Args:
387+
realm: The realm to retrieve the manager configuration from defaulting to MASTER_REALM.
388+
374389
Returns: ManagerConfig | None: The manager configuration or None
375390
"""
376391

377-
url = f"{self.openremote_url}/api/master/configuration/manager"
392+
url = f"{self.openremote_url}/api/{realm}/configuration/manager"
378393
request = self.__build_request("GET", url)
379394

380395
with httpx.Client(timeout=self.timeout) as client:
@@ -388,9 +403,12 @@ def retrieve_manager_config(self) -> ManagerConfig | None:
388403
self.logger.error(f"Error retrieving manager config: {e}")
389404
return None
390405

391-
def retrieve_accessible_realms(self, realm: str) -> list[BasicRealm] | None:
406+
def get_accessible_realms(self, realm: str = MASTER_REALM) -> list[BasicRealm] | None:
392407
"""Retrieves accessible realms for the current user.
393408
409+
Args:
410+
realm: The realm to retrieve realms from defaulting to MASTER_REALM.
411+
394412
Returns:
395413
list[BasicRealm] | None: List of accessible realms or None
396414
"""
@@ -408,14 +426,17 @@ def retrieve_accessible_realms(self, realm: str) -> list[BasicRealm] | None:
408426
self.logger.error(f"Error retrieving accessible realms: {e}")
409427
return None
410428

411-
def retrieve_all_realms(self) -> list[Realm] | None:
429+
def get_all_enabled_realms(self, realm: str = MASTER_REALM) -> list[Realm] | None:
412430
"""Retrieves all realms and filters out disabled ones.
413431
432+
Args:
433+
realm: The realm to retrieve realms from defaulting to MASTER_REALM.
434+
414435
Returns:
415436
list[Realm] | None: List of enabled realms or None
416437
"""
417438

418-
url = f"{self.openremote_url}/api/master/realm"
439+
url = f"{self.openremote_url}/api/{realm}/realm"
419440
request = self.__build_request("GET", url)
420441

421442
with httpx.Client(timeout=self.timeout) as client:

0 commit comments

Comments
 (0)