Using Extensions with SQLAlchemy
PostgreSQL extensions are add-on modules that extend the functionality of the database by providing new data types, operators, functions, procedural languages, and more.
This guide explains how to define a schema field that uses the Geometry data type provided by the PostGIS extension, and configure the schema migration to manage both PostgreSQL extension installations and the SQLAlchemy models as a single migration unit using Atlas. This explanation can be extrapolated to other extensions and libraries that link with SQLAlchemy.
Atlas support for Extensions is available exclusively to Pro users. To use this feature, run:
atlas login
Getting started with Atlas and SQLAlchemy
Before following this guide, make sure you are properly set up with Atlas for SQLAlchemy integration.
Composite Schema
In order to expand our PostgreSQL schema migration to include both extensions and our SQLAlchemy models, we configure Atlas to read the state of the schema from a composite schema data source. Follow the steps below to configure this for your project:
- Create a SQL or HCL file that defines the necessary extensions used by your database.
- SQL
- HCL
CREATE EXTENSION postgis;
schema "public" {}
extension "postgis" {
schema = schema.public
version = "3.4.2"
comment = "PostGIS geometry and geography spatial types and functions"
}
- In your SQLAlchemy model, define a field that uses the data type provided by the extension.
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship, DeclarativeBase
from geoalchemy2 import Geometry
class Base(DeclarativeBase):
pass
class Venue(Base):
__tablename__ = "venues"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
address = Column(Geometry('POINT', srid=4326), nullable=False)
- In your
atlas.hcl
config file, add acomposite_schema
that includes your extensions defined inschema.sql
and your SQLAlchemy model:
data "external_schema" "sqlalchemy" {
program = [
"atlas-provider-sqlalchemy",
"--path", "./models.py", // path to your SQLAlchemy models directory or file
"--dialect", "postgresql"
]
}
data "composite_schema" "app" {
# Load custom types first to define extensions
schema {
url = "file://schema.sql"
}
# Then, load SQLAlchemy models
schema "public" {
url = data.external_schema.sqlalchemy.url
}
}
env "local" {
src = data.composite_schema.app.url // link to current schema state
dev = "docker://postgis/latest/dev" // dev database
}
Usage
After setting up our composite schema, we can get its representation using the atlas schema inspect
command, generate
schema migrations for it, apply them to a database, and more. Below are a few commands to get you started with Atlas:
Inspect the Schema
The atlas schema inspect
command is commonly used to inspect databases. However, we can also use it to inspect our
composite_schema
and print the SQL representation of it:
atlas schema inspect \
--env local \
--url env://src \
--format '{{ sql . }}'
The command above prints the following SQL.
-- Add new schema named "public"
CREATE SCHEMA IF NOT EXISTS "public";
-- Set comment to schema: "public"
COMMENT ON SCHEMA "public" IS 'standard public schema';
-- Create extension "postgis"
CREATE EXTENSION "postgis" WITH SCHEMA "public" VERSION "3.5.2";
-- Create "venues" table
CREATE TABLE "public"."venues" ("id" serial NOT NULL, "name" character varying NOT NULL, "address"
public.geometry(Point,4326) NOT NULL, PRIMARY KEY ("id"));
-- Create index "idx_venues_loc" to table: "venues"
CREATE INDEX "idx_venues_loc" ON "public"."venues" USING gist ("loc");
-- Create index "ix_venues_id" to table: "venues"
CREATE INDEX "ix_venues_id" ON "public"."venues" ("id");
Generate Migrations For the Schema
To generate a migration for the schema, run the following command:
atlas migrate diff --env local
Note that a new migration file is created with the following content:
-- Create extension "postgis"
CREATE EXTENSION "postgis" WITH SCHEMA "public" VERSION "3.5.2";
-- Create "venues" table
CREATE TABLE "public"."venues" ("id" serial NOT NULL, "name" character varying NOT NULL,
"address" public.geometry(Point,4326) NOT NULL, PRIMARY KEY ("id"));
-- Create index "idx_venues_loc" to table: "venues"
CREATE INDEX "idx_venues_loc" ON "public"."venues" USING gist ("loc");
-- Create index "ix_venues_id" to table: "venues"
CREATE INDEX "ix_venues_id" ON "public"."venues" ("id");
Apply the Migrations
To apply the migration generated above to a running database, run the following command:
atlas migrate apply \
--env local \
--url "postgres://postgres:pass@localhost:5432/database?sslmode=disable"
Extensions are database-level objects, so they are installed at the database level. Therefore, when working with extensions,
the scope of the migration should be set to the database, where objects are qualified with the schema name. Hence, the
search_path
is dropped from the dev-database URL in the atlas.hcl
file.