ert 19.0.0__py3-none-any.whl → 19.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. ert/__main__.py +94 -63
  2. ert/analysis/_es_update.py +11 -14
  3. ert/config/_create_observation_dataframes.py +12 -228
  4. ert/config/_observations.py +164 -152
  5. ert/config/_read_summary.py +4 -5
  6. ert/config/ert_config.py +1 -56
  7. ert/config/observation_config_migrations.py +793 -0
  8. ert/config/rft_config.py +1 -1
  9. ert/dark_storage/compute/misfits.py +0 -42
  10. ert/dark_storage/endpoints/__init__.py +0 -2
  11. ert/dark_storage/endpoints/experiments.py +0 -3
  12. ert/dark_storage/json_schema/experiment.py +0 -1
  13. ert/field_utils/grdecl_io.py +9 -26
  14. ert/gui/main_window.py +2 -0
  15. ert/gui/tools/manage_experiments/export_dialog.py +4 -0
  16. ert/gui/tools/manage_experiments/storage_info_widget.py +1 -5
  17. ert/gui/tools/plot/plot_api.py +10 -10
  18. ert/gui/tools/plot/plot_widget.py +12 -14
  19. ert/gui/tools/plot/plot_window.py +1 -10
  20. ert/services/__init__.py +7 -3
  21. ert/services/_storage_main.py +59 -22
  22. ert/services/ert_server.py +186 -24
  23. ert/shared/version.py +3 -3
  24. ert/storage/local_ensemble.py +3 -107
  25. ert/storage/local_experiment.py +0 -16
  26. ert/storage/local_storage.py +1 -3
  27. ert/utils/__init__.py +20 -0
  28. {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/METADATA +2 -2
  29. {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/RECORD +40 -47
  30. {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/WHEEL +1 -1
  31. everest/bin/everest_script.py +5 -5
  32. everest/bin/kill_script.py +2 -2
  33. everest/bin/monitor_script.py +2 -2
  34. everest/bin/utils.py +4 -4
  35. everest/detached/everserver.py +6 -6
  36. everest/gui/main_window.py +2 -2
  37. everest/util/__init__.py +1 -19
  38. ert/dark_storage/compute/__init__.py +0 -0
  39. ert/dark_storage/endpoints/compute/__init__.py +0 -0
  40. ert/dark_storage/endpoints/compute/misfits.py +0 -95
  41. ert/services/_base_service.py +0 -387
  42. ert/services/webviz_ert_service.py +0 -20
  43. ert/shared/storage/command.py +0 -38
  44. ert/shared/storage/extraction.py +0 -42
  45. ert/storage/migration/to23.py +0 -49
  46. {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/entry_points.txt +0 -0
  47. {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/licenses/COPYING +0 -0
  48. {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/top_level.txt +0 -0
ert/config/rft_config.py CHANGED
@@ -194,7 +194,7 @@ class RFTConfig(ResponseConfig):
194
194
  .explode("location")
195
195
  for (well, time), inner_dict in fetched.items()
196
196
  for prop, vals in inner_dict.items()
197
- if prop != "DEPTH"
197
+ if prop != "DEPTH" and len(vals) > 0
198
198
  ]
199
199
  )
200
200
  except KeyError as err:
@@ -1,42 +0,0 @@
1
- from collections.abc import Mapping
2
-
3
- import numpy as np
4
- import numpy.typing as npt
5
- import pandas as pd
6
-
7
-
8
- def _calculate_signed_chi_squared_misfit(
9
- obs_value: npt.NDArray[np.float64],
10
- response_value: npt.NDArray[np.float64],
11
- obs_std: npt.NDArray[np.float64],
12
- ) -> list[float]:
13
- """The signed version is intended for visualization. For data assimiliation one
14
- would normally use the normal chi-square"""
15
- residual = response_value - obs_value
16
- return (np.sign(residual) * residual * residual / (obs_std * obs_std)).tolist()
17
-
18
-
19
- def calculate_signed_chi_squared_misfits(
20
- reponses_dict: Mapping[int, pd.DataFrame],
21
- observation: pd.DataFrame,
22
- summary_misfits: bool = False,
23
- ) -> pd.DataFrame:
24
- """
25
- Compute misfits from reponses_dict (real_id, values in dataframe)
26
- and observation
27
- """
28
- misfits_dict = {}
29
- for realization_index in reponses_dict:
30
- misfits_dict[realization_index] = _calculate_signed_chi_squared_misfit(
31
- observation["values"],
32
- reponses_dict[realization_index]
33
- .loc[:, observation.index]
34
- .to_numpy()
35
- .flatten(),
36
- observation["errors"],
37
- )
38
-
39
- df = pd.DataFrame(data=misfits_dict, index=observation.index)
40
- if summary_misfits:
41
- df = pd.DataFrame([df.abs().sum(axis=0)], columns=df.columns, index=[0])
42
- return df.T
@@ -1,6 +1,5 @@
1
1
  from fastapi import APIRouter
2
2
 
3
- from .compute.misfits import router as misfits_router
4
3
  from .ensembles import router as ensembles_router
5
4
  from .experiment_server import router as experiment_server_router
6
5
  from .experiments import router as experiments_router
@@ -15,7 +14,6 @@ router.include_router(experiments_router)
15
14
  router.include_router(ensembles_router)
16
15
  router.include_router(observations_router)
17
16
  router.include_router(updates_router)
18
- router.include_router(misfits_router)
19
17
  router.include_router(parameters_router)
20
18
  router.include_router(responses_router)
21
19
  router.include_router(experiment_server_router)
@@ -6,7 +6,6 @@ from fastapi import APIRouter, Body, Depends, HTTPException
6
6
  from ert.config import SurfaceConfig
7
7
  from ert.dark_storage import json_schema as js
8
8
  from ert.dark_storage.common import get_storage
9
- from ert.shared.storage.extraction import create_priors
10
9
  from ert.storage import Storage
11
10
 
12
11
  router = APIRouter(tags=["experiment"])
@@ -26,7 +25,6 @@ def get_experiments(
26
25
  id=experiment.id,
27
26
  name=experiment.name,
28
27
  ensemble_ids=[ens.id for ens in experiment.ensembles],
29
- priors=create_priors(experiment),
30
28
  userdata={},
31
29
  parameters={
32
30
  group: config.model_dump()
@@ -62,7 +60,6 @@ def get_experiment_by_id(
62
60
  name=experiment.name,
63
61
  id=experiment.id,
64
62
  ensemble_ids=[ens.id for ens in experiment.ensembles],
65
- priors=create_priors(experiment),
66
63
  userdata={},
67
64
  parameters={
68
65
  group: config.model_dump()
@@ -22,7 +22,6 @@ class ExperimentIn(_Experiment):
22
22
  class ExperimentOut(_Experiment):
23
23
  id: UUID
24
24
  ensemble_ids: list[UUID]
25
- priors: Mapping[str, dict[str, Any]]
26
25
  userdata: Mapping[str, Any]
27
26
  parameters: Mapping[str, dict[str, Any]]
28
27
  responses: Mapping[str, list[dict[str, Any]]]
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import io
4
3
  import operator
5
4
  import os
6
5
  from collections.abc import Iterator
@@ -258,9 +257,6 @@ def import_bgrdecl(
258
257
  raise ValueError(f"Did not find field parameter {field_name} in {file_path}")
259
258
 
260
259
 
261
- _BUFFER_SIZE = 2**20 # 1.04 megabytes
262
-
263
-
264
260
  def export_grdecl(
265
261
  values: np.ma.MaskedArray[Any, np.dtype[np.float32]] | npt.NDArray[np.float32],
266
262
  file_path: str | os.PathLike[str],
@@ -275,25 +271,12 @@ def export_grdecl(
275
271
  if binary:
276
272
  resfo.write(file_path, [(param_name.ljust(8), values.astype(np.float32))])
277
273
  else:
278
- length = values.shape[0]
279
- per_line = 6
280
- iters = 5
281
- per_iter = per_line * iters
282
- fmt = " ".join(["%3e"] * per_line)
283
- fmt = "\n".join([fmt] * iters) + "\n"
284
- with (
285
- open(file_path, "wb+", 0) as fh,
286
- io.BufferedWriter(fh, _BUFFER_SIZE) as bw,
287
- io.TextIOWrapper(bw, write_through=True, encoding="utf-8") as tw,
288
- ):
289
- tw.write(param_name + "\n")
290
- i = 0
291
- while i + per_iter <= length:
292
- tw.write(fmt % tuple(values[i : i + per_iter]))
293
- i += per_iter
294
-
295
- for j, v in enumerate(values[length - (length % per_iter) :]):
296
- tw.write(f" {v:3e}")
297
- if j % 6 == 5:
298
- tw.write("\n")
299
- tw.write(" /\n")
274
+ with open(file_path, "w", encoding="utf-8") as fh:
275
+ fh.write(param_name + "\n")
276
+ for i, v in enumerate(values):
277
+ fh.write(" ")
278
+ fh.write(f"{v:3e}")
279
+ if i % 6 == 5:
280
+ fh.write("\n")
281
+
282
+ fh.write(" /\n")
ert/gui/main_window.py CHANGED
@@ -116,8 +116,10 @@ class ErtMainWindow(QMainWindow):
116
116
 
117
117
  if is_dark_mode():
118
118
  self.side_frame.setStyleSheet("background-color: rgb(64, 64, 64);")
119
+ logger.info("Running Ert with dark mode")
119
120
  else:
120
121
  self.side_frame.setStyleSheet("background-color: lightgray;")
122
+ logger.info("Running Ert with light mode")
121
123
 
122
124
  if is_high_contrast_mode():
123
125
  msg_box = QMessageBox()
@@ -1,4 +1,5 @@
1
1
  import contextlib
2
+ import logging
2
3
  from pathlib import Path
3
4
  from typing import cast
4
5
 
@@ -22,6 +23,8 @@ from PyQt6.QtWidgets import (
22
23
  QWidget,
23
24
  )
24
25
 
26
+ logger = logging.getLogger(__name__)
27
+
25
28
 
26
29
  class ExportDialog(QDialog):
27
30
  """Base dialog for exporting ensemble-related data to files."""
@@ -85,6 +88,7 @@ class ExportDialog(QDialog):
85
88
  f"<span style='color: red;'>Could not export data: {e!s}</span><br>"
86
89
  )
87
90
  finally:
91
+ logger.info(f"Export dialog used: '{self.windowTitle()}'")
88
92
  self._export_button.setEnabled(True)
89
93
 
90
94
  @Slot()
@@ -11,7 +11,6 @@ from polars import DataFrame
11
11
  from PyQt6.QtCore import Qt
12
12
  from PyQt6.QtCore import pyqtSlot as Slot
13
13
  from PyQt6.QtWidgets import (
14
- QAbstractItemView,
15
14
  QFrame,
16
15
  QHBoxLayout,
17
16
  QLabel,
@@ -166,9 +165,6 @@ class _EnsembleWidget(QWidget):
166
165
  observations_frame.setLayout(observations_layout)
167
166
 
168
167
  self._parameters_table = QTableWidget()
169
- self._parameters_table.setEditTriggers(
170
- QAbstractItemView.EditTrigger.NoEditTriggers
171
- )
172
168
  self._export_params_button = QPushButton("Export...")
173
169
  self._export_params_button.clicked.connect(self.onClickExportParameters)
174
170
 
@@ -253,8 +249,8 @@ class _EnsembleWidget(QWidget):
253
249
  df[x_axis_col]
254
250
  .map_elements(
255
251
  lambda x: response_config.display_column(x, x_axis_col),
256
- return_dtype=pl.String,
257
252
  )
253
+ .cast(pl.String)
258
254
  .alias("temp")
259
255
  ).filter(pl.col("temp").eq(observation_label))[
260
256
  [x for x in df.columns if x != "temp"]
@@ -19,7 +19,7 @@ from pandas.errors import ParserError
19
19
  from resfo_utilities import history_key
20
20
 
21
21
  from ert.config import ParameterConfig, ResponseMetadata
22
- from ert.services import ErtServer
22
+ from ert.services import create_ertserver_client
23
23
  from ert.storage.local_experiment import _parameters_adapter as parameter_config_adapter
24
24
  from ert.storage.realization_storage_state import RealizationStorageState
25
25
 
@@ -51,13 +51,13 @@ class PlotApiKeyDefinition(NamedTuple):
51
51
 
52
52
  class PlotApi:
53
53
  def __init__(self, ens_path: Path) -> None:
54
- self.ens_path = ens_path
54
+ self.ens_path: Path = ens_path
55
55
  self._all_ensembles: list[EnsembleObject] | None = None
56
56
  self._timeout = 120
57
57
 
58
58
  @property
59
59
  def api_version(self) -> str:
60
- with ErtServer.session(project=self.ens_path) as client:
60
+ with create_ertserver_client(self.ens_path) as client:
61
61
  try:
62
62
  response = client.get("/version", timeout=self._timeout)
63
63
  self._check_response(response)
@@ -83,7 +83,7 @@ class PlotApi:
83
83
  return self._all_ensembles
84
84
 
85
85
  self._all_ensembles = []
86
- with ErtServer.session(project=self.ens_path) as client:
86
+ with create_ertserver_client(self.ens_path) as client:
87
87
  try:
88
88
  response = client.get("/experiments", timeout=self._timeout)
89
89
  self._check_response(response)
@@ -139,7 +139,7 @@ class PlotApi:
139
139
  all_keys: dict[str, PlotApiKeyDefinition] = {}
140
140
  all_params = {}
141
141
 
142
- with ErtServer.session(project=self.ens_path) as client:
142
+ with create_ertserver_client(self.ens_path) as client:
143
143
  response = client.get("/experiments", timeout=self._timeout)
144
144
  self._check_response(response)
145
145
 
@@ -166,7 +166,7 @@ class PlotApi:
166
166
  def responses_api_key_defs(self) -> list[PlotApiKeyDefinition]:
167
167
  key_defs: dict[str, PlotApiKeyDefinition] = {}
168
168
 
169
- with ErtServer.session(project=self.ens_path) as client:
169
+ with create_ertserver_client(self.ens_path) as client:
170
170
  response = client.get("/experiments", timeout=self._timeout)
171
171
  self._check_response(response)
172
172
 
@@ -228,7 +228,7 @@ class PlotApi:
228
228
  response_key: str,
229
229
  filter_on: dict[str, Any] | None = None,
230
230
  ) -> pd.DataFrame:
231
- with ErtServer.session(project=self.ens_path) as client:
231
+ with create_ertserver_client(self.ens_path) as client:
232
232
  response = client.get(
233
233
  f"/ensembles/{ensemble_id}/responses/{PlotApi.escape(response_key)}",
234
234
  headers={"accept": "application/x-parquet"},
@@ -256,7 +256,7 @@ class PlotApi:
256
256
  return df
257
257
 
258
258
  def data_for_parameter(self, ensemble_id: str, parameter_key: str) -> pd.DataFrame:
259
- with ErtServer.session(project=self.ens_path) as client:
259
+ with create_ertserver_client(self.ens_path) as client:
260
260
  parameter = client.get(
261
261
  f"/ensembles/{ensemble_id}/parameters/{PlotApi.escape(parameter_key)}",
262
262
  headers={"accept": "application/x-parquet"},
@@ -298,7 +298,7 @@ class PlotApi:
298
298
  assert key_def.response_metadata is not None
299
299
  actual_response_key = key_def.response_metadata.response_key
300
300
  filter_on = key_def.filter_on
301
- with ErtServer.session(project=self.ens_path) as client:
301
+ with create_ertserver_client(self.ens_path) as client:
302
302
  response = client.get(
303
303
  f"/ensembles/{ensemble.id}/responses/{PlotApi.escape(actual_response_key)}/observations",
304
304
  timeout=self._timeout,
@@ -386,7 +386,7 @@ class PlotApi:
386
386
  if not ensemble:
387
387
  return np.array([])
388
388
 
389
- with ErtServer.session(project=self.ens_path) as client:
389
+ with create_ertserver_client(self.ens_path) as client:
390
390
  response = client.get(
391
391
  f"/ensembles/{ensemble.id}/parameters/{PlotApi.escape(key)}/std_dev",
392
392
  params={"z": z},
@@ -153,6 +153,7 @@ class PlotWidget(QWidget):
153
153
  # only for histogram plot see _sync_log_checkbox
154
154
  self._log_checkbox.setVisible(False)
155
155
  self._log_checkbox.setToolTip("Toggle data domain to log scale and back")
156
+ self._log_checkbox.clicked.connect(self.logLogScaleButtonUsage)
156
157
 
157
158
  log_checkbox_row = QHBoxLayout()
158
159
  log_checkbox_row.addWidget(self._log_checkbox)
@@ -163,7 +164,6 @@ class PlotWidget(QWidget):
163
164
  vbox.addSpacing(8)
164
165
  self.setLayout(vbox)
165
166
 
166
- self._negative_values_in_data = False
167
167
  self._dirty = True
168
168
  self._active = False
169
169
  self.resetPlot()
@@ -176,15 +176,11 @@ class PlotWidget(QWidget):
176
176
  self._figure.clear()
177
177
 
178
178
  def _sync_log_checkbox(self) -> None:
179
- if (
180
- type(self._plotter).__name__
181
- in {
182
- "HistogramPlot",
183
- "DistributionPlot",
184
- "GaussianKDEPlot",
185
- }
186
- and self._negative_values_in_data is False
187
- ):
179
+ if type(self._plotter).__name__ in {
180
+ "HistogramPlot",
181
+ "DistributionPlot",
182
+ "GaussianKDEPlot",
183
+ }:
188
184
  self._log_checkbox.setVisible(True)
189
185
  else:
190
186
  self._log_checkbox.setVisible(False)
@@ -193,6 +189,10 @@ class PlotWidget(QWidget):
193
189
  def name(self) -> str:
194
190
  return self._name
195
191
 
192
+ def logLogScaleButtonUsage(self) -> None:
193
+ logger.info(f"Plotwidget utility used: 'Log scale button' in tab '{self.name}'")
194
+ self._log_checkbox.clicked.disconnect() # Log only once
195
+
196
196
  def updatePlot(
197
197
  self,
198
198
  plot_context: "PlotContext",
@@ -203,11 +203,8 @@ class PlotWidget(QWidget):
203
203
  ) -> None:
204
204
  self.resetPlot()
205
205
  try:
206
- self._sync_log_checkbox()
207
206
  plot_context.log_scale = (
208
- self._log_checkbox.isVisible()
209
- and self._log_checkbox.isChecked()
210
- and self._negative_values_in_data is False
207
+ self._log_checkbox.isVisible() and self._log_checkbox.isChecked()
211
208
  )
212
209
  self._plotter.plot(
213
210
  self._figure,
@@ -218,6 +215,7 @@ class PlotWidget(QWidget):
218
215
  key_def,
219
216
  )
220
217
  self._canvas.draw()
218
+ self._sync_log_checkbox()
221
219
  except Exception as e:
222
220
  exc_type, _, exc_tb = sys.exc_info()
223
221
  sys.stderr.write("-" * 80 + "\n")
@@ -25,7 +25,7 @@ from PyQt6.QtWidgets import (
25
25
  from ert.config.field import Field
26
26
  from ert.dark_storage.common import get_storage_api_version
27
27
  from ert.gui.ertwidgets import CopyButton, showWaitCursorWhileWaiting
28
- from ert.services._base_service import ServerBootFail
28
+ from ert.services import ServerBootFail
29
29
  from ert.utils import log_duration
30
30
 
31
31
  from .customize import PlotCustomizer
@@ -284,15 +284,6 @@ class PlotWindow(QMainWindow):
284
284
  except BaseException as e:
285
285
  handle_exception(e)
286
286
 
287
- negative_values_in_data = False
288
- if key_def.parameter is not None and key_def.parameter.type == "gen_kw":
289
- for data in ensemble_to_data_map.values():
290
- data = data.T
291
- if data.le(0).any().any():
292
- negative_values_in_data = True
293
- break
294
-
295
- plot_widget._negative_values_in_data = negative_values_in_data
296
287
  observations = None
297
288
  if key_def.observations and selected_ensembles:
298
289
  try:
ert/services/__init__.py CHANGED
@@ -1,4 +1,8 @@
1
- from .ert_server import ErtServer
2
- from .webviz_ert_service import WebvizErt
1
+ from .ert_server import (
2
+ ErtServer,
3
+ ErtServerExit,
4
+ ServerBootFail,
5
+ create_ertserver_client,
6
+ )
3
7
 
4
- __all__ = ["ErtServer", "WebvizErt"]
8
+ __all__ = ["ErtServer", "ErtServerExit", "ServerBootFail", "create_ertserver_client"]
@@ -13,6 +13,7 @@ import sys
13
13
  import threading
14
14
  import time
15
15
  import warnings
16
+ from argparse import ArgumentParser
16
17
  from base64 import b64encode
17
18
  from pathlib import Path
18
19
  from typing import Any
@@ -29,12 +30,11 @@ from uvicorn.supervisors import ChangeReload
29
30
 
30
31
  from ert.logging import STORAGE_LOG_CONFIG
31
32
  from ert.plugins import setup_site_logging
32
- from ert.services._base_service import BaseServiceExit
33
+ from ert.services import ErtServerExit
33
34
  from ert.shared import __file__ as ert_shared_path
34
35
  from ert.shared import find_available_socket, get_machine_name
35
- from ert.shared.storage.command import add_parser_options
36
36
  from ert.trace import tracer
37
- from everest.util import makedirs_if_needed
37
+ from ert.utils import makedirs_if_needed
38
38
 
39
39
  DARK_STORAGE_APP = "ert.dark_storage.app:app"
40
40
 
@@ -82,7 +82,7 @@ def _get_host_list() -> list[str]:
82
82
 
83
83
 
84
84
  def _create_connection_info(
85
- sock: socket.socket, authtoken: str, cert: str | os.PathLike[str]
85
+ sock: socket.socket, authtoken: str, cert: str | os.PathLike[str] | Path
86
86
  ) -> dict[str, Any]:
87
87
  connection_info = {
88
88
  "urls": [
@@ -91,7 +91,7 @@ def _create_connection_info(
91
91
  "authtoken": authtoken,
92
92
  "host": get_machine_name(),
93
93
  "port": sock.getsockname()[1],
94
- "cert": cert,
94
+ "cert": str(cert),
95
95
  "auth": authtoken,
96
96
  }
97
97
 
@@ -102,14 +102,17 @@ def _create_connection_info(
102
102
  return connection_info
103
103
 
104
104
 
105
- def _generate_certificate(cert_folder: str) -> tuple[str, str, bytes]:
105
+ def _generate_certificate(cert_folder: Path) -> tuple[Path, Path, bytes]:
106
106
  """Generate a private key and a certificate signed with it
107
107
 
108
108
  Both the certificate and the key are written to files in the folder given
109
109
  by `get_certificate_dir(config)`. The key is encrypted before being
110
110
  stored.
111
- Returns the path to the certificate file, the path to the key file, and
112
- the password used for encrypting the key
111
+
112
+ Returns a 3-tuple with
113
+ * Certificate file path
114
+ * Key file path
115
+ * Password used for encrypting the key
113
116
  """
114
117
  # Generate private key
115
118
  key = rsa.generate_private_key(
@@ -150,11 +153,11 @@ def _generate_certificate(cert_folder: str) -> tuple[str, str, bytes]:
150
153
 
151
154
  # Write certificate and key to disk
152
155
  makedirs_if_needed(cert_folder)
153
- cert_path = os.path.join(cert_folder, dns_name + ".crt")
154
- Path(cert_path).write_bytes(cert.public_bytes(serialization.Encoding.PEM))
155
- key_path = os.path.join(cert_folder, dns_name + ".key")
156
+ cert_path = cert_folder / f"{dns_name}.crt"
157
+ cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
158
+ key_path = cert_folder / f"{dns_name}.key"
156
159
  pw = bytes(os.urandom(28))
157
- Path(key_path).write_bytes(
160
+ key_path.write_bytes(
158
161
  key.private_bytes(
159
162
  encoding=serialization.Encoding.PEM,
160
163
  format=serialization.PrivateFormat.TraditionalOpenSSL,
@@ -184,16 +187,16 @@ def run_server(
184
187
 
185
188
  config_args: dict[str, Any] = {}
186
189
  if args.debug or debug:
187
- config_args.update(reload=True, reload_dirs=[os.path.dirname(ert_shared_path)])
190
+ config_args.update(reload=True, reload_dirs=[Path(ert_shared_path).parent])
188
191
  os.environ["ERT_STORAGE_DEBUG"] = "1"
189
192
 
190
- sock = find_available_socket(
193
+ sock: socket.socket = find_available_socket(
191
194
  host=get_machine_name(), port_range=range(51850, 51870 + 1)
192
195
  )
193
196
 
194
197
  # Appropriated from uvicorn.main:run
195
198
  os.environ["ERT_STORAGE_NO_TOKEN"] = "1"
196
- os.environ["ERT_STORAGE_ENS_PATH"] = os.path.abspath(args.project)
199
+ os.environ["ERT_STORAGE_ENS_PATH"] = str(args.project.absolute())
197
200
  config = (
198
201
  # uvicorn.Config() resets the logging config (overriding additional
199
202
  # handlers added to loggers like e.g. the ert_azurelogger handler
@@ -253,11 +256,11 @@ def _join_terminate_thread(terminate_on_parent_death_thread: threading.Thread) -
253
256
  """Join the terminate thread, handling BaseServiceExit (which is used by Everest)"""
254
257
  try:
255
258
  terminate_on_parent_death_thread.join()
256
- except BaseServiceExit:
259
+ except ErtServerExit:
257
260
  logger = logging.getLogger("ert.shared.storage.info")
258
261
  logger.info(
259
262
  "Got BaseServiceExit while joining terminate thread, "
260
- "as expected from _base_service.py"
263
+ "as expected from ert_server.py"
261
264
  )
262
265
 
263
266
 
@@ -265,9 +268,7 @@ def main() -> None:
265
268
  args = parse_args()
266
269
  authentication = _generate_authentication()
267
270
  os.environ["ERT_STORAGE_TOKEN"] = authentication
268
- cert_path, key_path, key_pw = _generate_certificate(
269
- os.path.join(args.project, "cert")
270
- )
271
+ cert_path, key_path, key_pw = _generate_certificate(args.project / "cert")
271
272
  config_args: dict[str, Any] = {
272
273
  "ssl_keyfile": key_path,
273
274
  "ssl_certfile": cert_path,
@@ -283,7 +284,7 @@ def main() -> None:
283
284
  warnings.filterwarnings("ignore", category=DeprecationWarning)
284
285
 
285
286
  if args.debug:
286
- config_args.update(reload=True, reload_dirs=[os.path.dirname(ert_shared_path)])
287
+ config_args.update(reload=True, reload_dirs=[Path(ert_shared_path).parent])
287
288
 
288
289
  # Need to run uvicorn.Config before entering the ErtPluginContext because
289
290
  # uvicorn.Config overrides the configuration of existing loggers, thus removing
@@ -310,12 +311,48 @@ def main() -> None:
310
311
  logger.info("Starting dark storage")
311
312
  logger.info(f"Started dark storage with parent {args.parent_pid}")
312
313
  run_server(args, debug=False, uvicorn_config=uvicorn_config)
313
- except (SystemExit, BaseServiceExit):
314
+ except (SystemExit, ErtServerExit):
314
315
  logger.info("Stopping dark storage")
315
316
  finally:
316
317
  stopped.set()
317
318
  _join_terminate_thread(terminate_on_parent_death_thread)
318
319
 
319
320
 
321
+ def add_parser_options(ap: ArgumentParser) -> None:
322
+ ap.add_argument(
323
+ "config",
324
+ type=str,
325
+ help=("ERT config file to start the server from "),
326
+ nargs="?", # optional
327
+ )
328
+ ap.add_argument(
329
+ "--project",
330
+ "-p",
331
+ type=Path,
332
+ help="Path to directory in which to create storage_server.json",
333
+ default=Path.cwd(),
334
+ )
335
+ ap.add_argument(
336
+ "--traceparent",
337
+ type=str,
338
+ help="Trace parent id to be used by the storage root span",
339
+ default=None,
340
+ )
341
+ ap.add_argument(
342
+ "--parent_pid",
343
+ type=int,
344
+ help="The parent process id",
345
+ default=os.getppid(),
346
+ )
347
+ ap.add_argument(
348
+ "--host", type=str, default=os.environ.get("ERT_STORAGE_HOST", "127.0.0.1")
349
+ )
350
+ ap.add_argument("--logging-config", type=str, default=None)
351
+ ap.add_argument(
352
+ "--verbose", action="store_true", help="Show verbose output.", default=False
353
+ )
354
+ ap.add_argument("--debug", action="store_true", default=False)
355
+
356
+
320
357
  if __name__ == "__main__":
321
358
  main()