-
Notifications
You must be signed in to change notification settings - Fork 106
Add PostgresSchema
to manage Postgres schemas with
#206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
2f1bad6
Add `PostgresSchema` to manage Postgres schemas with
Photonios 8212e3c
Document `using` parameter consistently
Photonios 5ec4e44
Warn about using schema-scoped connections with transaction pooler
Photonios 19a451f
Allow for both a random schema name and a time-based one
Photonios 10fb1ce
Improve `test_postgres_schema_delete_and_create`
Photonios 139acfd
Raise specific validation error for schema name prefix + suffix excee…
Photonios 44b204d
Make time-based schema names stable in length
Photonios 5dbcaee
Pass `get_schema_list` SQL directly to cursor
Photonios d22eb6a
Remove connections scoped to schema
Photonios 4ea04f5
Move `using` parameter for `PostgresSchema` to individual methods
Photonios cd67898
Remove wrapt dependency, it's unused
Photonios f77fd29
Make `extract_postgres_error` work with Psycopg 3.1
Photonios 13109e6
Take separator into account when computing maximum schema name prefix…
Photonios File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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,155 @@ | ||
.. 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 time-based name | ||
********************** | ||
|
||
.. warning:: | ||
|
||
The time-based suffix is precise up to the second. If two threads or processes both try to create a time-based schema name with the same suffix in the same second, they will have conflicts. | ||
|
||
.. code-block:: python | ||
|
||
for psqlextra.schema import PostgresSchema | ||
|
||
# schema name will be "myprefix_<timestamp>" | ||
schema = PostgresSchema.create_time_based("myprefix") | ||
print(schema.name) | ||
|
||
|
||
With a random name | ||
****************** | ||
|
||
A 8 character suffix is appended. Entropy is dependent on your system. See :meth:`~os.urandom` for more information. | ||
|
||
.. code-block:: python | ||
|
||
for psqlextra.schema import PostgresSchema | ||
|
||
# schema name will be "myprefix_<8 random characters>" | ||
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**. | ||
|
||
.. 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) |
This file contains hidden or 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 |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = "2.0.4" | ||
__version__ = "2.0.9rc3+swen.4" |
This file contains hidden or 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 hidden or 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 hidden or 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,53 @@ | ||
from typing import Optional, Union | ||
|
||
from django import db | ||
|
||
try: | ||
from psycopg2 import Error as Psycopg2Error | ||
except ImportError: | ||
Psycopg2Error = None | ||
|
||
try: | ||
from psycopg import Error as Psycopg3Error | ||
except ImportError: | ||
Psycopg3Error = None | ||
|
||
|
||
def extract_postgres_error( | ||
error: db.Error, | ||
) -> Optional[Union["Psycopg2Error", "Psycopg3Error"]]: | ||
"""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 (Psycopg2Error and not isinstance(error.__cause__, Psycopg2Error)) and ( | ||
Psycopg3Error and not isinstance(error.__cause__, Psycopg3Error) | ||
): | ||
return None | ||
|
||
return error.__cause__ | ||
|
||
|
||
def extract_postgres_error_code(error: db.Error) -> Optional[str]: | ||
"""Extracts the underlying Postgres error code. | ||
|
||
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. | ||
""" | ||
|
||
cause = error.__cause__ | ||
if not cause: | ||
return None | ||
|
||
if Psycopg2Error and isinstance(cause, Psycopg2Error): | ||
return cause.pgcode | ||
|
||
if Psycopg3Error and isinstance(cause, Psycopg3Error): | ||
return cause.sqlstate | ||
|
||
return None |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.