climate-ref 0.6.1__tar.gz → 0.6.3__tar.gz

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 (81) hide show
  1. {climate_ref-0.6.1 → climate_ref-0.6.3}/PKG-INFO +4 -3
  2. {climate_ref-0.6.1 → climate_ref-0.6.3}/pyproject.toml +6 -3
  3. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/__init__.py +3 -3
  4. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/config.py +6 -6
  5. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/datasets.py +9 -2
  6. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/executions.py +12 -2
  7. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/providers.py +4 -1
  8. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/solve.py +4 -0
  9. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/config.py +2 -0
  10. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/executor/hpc.py +24 -12
  11. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/slurm.py +8 -4
  12. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_config.py +6 -6
  13. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_datasets.py +1 -1
  14. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_executions.py +1 -1
  15. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_root.py +2 -2
  16. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/executor/test_hpc_executor.py +28 -3
  17. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/test_config.py +8 -0
  18. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/test_slurm.py +4 -0
  19. {climate_ref-0.6.1 → climate_ref-0.6.3}/.gitignore +0 -0
  20. {climate_ref-0.6.1 → climate_ref-0.6.3}/Dockerfile +0 -0
  21. {climate_ref-0.6.1 → climate_ref-0.6.3}/LICENCE +0 -0
  22. {climate_ref-0.6.1 → climate_ref-0.6.3}/NOTICE +0 -0
  23. {climate_ref-0.6.1 → climate_ref-0.6.3}/README.md +0 -0
  24. {climate_ref-0.6.1 → climate_ref-0.6.3}/conftest.py +0 -0
  25. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/__init__.py +0 -0
  26. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/_config_helpers.py +0 -0
  27. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/alembic.ini +0 -0
  28. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/cli/_utils.py +0 -0
  29. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/constants.py +0 -0
  30. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/database.py +0 -0
  31. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/dataset_registry/obs4ref_reference.txt +0 -0
  32. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/dataset_registry/sample_data.txt +0 -0
  33. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/datasets/__init__.py +0 -0
  34. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/datasets/base.py +0 -0
  35. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/datasets/cmip6.py +0 -0
  36. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/datasets/obs4mips.py +0 -0
  37. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/datasets/pmp_climatology.py +0 -0
  38. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/datasets/utils.py +0 -0
  39. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/executor/__init__.py +0 -0
  40. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/executor/local.py +0 -0
  41. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/executor/result_handling.py +0 -0
  42. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/executor/synchronous.py +0 -0
  43. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/migrations/README +0 -0
  44. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/migrations/env.py +0 -0
  45. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/migrations/script.py.mako +0 -0
  46. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/migrations/versions/2025-05-02T1418_341a4aa2551e_regenerate.py +0 -0
  47. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/migrations/versions/2025-05-09T2032_03dbb4998e49_series_metric_value.py +0 -0
  48. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/__init__.py +0 -0
  49. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/base.py +0 -0
  50. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/dataset.py +0 -0
  51. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/diagnostic.py +0 -0
  52. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/execution.py +0 -0
  53. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/metric_value.py +0 -0
  54. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/models/provider.py +0 -0
  55. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/provider_registry.py +0 -0
  56. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/py.typed +0 -0
  57. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/solver.py +0 -0
  58. {climate_ref-0.6.1 → climate_ref-0.6.3}/src/climate_ref/testing.py +0 -0
  59. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_executions/test_inspect.txt +0 -0
  60. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_providers.py +0 -0
  61. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/cli/test_solve.py +0 -0
  62. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/conftest.py +0 -0
  63. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_cmip6/cmip6_catalog_db.yml +0 -0
  64. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_cmip6/cmip6_catalog_local.yml +0 -0
  65. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_cmip6.py +0 -0
  66. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_datasets.py +0 -0
  67. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_obs4mips/obs4mips_catalog_db.yml +0 -0
  68. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_obs4mips/obs4mips_catalog_local.yml +0 -0
  69. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_obs4mips.py +0 -0
  70. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_pmp_climatology/pmp_catalog_local.yml +0 -0
  71. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_pmp_climatology.py +0 -0
  72. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/datasets/test_utils.py +0 -0
  73. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/executor/test_local_executor.py +0 -0
  74. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/executor/test_result_handling.py +0 -0
  75. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/executor/test_synchronous_executor.py +0 -0
  76. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/models/test_metric_execution.py +0 -0
  77. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/models/test_metric_value.py +0 -0
  78. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/test_database.py +0 -0
  79. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/test_provider_registry.py +0 -0
  80. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/test_solver/test_solve_metrics.yml +0 -0
  81. {climate_ref-0.6.1 → climate_ref-0.6.3}/tests/unit/test_solver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: climate-ref
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Application which runs the CMIP Rapid Evaluation Framework
5
5
  Author-email: Jared Lewis <jared.lewis@climate-resource.com>, Mika Pflueger <mika.pflueger@climate-resource.com>, Bouwe Andela <b.andela@esciencecenter.nl>, Jiwoo Lee <lee1043@llnl.gov>, Min Xu <xum1@ornl.gov>, Nathan Collier <collierno@ornl.gov>, Dora Hegedus <dora.hegedus@stfc.ac.uk>
6
6
  License-Expression: Apache-2.0
@@ -10,7 +10,8 @@ Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Intended Audience :: Science/Research
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Operating System :: OS Independent
13
+ Classifier: Operating System :: MacOS :: MacOS X
14
+ Classifier: Operating System :: POSIX :: Linux
14
15
  Classifier: Programming Language :: Python
15
16
  Classifier: Programming Language :: Python :: 3
16
17
  Classifier: Programming Language :: Python :: 3.11
@@ -25,7 +26,7 @@ Requires-Dist: climate-ref-core
25
26
  Requires-Dist: ecgtools>=2024.7.31
26
27
  Requires-Dist: environs>=11.0.0
27
28
  Requires-Dist: loguru>=0.7.2
28
- Requires-Dist: parsl>=2025.5.19
29
+ Requires-Dist: parsl>=2025.5.19; sys_platform != 'win32'
29
30
  Requires-Dist: platformdirs>=4.3.6
30
31
  Requires-Dist: sqlalchemy>=2.0.36
31
32
  Requires-Dist: tomlkit>=0.13.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "climate-ref"
3
- version = "0.6.1"
3
+ version = "0.6.3"
4
4
  description = "Application which runs the CMIP Rapid Evaluation Framework"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -16,7 +16,6 @@ license = "Apache-2.0"
16
16
  requires-python = ">=3.11"
17
17
  classifiers = [
18
18
  "Development Status :: 3 - Alpha",
19
- "Operating System :: OS Independent",
20
19
  "Intended Audience :: Developers",
21
20
  "Intended Audience :: Science/Research",
22
21
  "Programming Language :: Python",
@@ -26,6 +25,8 @@ classifiers = [
26
25
  "Programming Language :: Python :: 3.13",
27
26
  "Topic :: Scientific/Engineering",
28
27
  "License :: OSI Approved :: Apache Software License",
28
+ "Operating System :: MacOS :: MacOS X",
29
+ "Operating System :: POSIX :: Linux",
29
30
  ]
30
31
  dependencies = [
31
32
  "climate-ref-core",
@@ -40,7 +41,9 @@ dependencies = [
40
41
  "ecgtools>=2024.7.31",
41
42
  "platformdirs>=4.3.6",
42
43
  "tqdm>=4.67.1",
43
- "parsl>=2025.5.19"
44
+ # parsl doesn't support Windows yet
45
+ # We don't target Windows either, but this __might__ allow Windows users to install the package
46
+ 'parsl>=2025.5.19; sys_platform != "win32"'
44
47
  ]
45
48
 
46
49
  [project.optional-dependencies]
@@ -88,7 +88,7 @@ def build_app() -> typer.Typer:
88
88
  :
89
89
  The CLI app
90
90
  """
91
- app = typer.Typer(name="climate_ref", no_args_is_help=True)
91
+ app = typer.Typer(name="ref", no_args_is_help=True)
92
92
 
93
93
  app.command(name="solve")(solve.solve)
94
94
  app.add_typer(config.app, name="config")
@@ -136,10 +136,10 @@ def main( # noqa: PLR0913
136
136
  ] = None,
137
137
  ) -> None:
138
138
  """
139
- climate_ref: A CLI for the Assessment Fast Track Rapid Evaluation Framework
139
+ A CLI for the Assessment Fast Track Rapid Evaluation Framework
140
140
 
141
141
  This CLI provides a number of commands for managing and executing diagnostics.
142
- """
142
+ """ # noqa: D401
143
143
  if quiet:
144
144
  log_level = LogLevel.Warning
145
145
  if verbose:
@@ -20,9 +20,9 @@ def list_(ctx: typer.Context) -> None:
20
20
  print(config.dumps(defaults=True))
21
21
 
22
22
 
23
- @app.command()
24
- def update() -> None:
25
- """
26
- Update a configuration value
27
- """
28
- print("config")
23
+ # @app.command()
24
+ # def update() -> None:
25
+ # """
26
+ # Update a configuration value
27
+ # """
28
+ # print("config")
@@ -1,5 +1,9 @@
1
1
  """
2
2
  View and ingest input datasets
3
+
4
+ The metadata from these datasets are stored in the database so that they can be used to determine
5
+ which executions are required for a given diagnostic without having to re-parse the datasets.
6
+
3
7
  """
4
8
 
5
9
  import errno
@@ -105,9 +109,12 @@ def ingest( # noqa: PLR0913
105
109
  ] = False,
106
110
  ) -> None:
107
111
  """
108
- Ingest a dataset
112
+ Ingest a directory of datasets into the database
113
+
114
+ Each dataset will be loaded and validated using the specified dataset adapter.
115
+ This will extract metadata from the datasets and store it in the database.
109
116
 
110
- This will register a dataset in the database to be used for diagnostics calculations.
117
+ A table of the datasets will be printed to the console at the end of the operation.
111
118
  """
112
119
  config = ctx.obj.config
113
120
  db = ctx.obj.database
@@ -1,5 +1,5 @@
1
1
  """
2
- View diagnostic executions
2
+ View execution groups and their results
3
3
  """
4
4
 
5
5
  import pathlib
@@ -29,11 +29,19 @@ console = Console()
29
29
  @app.command()
30
30
  def list_groups(
31
31
  ctx: typer.Context,
32
- column: Annotated[list[str] | None, typer.Option()] = None,
32
+ column: Annotated[
33
+ list[str] | None,
34
+ typer.Option(help="Only include specified columns in the output"),
35
+ ] = None,
33
36
  limit: int = typer.Option(100, help="Limit the number of rows to display"),
34
37
  ) -> None:
35
38
  """
36
39
  List the diagnostic execution groups that have been identified
40
+
41
+ The data catalog is sorted by the date that the execution group was created (first = newest).
42
+ If the `--column` option is provided, only the specified columns will be displayed.
43
+
44
+ The output will be in a tabular format.
37
45
  """
38
46
  session = ctx.obj.database.session
39
47
 
@@ -178,6 +186,8 @@ def _log_panel(result_directory: pathlib.Path) -> Panel | None:
178
186
  def inspect(ctx: typer.Context, execution_id: int) -> None:
179
187
  """
180
188
  Inspect a specific execution group by its ID
189
+
190
+ This will display the execution details, datasets, results directory, and logs if available.
181
191
  """
182
192
  config: Config = ctx.obj.config
183
193
  session = ctx.obj.database.session
@@ -56,7 +56,10 @@ def create_env(
56
56
  ] = None,
57
57
  ) -> None:
58
58
  """
59
- Create a virtual environment containing the provider software.
59
+ Create a conda environment containing the provider software.
60
+
61
+ If no provider is specified, all providers will be installed.
62
+ If the provider is up to date or does not use a virtual environment, it will be skipped.
60
63
  """
61
64
  config = ctx.obj.config
62
65
  db = ctx.obj.database
@@ -49,6 +49,10 @@ def solve( # noqa: PLR0913
49
49
 
50
50
  This may trigger a number of additional calculations depending on what data has been ingested
51
51
  since the last solve.
52
+ This command will block until all executions have been solved or the timeout is reached.
53
+
54
+ Filters can be applied to limit the diagnostics and providers that are considered, see the options
55
+ `--diagnostic` and `--provider` for more information.
52
56
  """
53
57
  config = ctx.obj.config
54
58
  db = ctx.obj.database
@@ -15,6 +15,7 @@ which always take precedence over any other configuration values.
15
15
  # https://github.com/ESGF/esgf-download/blob/main/esgpull/config.py
16
16
 
17
17
  import importlib.resources
18
+ import os
18
19
  from pathlib import Path
19
20
  from typing import TYPE_CHECKING, Any
20
21
 
@@ -64,6 +65,7 @@ def ensure_absolute_path(path: str | Path) -> Path:
64
65
  """
65
66
  if isinstance(path, str):
66
67
  path = Path(path)
68
+ path = Path(*[os.path.expandvars(p) for p in path.parts])
67
69
  return path.resolve()
68
70
 
69
71
 
@@ -7,9 +7,14 @@ If you want to
7
7
 
8
8
  """
9
9
 
10
+ try:
11
+ import parsl
12
+ except ImportError: # pragma: no cover
13
+ raise ImportError("The HPCExecutor requires the `parsl` package")
14
+
10
15
  import os
11
16
  import time
12
- from typing import Any, cast
17
+ from typing import Any
13
18
 
14
19
  import parsl
15
20
  from loguru import logger
@@ -41,7 +46,7 @@ def _process_run(definition: ExecutionDefinition, log_level: str) -> ExecutionRe
41
46
  except DiagnosticError as e: # pragma: no cover
42
47
  # any diagnostic error will be caught here
43
48
  logger.exception("Error running diagnostic")
44
- return cast(ExecutionResult, e.result)
49
+ raise e
45
50
 
46
51
 
47
52
  def _to_float(x: Any) -> float | None:
@@ -264,20 +269,27 @@ class HPCExecutor:
264
269
  # Iterate over a copy of the list and remove finished tasks
265
270
  for result in results[:]:
266
271
  if result.future.done():
267
- try:
268
- execution_result = result.future.result(timeout=0)
269
- except Exception as e:
270
- # Something went wrong when attempting to run the execution
271
- # This is likely a failure in the execution itself not the diagnostic
272
- raise ExecutionError(
273
- f"Failed to execute {result.definition.execution_slug()!r}"
274
- ) from e
272
+ # Cannot catch the execption raised by result.future.result
273
+ if result.future.exception() is None:
274
+ try:
275
+ execution_result = result.future.result(timeout=0)
276
+ except Exception as e:
277
+ # Something went wrong when attempting to run the execution
278
+ # This is likely a failure in the execution itself not the diagnostic
279
+ raise ExecutionError(
280
+ f"Failed to execute {result.definition.execution_slug()!r}"
281
+ ) from e
282
+ else:
283
+ err = result.future.exception()
284
+ if isinstance(err, DiagnosticError):
285
+ execution_result = err.result
286
+ else:
287
+ execution_result = None
275
288
 
276
289
  assert execution_result is not None, "Execution result should not be None"
277
290
  assert isinstance(execution_result, ExecutionResult), (
278
291
  "Execution result should be of type ExecutionResult"
279
292
  )
280
-
281
293
  # Process the result in the main process
282
294
  # The results should be committed after each execution
283
295
  with self.database.session.begin():
@@ -286,7 +298,7 @@ class HPCExecutor:
286
298
  if result.execution_id
287
299
  else None
288
300
  )
289
- process_result(self.config, self.database, result.future.result(), execution)
301
+ process_result(self.config, self.database, execution_result, execution)
290
302
  logger.debug(f"Execution completed: {result}")
291
303
  t.update(n=1)
292
304
  results.remove(result)
@@ -1,4 +1,5 @@
1
1
  import importlib.util
2
+ import os
2
3
  from typing import Any
3
4
 
4
5
  HAS_REAL_SLURM = importlib.util.find_spec("pyslurm") is not None
@@ -85,10 +86,13 @@ class SlurmChecker:
85
86
  return False
86
87
 
87
88
  sample_acc = account_info[0]
88
- for acc in account_info:
89
- if acc.user == "minxu":
90
- sample_acc = acc
91
- break
89
+ user_name = os.environ["USER"]
90
+
91
+ if user_name:
92
+ for acc in account_info:
93
+ if acc.user == user_name:
94
+ sample_acc = acc
95
+ break
92
96
 
93
97
  allowed_qoss = sample_acc.qos
94
98
  if allowed_qoss is None:
@@ -30,9 +30,9 @@ class TestConfigList:
30
30
  assert "Configuration file not found" in result.stdout
31
31
 
32
32
 
33
- class TestConfigUpdate:
34
- def test_config_update(self, invoke_cli):
35
- result = invoke_cli(["config", "update"])
36
-
37
- # TODO: actually implement this functionality
38
- assert "config" in result.stdout
33
+ # class TestConfigUpdate:
34
+ # def test_config_update(self, invoke_cli):
35
+ # result = invoke_cli(["config", "update"])
36
+ #
37
+ # # TODO: actually implement this functionality
38
+ # assert "config" in result.stdout
@@ -11,7 +11,7 @@ from climate_ref.models.dataset import CMIP6Dataset, DatasetFile
11
11
  def test_ingest_help(invoke_cli):
12
12
  result = invoke_cli(["datasets", "ingest", "--help"])
13
13
 
14
- assert "Ingest a dataset" in result.stdout
14
+ assert "Ingest a directory of dataset" in result.stdout
15
15
 
16
16
 
17
17
  class TestDatasetsList:
@@ -11,7 +11,7 @@ from climate_ref.models.execution import execution_datasets
11
11
  def test_execution_help(invoke_cli):
12
12
  result = invoke_cli(["executions", "--help"])
13
13
 
14
- assert "View diagnostic executions" in result.stdout
14
+ assert "View execution groups" in result.stdout
15
15
 
16
16
 
17
17
  class TestExecutionList:
@@ -16,8 +16,8 @@ def escape_ansi(line):
16
16
  def test_without_subcommand(invoke_cli):
17
17
  result = invoke_cli([])
18
18
  assert "Usage:" in result.stdout
19
- assert "climate_ref [OPTIONS] COMMAND [ARGS]" in result.stdout
20
- assert "climate_ref: A CLI for the Assessment Fast Track Rapid Evaluation Framework" in result.stdout
19
+ assert "ref [OPTIONS] COMMAND [ARGS]" in result.stdout
20
+ assert "A CLI for the Assessment Fast Track Rapid Evaluation Framework" in result.stdout
21
21
 
22
22
 
23
23
  def test_version(invoke_cli):
@@ -8,7 +8,7 @@ from parsl.dataflow import futures
8
8
  from climate_ref.executor.hpc import HPCExecutor, execute_locally
9
9
  from climate_ref.executor.local import ExecutionFuture
10
10
  from climate_ref_core.diagnostics import ExecutionResult
11
- from climate_ref_core.exceptions import DiagnosticError, ExecutionError
11
+ from climate_ref_core.exceptions import DiagnosticError
12
12
  from climate_ref_core.executor import Executor
13
13
 
14
14
 
@@ -74,12 +74,37 @@ class TestHPCExecutor:
74
74
 
75
75
  assert len(executor.parsl_results) == 0
76
76
 
77
- def test_join_exception(self, metric_definition, tmp_path):
77
+ def test_join_diagnostic_exception(self, metric_definition, tmp_path):
78
+ executor = HPCExecutor(log_dir=tmp_path / "parsl_runinfo")
79
+ future = futures.AppFuture(1)
80
+ executor.parsl_results = [ExecutionFuture(future, definition=metric_definition, execution_id=None)]
81
+
82
+ execution_result = ExecutionResult(
83
+ definition=metric_definition,
84
+ successful=False,
85
+ output_bundle_filename=None,
86
+ metric_bundle_filename=None,
87
+ )
88
+
89
+ future.set_exception(DiagnosticError("Some thing bad went wrong", execution_result))
90
+ err_result = executor.parsl_results[0].future.exception().result
91
+
92
+ executor.join(0.1)
93
+
94
+ assert err_result == ExecutionResult(
95
+ definition=metric_definition,
96
+ successful=False,
97
+ output_bundle_filename=None,
98
+ metric_bundle_filename=None,
99
+ )
100
+ assert len(executor.parsl_results) == 0
101
+
102
+ def test_join_other_exception(self, metric_definition, tmp_path):
78
103
  executor = HPCExecutor(log_dir=tmp_path / "parsl_runinfo")
79
104
  future = futures.AppFuture(1)
80
105
  executor.parsl_results = [ExecutionFuture(future, definition=metric_definition, execution_id=None)]
81
106
 
82
107
  future.set_exception(ValueError("Some thing bad went wrong"))
83
108
 
84
- with pytest.raises(ExecutionError, match=re.escape("Failed to execute 'mock_provider/mock/key'")):
109
+ with pytest.raises(AssertionError, match=re.escape("Execution result should not be None")):
85
110
  executor.join(0.1)
@@ -183,6 +183,14 @@ filename = "sqlite://climate_ref.db"
183
183
  assert config_new.paths.log == Path("/my/test/logs")
184
184
  assert config_new.paths.results == Path("/my/test/executions")
185
185
 
186
+ def test_custom_env_variable(self, monkeypatch, tmp_path, config):
187
+ monkeypatch.setenv("ABC", "/my")
188
+ config.paths.results = "${ABC}/test/executions"
189
+ # Environment variables are only expanded when loading from file.
190
+ config.save(tmp_path / "ref.toml")
191
+ config_new = Config.load(tmp_path / "ref.toml")
192
+ assert config_new.paths.results == Path("/my/test/executions")
193
+
186
194
  def test_executor_build(self, config, db):
187
195
  executor = config.executor.build(config, db)
188
196
  assert executor.name == "synchronous"
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from unittest.mock import MagicMock
2
3
 
3
4
  import pytest
@@ -343,6 +344,9 @@ def test_slurm_checker(
343
344
  assert checker.can_account_use_qos("climate_ref1", "normal") is True
344
345
  assert checker.can_account_use_qos("climate_ref3", "normal") is False
345
346
 
347
+ os.environ["USER"] = "test_user"
348
+ assert checker.can_account_use_qos("climate_ref1", "normal") is True
349
+
346
350
  assert checker.get_partition_limits("cpu") == {
347
351
  "max_time_minutes": 7200,
348
352
  "default_time_minutes": 720,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes