plain.postgres 0.84.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.
Files changed (93) hide show
  1. plain/postgres/CHANGELOG.md +1028 -0
  2. plain/postgres/README.md +925 -0
  3. plain/postgres/__init__.py +120 -0
  4. plain/postgres/agents/.claude/rules/plain-postgres.md +78 -0
  5. plain/postgres/aggregates.py +236 -0
  6. plain/postgres/backups/__init__.py +0 -0
  7. plain/postgres/backups/cli.py +148 -0
  8. plain/postgres/backups/clients.py +94 -0
  9. plain/postgres/backups/core.py +172 -0
  10. plain/postgres/base.py +1415 -0
  11. plain/postgres/cli/__init__.py +3 -0
  12. plain/postgres/cli/db.py +142 -0
  13. plain/postgres/cli/migrations.py +1085 -0
  14. plain/postgres/config.py +18 -0
  15. plain/postgres/connection.py +1331 -0
  16. plain/postgres/connections.py +77 -0
  17. plain/postgres/constants.py +13 -0
  18. plain/postgres/constraints.py +495 -0
  19. plain/postgres/database_url.py +94 -0
  20. plain/postgres/db.py +59 -0
  21. plain/postgres/default_settings.py +38 -0
  22. plain/postgres/deletion.py +475 -0
  23. plain/postgres/dialect.py +640 -0
  24. plain/postgres/entrypoints.py +4 -0
  25. plain/postgres/enums.py +103 -0
  26. plain/postgres/exceptions.py +217 -0
  27. plain/postgres/expressions.py +1912 -0
  28. plain/postgres/fields/__init__.py +2118 -0
  29. plain/postgres/fields/encrypted.py +354 -0
  30. plain/postgres/fields/json.py +413 -0
  31. plain/postgres/fields/mixins.py +30 -0
  32. plain/postgres/fields/related.py +1192 -0
  33. plain/postgres/fields/related_descriptors.py +290 -0
  34. plain/postgres/fields/related_lookups.py +223 -0
  35. plain/postgres/fields/related_managers.py +661 -0
  36. plain/postgres/fields/reverse_descriptors.py +229 -0
  37. plain/postgres/fields/reverse_related.py +328 -0
  38. plain/postgres/fields/timezones.py +143 -0
  39. plain/postgres/forms.py +773 -0
  40. plain/postgres/functions/__init__.py +189 -0
  41. plain/postgres/functions/comparison.py +127 -0
  42. plain/postgres/functions/datetime.py +454 -0
  43. plain/postgres/functions/math.py +140 -0
  44. plain/postgres/functions/mixins.py +59 -0
  45. plain/postgres/functions/text.py +282 -0
  46. plain/postgres/functions/window.py +125 -0
  47. plain/postgres/indexes.py +286 -0
  48. plain/postgres/lookups.py +758 -0
  49. plain/postgres/meta.py +584 -0
  50. plain/postgres/migrations/__init__.py +53 -0
  51. plain/postgres/migrations/autodetector.py +1379 -0
  52. plain/postgres/migrations/exceptions.py +54 -0
  53. plain/postgres/migrations/executor.py +188 -0
  54. plain/postgres/migrations/graph.py +364 -0
  55. plain/postgres/migrations/loader.py +377 -0
  56. plain/postgres/migrations/migration.py +180 -0
  57. plain/postgres/migrations/operations/__init__.py +34 -0
  58. plain/postgres/migrations/operations/base.py +139 -0
  59. plain/postgres/migrations/operations/fields.py +373 -0
  60. plain/postgres/migrations/operations/models.py +798 -0
  61. plain/postgres/migrations/operations/special.py +184 -0
  62. plain/postgres/migrations/optimizer.py +74 -0
  63. plain/postgres/migrations/questioner.py +340 -0
  64. plain/postgres/migrations/recorder.py +119 -0
  65. plain/postgres/migrations/serializer.py +378 -0
  66. plain/postgres/migrations/state.py +882 -0
  67. plain/postgres/migrations/utils.py +147 -0
  68. plain/postgres/migrations/writer.py +302 -0
  69. plain/postgres/options.py +207 -0
  70. plain/postgres/otel.py +231 -0
  71. plain/postgres/preflight.py +336 -0
  72. plain/postgres/query.py +2242 -0
  73. plain/postgres/query_utils.py +456 -0
  74. plain/postgres/registry.py +217 -0
  75. plain/postgres/schema.py +1885 -0
  76. plain/postgres/sql/__init__.py +40 -0
  77. plain/postgres/sql/compiler.py +1869 -0
  78. plain/postgres/sql/constants.py +22 -0
  79. plain/postgres/sql/datastructures.py +222 -0
  80. plain/postgres/sql/query.py +2947 -0
  81. plain/postgres/sql/where.py +374 -0
  82. plain/postgres/test/__init__.py +0 -0
  83. plain/postgres/test/pytest.py +117 -0
  84. plain/postgres/test/utils.py +18 -0
  85. plain/postgres/transaction.py +222 -0
  86. plain/postgres/types.py +92 -0
  87. plain/postgres/types.pyi +751 -0
  88. plain/postgres/utils.py +345 -0
  89. plain_postgres-0.84.0.dist-info/METADATA +937 -0
  90. plain_postgres-0.84.0.dist-info/RECORD +93 -0
  91. plain_postgres-0.84.0.dist-info/WHEEL +4 -0
  92. plain_postgres-0.84.0.dist-info/entry_points.txt +5 -0
  93. plain_postgres-0.84.0.dist-info/licenses/LICENSE +61 -0
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ from types import DynamicClassAttribute
5
+ from typing import Any
6
+
7
+ from plain.utils.functional import Promise
8
+
9
+ __all__ = ["Choices", "IntegerChoices", "TextChoices"]
10
+
11
+
12
+ class ChoicesMeta(enum.EnumMeta):
13
+ """A metaclass for creating a enum choices."""
14
+
15
+ def __new__(
16
+ metacls: type,
17
+ classname: str,
18
+ bases: tuple[type, ...],
19
+ classdict: Any,
20
+ **kwds: Any,
21
+ ) -> type:
22
+ labels = []
23
+ for key in classdict._member_names:
24
+ value = classdict[key]
25
+ if (
26
+ isinstance(value, list | tuple)
27
+ and len(value) > 1
28
+ and isinstance(value[-1], Promise | str)
29
+ ):
30
+ *value, label = value
31
+ value = tuple(value)
32
+ else:
33
+ label = key.replace("_", " ").title()
34
+ labels.append(label)
35
+ # Use dict.__setitem__() to suppress defenses against double
36
+ # assignment in enum's classdict.
37
+ dict.__setitem__(classdict, key, value)
38
+ cls = super().__new__(metacls, classname, bases, classdict, **kwds) # type: ignore[misc]
39
+ for member, label in zip(cls.__members__.values(), labels):
40
+ member._label_ = label
41
+ return enum.unique(cls)
42
+
43
+ def __contains__(cls, member: object) -> bool: # type: ignore[override]
44
+ if not isinstance(member, enum.Enum):
45
+ # Allow non-enums to match against member values.
46
+ return any(x.value == member for x in cls) # type: ignore[attr-defined]
47
+ return super().__contains__(member)
48
+
49
+ @property
50
+ def names(cls) -> list[str]:
51
+ empty = ["__empty__"] if hasattr(cls, "__empty__") else []
52
+ return empty + [member.name for member in cls] # type: ignore[attr-defined]
53
+
54
+ @property
55
+ def choices(cls) -> list[tuple[Any, str]]:
56
+ empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []
57
+ return empty + [(member.value, member.label) for member in cls] # type: ignore[attr-defined]
58
+
59
+ @property
60
+ def labels(cls) -> list[str]:
61
+ return [label for _, label in cls.choices]
62
+
63
+ @property
64
+ def values(cls) -> list[Any]:
65
+ return [value for value, _ in cls.choices]
66
+
67
+
68
+ class Choices(enum.Enum, metaclass=ChoicesMeta):
69
+ """Class for creating enumerated choices."""
70
+
71
+ # Dynamically set by metaclass
72
+ _label_: str
73
+
74
+ @DynamicClassAttribute
75
+ def label(self) -> str:
76
+ return self._label_
77
+
78
+ def __str__(self) -> str:
79
+ """
80
+ Use value when cast to str, so that Choices set as model instance
81
+ attributes are rendered as expected in templates and similar contexts.
82
+ """
83
+ return str(self.value)
84
+
85
+ # A similar format was proposed for Python 3.10.
86
+ def __repr__(self) -> str:
87
+ return f"{self.__class__.__qualname__}.{self._name_}"
88
+
89
+
90
+ class IntegerChoices(int, Choices):
91
+ """Class for creating enumerated integer choices."""
92
+
93
+ pass
94
+
95
+
96
+ class TextChoices(str, Choices):
97
+ """Class for creating enumerated string choices."""
98
+
99
+ @staticmethod
100
+ def _generate_next_value_(
101
+ name: str, start: int, count: int, last_values: list[str]
102
+ ) -> str:
103
+ return name
@@ -0,0 +1,217 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
5
+
6
+ import psycopg
7
+
8
+ if TYPE_CHECKING:
9
+ from plain.postgres.connection import DatabaseConnection
10
+
11
+ F = TypeVar("F", bound=Callable[..., Any])
12
+
13
+ # MARK: Database Query Exceptions
14
+
15
+
16
+ class EmptyResultSet(Exception):
17
+ """A database query predicate is impossible."""
18
+
19
+ pass
20
+
21
+
22
+ class FullResultSet(Exception):
23
+ """A database query predicate is matches everything."""
24
+
25
+ pass
26
+
27
+
28
+ # MARK: Model and Field Errors
29
+
30
+
31
+ class FieldDoesNotExist(Exception):
32
+ """The requested model field does not exist"""
33
+
34
+ pass
35
+
36
+
37
+ class FieldError(Exception):
38
+ """Some kind of problem with a model field."""
39
+
40
+ pass
41
+
42
+
43
+ class ObjectDoesNotExist(Exception):
44
+ """The requested object does not exist"""
45
+
46
+ pass
47
+
48
+
49
+ class MultipleObjectsReturned(Exception):
50
+ """The query returned multiple objects when only one was expected."""
51
+
52
+ pass
53
+
54
+
55
+ # MARK: Model Exception Descriptors
56
+
57
+
58
+ class DoesNotExistDescriptor:
59
+ """Descriptor that creates a unique DoesNotExist exception class per model."""
60
+
61
+ def __init__(self) -> None:
62
+ self._exceptions_by_class: dict[type, type[ObjectDoesNotExist]] = {}
63
+
64
+ def __get__(self, instance: Any, owner: type | None) -> type[ObjectDoesNotExist]:
65
+ if owner is None:
66
+ return ObjectDoesNotExist # Return base class as fallback
67
+
68
+ # Create a unique exception class for this model if we haven't already
69
+ if owner not in self._exceptions_by_class:
70
+ # type() returns a subclass of ObjectDoesNotExist
71
+ exc_class: type[ObjectDoesNotExist] = cast(
72
+ type[ObjectDoesNotExist],
73
+ type(
74
+ "DoesNotExist",
75
+ (ObjectDoesNotExist,),
76
+ {
77
+ "__module__": owner.__module__,
78
+ "__qualname__": f"{owner.__qualname__}.DoesNotExist",
79
+ },
80
+ ),
81
+ )
82
+ self._exceptions_by_class[owner] = exc_class
83
+
84
+ return self._exceptions_by_class[owner]
85
+
86
+ def __set__(self, instance: Any, value: Any) -> None:
87
+ raise AttributeError("Cannot set DoesNotExist")
88
+
89
+
90
+ class MultipleObjectsReturnedDescriptor:
91
+ """Descriptor that creates a unique MultipleObjectsReturned exception class per model."""
92
+
93
+ def __init__(self) -> None:
94
+ self._exceptions_by_class: dict[type, type[MultipleObjectsReturned]] = {}
95
+
96
+ def __get__(
97
+ self, instance: Any, owner: type | None
98
+ ) -> type[MultipleObjectsReturned]:
99
+ if owner is None:
100
+ return MultipleObjectsReturned # Return base class as fallback
101
+
102
+ # Create a unique exception class for this model if we haven't already
103
+ if owner not in self._exceptions_by_class:
104
+ # type() returns a subclass of MultipleObjectsReturned
105
+ exc_class = cast(
106
+ type[MultipleObjectsReturned],
107
+ type(
108
+ "MultipleObjectsReturned",
109
+ (MultipleObjectsReturned,),
110
+ {
111
+ "__module__": owner.__module__,
112
+ "__qualname__": f"{owner.__qualname__}.MultipleObjectsReturned",
113
+ },
114
+ ),
115
+ )
116
+ self._exceptions_by_class[owner] = exc_class
117
+
118
+ return self._exceptions_by_class[owner]
119
+
120
+ def __set__(self, instance: Any, value: Any) -> None:
121
+ raise AttributeError("Cannot set MultipleObjectsReturned")
122
+
123
+
124
+ # MARK: Database Exceptions (PEP-249)
125
+
126
+
127
+ class Error(Exception):
128
+ pass
129
+
130
+
131
+ class InterfaceError(Error):
132
+ pass
133
+
134
+
135
+ class DatabaseError(Error):
136
+ pass
137
+
138
+
139
+ class DataError(DatabaseError):
140
+ pass
141
+
142
+
143
+ class OperationalError(DatabaseError):
144
+ pass
145
+
146
+
147
+ class IntegrityError(DatabaseError):
148
+ pass
149
+
150
+
151
+ class InternalError(DatabaseError):
152
+ pass
153
+
154
+
155
+ class ProgrammingError(DatabaseError):
156
+ pass
157
+
158
+
159
+ class NotSupportedError(DatabaseError):
160
+ pass
161
+
162
+
163
+ class DatabaseErrorWrapper:
164
+ """
165
+ Context manager and decorator that reraises backend-specific database
166
+ exceptions using Plain's common wrappers.
167
+ """
168
+
169
+ def __init__(self, wrapper: DatabaseConnection) -> None:
170
+ """
171
+ wrapper is a database wrapper.
172
+
173
+ It must have a Database attribute defining PEP-249 exceptions.
174
+ """
175
+ self.wrapper = wrapper
176
+
177
+ def __enter__(self) -> None:
178
+ pass
179
+
180
+ def __exit__(
181
+ self,
182
+ exc_type: type[BaseException] | None,
183
+ exc_value: BaseException | None,
184
+ traceback: Any,
185
+ ) -> None:
186
+ if exc_type is None:
187
+ return
188
+ for plain_exc_type in (
189
+ DataError,
190
+ OperationalError,
191
+ IntegrityError,
192
+ InternalError,
193
+ ProgrammingError,
194
+ NotSupportedError,
195
+ DatabaseError,
196
+ InterfaceError,
197
+ Error,
198
+ ):
199
+ db_exc_type = getattr(psycopg, plain_exc_type.__name__)
200
+ if issubclass(exc_type, db_exc_type):
201
+ plain_exc_value = (
202
+ plain_exc_type(*exc_value.args) if exc_value else plain_exc_type()
203
+ )
204
+ # Only set the 'errors_occurred' flag for errors that may make
205
+ # the connection unusable.
206
+ if plain_exc_type not in (DataError, IntegrityError):
207
+ self.wrapper.errors_occurred = True
208
+ raise plain_exc_value.with_traceback(traceback) from exc_value
209
+
210
+ def __call__(self, func: F) -> F:
211
+ # Note that we are intentionally not using @wraps here for performance
212
+ # reasons. Refs #21109.
213
+ def inner(*args: Any, **kwargs: Any) -> Any:
214
+ with self:
215
+ return func(*args, **kwargs)
216
+
217
+ return cast(F, inner)