feat(projects): automate project infrastructure provisioning
This commit is contained in:
@@ -89,6 +89,56 @@ def _table_columns(
|
||||
return [row["column_name"] for row in cur.fetchall()]
|
||||
|
||||
|
||||
def _geometry_srids(
|
||||
conn: Connection, schema_name: str, table_name: str
|
||||
) -> dict[str, int]:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
select f_geometry_column as column_name, srid
|
||||
from geometry_columns
|
||||
where f_table_schema = %s and f_table_name = %s
|
||||
order by f_geometry_column
|
||||
""",
|
||||
(schema_name, table_name),
|
||||
)
|
||||
return {row["column_name"]: int(row["srid"]) for row in cur.fetchall()}
|
||||
|
||||
|
||||
def _copy_out_statement(
|
||||
schema_name: str,
|
||||
table_name: str,
|
||||
columns: list[str],
|
||||
*,
|
||||
source_geometry_srids: dict[str, int],
|
||||
target_geometry_srids: dict[str, int],
|
||||
) -> sql.Composed:
|
||||
relation = sql.Identifier(schema_name, table_name)
|
||||
column_list = sql.SQL(", ").join(map(sql.Identifier, columns))
|
||||
srid_changes = {
|
||||
column_name: target_geometry_srids[column_name]
|
||||
for column_name, source_srid in source_geometry_srids.items()
|
||||
if target_geometry_srids[column_name] != source_srid
|
||||
}
|
||||
if not srid_changes:
|
||||
return sql.SQL("copy {} ({}) to stdout").format(relation, column_list)
|
||||
|
||||
select_list = sql.SQL(", ").join(
|
||||
sql.SQL("st_setsrid({}, {}) as {}").format(
|
||||
sql.Identifier(column_name),
|
||||
sql.Literal(srid_changes[column_name]),
|
||||
sql.Identifier(column_name),
|
||||
)
|
||||
if column_name in srid_changes
|
||||
else sql.Identifier(column_name)
|
||||
for column_name in columns
|
||||
)
|
||||
return sql.SQL("copy (select {} from {}) to stdout").format(
|
||||
select_list,
|
||||
relation,
|
||||
)
|
||||
|
||||
|
||||
def _external_references(conn: Connection) -> set[tuple[str, str, str, str]]:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
@@ -128,7 +178,20 @@ def _copy_table(
|
||||
) -> None:
|
||||
relation = sql.Identifier(schema_name, table_name)
|
||||
column_list = sql.SQL(", ").join(map(sql.Identifier, columns))
|
||||
copy_out = sql.SQL("copy {} ({}) to stdout").format(relation, column_list)
|
||||
source_geometry_srids = _geometry_srids(source_conn, schema_name, table_name)
|
||||
target_geometry_srids = _geometry_srids(target_conn, schema_name, table_name)
|
||||
if source_geometry_srids.keys() != target_geometry_srids.keys():
|
||||
raise RuntimeError(
|
||||
"Source/target geometry columns differ for "
|
||||
f"{schema_name}.{table_name}"
|
||||
)
|
||||
copy_out = _copy_out_statement(
|
||||
schema_name,
|
||||
table_name,
|
||||
columns,
|
||||
source_geometry_srids=source_geometry_srids,
|
||||
target_geometry_srids=target_geometry_srids,
|
||||
)
|
||||
copy_in = sql.SQL("copy {} ({}) from stdin").format(relation, column_list)
|
||||
with source_conn.cursor().copy(copy_out) as source_copy:
|
||||
with target_conn.cursor().copy(copy_in) as target_copy:
|
||||
@@ -136,6 +199,47 @@ def _copy_table(
|
||||
target_copy.write(chunk)
|
||||
|
||||
|
||||
def copy_network_tables(source_project: str, target_project: str) -> None:
|
||||
"""Copy only the immutable simulation input tables into a project template."""
|
||||
with project_connection(source_project) as source_conn, source_conn.transaction():
|
||||
with source_conn.cursor() as cur:
|
||||
cur.execute("set transaction isolation level repeatable read, read only")
|
||||
source_tables = [
|
||||
table for table in _model_tables(source_conn) if table[0] == "network"
|
||||
]
|
||||
source_columns = {
|
||||
table: _table_columns(source_conn, *table) for table in source_tables
|
||||
}
|
||||
copy_order = _copy_order(source_conn, source_tables)
|
||||
|
||||
with project_transaction(target_project) as target_conn:
|
||||
target_tables = [
|
||||
table for table in _model_tables(target_conn) if table[0] == "network"
|
||||
]
|
||||
if set(target_tables) != set(source_tables):
|
||||
raise RuntimeError("Source/target network schemas differ")
|
||||
for table, columns in source_columns.items():
|
||||
if _table_columns(target_conn, *table) != columns:
|
||||
raise RuntimeError(
|
||||
f"Source/target columns differ for {table[0]}.{table[1]}"
|
||||
)
|
||||
with target_conn.cursor() as cur:
|
||||
for schema_name, table_name in reversed(copy_order):
|
||||
cur.execute(
|
||||
sql.SQL("delete from {}").format(
|
||||
sql.Identifier(schema_name, table_name)
|
||||
)
|
||||
)
|
||||
for schema_name, table_name in copy_order:
|
||||
_copy_table(
|
||||
source_conn,
|
||||
target_conn,
|
||||
schema_name,
|
||||
table_name,
|
||||
source_columns[(schema_name, table_name)],
|
||||
)
|
||||
|
||||
|
||||
def replace_project_model(
|
||||
target_project: str,
|
||||
source_project: str,
|
||||
|
||||
Reference in New Issue
Block a user