-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
PostgresSchema
to manage Postgres schemas with
- Loading branch information
Showing
9 changed files
with
700 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
.. include:: ./snippets/postgres_doc_links.rst | ||
|
||
.. _schemas_page: | ||
|
||
Schema | ||
====== | ||
|
||
The :meth:`~psqlextra.schema.PostgresSchema` class provides basic schema management functionality. | ||
|
||
Django does **NOT** support custom schemas. This module does not attempt to solve that problem. | ||
|
||
This module merely allows you to create/drop schemas and allow you to execute raw SQL in a schema. It is not attempt at bringing multi-schema support to Django. | ||
|
||
|
||
Reference an existing schema | ||
---------------------------- | ||
|
||
.. code-block:: python | ||
for psqlextra.schema import PostgresSchema | ||
schema = PostgresSchema("myschema") | ||
with schema.connection.cursor() as cursor: | ||
cursor.execute("SELECT * FROM tablethatexistsinmyschema") | ||
Checking if a schema exists | ||
--------------------------- | ||
.. code-block:: python | ||
for psqlextra.schema import PostgresSchema | ||
schema = PostgresSchema("myschema") | ||
if PostgresSchema.exists("myschema"): | ||
print("exists!") | ||
else: | ||
print('does not exist!") | ||
Creating a new schema | ||
--------------------- | ||
With a custom name | ||
****************** | ||
.. code-block:: python | ||
for psqlextra.schema import PostgresSchema | ||
# will raise an error if the schema already exists | ||
schema = PostgresSchema.create("myschema") | ||
Re-create if necessary with a custom name | ||
***************************************** | ||
.. warning:: | ||
If the schema already exists and it is non-empty or something is referencing it, it will **NOT** be dropped. Specify ``cascade=True`` to drop all of the schema's contents and **anything referencing it**. | ||
.. code-block:: python | ||
for psqlextra.schema import PostgresSchema | ||
# will drop existing schema named `myschema` if it | ||
# exists and re-create it | ||
schema = PostgresSchema.drop_and_create("myschema") | ||
# will drop the schema and cascade it to its contents | ||
# and anything referencing the schema | ||
schema = PostgresSchema.drop_and_create("otherschema", cascade=True) | ||
With a random name | ||
****************** | ||
.. code-block:: python | ||
for psqlextra.schema import PostgresSchema | ||
# schema name will be "myprefix_<timestamp>" | ||
schema = PostgresSchema.create_random("myprefix") | ||
print(schema.name) | ||
Temporary schema with random name | ||
********************************* | ||
Use the :meth:`~psqlextra.schema.postgres_temporary_schema` context manager to create a schema with a random name. The schema will only exist within the context manager. | ||
By default, the schema is not dropped if an exception occurs in the context manager. This prevents unexpected data loss. Specify ``drop_on_throw=True`` to drop the schema if an exception occurs. | ||
Without an outer transaction, the temporary schema might not be dropped when your program is exits unexpectedly (for example; if it is killed with SIGKILL). Wrap the creation of the schema in a transaction to make sure the schema is cleaned up when an error occurs or your program exits suddenly. | ||
.. warning:: | ||
By default, the drop will fail if the schema is not empty or there is anything referencing the schema. Specify ``cascade=True`` to drop all of the schema's contents and **anything referencing it**. | ||
.. note:: | ||
.. code-block:: python | ||
for psqlextra.schema import postgres_temporary_schema | ||
with postgres_temporary_schema("myprefix") as schema: | ||
pass | ||
with postgres_temporary_schema("otherprefix", drop_on_throw=True) as schema: | ||
raise ValueError("drop it like it's hot") | ||
with postgres_temporary_schema("greatprefix", cascade=True) as schema: | ||
with schema.connection.cursor() as cursor: | ||
cursor.execute(f"CREATE TABLE {schema.name} AS SELECT 'hello'") | ||
with postgres_temporary_schema("amazingprefix", drop_on_throw=True, cascade=True) as schema: | ||
with schema.connection.cursor() as cursor: | ||
cursor.execute(f"CREATE TABLE {schema.name} AS SELECT 'hello'") | ||
raise ValueError("oops") | ||
Deleting a schema | ||
----------------- | ||
Any schema can be dropped, including ones not created by :class:`~psqlextra.schema.PostgresSchema`. | ||
The ``public`` schema cannot be dropped. This is a Postgres built-in and it is almost always a mistake to drop it. A :class:`~django.core.exceptions.SuspiciousOperation` erorr will be raised if you attempt to drop the ``public`` schema. | ||
.. warning:: | ||
By default, the drop will fail if the schema is not empty or there is anything referencing the schema. Specify ``cascade=True`` to drop all of the schema's contents and **anything referencing it**. | ||
.. code-block:: python | ||
for psqlextra.schema import PostgresSchema | ||
schema = PostgresSchema.drop("myprefix") | ||
schema = PostgresSchema.drop("myprefix", cascade=True) | ||
Executing queries within a schema | ||
--------------------------------- | ||
By default, a connection operates in the ``public`` schema. The schema offers a connection scoped to that schema that sets the Postgres ``search_path`` to only search within that schema. | ||
.. warning:: | ||
This can be abused to manage Django models in a custom schema. This is not a supported workflow and there might be unexpected issues from attempting to do so. | ||
.. warning:: | ||
Do not pass the connection to a different thread. It is **NOT** thread safe. | ||
.. code-block:: python | ||
from psqlextra.schema import PostgresSchema | ||
schema = PostgresSchema.create("myschema") | ||
with schema.connection.cursor() as cursor: | ||
# table gets created within the `myschema` schema, without | ||
# explicitly specifying the schema name | ||
cursor.execute("CREATE TABLE mytable AS SELECT 'hello'") | ||
with schema.connection.schema_editor() as schema_editor: | ||
# creates a table for the model within the schema | ||
schema_editor.create_model(MyModel) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from typing import Optional | ||
|
||
import psycopg2 | ||
|
||
from django import db | ||
|
||
|
||
def extract_postgres_error(error: db.Error) -> Optional[psycopg2.Error]: | ||
"""Extracts the underlying :see:psycopg2.Error from the specified Django | ||
database error. | ||
As per PEP-249, Django wraps all database errors in its own | ||
exception. We can extract the underlying database error by examaning | ||
the cause of the error. | ||
""" | ||
|
||
if not isinstance(error.__cause__, psycopg2.Error): | ||
return None | ||
|
||
return error.__cause__ |
Oops, something went wrong.