44import json
55import logging
66from dataclasses import dataclass
7- from functools import cached_property
7+ from functools import cached_property , lru_cache
88from itertools import combinations as combi
99from typing import TYPE_CHECKING , Any , NamedTuple
1010from urllib .parse import quote
2626from ert .storage .local_experiment import _parameters_adapter as parameter_config_adapter
2727from ert .storage .local_experiment import _responses_adapter as response_config_adapter
2828from ert .storage .realization_storage_state import RealizationStorageState
29+ from ert .utils import process_arg
2930
3031logger = logging .getLogger (__name__ )
3132
3233if TYPE_CHECKING :
3334 from pathlib import Path
3435
3536
37+ TIMEOUT = 120
38+
39+
3640@dataclass (frozen = True , eq = True )
3741class EnsembleObject :
3842 name : str
@@ -59,13 +63,12 @@ class PlotApi:
5963 def __init__ (self , ens_path : Path ) -> None :
6064 self .ens_path : Path = ens_path
6165 self ._all_ensembles : list [EnsembleObject ] | None = None
62- self ._timeout = 120
6366
6467 @property
6568 def api_version (self ) -> str :
6669 with create_ertserver_client (self .ens_path ) as client :
6770 try :
68- http_response = client .get ("/version" , timeout = self . _timeout )
71+ http_response = client .get ("/version" , timeout = TIMEOUT )
6972 self ._check_http_response (http_response )
7073 api_version = str (http_response .json ())
7174 except Exception as exc :
@@ -91,13 +94,13 @@ def get_all_ensembles(self) -> list[EnsembleObject]:
9194 self ._all_ensembles = []
9295 with create_ertserver_client (self .ens_path ) as client :
9396 try : # noqa: PLW0717
94- http_response = client .get ("/experiments" , timeout = self . _timeout )
97+ http_response = client .get ("/experiments" , timeout = TIMEOUT )
9598 self ._check_http_response (http_response )
9699 experiments = http_response .json ()
97100 for experiment in experiments :
98101 for ensemble_id in experiment ["ensemble_ids" ]:
99102 http_response = client .get (
100- f"/ensembles/{ ensemble_id } " , timeout = self . _timeout
103+ f"/ensembles/{ ensemble_id } " , timeout = TIMEOUT
101104 )
102105 self ._check_http_response (http_response )
103106 response_json : dict [str , Any ] = http_response .json ()
@@ -154,7 +157,7 @@ def parameters_api_key_defs(self) -> list[PlotApiKeyDefinition]:
154157 all_params = {}
155158
156159 with create_ertserver_client (self .ens_path ) as client :
157- http_response = client .get ("/experiments" , timeout = self . _timeout )
160+ http_response = client .get ("/experiments" , timeout = TIMEOUT )
158161 self ._check_http_response (http_response )
159162
160163 for experiment in http_response .json ():
@@ -181,7 +184,7 @@ def responses_api_key_defs(self) -> list[PlotApiKeyDefinition]:
181184 key_defs : dict [str , PlotApiKeyDefinition ] = {}
182185
183186 with create_ertserver_client (self .ens_path ) as client :
184- http_response = client .get ("/experiments" , timeout = self . _timeout )
187+ http_response = client .get ("/experiments" , timeout = TIMEOUT )
185188 self ._check_http_response (http_response )
186189
187190 def update_keydef (plot_key_def : PlotApiKeyDefinition ) -> None :
@@ -272,7 +275,7 @@ def data_for_response(
272275 params = {"filter_on" : json .dumps (filter_on )}
273276 if filter_on is not None
274277 else None ,
275- timeout = self . _timeout ,
278+ timeout = TIMEOUT ,
276279 )
277280 self ._check_http_response (http_response )
278281
@@ -341,16 +344,17 @@ def data_for_response(
341344 except ValueError :
342345 return df
343346
344- def data_for_gradient (self , ensemble_id : str , key : str ) -> pd .DataFrame :
345- if "@" in key :
346- key = key .split ("@" , maxsplit = 1 )[0 ]
347- with create_ertserver_client (self .ens_path ) as client :
347+ @staticmethod
348+ @process_arg (key = "key" , process = lambda s : s .split ("@" , maxsplit = 1 )[0 ])
349+ @lru_cache (maxsize = 256 )
350+ def data_for_gradient (ensemble_id : str , key : str , ens_path : Path ) -> pd .DataFrame :
351+ with create_ertserver_client (ens_path ) as client :
348352 http_response = client .get (
349353 f"/ensembles/{ ensemble_id } /gradients/{ PlotApi .escape (key )} " ,
350354 headers = {"accept" : "application/x-parquet" },
351- timeout = self . _timeout ,
355+ timeout = TIMEOUT ,
352356 )
353- self ._check_http_response (http_response )
357+ PlotApi ._check_http_response (http_response )
354358
355359 stream = io .BytesIO (http_response .content )
356360 df = pd .read_parquet (stream )
@@ -366,13 +370,15 @@ def data_for_gradient(self, ensemble_id: str, key: str) -> pd.DataFrame:
366370 }
367371 )
368372
373+ @staticmethod
374+ @lru_cache (maxsize = 128 )
369375 def data_for_controls (
370- self , ensemble_id : str , parameter_keys : list [str ]
376+ ensemble_id : str , parameter_keys : tuple [str , ...], ens_path : Path
371377 ) -> pd .DataFrame :
372378 frames = []
373379
374380 for parameter_key in parameter_keys :
375- df = self .data_for_parameter (ensemble_id , parameter_key )
381+ df = PlotApi .data_for_parameter (ensemble_id , parameter_key , ens_path )
376382 if not df .empty and {"batch_id" , "realization" }.issubset (df .columns ):
377383 value_cols = [
378384 c for c in df .columns if c not in {"batch_id" , "realization" }
@@ -388,14 +394,18 @@ def data_for_controls(
388394 return pd .DataFrame ()
389395 return pd .concat (frames , ignore_index = True )
390396
391- def data_for_parameter (self , ensemble_id : str , parameter_key : str ) -> pd .DataFrame :
392- with create_ertserver_client (self .ens_path ) as client :
397+ @staticmethod
398+ @lru_cache (maxsize = 256 )
399+ def data_for_parameter (
400+ ensemble_id : str , parameter_key : str , ens_path : Path
401+ ) -> pd .DataFrame :
402+ with create_ertserver_client (ens_path ) as client :
393403 http_response = client .get (
394404 f"/ensembles/{ ensemble_id } /parameters/{ PlotApi .escape (parameter_key )} " ,
395405 headers = {"accept" : "application/x-parquet" },
396- timeout = self . _timeout ,
406+ timeout = TIMEOUT ,
397407 )
398- self ._check_http_response (http_response )
408+ PlotApi ._check_http_response (http_response )
399409
400410 stream = io .BytesIO (http_response .content )
401411 df = pd .read_parquet (stream )
@@ -415,7 +425,7 @@ def data_for_parameter(self, ensemble_id: str, parameter_key: str) -> pd.DataFra
415425
416426 def observation_locations (self ) -> pd .DataFrame :
417427 with create_ertserver_client (self .ens_path ) as client :
418- http_response = client .get ("/experiments" , timeout = self . _timeout )
428+ http_response = client .get ("/experiments" , timeout = TIMEOUT )
419429 self ._check_http_response (http_response )
420430 experiments = http_response .json ()
421431
@@ -424,7 +434,7 @@ def observation_locations(self) -> pd.DataFrame:
424434 experiment_id = str (experiment ["id" ])
425435 http_response = client .get (
426436 f"/experiments/{ experiment_id } /observations" ,
427- timeout = self . _timeout ,
437+ timeout = TIMEOUT ,
428438 )
429439 self ._check_http_response (http_response )
430440 observations = http_response .json ()
@@ -483,7 +493,7 @@ def observations_for_key(self, ensemble_ids: list[str], key: str) -> pd.DataFram
483493 with create_ertserver_client (self .ens_path ) as client :
484494 http_response = client .get (
485495 f"/ensembles/{ ensemble .id } /responses/{ PlotApi .escape (actual_response_key )} /observations" ,
486- timeout = self . _timeout ,
496+ timeout = TIMEOUT ,
487497 params = {"filter_on" : json .dumps (filter_on )}
488498 if filter_on is not None
489499 else None ,
@@ -574,7 +584,7 @@ def std_dev_for_parameter(
574584 http_response = client .get (
575585 f"/ensembles/{ ensemble .id } /parameters/{ PlotApi .escape (key )} /std_dev" ,
576586 params = {"z" : z },
577- timeout = self . _timeout ,
587+ timeout = TIMEOUT ,
578588 )
579589
580590 if http_response .status_code == 200 :
0 commit comments