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.
- climate_ref/__init__.py +30 -0
- climate_ref/_config_helpers.py +214 -0
- climate_ref/alembic.ini +114 -0
- climate_ref/cli/__init__.py +138 -0
- climate_ref/cli/_utils.py +68 -0
- climate_ref/cli/config.py +28 -0
- climate_ref/cli/datasets.py +205 -0
- climate_ref/cli/executions.py +201 -0
- climate_ref/cli/providers.py +84 -0
- climate_ref/cli/solve.py +23 -0
- climate_ref/config.py +475 -0
- climate_ref/constants.py +8 -0
- climate_ref/database.py +223 -0
- climate_ref/dataset_registry/obs4ref_reference.txt +2 -0
- climate_ref/dataset_registry/sample_data.txt +60 -0
- climate_ref/datasets/__init__.py +40 -0
- climate_ref/datasets/base.py +214 -0
- climate_ref/datasets/cmip6.py +202 -0
- climate_ref/datasets/obs4mips.py +224 -0
- climate_ref/datasets/pmp_climatology.py +15 -0
- climate_ref/datasets/utils.py +16 -0
- climate_ref/executor/__init__.py +274 -0
- climate_ref/executor/local.py +89 -0
- climate_ref/migrations/README +22 -0
- climate_ref/migrations/env.py +139 -0
- climate_ref/migrations/script.py.mako +26 -0
- climate_ref/migrations/versions/2025-05-02T1418_341a4aa2551e_regenerate.py +292 -0
- climate_ref/models/__init__.py +33 -0
- climate_ref/models/base.py +42 -0
- climate_ref/models/dataset.py +206 -0
- climate_ref/models/diagnostic.py +61 -0
- climate_ref/models/execution.py +306 -0
- climate_ref/models/metric_value.py +195 -0
- climate_ref/models/provider.py +39 -0
- climate_ref/provider_registry.py +146 -0
- climate_ref/py.typed +0 -0
- climate_ref/solver.py +395 -0
- climate_ref/testing.py +109 -0
- climate_ref-0.5.0.dist-info/METADATA +97 -0
- climate_ref-0.5.0.dist-info/RECORD +44 -0
- climate_ref-0.5.0.dist-info/WHEEL +4 -0
- climate_ref-0.5.0.dist-info/entry_points.txt +2 -0
- climate_ref-0.5.0.dist-info/licenses/LICENCE +201 -0
- 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
|
+
"""
|