Source code for codegen_database.ext.postgis

"""PostGIS extension for codegen_database.

:class:`PostGISExtension` wires PostGIS-suite support into the
codegen_database lifecycle.  Register it on a
:class:`~codegen_database.config.CodegenDatabaseConfig` to enable the
PostGIS column types (currently
:class:`~codegen_database.types.postgis.STDADDR`, the ``stdaddr``
composite produced by the ``address_standardizer`` extension).

Usage::

    from codegen_database.config import CodegenDatabaseConfig
    from codegen_database.ext.postgis import PostGISExtension

    config = CodegenDatabaseConfig()
    config.use(PostGISExtension())

``PostGISExtension`` is the grouping for the whole PostGIS suite; which
PostgreSQL extensions it actually installs are controlled by boolean
flags.  By default it declares only ``address_standardizer`` (which
provides ``stdaddr``), since that installs independently of the heavy
``postgis`` core.  Enable the core when you need geometry::

    config.use(PostGISExtension(postgis=True))

Enable the TIGER geocoder when you need to turn a free-form address
string into ranked candidate addresses with coordinates (the engine
behind :func:`fsh_lib.geocode.find_address_candidates`)::

    config.use(PostGISExtension(tiger_geocoder=True))

``postgis_tiger_geocoder`` is declared ``CASCADE`` because it depends on
``postgis`` and ``fuzzystrmatch``; ``CASCADE`` installs those
automatically.  (It does **not** pull in ``address_standardizer`` -- that
is a sibling extension for ``STDADDR``, installed by the default
``address_standardizer`` flag above; the geocoder does not require it.)
The extension only ships the ``geocode`` machinery -- it does **not**
load the TIGER census data the geocoder matches against.  That data load
(``Loader_Generate_Nation_Script`` / ``Loader_Generate_Census_Script``
per state, US-only) is a separate operational step; ``geocode`` returns
no rows until it has run.  See ``tiger-loader/`` for the loader image
that performs it.

After registration the extension auto-declares the selected PostgreSQL
extensions in metadata, so the Alembic comparator emits
``CREATE EXTENSION IF NOT EXISTS ...`` for any that are missing from the
database.  Columns then use the type directly::

    from sqlalchemy import Column
    from codegen_database.ext.postgis import STDADDR, StdAddr

    Column("address", STDADDR(), nullable=True)
    # store StdAddr(house_num="123", name="MAIN", suftype="ST")

Convenience re-exports::

    from codegen_database.ext.postgis import (
        PostGISExtension,
        STDADDR,
        StdAddr,
        PGExtension,
        register_pg_extension,
    )
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from codegen_database.extension import CodegenDatabaseExtension
from codegen_database.pg_extension import (
    PGExtension,
    register_pg_extension,
)
from codegen_database.types.postgis import STDADDR, StdAddr

if TYPE_CHECKING:
    from sqlalchemy import MetaData

__all__ = [
    "STDADDR",
    "PGExtension",
    "PostGISExtension",
    "StdAddr",
    "register_pg_extension",
]

_POSTGIS_EXTENSION = PGExtension("postgis", cascade=True)
_GEOGRAPHY_EXTENSION = PGExtension("geography")
_ADDRESS_STANDARDIZER_EXTENSION = PGExtension("address_standardizer")
# CASCADE so its required deps (postgis, fuzzystrmatch) install with it
# rather than failing on a missing prerequisite.  address_standardizer is
# NOT a dep of the geocoder -- it rides the separate flag above.
_TIGER_GEOCODER_EXTENSION = PGExtension("postgis_tiger_geocoder", cascade=True)


[docs] @dataclass class PostGISExtension(CodegenDatabaseExtension): """Wire PostGIS-suite support into the codegen_database lifecycle. The extension groups the PostGIS suite; the boolean flags select which PostgreSQL extensions it installs. Args: name: Extension name. Defaults to ``"postgis"``. address_standardizer: Important for ``STDADDR`` geography: Important for ``COORDINATE`` postgis: Important for...... everything else tiger_geocoder: Important for free-string geocoding (``geocode``); ``CASCADE`` pulls in its dependencies. """ name: str = "postgis" postgis: bool = False address_standardizer: bool = True geography: bool = False tiger_geocoder: bool = False
[docs] def configure_metadata(self, metadata: MetaData) -> None: """Declare the selected PostgreSQL extensions.""" if self.postgis: register_pg_extension(metadata, _POSTGIS_EXTENSION) if self.address_standardizer: register_pg_extension(metadata, _ADDRESS_STANDARDIZER_EXTENSION) if self.geography: register_pg_extension(metadata, _GEOGRAPHY_EXTENSION) if self.tiger_geocoder: register_pg_extension(metadata, _TIGER_GEOCODER_EXTENSION)