Skip to content

Commit

Permalink
WIP: Feature/fix infra spatialite (#79)
Browse files Browse the repository at this point in the history
Introduces changes to fix spatialite extension loading issue to support
data fields in sqlachemy model type `POINT`. This change directly
supports the work in #26
- Fixes spatialite extension as loadable with the updated dockerfile
setup and correct implementation using aiosqlite compatibility
- Temporary disables integration test. The repository return type needs
to be refactored and return type changed to list dto facilities results

<!-- Generated by sourcery-ai[bot]: start summary -->

Fix SpatiaLite extension loading issue to support spatial data fields in
SQLAlchemy models. Introduce a new `PointType` for handling geopoint
data. Refactor test database setup and temporarily disable integration
tests pending refactoring. Update build scripts and Makefile for
improved Docker handling.

New Features:
- Introduce a new `PointType` class to handle geopoint data types in
SQLAlchemy models, enabling support for spatial data fields like
`POINT`.

Bug Fixes:
- Fix the loading of the SpatiaLite extension in the database setup to
support spatial data operations.

Enhancements:
- Refactor the test database setup to use a new `SetUpTestDatabase`
class, improving the management of test database lifecycle and session
handling.

Build:
- Update the Makefile to include a new `set-docker` target and modify
the `docker-run-server` target to remove orphan containers.

Tests:
- Temporarily disable integration tests due to the need for refactoring
repository return types and changing return types to list DTO facilities
results.

Chores:
- Update the `run.sh` script to include additional logging for file
listing and database removal.

<!-- Generated by sourcery-ai[bot]: end summary -->

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
  • Loading branch information
codecakes and sourcery-ai[bot] committed Sep 22, 2024
1 parent 71f5c43 commit b98428a
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 39 deletions.
48 changes: 23 additions & 25 deletions xcov19/app/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,45 +49,43 @@ def __call__(self) -> async_sessionmaker[AsyncSessionWrapper]:
)


async def setup_database(engine: AsyncEngine) -> None:
"""Sets up tables for database."""
async def _load_spatialite(dbapi_conn: AsyncAdapt_aiosqlite_connection) -> None:
"""Loads spatialite sqlite extension."""
conn: aiosqlite.Connection = dbapi_conn.driver_connection
await conn.enable_load_extension(True)
await conn.load_extension("mod_spatialite")
db_logger.info("======= PRAGMA load_extension successful =======")
try:
async with conn.execute("SELECT spatialite_version() as version") as cursor:
result = await cursor.fetchone()
db_logger.info(f"==== Spatialite Version: {result} ====")
db_logger.info("===== mod_spatialite loaded =====")
except (AttributeError, aiosqlite.OperationalError) as e:
db_logger.error(e)
raise (e)


def setup_spatialite(engine: AsyncEngine) -> None:
"""An event listener hook to setup spatialite using aiosqlite."""

@event.listens_for(engine.sync_engine, "connect")
def load_spatialite(
dbapi_conn: AsyncAdapt_aiosqlite_connection, _connection_record
):
loop = asyncio.get_running_loop()
# Schedule the coroutine in the existing event loop
loop.create_task(_load_spatialite(dbapi_conn))

async def load_async_extension():
conn: aiosqlite.Connection = dbapi_conn.driver_connection
await conn.enable_load_extension(True)
await conn.load_extension("mod_spatialite")
db_logger.info("======= PRAGMA load_extension successful =======")
try:
async with conn.execute(
"SELECT spatialite_version() as version"
) as cursor:
result = await cursor.fetchone()
db_logger.info(f"==== Spatialite Version: {result} ====")
db_logger.info("===== mod_spatialite loaded =====")
except (AttributeError, aiosqlite.OperationalError) as e:
db_logger.error(e)
raise (e)

# Schedule the coroutine in the existing event loop
loop.create_task(load_async_extension())
async def setup_database(engine: AsyncEngine) -> None:
"""Sets up tables for database."""

setup_spatialite(engine)
async with engine.begin() as conn:
# Enable extension loading
await conn.execute(text("PRAGMA load_extension = 1"))
# db_logger.info("SQLAlchemy setup to load the SpatiaLite extension.")
# await conn.execute(text("SELECT load_extension('/opt/homebrew/Cellar/libspatialite/5.1.0_1/lib/mod_spatialite.dylib')"))
# await conn.execute(text("SELECT load_extension('mod_spatialite')"))
# see: https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/cascade-delete-relationships/#enable-foreign-key-support-in-sqlite
await conn.execute(text("PRAGMA foreign_keys=ON"))
# test_result = await conn.execute(text("SELECT spatialite_version() as version;"))
# print(f"==== Spatialite Version: {test_result.fetchone()} ====")

await conn.run_sync(SQLModel.metadata.create_all)
await conn.commit()
db_logger.info("===== Database tables setup. =====")
Expand Down
22 changes: 9 additions & 13 deletions xcov19/infra/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,14 @@ def pydantic_adapter(cls) -> TypeAdapter:
return TypeAdapter(cls)


def generate_uuid() -> str:
return str(uuid.uuid4())


### These tables map to the domain models for Patient
class Patient(SQLModel, table=True):
patient_id: str = Field(
sa_column=Column(
TEXT, unique=True, primary_key=True, default=str(uuid.uuid4())
),
sa_column=Column(TEXT, unique=True, primary_key=True, default=generate_uuid),
allow_mutation=False,
)
queries: Mapped[List["Query"]] = Relationship(
Expand All @@ -109,9 +111,7 @@ class Query(SQLModel, table=True):
"""Every Query must have both a Patient and a Location."""

query_id: str = Field(
sa_column=Column(
TEXT, unique=True, primary_key=True, default=str(uuid.uuid4())
),
sa_column=Column(TEXT, unique=True, primary_key=True, default=generate_uuid),
allow_mutation=False,
)
query: str = Field(allow_mutation=False, sa_column=Column(Text))
Expand All @@ -128,9 +128,7 @@ class Location(SQLModel, table=True):
Index("ix_location_composite_lat_lng", "latitude", "longitude", unique=True),
)
location_id: str = Field(
sa_column=Column(
TEXT, unique=True, primary_key=True, default=str(uuid.uuid4())
),
sa_column=Column(TEXT, unique=True, primary_key=True, default=generate_uuid),
allow_mutation=False,
)
latitude: float = Field(sa_column=Column(Float))
Expand All @@ -148,9 +146,7 @@ class Location(SQLModel, table=True):
### These tables map to the domain models for Provider
class Provider(SQLModel, table=True):
provider_id: str = Field(
sa_column=Column(
TEXT, unique=True, primary_key=True, default=str(uuid.uuid4())
),
sa_column=Column(TEXT, unique=True, primary_key=True, default=generate_uuid),
allow_mutation=False,
)
name: str = Field(
Expand All @@ -160,7 +156,7 @@ class Provider(SQLModel, table=True):
geopoint: Annotated[
tuple, lambda geom: PointType.pydantic_adapter().validate_python(geom)
] = Field(sa_column=Column(PointType, nullable=False), allow_mutation=False)
contact: str = Field(sa_column=Column(NUMERIC, nullable=False))
contact: int = Field(sa_column=Column(NUMERIC, nullable=False))
facility_type: str = Field(sa_column=Column(TEXT, nullable=False))
ownership_type: str = Field(sa_column=Column(TEXT, nullable=False))
specialties: List[str] = Field(sa_column=Column(JSON, nullable=False))
Expand Down
3 changes: 2 additions & 1 deletion xcov19/tests/data/seed_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@


async def seed_data(session: AsyncSessionWrapper):
"""
"""Seeds database with initial data.
dummy GeoLocation:
lat=0
lng=0
Expand Down

0 comments on commit b98428a

Please sign in to comment.