climate-ref 0.5.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 (44) hide show
  1. climate_ref/__init__.py +30 -0
  2. climate_ref/_config_helpers.py +214 -0
  3. climate_ref/alembic.ini +114 -0
  4. climate_ref/cli/__init__.py +138 -0
  5. climate_ref/cli/_utils.py +68 -0
  6. climate_ref/cli/config.py +28 -0
  7. climate_ref/cli/datasets.py +205 -0
  8. climate_ref/cli/executions.py +201 -0
  9. climate_ref/cli/providers.py +84 -0
  10. climate_ref/cli/solve.py +23 -0
  11. climate_ref/config.py +475 -0
  12. climate_ref/constants.py +8 -0
  13. climate_ref/database.py +223 -0
  14. climate_ref/dataset_registry/obs4ref_reference.txt +2 -0
  15. climate_ref/dataset_registry/sample_data.txt +60 -0
  16. climate_ref/datasets/__init__.py +40 -0
  17. climate_ref/datasets/base.py +214 -0
  18. climate_ref/datasets/cmip6.py +202 -0
  19. climate_ref/datasets/obs4mips.py +224 -0
  20. climate_ref/datasets/pmp_climatology.py +15 -0
  21. climate_ref/datasets/utils.py +16 -0
  22. climate_ref/executor/__init__.py +274 -0
  23. climate_ref/executor/local.py +89 -0
  24. climate_ref/migrations/README +22 -0
  25. climate_ref/migrations/env.py +139 -0
  26. climate_ref/migrations/script.py.mako +26 -0
  27. climate_ref/migrations/versions/2025-05-02T1418_341a4aa2551e_regenerate.py +292 -0
  28. climate_ref/models/__init__.py +33 -0
  29. climate_ref/models/base.py +42 -0
  30. climate_ref/models/dataset.py +206 -0
  31. climate_ref/models/diagnostic.py +61 -0
  32. climate_ref/models/execution.py +306 -0
  33. climate_ref/models/metric_value.py +195 -0
  34. climate_ref/models/provider.py +39 -0
  35. climate_ref/provider_registry.py +146 -0
  36. climate_ref/py.typed +0 -0
  37. climate_ref/solver.py +395 -0
  38. climate_ref/testing.py +109 -0
  39. climate_ref-0.5.0.dist-info/METADATA +97 -0
  40. climate_ref-0.5.0.dist-info/RECORD +44 -0
  41. climate_ref-0.5.0.dist-info/WHEEL +4 -0
  42. climate_ref-0.5.0.dist-info/entry_points.txt +2 -0
  43. climate_ref-0.5.0.dist-info/licenses/LICENCE +201 -0
  44. climate_ref-0.5.0.dist-info/licenses/NOTICE +3 -0
@@ -0,0 +1,139 @@
1
+ from alembic import context, op
2
+ from loguru import logger
3
+ from sqlalchemy import Connection, inspect
4
+
5
+ from climate_ref.config import Config
6
+ from climate_ref.database import Database
7
+ from climate_ref.models import Base, MetricValue
8
+ from climate_ref_core.logging import capture_logging
9
+ from climate_ref_core.pycmec.controlled_vocabulary import CV
10
+
11
+ # Setup logging
12
+ capture_logging()
13
+ logger.debug("Running alembic env")
14
+
15
+ # this is the Alembic Config object, which provides
16
+ # access to the values within the .ini file in use.
17
+ config = context.config
18
+ ref_config: Config = config.attributes.get("ref_config") or Config.default()
19
+
20
+ target_metadata = Base.metadata
21
+
22
+ # other values from the config, defined by the needs of env.py,
23
+ # can be acquired:
24
+ # my_important_option = config.get_main_option("my_important_option")
25
+ # ... etc.
26
+
27
+
28
+ # Custom migration functions that are run on every migration
29
+
30
+
31
+ def _add_metric_value_columns(connection: Connection) -> None:
32
+ """
33
+ Add any missing columns in the current CV to the database
34
+
35
+ This must be run in online mode
36
+
37
+ Parameters
38
+ ----------
39
+ connection
40
+ Open connection to the database
41
+ """
42
+ metric_value_table = "metric_value"
43
+
44
+ inspector = inspect(connection)
45
+
46
+ # Check if table already exists
47
+ # Skip if it doesn't
48
+ tables = inspector.get_table_names()
49
+ if metric_value_table not in tables:
50
+ logger.warning(f"No table named {metric_value_table!r} found")
51
+ return
52
+
53
+ # Extract the current columns in the DB
54
+ existing_columns = [c["name"] for c in inspector.get_columns(metric_value_table)]
55
+
56
+ cv_file = ref_config.paths.dimensions_cv
57
+ cv = CV.load_from_file(cv_file)
58
+
59
+ for dimension in cv.dimensions:
60
+ if dimension.name not in existing_columns:
61
+ logger.info(f"Adding missing metric value dimension: {dimension.name!r}")
62
+ op.add_column(metric_value_table, MetricValue.build_dimension_column(dimension))
63
+
64
+
65
+ def include_object(object_, name: str, type_, reflected, compare_to) -> bool:
66
+ """
67
+ Object-level check to include or exclude objects from the migration
68
+
69
+ Excludes columns that are marked with `skip_autogenerate` in the info dict
70
+
71
+ Based on https://alembic.sqlalchemy.org/en/latest/autogenerate.html#omitting-based-on-object
72
+ """
73
+ if object_.info.get("skip_autogenerate", False):
74
+ return False
75
+ else:
76
+ return True
77
+
78
+
79
+ def run_migrations_offline() -> None:
80
+ """Run migrations in 'offline' mode.
81
+
82
+ This configures the context with just a URL
83
+ and not an Engine, though an Engine is acceptable
84
+ here as well. By skipping the Engine creation
85
+ we don't even need a DBAPI to be available.
86
+
87
+ Calls to context.execute() here emit the given string to the
88
+ script output.
89
+
90
+ """
91
+ url = config.get_main_option("sqlalchemy.url")
92
+ context.configure(
93
+ url=url,
94
+ target_metadata=target_metadata,
95
+ literal_binds=True,
96
+ dialect_name="sqlite",
97
+ dialect_opts={"paramstyle": "named"},
98
+ render_as_batch=True,
99
+ include_object=include_object,
100
+ )
101
+
102
+ with context.begin_transaction():
103
+ context.run_migrations()
104
+
105
+
106
+ def run_migrations_online() -> None:
107
+ """Run migrations in 'online' mode.
108
+
109
+ In this scenario we need to create an Engine
110
+ and associate a connection with the context.
111
+ """
112
+ connectable = config.attributes.get("connection", None)
113
+
114
+ if connectable is None:
115
+ db = Database.from_config(ref_config, run_migrations=False)
116
+ connectable = db._engine
117
+
118
+ with connectable.connect() as connection:
119
+ context.configure(
120
+ connection=connection,
121
+ target_metadata=target_metadata,
122
+ render_as_batch=True,
123
+ include_object=include_object,
124
+ )
125
+
126
+ with context.begin_transaction():
127
+ context.run_migrations()
128
+
129
+ # Set up the Operations context
130
+ # This is needed to alter the tables
131
+ with op.Operations.context(context.get_context()): # type: ignore
132
+ _add_metric_value_columns(connection)
133
+
134
+
135
+ if context.is_offline_mode():
136
+ logger.warning("Running in offline mode")
137
+ run_migrations_offline()
138
+ else:
139
+ run_migrations_online()
@@ -0,0 +1,26 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ ${upgrades if upgrades else "pass"}
23
+
24
+
25
+ def downgrade() -> None:
26
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,292 @@
1
+ """regenerate
2
+
3
+ Revision ID: 341a4aa2551e
4
+ Revises:
5
+ Create Date: 2025-05-02 14:18:54.273708
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+ from typing import Union
11
+
12
+ import sqlalchemy as sa
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "341a4aa2551e"
17
+ down_revision: Union[str, None] = None
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "dataset",
26
+ sa.Column("id", sa.Integer(), nullable=False),
27
+ sa.Column("slug", sa.String(), nullable=False),
28
+ sa.Column(
29
+ "dataset_type",
30
+ sa.Enum("CMIP6", "CMIP7", "obs4MIPs", "PMPClimatology", name="sourcedatasettype"),
31
+ nullable=False,
32
+ ),
33
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
34
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
35
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_dataset")),
36
+ sa.UniqueConstraint("slug", name=op.f("uq_dataset_slug")),
37
+ )
38
+ op.create_table(
39
+ "provider",
40
+ sa.Column("id", sa.Integer(), nullable=False),
41
+ sa.Column("slug", sa.String(), nullable=False),
42
+ sa.Column("name", sa.String(), nullable=False),
43
+ sa.Column("version", sa.String(), nullable=False),
44
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
45
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
46
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_provider")),
47
+ sa.UniqueConstraint("slug", name=op.f("uq_provider_slug")),
48
+ )
49
+ op.create_table(
50
+ "cmip6_dataset",
51
+ sa.Column("id", sa.Integer(), nullable=False),
52
+ sa.Column("activity_id", sa.String(), nullable=False),
53
+ sa.Column("branch_method", sa.String(), nullable=True),
54
+ sa.Column("branch_time_in_child", sa.Float(), nullable=True),
55
+ sa.Column("branch_time_in_parent", sa.Float(), nullable=True),
56
+ sa.Column("experiment", sa.String(), nullable=False),
57
+ sa.Column("experiment_id", sa.String(), nullable=False),
58
+ sa.Column("frequency", sa.String(), nullable=False),
59
+ sa.Column("grid", sa.String(), nullable=False),
60
+ sa.Column("grid_label", sa.String(), nullable=False),
61
+ sa.Column("institution_id", sa.String(), nullable=False),
62
+ sa.Column("long_name", sa.String(), nullable=True),
63
+ sa.Column("member_id", sa.String(), nullable=False),
64
+ sa.Column("nominal_resolution", sa.String(), nullable=False),
65
+ sa.Column("parent_activity_id", sa.String(), nullable=True),
66
+ sa.Column("parent_experiment_id", sa.String(), nullable=True),
67
+ sa.Column("parent_source_id", sa.String(), nullable=True),
68
+ sa.Column("parent_time_units", sa.String(), nullable=True),
69
+ sa.Column("parent_variant_label", sa.String(), nullable=True),
70
+ sa.Column("realm", sa.String(), nullable=False),
71
+ sa.Column("product", sa.String(), nullable=False),
72
+ sa.Column("source_id", sa.String(), nullable=False),
73
+ sa.Column("standard_name", sa.String(), nullable=False),
74
+ sa.Column("source_type", sa.String(), nullable=False),
75
+ sa.Column("sub_experiment", sa.String(), nullable=False),
76
+ sa.Column("sub_experiment_id", sa.String(), nullable=False),
77
+ sa.Column("table_id", sa.String(), nullable=False),
78
+ sa.Column("units", sa.String(), nullable=False),
79
+ sa.Column("variable_id", sa.String(), nullable=False),
80
+ sa.Column("variant_label", sa.String(), nullable=False),
81
+ sa.Column("vertical_levels", sa.Integer(), nullable=True),
82
+ sa.Column("version", sa.String(), nullable=False),
83
+ sa.Column("instance_id", sa.String(), nullable=False),
84
+ sa.ForeignKeyConstraint(["id"], ["dataset.id"], name=op.f("fk_cmip6_dataset_id_dataset")),
85
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_cmip6_dataset")),
86
+ )
87
+ op.create_table(
88
+ "dataset_file",
89
+ sa.Column("id", sa.Integer(), nullable=False),
90
+ sa.Column("dataset_id", sa.Integer(), nullable=False),
91
+ sa.Column("end_time", sa.DateTime(), nullable=True),
92
+ sa.Column("start_time", sa.DateTime(), nullable=True),
93
+ sa.Column("path", sa.String(), nullable=False),
94
+ sa.ForeignKeyConstraint(
95
+ ["dataset_id"],
96
+ ["dataset.id"],
97
+ name=op.f("fk_dataset_file_dataset_id_dataset"),
98
+ ondelete="CASCADE",
99
+ ),
100
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_dataset_file")),
101
+ )
102
+ op.create_table(
103
+ "diagnostic",
104
+ sa.Column("id", sa.Integer(), nullable=False),
105
+ sa.Column("slug", sa.String(), nullable=False),
106
+ sa.Column("name", sa.String(), nullable=False),
107
+ sa.Column("provider_id", sa.Integer(), nullable=False),
108
+ sa.Column("enabled", sa.Boolean(), nullable=False),
109
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
110
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
111
+ sa.ForeignKeyConstraint(
112
+ ["provider_id"], ["provider.id"], name=op.f("fk_diagnostic_provider_id_provider")
113
+ ),
114
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_diagnostic")),
115
+ sa.UniqueConstraint("provider_id", "slug", name="diagnostic_ident"),
116
+ sa.UniqueConstraint("slug", name=op.f("uq_diagnostic_slug")),
117
+ )
118
+ op.create_table(
119
+ "obs4mips_dataset",
120
+ sa.Column("id", sa.Integer(), nullable=False),
121
+ sa.Column("activity_id", sa.String(), nullable=False),
122
+ sa.Column("frequency", sa.String(), nullable=False),
123
+ sa.Column("grid", sa.String(), nullable=False),
124
+ sa.Column("grid_label", sa.String(), nullable=False),
125
+ sa.Column("institution_id", sa.String(), nullable=False),
126
+ sa.Column("long_name", sa.String(), nullable=False),
127
+ sa.Column("nominal_resolution", sa.String(), nullable=False),
128
+ sa.Column("realm", sa.String(), nullable=False),
129
+ sa.Column("product", sa.String(), nullable=False),
130
+ sa.Column("source_id", sa.String(), nullable=False),
131
+ sa.Column("source_type", sa.String(), nullable=False),
132
+ sa.Column("units", sa.String(), nullable=False),
133
+ sa.Column("variable_id", sa.String(), nullable=False),
134
+ sa.Column("variant_label", sa.String(), nullable=False),
135
+ sa.Column("vertical_levels", sa.Integer(), nullable=False),
136
+ sa.Column("source_version_number", sa.String(), nullable=False),
137
+ sa.Column("instance_id", sa.String(), nullable=False),
138
+ sa.ForeignKeyConstraint(["id"], ["dataset.id"], name=op.f("fk_obs4mips_dataset_id_dataset")),
139
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_obs4mips_dataset")),
140
+ )
141
+ op.create_table(
142
+ "pmp_climatology_dataset",
143
+ sa.Column("id", sa.Integer(), nullable=False),
144
+ sa.Column("activity_id", sa.String(), nullable=False),
145
+ sa.Column("frequency", sa.String(), nullable=False),
146
+ sa.Column("grid", sa.String(), nullable=False),
147
+ sa.Column("grid_label", sa.String(), nullable=False),
148
+ sa.Column("institution_id", sa.String(), nullable=False),
149
+ sa.Column("long_name", sa.String(), nullable=False),
150
+ sa.Column("nominal_resolution", sa.String(), nullable=False),
151
+ sa.Column("realm", sa.String(), nullable=False),
152
+ sa.Column("product", sa.String(), nullable=False),
153
+ sa.Column("source_id", sa.String(), nullable=False),
154
+ sa.Column("source_type", sa.String(), nullable=False),
155
+ sa.Column("units", sa.String(), nullable=False),
156
+ sa.Column("variable_id", sa.String(), nullable=False),
157
+ sa.Column("variant_label", sa.String(), nullable=False),
158
+ sa.Column("vertical_levels", sa.Integer(), nullable=False),
159
+ sa.Column("source_version_number", sa.String(), nullable=False),
160
+ sa.Column("instance_id", sa.String(), nullable=False),
161
+ sa.ForeignKeyConstraint(["id"], ["dataset.id"], name=op.f("fk_pmp_climatology_dataset_id_dataset")),
162
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_pmp_climatology_dataset")),
163
+ )
164
+ op.create_table(
165
+ "execution_group",
166
+ sa.Column("id", sa.Integer(), nullable=False),
167
+ sa.Column("diagnostic_id", sa.Integer(), nullable=False),
168
+ sa.Column("key", sa.String(), nullable=False),
169
+ sa.Column("dirty", sa.Boolean(), nullable=False),
170
+ sa.Column("selectors", sa.JSON(), nullable=False),
171
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
172
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
173
+ sa.ForeignKeyConstraint(
174
+ ["diagnostic_id"], ["diagnostic.id"], name=op.f("fk_execution_group_diagnostic_id_diagnostic")
175
+ ),
176
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_execution_group")),
177
+ sa.UniqueConstraint("diagnostic_id", "key", name="execution_ident"),
178
+ )
179
+ with op.batch_alter_table("execution_group", schema=None) as batch_op:
180
+ batch_op.create_index(batch_op.f("ix_execution_group_key"), ["key"], unique=False)
181
+
182
+ op.create_table(
183
+ "execution",
184
+ sa.Column("id", sa.Integer(), nullable=False),
185
+ sa.Column("output_fragment", sa.String(), nullable=False),
186
+ sa.Column("execution_group_id", sa.Integer(), nullable=False),
187
+ sa.Column("dataset_hash", sa.String(), nullable=False),
188
+ sa.Column("successful", sa.Boolean(), nullable=True),
189
+ sa.Column("path", sa.String(), nullable=True),
190
+ sa.Column("retracted", sa.Boolean(), nullable=False),
191
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
192
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
193
+ sa.ForeignKeyConstraint(["execution_group_id"], ["execution_group.id"], name="fk_execution_id"),
194
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_execution")),
195
+ )
196
+ with op.batch_alter_table("execution", schema=None) as batch_op:
197
+ batch_op.create_index(batch_op.f("ix_execution_dataset_hash"), ["dataset_hash"], unique=False)
198
+
199
+ op.create_table(
200
+ "execution_dataset",
201
+ sa.Column("execution_id", sa.Integer(), nullable=True),
202
+ sa.Column("dataset_id", sa.Integer(), nullable=True),
203
+ sa.ForeignKeyConstraint(
204
+ ["dataset_id"], ["dataset.id"], name=op.f("fk_execution_dataset_dataset_id_dataset")
205
+ ),
206
+ sa.ForeignKeyConstraint(
207
+ ["execution_id"], ["execution.id"], name=op.f("fk_execution_dataset_execution_id_execution")
208
+ ),
209
+ )
210
+ op.create_table(
211
+ "execution_output",
212
+ sa.Column("id", sa.Integer(), nullable=False),
213
+ sa.Column("execution_id", sa.Integer(), nullable=False),
214
+ sa.Column("output_type", sa.Enum("Plot", "Data", "HTML", name="resultoutputtype"), nullable=False),
215
+ sa.Column("filename", sa.String(), nullable=True),
216
+ sa.Column("short_name", sa.String(), nullable=True),
217
+ sa.Column("long_name", sa.String(), nullable=True),
218
+ sa.Column("description", sa.String(), nullable=True),
219
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
220
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
221
+ sa.ForeignKeyConstraint(
222
+ ["execution_id"], ["execution.id"], name=op.f("fk_execution_output_execution_id_execution")
223
+ ),
224
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_execution_output")),
225
+ )
226
+ with op.batch_alter_table("execution_output", schema=None) as batch_op:
227
+ batch_op.create_index(batch_op.f("ix_execution_output_execution_id"), ["execution_id"], unique=False)
228
+ batch_op.create_index(batch_op.f("ix_execution_output_output_type"), ["output_type"], unique=False)
229
+
230
+ op.create_table(
231
+ "metric_value",
232
+ sa.Column("id", sa.Integer(), nullable=False),
233
+ sa.Column("execution_id", sa.Integer(), nullable=False),
234
+ sa.Column("value", sa.Float(), nullable=False),
235
+ sa.Column("attributes", sa.JSON(), nullable=False),
236
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
237
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False),
238
+ sa.Column("model", sa.Text(), nullable=True),
239
+ sa.Column("source_id", sa.Text(), nullable=True),
240
+ sa.Column("variant_label", sa.Text(), nullable=True),
241
+ sa.Column("metric", sa.Text(), nullable=True),
242
+ sa.Column("region", sa.Text(), nullable=True),
243
+ sa.Column("statistic", sa.Text(), nullable=True),
244
+ sa.ForeignKeyConstraint(
245
+ ["execution_id"], ["execution.id"], name=op.f("fk_metric_value_execution_id_execution")
246
+ ),
247
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_metric_value")),
248
+ )
249
+ with op.batch_alter_table("metric_value", schema=None) as batch_op:
250
+ batch_op.create_index(batch_op.f("ix_metric_value_metric"), ["metric"], unique=False)
251
+ batch_op.create_index(batch_op.f("ix_metric_value_model"), ["model"], unique=False)
252
+ batch_op.create_index(batch_op.f("ix_metric_value_region"), ["region"], unique=False)
253
+ batch_op.create_index(batch_op.f("ix_metric_value_source_id"), ["source_id"], unique=False)
254
+ batch_op.create_index(batch_op.f("ix_metric_value_statistic"), ["statistic"], unique=False)
255
+ batch_op.create_index(batch_op.f("ix_metric_value_variant_label"), ["variant_label"], unique=False)
256
+
257
+ # ### end Alembic commands ###
258
+
259
+
260
+ def downgrade() -> None:
261
+ # ### commands auto generated by Alembic - please adjust! ###
262
+ with op.batch_alter_table("metric_value", schema=None) as batch_op:
263
+ batch_op.drop_index(batch_op.f("ix_metric_value_variant_label"))
264
+ batch_op.drop_index(batch_op.f("ix_metric_value_statistic"))
265
+ batch_op.drop_index(batch_op.f("ix_metric_value_source_id"))
266
+ batch_op.drop_index(batch_op.f("ix_metric_value_region"))
267
+ batch_op.drop_index(batch_op.f("ix_metric_value_model"))
268
+ batch_op.drop_index(batch_op.f("ix_metric_value_metric"))
269
+
270
+ op.drop_table("metric_value")
271
+ with op.batch_alter_table("execution_output", schema=None) as batch_op:
272
+ batch_op.drop_index(batch_op.f("ix_execution_output_output_type"))
273
+ batch_op.drop_index(batch_op.f("ix_execution_output_execution_id"))
274
+
275
+ op.drop_table("execution_output")
276
+ op.drop_table("execution_dataset")
277
+ with op.batch_alter_table("execution", schema=None) as batch_op:
278
+ batch_op.drop_index(batch_op.f("ix_execution_dataset_hash"))
279
+
280
+ op.drop_table("execution")
281
+ with op.batch_alter_table("execution_group", schema=None) as batch_op:
282
+ batch_op.drop_index(batch_op.f("ix_execution_group_key"))
283
+
284
+ op.drop_table("execution_group")
285
+ op.drop_table("pmp_climatology_dataset")
286
+ op.drop_table("obs4mips_dataset")
287
+ op.drop_table("diagnostic")
288
+ op.drop_table("dataset_file")
289
+ op.drop_table("cmip6_dataset")
290
+ op.drop_table("provider")
291
+ op.drop_table("dataset")
292
+ # ### end Alembic commands ###
@@ -0,0 +1,33 @@
1
+ """
2
+ Declaration of the models used by the REF.
3
+
4
+ These models are used to represent the data that is stored in the database.
5
+ """
6
+
7
+ from typing import TypeVar
8
+
9
+ from climate_ref.models.base import Base
10
+ from climate_ref.models.dataset import Dataset
11
+ from climate_ref.models.diagnostic import Diagnostic
12
+ from climate_ref.models.execution import (
13
+ Execution,
14
+ ExecutionGroup,
15
+ ExecutionOutput,
16
+ )
17
+ from climate_ref.models.metric_value import MetricValue
18
+ from climate_ref.models.provider import Provider
19
+
20
+ Table = TypeVar("Table", bound=Base)
21
+
22
+
23
+ __all__ = [
24
+ "Base",
25
+ "Dataset",
26
+ "Diagnostic",
27
+ "Execution",
28
+ "ExecutionGroup",
29
+ "ExecutionOutput",
30
+ "MetricValue",
31
+ "Provider",
32
+ "Table",
33
+ ]
@@ -0,0 +1,42 @@
1
+ import datetime
2
+ from typing import Any
3
+
4
+ from sqlalchemy import JSON, MetaData, func
5
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
6
+
7
+
8
+ class Base(DeclarativeBase):
9
+ """
10
+ Base class for all models
11
+ """
12
+
13
+ type_annotation_map = { # noqa: RUF012
14
+ dict[str, Any]: JSON,
15
+ }
16
+ metadata = MetaData(
17
+ # Enforce a common naming convention for constraints
18
+ # https://alembic.sqlalchemy.org/en/latest/naming.html
19
+ naming_convention={
20
+ "ix": "ix_%(column_0_label)s",
21
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
22
+ "ck": "ck_%(table_name)s_`%(constraint_name)s`",
23
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
24
+ "pk": "pk_%(table_name)s",
25
+ }
26
+ )
27
+
28
+
29
+ class CreatedUpdatedMixin:
30
+ """
31
+ Mixin for models that have a created_at and updated_at fields
32
+ """
33
+
34
+ created_at: Mapped[datetime.datetime] = mapped_column(server_default=func.now())
35
+ """
36
+ When the dataset was added to the database
37
+ """
38
+
39
+ updated_at: Mapped[datetime.datetime] = mapped_column(server_default=func.now(), onupdate=func.now())
40
+ """
41
+ When the dataset was updated.
42
+ """