Skip to content

Commit a9695c6

Browse files
committed
ENH: Add endpoint to get sumo assets
1 parent 9f54134 commit a9695c6

File tree

9 files changed

+434
-3
lines changed

9 files changed

+434
-3
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Interfaces for interacting with outside services."""
22

33
from .smda_api import SmdaAPI
4+
from .sumo_api import SumoApi
45

56
__all__ = [
67
"SmdaAPI",
8+
"SumoApi",
79
]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Interface for querying Sumo's API."""
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Final, Self
6+
7+
from pydantic import TypeAdapter
8+
9+
from fmu_settings_api.models.project import SumoAsset
10+
11+
12+
class SumoApi:
13+
"""Class for interacting with Sumos API."""
14+
15+
def __init__(self: Self) -> None:
16+
"""Initializes the SumoApi interface."""
17+
self._asset_filepath: Final[Path] = Path(__file__).parent / Path(
18+
"sumo_assets.json"
19+
)
20+
21+
def get_assets(self: Self) -> list[SumoAsset]:
22+
"""Gets the Sumo assets."""
23+
return self._read_assets_from_file(self._asset_filepath)
24+
25+
@staticmethod
26+
def _read_assets_from_file(filepath: Path) -> list[SumoAsset]:
27+
"""Reads the valid Sumo assets from file.
28+
29+
The file serves as a temporary alternative to the Sumo endpoint, until
30+
we set up the intergration towards Sumo. The file is maintained and kept in
31+
sync with the assets that are onboarded to Sumo.
32+
33+
Raises:
34+
ValidationError: If Sumo assets read from file are not valid.
35+
JSONDecodeError: If json file to read is not a valid json.
36+
FileNotFoundError: If the file to read is not found.
37+
"""
38+
with open(filepath, encoding="utf-8") as stream:
39+
sumo_assets = json.load(stream)
40+
return TypeAdapter(list[SumoAsset]).validate_python(sumo_assets)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
[
2+
{
3+
"name": "Drogon",
4+
"code": "001",
5+
"roleprefix": "DROGON"
6+
},
7+
{
8+
"name": "Johan Sverdrup",
9+
"code": "002",
10+
"roleprefix": "JOHAN-SVERDRUP"
11+
},
12+
{
13+
"name": "Breidablikk",
14+
"code": "003",
15+
"roleprefix": "BREIDABLIKK"
16+
},
17+
{
18+
"name": "Smørbukk",
19+
"code": "004",
20+
"roleprefix": "SMORBUKK"
21+
},
22+
{
23+
"name": "Heidrun",
24+
"code": "005",
25+
"roleprefix": "HEIDRUN"
26+
},
27+
{
28+
"name": "Johan Castberg",
29+
"code": "006",
30+
"roleprefix": "JOHAN-CASTBERG"
31+
},
32+
{
33+
"name": "Snorre",
34+
"code": "007",
35+
"roleprefix": "SNORRE"
36+
},
37+
{
38+
"name": "Grosbeak",
39+
"code": "008",
40+
"roleprefix": "GROSBEAK"
41+
},
42+
{
43+
"name": "Troll",
44+
"code": "009",
45+
"roleprefix": "TROLL"
46+
},
47+
{
48+
"name": "Rosebank",
49+
"code": "010",
50+
"roleprefix": "ROSEBANK"
51+
},
52+
{
53+
"name": "Wisting",
54+
"code": "011",
55+
"roleprefix": "WISTING"
56+
},
57+
{
58+
"name": "Oseberg",
59+
"code": "012",
60+
"roleprefix": "OSEBERG"
61+
},
62+
{
63+
"name": "DIG_CCS_SUB",
64+
"code": "013",
65+
"roleprefix": "DIG_CCS_SUB"
66+
},
67+
{
68+
"name": "Tordis",
69+
"code": "014",
70+
"roleprefix": "TORDIS"
71+
},
72+
{
73+
"name": "Bauge",
74+
"code": "015",
75+
"roleprefix": "BAUGE"
76+
},
77+
{
78+
"name": "Grane",
79+
"code": "016",
80+
"roleprefix": "GRANE"
81+
},
82+
{
83+
"name": "Martin Linge",
84+
"code": "017",
85+
"roleprefix": "MARTIN-LINGE"
86+
},
87+
{
88+
"name": "Njord",
89+
"code": "018",
90+
"roleprefix": "NJORD"
91+
},
92+
{
93+
"name": "Raia",
94+
"code": "019",
95+
"roleprefix": "RAIA"
96+
},
97+
{
98+
"name": "Aasta Hansteen",
99+
"code": "020",
100+
"roleprefix": "AASTA-HANSTEEN"
101+
},
102+
{
103+
"name": "Tyrihans",
104+
"code": "021",
105+
"roleprefix": "TYRIHANS"
106+
},
107+
{
108+
"name": "Fram",
109+
"code": "022",
110+
"roleprefix": "FRAM"
111+
},
112+
{
113+
"name": "Smørbukk Midt",
114+
"code": "023",
115+
"roleprefix": "SMORBUKK-MIDT"
116+
}
117+
]

src/fmu_settings_api/models/project.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,16 @@ class LockStatus(BaseResponseModel):
6666

6767
last_lock_refresh_error: str | None = Field(default=None)
6868
"""Error message from the last attempt to refresh the lock."""
69+
70+
71+
class SumoAsset(BaseResponseModel):
72+
"""A valid asset in Sumo."""
73+
74+
name: str
75+
"""Name of the asset in Sumo."""
76+
77+
code: str
78+
"""Code of the asset in Sumo."""
79+
80+
roleprefix: str
81+
"""Roleprefix of the asset in Sumo."""

src/fmu_settings_api/services/project.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
RmsWell,
1414
)
1515

16+
from fmu_settings_api.interfaces import SumoApi
1617
from fmu_settings_api.models import FMUProject
17-
from fmu_settings_api.models.project import CacheRetention, GlobalConfigPath
18+
from fmu_settings_api.models.project import CacheRetention, GlobalConfigPath, SumoAsset
1819

1920
from .rms import RmsService
2021

@@ -156,3 +157,7 @@ def update_rms_wells(self, wells: list[RmsWell]) -> None:
156157
self._fmu_dir.set_config_value(
157158
"rms.wells", [well.model_dump() for well in wells]
158159
)
160+
161+
def get_sumo_assets(self) -> list[SumoAsset]:
162+
"""Get the Sumo assets."""
163+
return SumoApi().get_assets()

src/fmu_settings_api/v1/routes/project.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Routes to add an FMU project to an existing session."""
22

3+
import json
34
from pathlib import Path
45
from textwrap import dedent
56
from typing import Final
@@ -42,6 +43,7 @@
4243
CacheRetention,
4344
GlobalConfigPath,
4445
LockStatus,
46+
SumoAsset,
4547
)
4648
from fmu_settings_api.models.resource import CacheContent, CacheList
4749
from fmu_settings_api.models.rms import (
@@ -292,6 +294,22 @@
292294
),
293295
}
294296

297+
SumoAssetsResponses: Final[Responses] = {
298+
**inline_add_response(
299+
404,
300+
"Sumo assets file not found",
301+
[{"detail": "Sumo assets file not found: {error}"}],
302+
),
303+
**inline_add_response(
304+
422,
305+
"Invalid file content in Sumo assets file",
306+
[
307+
{"detail": "Sumo assets file contains invalid assets: {errors}"},
308+
{"detail": "Sumo assets file is not a valid JSON: {error}"},
309+
],
310+
),
311+
}
312+
295313

296314
@router.get(
297315
"/",
@@ -335,6 +353,38 @@ async def get_project(session_service: SessionServiceDep) -> FMUProject:
335353
return _create_opened_project_response(fmu_dir)
336354

337355

356+
@router.get(
357+
"/sumo_assets",
358+
response_model=list[SumoAsset],
359+
summary="Returns a list of Sumo assets.",
360+
description=dedent(
361+
"""
362+
Returns a list of the assets that have been onboarded to
363+
the Sumo platform.
364+
"""
365+
),
366+
responses={**GetSessionResponses, **SumoAssetsResponses},
367+
)
368+
async def get_sumo_assets(project_service: ProjectServiceDep) -> list[SumoAsset]:
369+
"""Returns a list of the Sumo assets."""
370+
try:
371+
return project_service.get_sumo_assets()
372+
except ValidationError as e:
373+
errors = [error.get("msg", str(error)) for error in e.errors()]
374+
raise HTTPException(
375+
status_code=422,
376+
detail=f"Sumo assets file contains invalid assets: {'; '.join(errors)}",
377+
) from e
378+
except json.JSONDecodeError as e:
379+
raise HTTPException(
380+
status_code=422, detail=f"Sumo assets file is not a valid JSON: {str(e)}"
381+
) from e
382+
except FileNotFoundError as e:
383+
raise HTTPException(
384+
status_code=404, detail=f"Sumo assets file not found: {str(e)}"
385+
) from e
386+
387+
338388
@router.get(
339389
"/global_config_status",
340390
response_model=Ok,

0 commit comments

Comments
 (0)