djanquiltdb-plugin-postgres-objects 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ """
2
+ The plugin object DjanQuiltDB discovers through the ``djanquiltdb.plugins`` entry-point group.
3
+
4
+ It exposes two things. ``decorators`` is the module DjanQuiltDB serves from ``djanquiltdb.decorators``, so the
5
+ placement decorators read the same as the model ones. ``install()`` runs when the app registry is ready, and gives
6
+ declared objects their default placement.
7
+ """
8
+
9
+ from types import SimpleNamespace
10
+
11
+ from djanquiltdb_plugin_postgres_objects import decorators
12
+
13
+ __version__ = '1.0.0'
14
+
15
+
16
+ def install():
17
+ """
18
+ Give declared database objects a default placement.
19
+
20
+ django-postgres-objects manages Postgres objects that are not tables, and has no notion of placement: a declaration
21
+ carries no routing hints unless something puts them there, which is exactly right for the single-database projects
22
+ it is also meant to serve. Inside a sharded project the sensible default is the public schema, since that is where
23
+ a function called by a stored generated column belongs and it stays reachable from every shard's search path.
24
+
25
+ The placement decorators set the hints per declaration and so override this.
26
+ """
27
+ from djanquiltdb import ShardingMode
28
+ from postgres_objects.base import DeclarativeObject
29
+
30
+ if not DeclarativeObject.router_hints:
31
+ DeclarativeObject.router_hints = {'sharding_mode': ShardingMode.PUBLIC}
32
+
33
+
34
+ plugin = SimpleNamespace(decorators=decorators, install=install)
@@ -0,0 +1,250 @@
1
+ from djanquiltdb import ShardingMode
2
+ from djanquiltdb.postgresql_backend.base import PUBLIC_SCHEMA_NAME
3
+ from djanquiltdb.router import get_active_connection
4
+ from djanquiltdb.utils import get_all_databases, transaction_for_every_node, use_shard
5
+
6
+
7
+ def _annotate_refresh(cls, sharding_mode):
8
+ """
9
+ Point a declared materialized view's refresh at the correct copy.
10
+
11
+ Left alone, django-postgres-objects sends a refresh to the connection its own routing picks, which for a raw-sql
12
+ declaration is the default one: the wrong copy for any view that does not live on the public schema of the default
13
+ node. ``db_for_refresh`` is the hook it documents for exactly this, so the active connection goes there instead.
14
+ Which schema that is resolves through the search path, since the view is named unqualified, and a view is either
15
+ sharded or public and never both.
16
+
17
+ A mirrored view is the one mode no single connection answers for: its copies sit on the public schema of every
18
+ node. Its refresh is wrapped to visit them all inside one cascading transaction, the way
19
+ ``atomic_write_to_every_node`` propagates any other mirrored write, so either every copy moves or none does.
20
+ Naming a connection with ``using`` still pins it to one.
21
+ """
22
+
23
+ def db_for_refresh(declaration):
24
+ return get_active_connection()
25
+
26
+ cls.db_for_refresh = classmethod(db_for_refresh)
27
+
28
+ if sharding_mode is not ShardingMode.MIRRORED:
29
+ # Re-annotating away from MIRRORED, either on the declaration itself or on a subclass of a mirrored one, has to
30
+ # drop the fan-out the mirrored annotation wraps around refresh.
31
+ base = getattr(cls.refresh.__func__, '__wrapped_refresh__', None)
32
+ if base is not None:
33
+ cls.refresh = classmethod(base)
34
+ return
35
+
36
+ # The plain function rather than the bound classmethod, so a subclassed declaration refreshes its own view and not
37
+ # its parent's, and unwrapped first, so annotating an already annotated declaration cannot nest the loop.
38
+ base = getattr(cls.refresh.__func__, '__wrapped_refresh__', cls.refresh.__func__)
39
+
40
+ def refresh(declaration, concurrently=False, using=None):
41
+ if using is not None:
42
+ return base(declaration, concurrently=concurrently, using=using)
43
+
44
+ with transaction_for_every_node():
45
+ for node_name in get_all_databases():
46
+ with use_shard(node_name=node_name, schema_name=PUBLIC_SCHEMA_NAME):
47
+ base(declaration, concurrently=concurrently)
48
+
49
+ refresh.__wrapped_refresh__ = base
50
+ cls.refresh = classmethod(refresh)
51
+
52
+
53
+ def _annotate_object(cls, sharding_mode):
54
+ """
55
+ Mark a declared database object as belonging to a sharding mode.
56
+
57
+ django-postgres-objects hands ``router_hints`` to ``allow_migrate`` for every operation on the object, so putting
58
+ the sharding mode there routes it exactly the way a model of that mode is routed. It knows nothing about sharding
59
+ itself; this is the whole of the seam between the two libraries.
60
+ """
61
+ cls.router_hints = {'sharding_mode': sharding_mode}
62
+
63
+ if hasattr(cls, 'db_for_refresh'):
64
+ _annotate_refresh(cls, sharding_mode)
65
+
66
+ return cls
67
+
68
+
69
+ def mirrored_function():
70
+ """
71
+ A decorator for marking a declared Postgres function as being mirrored across the various nodes.
72
+
73
+ The function is created on the public schema of every node, which is where ``@public_function()`` puts it too: a
74
+ function holds no data, so there is nothing for the two modes to differ by. Pick whichever names the intent, the
75
+ way the model decorators do.
76
+
77
+ :Example:
78
+ .. code-block:: python
79
+
80
+ from djanquiltdb.decorators import mirrored_function
81
+ from postgres_objects import Function
82
+
83
+
84
+ @mirrored_function()
85
+ class AllUppercase(Function):
86
+ arguments = 'input TEXT'
87
+ returns = 'TEXT'
88
+ volatility = 'IMMUTABLE'
89
+ body = '''
90
+ BEGIN
91
+ RETURN UPPER(input);
92
+ END;
93
+ '''
94
+ """
95
+
96
+ def configure(cls):
97
+ return _annotate_object(cls, ShardingMode.MIRRORED)
98
+
99
+ return configure
100
+
101
+
102
+ def public_function():
103
+ """
104
+ A decorator for marking a declared Postgres function as living in the public schema.
105
+
106
+ This is what a function used by a stored generated column normally wants. The search path of a shard covers the
107
+ shard's own schema and the public schema, so an unqualified call from inside a shard resolves.
108
+
109
+ The function is created on the public schema of every node, since that is where every shard's search path can
110
+ reach it. That is the same placement ``@mirrored_function()`` gives, and for a function the two are equivalent;
111
+ what a mode records here is intent rather than a different set of schemas.
112
+
113
+ :Example:
114
+ .. code-block:: python
115
+
116
+ from djanquiltdb.decorators import public_function
117
+ from postgres_objects import Function
118
+
119
+
120
+ @public_function()
121
+ class AllUppercase(Function):
122
+ arguments = 'input TEXT'
123
+ returns = 'TEXT'
124
+ volatility = 'IMMUTABLE'
125
+ strict = True
126
+ parallel = 'SAFE'
127
+ body = '''
128
+ BEGIN
129
+ RETURN UPPER(input);
130
+ END;
131
+ '''
132
+ """
133
+
134
+ def configure(cls):
135
+ return _annotate_object(cls, ShardingMode.PUBLIC)
136
+
137
+ return configure
138
+
139
+
140
+ def sharded_function():
141
+ """
142
+ A decorator for marking a declared Postgres function as being sharded.
143
+
144
+ The function is created on the template schema and on every shard, and not on the public schema. Note that a
145
+ ``PUBLIC`` function can already read sharded tables: as long as the table names in its body are not
146
+ schema-qualified, they resolve at execution time to the tables of whichever schema the caller is pointed at. Reach
147
+ for a sharded function when the *function itself* has to differ per shard.
148
+
149
+ :Example:
150
+ .. code-block:: python
151
+
152
+ from djanquiltdb.decorators import sharded_function
153
+ from postgres_objects import Function
154
+
155
+
156
+ @sharded_function()
157
+ class ShardTotal(Function):
158
+ returns = 'BIGINT'
159
+ body = '''
160
+ BEGIN
161
+ RETURN (SELECT COUNT(*) FROM example_cake);
162
+ END;
163
+ '''
164
+ """
165
+
166
+ def configure(cls):
167
+ return _annotate_object(cls, ShardingMode.SHARDED)
168
+
169
+ return configure
170
+
171
+
172
+ def mirrored_view():
173
+ """
174
+ A decorator for marking a declared Postgres view as being mirrored across the various nodes.
175
+
176
+ The view is created on the public schema of every node, which is where a view over mirrored tables belongs. A
177
+ ``@public_view()`` reaches those same schemas; what MIRRORED adds is that the copies are kept in step, so a
178
+ materialized one refreshes on every node at once rather than on the connection in context.
179
+
180
+ :Example:
181
+ .. code-block:: python
182
+
183
+ from djanquiltdb.decorators import mirrored_view
184
+ from postgres_objects import View
185
+
186
+
187
+ @mirrored_view()
188
+ class ActiveTypes(View):
189
+ sql = 'SELECT id, name FROM example_type WHERE active'
190
+ """
191
+
192
+ def configure(cls):
193
+ return _annotate_object(cls, ShardingMode.MIRRORED)
194
+
195
+ return configure
196
+
197
+
198
+ def public_view():
199
+ """
200
+ A decorator for marking a declared Postgres view as living in the public schema.
201
+
202
+ A view is created on the public schema of every node and reads the tables that live there. Those are the same
203
+ schemas a mirrored view reaches; what PUBLIC says is that each copy stands on its own, over sources that may differ
204
+ per node, and that a materialized one is refreshed on the connection in context rather than everywhere at once.
205
+
206
+ Note that unlike a function, a public view is *not* a way to read sharded tables: a view's body is resolved when it
207
+ is created, not when it is queried, so the tables it names are pinned to the schema it was created in. A view over
208
+ sharded tables has to be sharded itself.
209
+
210
+ :Example:
211
+ .. code-block:: python
212
+
213
+ from djanquiltdb.decorators import public_view
214
+ from postgres_objects import View
215
+
216
+
217
+ @public_view()
218
+ class CakeTypeNames(View):
219
+ sql = 'SELECT id, name FROM example_caketype'
220
+ """
221
+
222
+ def configure(cls):
223
+ return _annotate_object(cls, ShardingMode.PUBLIC)
224
+
225
+ return configure
226
+
227
+
228
+ def sharded_view():
229
+ """
230
+ A decorator for marking a declared Postgres view as being sharded.
231
+
232
+ The view is created on the template schema and on every shard, and not on the public schema. This is what a view
233
+ over sharded tables needs: each shard gets its own copy, reading the tables of the schema it was created in.
234
+
235
+ :Example:
236
+ .. code-block:: python
237
+
238
+ from djanquiltdb.decorators import sharded_view
239
+ from postgres_objects import View
240
+
241
+
242
+ @sharded_view()
243
+ class LoudCakes(View):
244
+ sql = 'SELECT id, upper(name) AS name FROM example_cake'
245
+ """
246
+
247
+ def configure(cls):
248
+ return _annotate_object(cls, ShardingMode.SHARDED)
249
+
250
+ return configure
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: djanquiltdb-plugin-postgres-objects
3
+ Version: 1.0.0
4
+ Summary: DjanQuiltDB decorators for functions/views as declared by django-postgres-objects
5
+ Author-email: DjanQuiltDB Project <djanquiltdb@portal42.net>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/DjanQuiltDB/djanquiltdb
8
+ Project-URL: Documentation, https://djanquiltdb.readthedocs.io/en/latest/plugins/postgres-objects/index.html
9
+ Project-URL: Source, https://github.com/DjanQuiltDB/djanquiltdb/tree/master/packages/djanquiltdb-plugin-postgres-objects
10
+ Project-URL: Issues, https://github.com/DjanQuiltDB/djanquiltdb/issues
11
+ Project-URL: Changelog, https://github.com/DjanQuiltDB/djanquiltdb/blob/master/packages/djanquiltdb-plugin-postgres-objects/CHANGELOG.rst
12
+ Keywords: django,database,sharding,postgresql,migrations
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Framework :: Django
21
+ Classifier: Framework :: Django :: 6.0
22
+ Classifier: Framework :: Django :: 6.1
23
+ Classifier: Topic :: Database
24
+ Requires-Python: >=3.14
25
+ Description-Content-Type: text/x-rst
26
+ License-File: LICENSE
27
+ Requires-Dist: django<7.0,>=6.0
28
+ Requires-Dist: djanquiltdb<5.0,>=4.0
29
+ Requires-Dist: django-postgres-objects<2.0,>=1.0
30
+ Provides-Extra: lint
31
+ Requires-Dist: ruff==0.15.21; extra == "lint"
32
+ Provides-Extra: test
33
+ Requires-Dist: coverage; extra == "test"
34
+ Requires-Dist: psycopg[binary]>=3.0.0; extra == "test"
35
+ Requires-Dist: dj-database-url; extra == "test"
36
+ Provides-Extra: docs
37
+ Requires-Dist: sphinx<10,>=8; extra == "docs"
38
+ Provides-Extra: dev
39
+ Requires-Dist: djanquiltdb-plugin-postgres-objects[docs,lint,test]; extra == "dev"
40
+ Requires-Dist: pre-commit; extra == "dev"
41
+ Requires-Dist: tox>=4.21; extra == "dev"
42
+ Requires-Dist: build; extra == "dev"
43
+ Requires-Dist: twine; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ .. The logo needs an absolute URL: this README is the PyPI long description, where a relative path renders broken.
47
+
48
+ .. image:: https://raw.githubusercontent.com/DjanQuiltDB/djanquiltdb/master/assets/icon-128.png
49
+ :alt: DjanQuiltDB
50
+ :width: 128
51
+
52
+ ===================================
53
+ djanquiltdb-plugin-postgres-objects
54
+ ===================================
55
+
56
+ .. image:: https://img.shields.io/pypi/v/djanquiltdb-plugin-postgres-objects.svg
57
+ :target: https://pypi.org/project/djanquiltdb-plugin-postgres-objects/
58
+ :alt: PyPI
59
+
60
+ .. image:: https://img.shields.io/pypi/pyversions/djanquiltdb-plugin-postgres-objects.svg
61
+ :target: https://pypi.org/project/djanquiltdb-plugin-postgres-objects/
62
+ :alt: Supported Python versions
63
+
64
+ .. image:: https://img.shields.io/pypi/frameworkversions/django/djanquiltdb-plugin-postgres-objects.svg
65
+ :target: https://pypi.org/project/djanquiltdb-plugin-postgres-objects/
66
+ :alt: Supported Django versions
67
+
68
+ .. image:: https://img.shields.io/badge/postgres-17%20%7C%2018-4169e1?logo=postgresql&logoColor=white
69
+ :target: https://www.postgresql.org/
70
+ :alt: Supported PostgreSQL versions
71
+
72
+ .. image:: https://github.com/DjanQuiltDB/djanquiltdb/actions/workflows/ci.yml/badge.svg
73
+ :target: https://github.com/DjanQuiltDB/djanquiltdb/actions/workflows/ci.yml
74
+ :alt: CI
75
+
76
+ .. image:: https://readthedocs.org/projects/djanquiltdb/badge/?version=latest
77
+ :target: https://djanquiltdb.readthedocs.io/en/latest/plugins/postgres-objects/index.html
78
+ :alt: Documentation
79
+
80
+ .. image:: https://img.shields.io/pypi/l/djanquiltdb-plugin-postgres-objects.svg
81
+ :target: https://github.com/DjanQuiltDB/djanquiltdb/blob/master/LICENSE
82
+ :alt: BSD-3-Clause licence
83
+
84
+ `django-postgres-objects <https://github.com/djanquiltdb/django-postgres-objects>`_ declares Postgres functions and
85
+ views as classes and lets ``makemigrations`` manage them. `DjanQuiltDB <https://github.com/djanquiltdb/djanquiltdb>`_
86
+ shards a database across schemas and nodes. This library serves as a compatibility layer between them, allowing you to
87
+ mark declarative functions and views as sharded::
88
+
89
+ # example/functions.py
90
+ from djanquiltdb.decorators import public_function
91
+ from postgres_objects import Function
92
+
93
+
94
+ @public_function()
95
+ class AllUppercase(Function):
96
+ arguments = 'input TEXT'
97
+ returns = 'TEXT'
98
+ volatility = 'IMMUTABLE'
99
+ strict = True
100
+ parallel = 'SAFE'
101
+ body = """
102
+ BEGIN
103
+ RETURN UPPER(input);
104
+ END;
105
+ """
106
+
107
+ Install it through DjanQuiltDB's extra::
108
+
109
+ pip install djanquiltdb[postgres-objects]
110
+
111
+ The decorators are importable from ``djanquiltdb.decorators`` beside the model ones, as above.
112
+
113
+ Functions get ``@public_function``, ``@mirrored_function`` and ``@sharded_function``; views get ``@public_view``,
114
+ ``@mirrored_view`` and ``@sharded_view``. An unannotated declaration is placed in the public schema, so declarations
115
+ written for a single-database project keep working once that project is sharded.
116
+
117
+ Full documentation is at https://djanquiltdb.readthedocs.io/en/latest/plugins/postgres-objects/index.html.
118
+
119
+ Development
120
+ ===========
121
+
122
+ This package is developed in the `DjanQuiltDB repository <https://github.com/DjanQuiltDB/djanquiltdb>`_ beside
123
+ ``djanquiltdb`` itself. Tests run through tox against the Postgres containers in the repository's
124
+ ``docker-compose.yml``; see ``DOCKER.md`` at the repository root::
125
+
126
+ docker compose run --rm test tox
127
+
128
+ Requirements
129
+ ------------
130
+
131
+ * Python 3.14
132
+ * Django 6.0 or 6.1
133
+ * PostgreSQL 17 or 18
134
+ * djanquiltdb 4.x
135
+ * django-postgres-objects 1.x
@@ -0,0 +1,8 @@
1
+ djanquiltdb_plugin_postgres_objects/__init__.py,sha256=-pcKqDW1csaXDO5fmVbn9IhjWyFf90OCQV62BqRF6CE,1422
2
+ djanquiltdb_plugin_postgres_objects/decorators.py,sha256=n4Hdc7NcBfbk9M0aGMHK4Tpf6fIOs13JtUtDNQH58Ng,9356
3
+ djanquiltdb_plugin_postgres_objects-1.0.0.dist-info/licenses/LICENSE,sha256=SjqLhDqFqtKwwJqhC-80QG9cPUmuTv3yBPEp67_7dQE,1505
4
+ djanquiltdb_plugin_postgres_objects-1.0.0.dist-info/METADATA,sha256=8YUqJialoDZRxrv1XHihzKGT5TxKC6y8hBE3ohli3Qo,5692
5
+ djanquiltdb_plugin_postgres_objects-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ djanquiltdb_plugin_postgres_objects-1.0.0.dist-info/entry_points.txt,sha256=6DJMa7zy91HQhZJhID_Ip4eMx7oEZMdwooya5uem6_8,84
7
+ djanquiltdb_plugin_postgres_objects-1.0.0.dist-info/top_level.txt,sha256=jjrA6v2_W4CmFSOIl1MpyQnc-IiRfrV07o3DuEoBQ2Y,36
8
+ djanquiltdb_plugin_postgres_objects-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [djanquiltdb.plugins]
2
+ postgres_objects = djanquiltdb_plugin_postgres_objects:plugin
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2026, DjanQuiltDB Project
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+ * Redistributions of source code must retain the above copyright notice, this
6
+ list of conditions and the following disclaimer.
7
+ * Redistributions in binary form must reproduce the above copyright notice,
8
+ this list of conditions and the following disclaimer in the documentation
9
+ and/or other materials provided with the distribution.
10
+ * Neither the name of the copyright holder nor the names of its
11
+ contributors may be used to endorse or promote products derived from
12
+ this software without specific prior written permission.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ djanquiltdb_plugin_postgres_objects