diff --git a/backend/app/alembic/versions/966d8d72887e_add_gerrydb_schema.py b/backend/app/alembic/versions/966d8d72887e_add_gerrydb_schema.py new file mode 100644 index 00000000..7ca386d7 --- /dev/null +++ b/backend/app/alembic/versions/966d8d72887e_add_gerrydb_schema.py @@ -0,0 +1,57 @@ +"""add gerrydb schema + +Revision ID: 966d8d72887e +Revises: +Create Date: 2024-07-20 10:50:48.136439 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from alembic import op +from app.models import UUIDType + + +# revision identifiers, used by Alembic. +revision: str = "966d8d72887e" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.execute(sa.text("CREATE EXTENSION IF NOT EXISTS postgis")) + op.execute(sa.text("CREATE SCHEMA IF NOT EXISTS gerrydb")) + op.create_table( + "gerrydbtable", + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=False, + ), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("uuid", UUIDType(), nullable=True), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + sa.UniqueConstraint("uuid"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("gerrydbtable") + op.execute(sa.text("DROP SCHEMA IF EXISTS gerrydb CASCADE")) + op.execute(sa.text("DROP EXTENSION IF EXISTS postgis")) + # ### end Alembic commands ### diff --git a/backend/app/alembic/versions/cb7cd5fd35d8_pop_table.py b/backend/app/alembic/versions/cb7cd5fd35d8_pop_table.py deleted file mode 100644 index 8cb3627b..00000000 --- a/backend/app/alembic/versions/cb7cd5fd35d8_pop_table.py +++ /dev/null @@ -1,61 +0,0 @@ -"""pop table - -Revision ID: cb7cd5fd35d8 -Revises: -Create Date: 2024-07-16 22:47:09.900729 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.sql import text -from sqlmodel.sql import sqltypes -import geoalchemy2 - - -# revision identifiers, used by Alembic. -revision: str = "cb7cd5fd35d8" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.execute(text("CREATE EXTENSION IF NOT EXISTS postgis")) - op.create_table( - "population", - sa.Column("path", sqltypes.AutoString(), nullable=False), - sa.Column("area_land", sa.Integer(), nullable=False), - sa.Column("area_water", sa.Integer(), nullable=False), - sa.Column("other_pop", sa.Integer(), nullable=False), - sa.Column("total_pop", sa.Integer(), nullable=False), - sa.Column( - "geography", - geoalchemy2.types.Geometry( - geometry_type="POLYGON", - srid=4269, - from_text="ST_GeomFromEWKT", - name="geometry", - ), - nullable=True, - ), - sa.PrimaryKeyConstraint("path"), - ) - # NOTE: geospatial indices are created by default - # op.create_index('idx_population_geography', 'population', ['geography'], unique=False, postgresql_using='gist') - op.create_index(op.f("ix_population_path"), "population", ["path"], unique=True) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f("ix_population_path"), table_name="population") - op.drop_index( - "idx_population_geography", table_name="population", postgresql_using="gist" - ) - op.drop_table("population") - op.execute(text("DROP EXTENSION IF EXISTS postgis")) - # ### end Alembic commands ### diff --git a/backend/app/models.py b/backend/app/models.py index 6f2656da..f7c4b5b2 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,7 +1,6 @@ from datetime import datetime -from typing import Any, Optional +from typing import Optional from sqlmodel import Field, SQLModel, UUID, TIMESTAMP, text, Column -from geoalchemy2 import Geometry class UUIDType(UUID): @@ -30,13 +29,10 @@ class TimeStampMixin(SQLModel): ) -class Population(SQLModel, table=True): - name: str = Field(nullable=False, index=True) - path: str = Field(unique=False, nullable=False, index=True, primary_key=False) - area_land: int - area_water: int - other_pop: int - total_pop: int - geography: Any = Field( - sa_column=Column(Geometry(geometry_type="POLYGON", srid=4269)) - ) +class GerryDBTableBase(TimeStampMixin, SQLModel): + id: int = Field(default=None, primary_key=True) + + +class GerryDBTable(GerryDBTableBase, table=True): + uuid: str = Field(sa_column=Column(UUIDType, unique=True)) + name: str = Field(nullable=False, unique=True) diff --git a/backend/cli.py b/backend/cli.py index 790ba323..d016431b 100644 --- a/backend/cli.py +++ b/backend/cli.py @@ -1,14 +1,21 @@ import os import click import logging +from app.main import get_session from app.core.config import settings import subprocess from urllib.parse import urlparse +from sqlalchemy import text +from uuid import uuid4 +# from fastapi import Depends logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) +GERRY_DB_SCHEMA = "gerrydb" + + @click.group() def cli(): pass @@ -67,7 +74,7 @@ def import_gerrydb_view(layer: str, gpkg: str, replace: bool, rm: bool): "-lco", "OVERWRITE=yes", "-nln", - layer, + f"{GERRY_DB_SCHEMA}.{layer}", # Forced that the layer is imported into the gerrydb schema ], ) @@ -81,6 +88,36 @@ def import_gerrydb_view(layer: str, gpkg: str, replace: bool, rm: bool): os.remove(path) logger.info("Deleted file %s", path) + print("GerryDB view imported successfully") + + _session = get_session() + session = next(_session) + + uuid = str(uuid4()) + + upsert_query = text(""" + INSERT INTO gerrydbtable (uuid, name, updated_at) + VALUES (:uuid, :name, now()) + ON CONFLICT (name) + DO UPDATE SET + updated_at = now() + """) + + try: + session.execute( + upsert_query, + { + "uuid": uuid, + "name": layer, + }, + ) + session.commit() + logger.info("GerryDB view upserted successfully.") + except Exception as e: + session.rollback() + logger.error("Failed to upsert GerryDB view. Got %s", e) + raise ValueError(f"Failed to upsert GerryDB view. Got {e}") + if __name__ == "__main__": cli()