climate-ref 0.6.5__py3-none-any.whl → 0.7.0__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 (40) hide show
  1. climate_ref/cli/__init__.py +12 -3
  2. climate_ref/cli/_utils.py +56 -2
  3. climate_ref/cli/datasets.py +48 -9
  4. climate_ref/cli/executions.py +351 -24
  5. climate_ref/cli/providers.py +1 -2
  6. climate_ref/config.py +4 -4
  7. climate_ref/database.py +62 -4
  8. climate_ref/dataset_registry/obs4ref_reference.txt +0 -9
  9. climate_ref/dataset_registry/sample_data.txt +269 -107
  10. climate_ref/datasets/__init__.py +3 -3
  11. climate_ref/datasets/base.py +121 -20
  12. climate_ref/datasets/cmip6.py +2 -0
  13. climate_ref/datasets/obs4mips.py +26 -15
  14. climate_ref/executor/__init__.py +8 -1
  15. climate_ref/executor/hpc.py +7 -1
  16. climate_ref/executor/result_handling.py +151 -64
  17. climate_ref/migrations/env.py +12 -10
  18. climate_ref/migrations/versions/2025-07-20T1521_94beace57a9c_cmip6_finalised.py +1 -1
  19. climate_ref/migrations/versions/2025-08-05T0327_a1b2c3d4e5f6_finalised_on_base_dataset.py +1 -1
  20. climate_ref/migrations/versions/2025-09-05T2019_8d28e5e0f9c3_add_indexes.py +108 -0
  21. climate_ref/migrations/versions/2025-09-10T1358_2f6e36738e06_use_version_as_version_facet_for_.py +35 -0
  22. climate_ref/migrations/versions/2025-09-22T2359_20cd136a5b04_add_pmp_version.py +35 -0
  23. climate_ref/models/__init__.py +1 -6
  24. climate_ref/models/base.py +4 -18
  25. climate_ref/models/dataset.py +10 -6
  26. climate_ref/models/diagnostic.py +2 -1
  27. climate_ref/models/execution.py +225 -12
  28. climate_ref/models/metric_value.py +27 -112
  29. climate_ref/models/mixins.py +144 -0
  30. climate_ref/models/provider.py +2 -1
  31. climate_ref/provider_registry.py +4 -4
  32. climate_ref/slurm.py +2 -2
  33. climate_ref/testing.py +1 -1
  34. {climate_ref-0.6.5.dist-info → climate_ref-0.7.0.dist-info}/METADATA +2 -2
  35. climate_ref-0.7.0.dist-info/RECORD +58 -0
  36. climate_ref-0.6.5.dist-info/RECORD +0 -54
  37. {climate_ref-0.6.5.dist-info → climate_ref-0.7.0.dist-info}/WHEEL +0 -0
  38. {climate_ref-0.6.5.dist-info → climate_ref-0.7.0.dist-info}/entry_points.txt +0 -0
  39. {climate_ref-0.6.5.dist-info → climate_ref-0.7.0.dist-info}/licenses/LICENCE +0 -0
  40. {climate_ref-0.6.5.dist-info → climate_ref-0.7.0.dist-info}/licenses/NOTICE +0 -0
@@ -7,14 +7,12 @@ from typing import Annotated
7
7
  import pandas as pd
8
8
  import typer
9
9
  from loguru import logger
10
- from rich.console import Console
11
10
 
12
11
  from climate_ref.cli._utils import pretty_print_df
13
12
  from climate_ref.provider_registry import ProviderRegistry
14
13
  from climate_ref_core.providers import CondaDiagnosticProvider, DiagnosticProvider
15
14
 
16
15
  app = typer.Typer(help=__doc__)
17
- console = Console()
18
16
 
19
17
 
20
18
  @app.command(name="list")
@@ -24,6 +22,7 @@ def list_(ctx: typer.Context) -> None:
24
22
  """
25
23
  config = ctx.obj.config
26
24
  db = ctx.obj.database
25
+ console = ctx.obj.console
27
26
  provider_registry = ProviderRegistry.build_from_config(config, db)
28
27
 
29
28
  def get_env(provider: DiagnosticProvider) -> str:
climate_ref/config.py CHANGED
@@ -364,10 +364,10 @@ class Config:
364
364
  - `complete`: Use the complete parser, which parses the dataset based on all available metadata.
365
365
  """
366
366
 
367
- paths: PathConfig = Factory(PathConfig) # noqa
368
- db: DbConfig = Factory(DbConfig) # noqa
369
- executor: ExecutorConfig = Factory(ExecutorConfig) # noqa
370
- diagnostic_providers: list[DiagnosticProviderConfig] = Factory(default_providers) # noqa
367
+ paths: PathConfig = Factory(PathConfig)
368
+ db: DbConfig = Factory(DbConfig)
369
+ executor: ExecutorConfig = Factory(ExecutorConfig)
370
+ diagnostic_providers: list[DiagnosticProviderConfig] = Factory(default_providers) # noqa: RUF009, RUF100
371
371
  _raw: TOMLDocument | None = field(init=False, default=None, repr=False)
372
372
  _config_file: Path | None = field(init=False, default=None, repr=False)
373
373
 
climate_ref/database.py CHANGED
@@ -8,6 +8,7 @@ The `Database` class is the main entry point for interacting with the database.
8
8
  It provides a session object that can be used to interact with the database and run queries.
9
9
  """
10
10
 
11
+ import enum
11
12
  import importlib.resources
12
13
  import shutil
13
14
  from datetime import datetime
@@ -23,6 +24,7 @@ from loguru import logger
23
24
  from sqlalchemy.orm import Session
24
25
 
25
26
  from climate_ref.models import MetricValue, Table
27
+ from climate_ref.models.execution import ExecutionOutput
26
28
  from climate_ref_core.pycmec.controlled_vocabulary import CV
27
29
 
28
30
  if TYPE_CHECKING:
@@ -135,6 +137,16 @@ def validate_database_url(database_url: str) -> str:
135
137
  return database_url
136
138
 
137
139
 
140
+ class ModelState(enum.Enum):
141
+ """
142
+ State of a model instance
143
+ """
144
+
145
+ CREATED = "created"
146
+ UPDATED = "updated"
147
+ DELETED = "deleted"
148
+
149
+
138
150
  class Database:
139
151
  """
140
152
  Manage the database connection and migrations
@@ -234,11 +246,57 @@ class Database:
234
246
  # This will add new columns to the db if the CVs have changed
235
247
  MetricValue.register_cv_dimensions(cv)
236
248
 
249
+ # Register the CV dimensions with the ExecutionOutput model
250
+ # This enables dimension-based filtering of outputs
251
+ ExecutionOutput.register_cv_dimensions(cv)
252
+
237
253
  return db
238
254
 
255
+ def update_or_create(
256
+ self, model: type[Table], defaults: dict[str, Any] | None = None, **kwargs: Any
257
+ ) -> tuple[Table, ModelState | None]:
258
+ """
259
+ Update an existing instance or create a new one
260
+
261
+ This doesn't commit the transaction,
262
+ so you will need to call `session.commit()` after this method
263
+ or use a transaction context manager.
264
+
265
+ Parameters
266
+ ----------
267
+ model
268
+ The model to update or create
269
+ defaults
270
+ Default values to use when creating a new instance, or values to update on existing instance
271
+ kwargs
272
+ The filter parameters to use when querying for an instance
273
+
274
+ Returns
275
+ -------
276
+ :
277
+ A tuple containing the instance and a state enum indicating if the instance was created or updated
278
+ """
279
+ instance = self.session.query(model).filter_by(**kwargs).first()
280
+ state: ModelState | None = None
281
+ if instance:
282
+ # Update existing instance with defaults
283
+ if defaults:
284
+ for key, value in defaults.items():
285
+ if getattr(instance, key) != value:
286
+ logger.debug(f"Updating {model.__name__} {key} to {value}")
287
+ setattr(instance, key, value)
288
+ state = ModelState.UPDATED
289
+ return instance, state
290
+ else:
291
+ # Create new instance
292
+ params = {**kwargs, **(defaults or {})}
293
+ instance = model(**params)
294
+ self.session.add(instance)
295
+ return instance, ModelState.CREATED
296
+
239
297
  def get_or_create(
240
298
  self, model: type[Table], defaults: dict[str, Any] | None = None, **kwargs: Any
241
- ) -> tuple[Table, bool]:
299
+ ) -> tuple[Table, ModelState | None]:
242
300
  """
243
301
  Get or create an instance of a model
244
302
 
@@ -258,13 +316,13 @@ class Database:
258
316
  Returns
259
317
  -------
260
318
  :
261
- A tuple containing the instance and a boolean indicating if the instance was created
319
+ A tuple containing the instance and enum indicating if the instance was created
262
320
  """
263
321
  instance = self.session.query(model).filter_by(**kwargs).first()
264
322
  if instance:
265
- return instance, False
323
+ return instance, None
266
324
  else:
267
325
  params = {**kwargs, **(defaults or {})}
268
326
  instance = model(**params)
269
327
  self.session.add(instance)
270
- return instance, True
328
+ return instance, ModelState.CREATED
@@ -5,15 +5,6 @@ obs4REF/ColumbiaU/WECANN-1-0/mon/hfls/gn/20250516/hfls_mon_WECANN-1-0_REF_gn_200
5
5
  obs4REF/ColumbiaU/WECANN-1-0/mon/hfss/gn/20250516/hfss_mon_WECANN-1-0_REF_gn_200701-201512.nc md5:b7a911e0fc164d07d3ab42a86d09b18b
6
6
  obs4REF/ECMWF/ERA-20C/mon/psl/gn/v20210727/psl_mon_ERA-20C_PCMDI_gn_190001-201012.nc md5:c100cf25d5681c375cd6c1ee60b678ba
7
7
  obs4REF/ECMWF/ERA-20C/mon/ts/gn/v20210727/ts_mon_ERA-20C_PCMDI_gn_190001-201012.nc md5:9ed8dfbb805ed4caa282ed70f873a3a0
8
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_200701-200712.nc md5:695633a2b401cfb66c8addbf58073dbc
9
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_200801-200812.nc md5:404f1e1f111859be06c00bcb8d740ff2
10
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_200901-200912.nc md5:a1bb8584d60cdd71154c01a692fa1fb4
11
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_201001-201012.nc md5:b78016a3c61d99dc0fd29563aa344ca1
12
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_201101-201112.nc md5:d64c231a7f798a255997ffe196613ea1
13
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_201201-201212.nc md5:7d90ce60b872dc4f044b9b0101114983
14
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_201301-201312.nc md5:2fc032707cb8a31ac60fa4abe9efe183
15
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_201401-201412.nc md5:6022d17e11df7818f5b0429d6e401d17
16
- obs4REF/ECMWF/ERA-5/mon/ta/gn/v20250220/ta_mon_ERA-5_PCMDI_gn_201501-201512.nc md5:c68fdabf6eeb4813befceace089c9494
17
8
  obs4REF/ECMWF/ERA-INT/mon/hfls/gn/v20210727/hfls_mon_ERA-INT_PCMDI_gn_197901-201903.nc md5:1ae4587143f05ee81432b3d9960aab63
18
9
  obs4REF/ECMWF/ERA-INT/mon/hfss/gn/v20210727/hfss_mon_ERA-INT_PCMDI_gn_197901-201903.nc md5:261f02b8cbce18486548882a11f9aa34
19
10
  obs4REF/ECMWF/ERA-INT/mon/hur/gn/v20210727/hur_mon_ERA-INT_PCMDI_gn_198901-201001.nc md5:56fcd2df8ed2879f18b5e8c78134a148