SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.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 (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1031 @@
1
+ # dialects/postgresql/ranges.py
2
+ # Copyright (C) 2013-2026 the SQLAlchemy authors and contributors
3
+ # <see AUTHORS file>
4
+ #
5
+ # This module is part of SQLAlchemy and is released under
6
+ # the MIT License: https://www.opensource.org/licenses/mit-license.php
7
+
8
+ from __future__ import annotations
9
+
10
+ import dataclasses
11
+ from datetime import date
12
+ from datetime import datetime
13
+ from datetime import timedelta
14
+ from decimal import Decimal
15
+ from typing import Any
16
+ from typing import cast
17
+ from typing import Generic
18
+ from typing import List
19
+ from typing import Optional
20
+ from typing import overload
21
+ from typing import Sequence
22
+ from typing import Tuple
23
+ from typing import Type
24
+ from typing import TYPE_CHECKING
25
+ from typing import TypeVar
26
+ from typing import Union
27
+
28
+ from .operators import ADJACENT_TO
29
+ from .operators import CONTAINED_BY
30
+ from .operators import CONTAINS
31
+ from .operators import NOT_EXTEND_LEFT_OF
32
+ from .operators import NOT_EXTEND_RIGHT_OF
33
+ from .operators import OVERLAP
34
+ from .operators import STRICTLY_LEFT_OF
35
+ from .operators import STRICTLY_RIGHT_OF
36
+ from ... import types as sqltypes
37
+ from ...sql import operators
38
+ from ...sql.type_api import TypeEngine
39
+ from ...util import py310
40
+ from ...util.typing import Literal
41
+
42
+ if TYPE_CHECKING:
43
+ from ...sql.elements import ColumnElement
44
+ from ...sql.type_api import _TE
45
+ from ...sql.type_api import TypeEngineMixin
46
+
47
+ _T = TypeVar("_T", bound=Any)
48
+
49
+ _BoundsType = Literal["()", "[)", "(]", "[]"]
50
+
51
+ if py310:
52
+ dc_slots = {"slots": True}
53
+ dc_kwonly = {"kw_only": True}
54
+ else:
55
+ dc_slots = {}
56
+ dc_kwonly = {}
57
+
58
+
59
+ @dataclasses.dataclass(frozen=True, **dc_slots)
60
+ class Range(Generic[_T]):
61
+ """Represent a PostgreSQL range.
62
+
63
+ E.g.::
64
+
65
+ r = Range(10, 50, bounds="()")
66
+
67
+ The calling style is similar to that of psycopg and psycopg2, in part
68
+ to allow easier migration from previous SQLAlchemy versions that used
69
+ these objects directly.
70
+
71
+ :param lower: Lower bound value, or None
72
+ :param upper: Upper bound value, or None
73
+ :param bounds: keyword-only, optional string value that is one of
74
+ ``"()"``, ``"[)"``, ``"(]"``, ``"[]"``. Defaults to ``"[)"``.
75
+ :param empty: keyword-only, optional bool indicating this is an "empty"
76
+ range
77
+
78
+ .. versionadded:: 2.0
79
+
80
+ """
81
+
82
+ lower: Optional[_T] = None
83
+ """the lower bound"""
84
+
85
+ upper: Optional[_T] = None
86
+ """the upper bound"""
87
+
88
+ if TYPE_CHECKING:
89
+ bounds: _BoundsType = dataclasses.field(default="[)")
90
+ empty: bool = dataclasses.field(default=False)
91
+ else:
92
+ bounds: _BoundsType = dataclasses.field(default="[)", **dc_kwonly)
93
+ empty: bool = dataclasses.field(default=False, **dc_kwonly)
94
+
95
+ if not py310:
96
+
97
+ def __init__(
98
+ self,
99
+ lower: Optional[_T] = None,
100
+ upper: Optional[_T] = None,
101
+ *,
102
+ bounds: _BoundsType = "[)",
103
+ empty: bool = False,
104
+ ):
105
+ # no __slots__ either so we can update dict
106
+ self.__dict__.update(
107
+ {
108
+ "lower": lower,
109
+ "upper": upper,
110
+ "bounds": bounds,
111
+ "empty": empty,
112
+ }
113
+ )
114
+
115
+ def __bool__(self) -> bool:
116
+ return not self.empty
117
+
118
+ @property
119
+ def isempty(self) -> bool:
120
+ "A synonym for the 'empty' attribute."
121
+
122
+ return self.empty
123
+
124
+ @property
125
+ def is_empty(self) -> bool:
126
+ "A synonym for the 'empty' attribute."
127
+
128
+ return self.empty
129
+
130
+ @property
131
+ def lower_inc(self) -> bool:
132
+ """Return True if the lower bound is inclusive."""
133
+
134
+ return self.bounds[0] == "["
135
+
136
+ @property
137
+ def lower_inf(self) -> bool:
138
+ """Return True if this range is non-empty and lower bound is
139
+ infinite."""
140
+
141
+ return not self.empty and self.lower is None
142
+
143
+ @property
144
+ def upper_inc(self) -> bool:
145
+ """Return True if the upper bound is inclusive."""
146
+
147
+ return self.bounds[1] == "]"
148
+
149
+ @property
150
+ def upper_inf(self) -> bool:
151
+ """Return True if this range is non-empty and the upper bound is
152
+ infinite."""
153
+
154
+ return not self.empty and self.upper is None
155
+
156
+ @property
157
+ def __sa_type_engine__(self) -> AbstractSingleRange[_T]:
158
+ return AbstractSingleRange()
159
+
160
+ def _contains_value(self, value: _T) -> bool:
161
+ """Return True if this range contains the given value."""
162
+
163
+ if self.empty:
164
+ return False
165
+
166
+ if self.lower is None:
167
+ return self.upper is None or (
168
+ value < self.upper
169
+ if self.bounds[1] == ")"
170
+ else value <= self.upper
171
+ )
172
+
173
+ if self.upper is None:
174
+ return ( # type: ignore
175
+ value > self.lower
176
+ if self.bounds[0] == "("
177
+ else value >= self.lower
178
+ )
179
+
180
+ return ( # type: ignore
181
+ value > self.lower
182
+ if self.bounds[0] == "("
183
+ else value >= self.lower
184
+ ) and (
185
+ value < self.upper
186
+ if self.bounds[1] == ")"
187
+ else value <= self.upper
188
+ )
189
+
190
+ def _get_discrete_step(self) -> Any:
191
+ "Determine the “step” for this range, if it is a discrete one."
192
+
193
+ # See
194
+ # https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-DISCRETE
195
+ # for the rationale
196
+
197
+ if isinstance(self.lower, int) or isinstance(self.upper, int):
198
+ return 1
199
+ elif isinstance(self.lower, datetime) or isinstance(
200
+ self.upper, datetime
201
+ ):
202
+ # This is required, because a `isinstance(datetime.now(), date)`
203
+ # is True
204
+ return None
205
+ elif isinstance(self.lower, date) or isinstance(self.upper, date):
206
+ return timedelta(days=1)
207
+ else:
208
+ return None
209
+
210
+ def _compare_edges(
211
+ self,
212
+ value1: Optional[_T],
213
+ bound1: str,
214
+ value2: Optional[_T],
215
+ bound2: str,
216
+ only_values: bool = False,
217
+ ) -> int:
218
+ """Compare two range bounds.
219
+
220
+ Return -1, 0 or 1 respectively when `value1` is less than,
221
+ equal to or greater than `value2`.
222
+
223
+ When `only_value` is ``True``, do not consider the *inclusivity*
224
+ of the edges, just their values.
225
+ """
226
+
227
+ value1_is_lower_bound = bound1 in {"[", "("}
228
+ value2_is_lower_bound = bound2 in {"[", "("}
229
+
230
+ # Infinite edges are equal when they are on the same side,
231
+ # otherwise a lower edge is considered less than the upper end
232
+ if value1 is value2 is None:
233
+ if value1_is_lower_bound == value2_is_lower_bound:
234
+ return 0
235
+ else:
236
+ return -1 if value1_is_lower_bound else 1
237
+ elif value1 is None:
238
+ return -1 if value1_is_lower_bound else 1
239
+ elif value2 is None:
240
+ return 1 if value2_is_lower_bound else -1
241
+
242
+ # Short path for trivial case
243
+ if bound1 == bound2 and value1 == value2:
244
+ return 0
245
+
246
+ value1_inc = bound1 in {"[", "]"}
247
+ value2_inc = bound2 in {"[", "]"}
248
+ step = self._get_discrete_step()
249
+
250
+ if step is not None:
251
+ # "Normalize" the two edges as '[)', to simplify successive
252
+ # logic when the range is discrete: otherwise we would need
253
+ # to handle the comparison between ``(0`` and ``[1`` that
254
+ # are equal when dealing with integers while for floats the
255
+ # former is lesser than the latter
256
+
257
+ if value1_is_lower_bound:
258
+ if not value1_inc:
259
+ value1 += step
260
+ value1_inc = True
261
+ else:
262
+ if value1_inc:
263
+ value1 += step
264
+ value1_inc = False
265
+ if value2_is_lower_bound:
266
+ if not value2_inc:
267
+ value2 += step
268
+ value2_inc = True
269
+ else:
270
+ if value2_inc:
271
+ value2 += step
272
+ value2_inc = False
273
+
274
+ if value1 < value2:
275
+ return -1
276
+ elif value1 > value2:
277
+ return 1
278
+ elif only_values:
279
+ return 0
280
+ else:
281
+ # Neither one is infinite but are equal, so we
282
+ # need to consider the respective inclusive/exclusive
283
+ # flag
284
+
285
+ if value1_inc and value2_inc:
286
+ return 0
287
+ elif not value1_inc and not value2_inc:
288
+ if value1_is_lower_bound == value2_is_lower_bound:
289
+ return 0
290
+ else:
291
+ return 1 if value1_is_lower_bound else -1
292
+ elif not value1_inc:
293
+ return 1 if value1_is_lower_bound else -1
294
+ elif not value2_inc:
295
+ return -1 if value2_is_lower_bound else 1
296
+ else:
297
+ return 0
298
+
299
+ def __eq__(self, other: Any) -> bool:
300
+ """Compare this range to the `other` taking into account
301
+ bounds inclusivity, returning ``True`` if they are equal.
302
+ """
303
+
304
+ if not isinstance(other, Range):
305
+ return NotImplemented
306
+
307
+ if self.empty and other.empty:
308
+ return True
309
+ elif self.empty != other.empty:
310
+ return False
311
+
312
+ slower = self.lower
313
+ slower_b = self.bounds[0]
314
+ olower = other.lower
315
+ olower_b = other.bounds[0]
316
+ supper = self.upper
317
+ supper_b = self.bounds[1]
318
+ oupper = other.upper
319
+ oupper_b = other.bounds[1]
320
+
321
+ return (
322
+ self._compare_edges(slower, slower_b, olower, olower_b) == 0
323
+ and self._compare_edges(supper, supper_b, oupper, oupper_b) == 0
324
+ )
325
+
326
+ def contained_by(self, other: Range[_T]) -> bool:
327
+ "Determine whether this range is a contained by `other`."
328
+
329
+ # Any range contains the empty one
330
+ if self.empty:
331
+ return True
332
+
333
+ # An empty range does not contain any range except the empty one
334
+ if other.empty:
335
+ return False
336
+
337
+ slower = self.lower
338
+ slower_b = self.bounds[0]
339
+ olower = other.lower
340
+ olower_b = other.bounds[0]
341
+
342
+ if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
343
+ return False
344
+
345
+ supper = self.upper
346
+ supper_b = self.bounds[1]
347
+ oupper = other.upper
348
+ oupper_b = other.bounds[1]
349
+
350
+ if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
351
+ return False
352
+
353
+ return True
354
+
355
+ def contains(self, value: Union[_T, Range[_T]]) -> bool:
356
+ "Determine whether this range contains `value`."
357
+
358
+ if isinstance(value, Range):
359
+ return value.contained_by(self)
360
+ else:
361
+ return self._contains_value(value)
362
+
363
+ __contains__ = contains
364
+
365
+ def overlaps(self, other: Range[_T]) -> bool:
366
+ "Determine whether this range overlaps with `other`."
367
+
368
+ # Empty ranges never overlap with any other range
369
+ if self.empty or other.empty:
370
+ return False
371
+
372
+ slower = self.lower
373
+ slower_b = self.bounds[0]
374
+ supper = self.upper
375
+ supper_b = self.bounds[1]
376
+ olower = other.lower
377
+ olower_b = other.bounds[0]
378
+ oupper = other.upper
379
+ oupper_b = other.bounds[1]
380
+
381
+ # Check whether this lower bound is contained in the other range
382
+ if (
383
+ self._compare_edges(slower, slower_b, olower, olower_b) >= 0
384
+ and self._compare_edges(slower, slower_b, oupper, oupper_b) <= 0
385
+ ):
386
+ return True
387
+
388
+ # Check whether other lower bound is contained in this range
389
+ if (
390
+ self._compare_edges(olower, olower_b, slower, slower_b) >= 0
391
+ and self._compare_edges(olower, olower_b, supper, supper_b) <= 0
392
+ ):
393
+ return True
394
+
395
+ return False
396
+
397
+ def strictly_left_of(self, other: Range[_T]) -> bool:
398
+ "Determine whether this range is completely to the left of `other`."
399
+
400
+ # Empty ranges are neither to left nor to the right of any other range
401
+ if self.empty or other.empty:
402
+ return False
403
+
404
+ supper = self.upper
405
+ supper_b = self.bounds[1]
406
+ olower = other.lower
407
+ olower_b = other.bounds[0]
408
+
409
+ # Check whether this upper edge is less than other's lower end
410
+ return self._compare_edges(supper, supper_b, olower, olower_b) < 0
411
+
412
+ __lshift__ = strictly_left_of
413
+
414
+ def strictly_right_of(self, other: Range[_T]) -> bool:
415
+ "Determine whether this range is completely to the right of `other`."
416
+
417
+ # Empty ranges are neither to left nor to the right of any other range
418
+ if self.empty or other.empty:
419
+ return False
420
+
421
+ slower = self.lower
422
+ slower_b = self.bounds[0]
423
+ oupper = other.upper
424
+ oupper_b = other.bounds[1]
425
+
426
+ # Check whether this lower edge is greater than other's upper end
427
+ return self._compare_edges(slower, slower_b, oupper, oupper_b) > 0
428
+
429
+ __rshift__ = strictly_right_of
430
+
431
+ def not_extend_left_of(self, other: Range[_T]) -> bool:
432
+ "Determine whether this does not extend to the left of `other`."
433
+
434
+ # Empty ranges are neither to left nor to the right of any other range
435
+ if self.empty or other.empty:
436
+ return False
437
+
438
+ slower = self.lower
439
+ slower_b = self.bounds[0]
440
+ olower = other.lower
441
+ olower_b = other.bounds[0]
442
+
443
+ # Check whether this lower edge is not less than other's lower end
444
+ return self._compare_edges(slower, slower_b, olower, olower_b) >= 0
445
+
446
+ def not_extend_right_of(self, other: Range[_T]) -> bool:
447
+ "Determine whether this does not extend to the right of `other`."
448
+
449
+ # Empty ranges are neither to left nor to the right of any other range
450
+ if self.empty or other.empty:
451
+ return False
452
+
453
+ supper = self.upper
454
+ supper_b = self.bounds[1]
455
+ oupper = other.upper
456
+ oupper_b = other.bounds[1]
457
+
458
+ # Check whether this upper edge is not greater than other's upper end
459
+ return self._compare_edges(supper, supper_b, oupper, oupper_b) <= 0
460
+
461
+ def _upper_edge_adjacent_to_lower(
462
+ self,
463
+ value1: Optional[_T],
464
+ bound1: str,
465
+ value2: Optional[_T],
466
+ bound2: str,
467
+ ) -> bool:
468
+ """Determine whether an upper bound is immediately successive to a
469
+ lower bound."""
470
+
471
+ # Since we need a peculiar way to handle the bounds inclusivity,
472
+ # just do a comparison by value here
473
+ res = self._compare_edges(value1, bound1, value2, bound2, True)
474
+ if res == -1:
475
+ step = self._get_discrete_step()
476
+ if step is None:
477
+ return False
478
+ if bound1 == "]":
479
+ if bound2 == "[":
480
+ return value1 == value2 - step # type: ignore
481
+ else:
482
+ return value1 == value2
483
+ else:
484
+ if bound2 == "[":
485
+ return value1 == value2
486
+ else:
487
+ return value1 == value2 - step # type: ignore
488
+ elif res == 0:
489
+ # Cover cases like [0,0] -|- [1,] and [0,2) -|- (1,3]
490
+ if (
491
+ bound1 == "]"
492
+ and bound2 == "["
493
+ or bound1 == ")"
494
+ and bound2 == "("
495
+ ):
496
+ step = self._get_discrete_step()
497
+ if step is not None:
498
+ return True
499
+ return (
500
+ bound1 == ")"
501
+ and bound2 == "["
502
+ or bound1 == "]"
503
+ and bound2 == "("
504
+ )
505
+ else:
506
+ return False
507
+
508
+ def adjacent_to(self, other: Range[_T]) -> bool:
509
+ "Determine whether this range is adjacent to the `other`."
510
+
511
+ # Empty ranges are not adjacent to any other range
512
+ if self.empty or other.empty:
513
+ return False
514
+
515
+ slower = self.lower
516
+ slower_b = self.bounds[0]
517
+ supper = self.upper
518
+ supper_b = self.bounds[1]
519
+ olower = other.lower
520
+ olower_b = other.bounds[0]
521
+ oupper = other.upper
522
+ oupper_b = other.bounds[1]
523
+
524
+ return self._upper_edge_adjacent_to_lower(
525
+ supper, supper_b, olower, olower_b
526
+ ) or self._upper_edge_adjacent_to_lower(
527
+ oupper, oupper_b, slower, slower_b
528
+ )
529
+
530
+ def union(self, other: Range[_T]) -> Range[_T]:
531
+ """Compute the union of this range with the `other`.
532
+
533
+ This raises a ``ValueError`` exception if the two ranges are
534
+ "disjunct", that is neither adjacent nor overlapping.
535
+ """
536
+
537
+ # Empty ranges are "additive identities"
538
+ if self.empty:
539
+ return other
540
+ if other.empty:
541
+ return self
542
+
543
+ if not self.overlaps(other) and not self.adjacent_to(other):
544
+ raise ValueError(
545
+ "Adding non-overlapping and non-adjacent"
546
+ " ranges is not implemented"
547
+ )
548
+
549
+ slower = self.lower
550
+ slower_b = self.bounds[0]
551
+ supper = self.upper
552
+ supper_b = self.bounds[1]
553
+ olower = other.lower
554
+ olower_b = other.bounds[0]
555
+ oupper = other.upper
556
+ oupper_b = other.bounds[1]
557
+
558
+ if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
559
+ rlower = slower
560
+ rlower_b = slower_b
561
+ else:
562
+ rlower = olower
563
+ rlower_b = olower_b
564
+
565
+ if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
566
+ rupper = supper
567
+ rupper_b = supper_b
568
+ else:
569
+ rupper = oupper
570
+ rupper_b = oupper_b
571
+
572
+ return Range(
573
+ rlower, rupper, bounds=cast(_BoundsType, rlower_b + rupper_b)
574
+ )
575
+
576
+ def __add__(self, other: Range[_T]) -> Range[_T]:
577
+ return self.union(other)
578
+
579
+ def difference(self, other: Range[_T]) -> Range[_T]:
580
+ """Compute the difference between this range and the `other`.
581
+
582
+ This raises a ``ValueError`` exception if the two ranges are
583
+ "disjunct", that is neither adjacent nor overlapping.
584
+ """
585
+
586
+ # Subtracting an empty range is a no-op
587
+ if self.empty or other.empty:
588
+ return self
589
+
590
+ slower = self.lower
591
+ slower_b = self.bounds[0]
592
+ supper = self.upper
593
+ supper_b = self.bounds[1]
594
+ olower = other.lower
595
+ olower_b = other.bounds[0]
596
+ oupper = other.upper
597
+ oupper_b = other.bounds[1]
598
+
599
+ sl_vs_ol = self._compare_edges(slower, slower_b, olower, olower_b)
600
+ su_vs_ou = self._compare_edges(supper, supper_b, oupper, oupper_b)
601
+ if sl_vs_ol < 0 and su_vs_ou > 0:
602
+ raise ValueError(
603
+ "Subtracting a strictly inner range is not implemented"
604
+ )
605
+
606
+ sl_vs_ou = self._compare_edges(slower, slower_b, oupper, oupper_b)
607
+ su_vs_ol = self._compare_edges(supper, supper_b, olower, olower_b)
608
+
609
+ # If the ranges do not overlap, result is simply the first
610
+ if sl_vs_ou > 0 or su_vs_ol < 0:
611
+ return self
612
+
613
+ # If this range is completely contained by the other, result is empty
614
+ if sl_vs_ol >= 0 and su_vs_ou <= 0:
615
+ return Range(None, None, empty=True)
616
+
617
+ # If this range extends to the left of the other and ends in its
618
+ # middle
619
+ if sl_vs_ol <= 0 and su_vs_ol >= 0 and su_vs_ou <= 0:
620
+ rupper_b = ")" if olower_b == "[" else "]"
621
+ if (
622
+ slower_b != "["
623
+ and rupper_b != "]"
624
+ and self._compare_edges(slower, slower_b, olower, rupper_b)
625
+ == 0
626
+ ):
627
+ return Range(None, None, empty=True)
628
+ else:
629
+ return Range(
630
+ slower,
631
+ olower,
632
+ bounds=cast(_BoundsType, slower_b + rupper_b),
633
+ )
634
+
635
+ # If this range starts in the middle of the other and extends to its
636
+ # right
637
+ if sl_vs_ol >= 0 and su_vs_ou >= 0 and sl_vs_ou <= 0:
638
+ rlower_b = "(" if oupper_b == "]" else "["
639
+ if (
640
+ rlower_b != "["
641
+ and supper_b != "]"
642
+ and self._compare_edges(oupper, rlower_b, supper, supper_b)
643
+ == 0
644
+ ):
645
+ return Range(None, None, empty=True)
646
+ else:
647
+ return Range(
648
+ oupper,
649
+ supper,
650
+ bounds=cast(_BoundsType, rlower_b + supper_b),
651
+ )
652
+
653
+ assert False, f"Unhandled case computing {self} - {other}"
654
+
655
+ def __sub__(self, other: Range[_T]) -> Range[_T]:
656
+ return self.difference(other)
657
+
658
+ def intersection(self, other: Range[_T]) -> Range[_T]:
659
+ """Compute the intersection of this range with the `other`.
660
+
661
+ .. versionadded:: 2.0.10
662
+
663
+ """
664
+ if self.empty or other.empty or not self.overlaps(other):
665
+ return Range(None, None, empty=True)
666
+
667
+ slower = self.lower
668
+ slower_b = self.bounds[0]
669
+ supper = self.upper
670
+ supper_b = self.bounds[1]
671
+ olower = other.lower
672
+ olower_b = other.bounds[0]
673
+ oupper = other.upper
674
+ oupper_b = other.bounds[1]
675
+
676
+ if self._compare_edges(slower, slower_b, olower, olower_b) < 0:
677
+ rlower = olower
678
+ rlower_b = olower_b
679
+ else:
680
+ rlower = slower
681
+ rlower_b = slower_b
682
+
683
+ if self._compare_edges(supper, supper_b, oupper, oupper_b) > 0:
684
+ rupper = oupper
685
+ rupper_b = oupper_b
686
+ else:
687
+ rupper = supper
688
+ rupper_b = supper_b
689
+
690
+ return Range(
691
+ rlower,
692
+ rupper,
693
+ bounds=cast(_BoundsType, rlower_b + rupper_b),
694
+ )
695
+
696
+ def __mul__(self, other: Range[_T]) -> Range[_T]:
697
+ return self.intersection(other)
698
+
699
+ def __str__(self) -> str:
700
+ return self._stringify()
701
+
702
+ def _stringify(self) -> str:
703
+ if self.empty:
704
+ return "empty"
705
+
706
+ l, r = self.lower, self.upper
707
+ l = "" if l is None else l # type: ignore
708
+ r = "" if r is None else r # type: ignore
709
+
710
+ b0, b1 = cast("Tuple[str, str]", self.bounds)
711
+
712
+ return f"{b0}{l},{r}{b1}"
713
+
714
+
715
+ class MultiRange(List[Range[_T]]):
716
+ """Represents a multirange sequence.
717
+
718
+ This list subclass is an utility to allow automatic type inference of
719
+ the proper multi-range SQL type depending on the single range values.
720
+ This is useful when operating on literal multi-ranges::
721
+
722
+ import sqlalchemy as sa
723
+ from sqlalchemy.dialects.postgresql import MultiRange, Range
724
+
725
+ value = literal(MultiRange([Range(2, 4)]))
726
+
727
+ select(tbl).where(tbl.c.value.op("@")(MultiRange([Range(-3, 7)])))
728
+
729
+ .. versionadded:: 2.0.26
730
+
731
+ .. seealso::
732
+
733
+ - :ref:`postgresql_multirange_list_use`.
734
+ """
735
+
736
+ @property
737
+ def __sa_type_engine__(self) -> AbstractMultiRange[_T]:
738
+ return AbstractMultiRange()
739
+
740
+
741
+ class AbstractRange(sqltypes.TypeEngine[_T]):
742
+ """Base class for single and multi Range SQL types."""
743
+
744
+ render_bind_cast = True
745
+
746
+ __abstract__ = True
747
+
748
+ @overload
749
+ def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
750
+
751
+ @overload
752
+ def adapt(
753
+ self, cls: Type[TypeEngineMixin], **kw: Any
754
+ ) -> TypeEngine[Any]: ...
755
+
756
+ def adapt(
757
+ self,
758
+ cls: Type[Union[TypeEngine[Any], TypeEngineMixin]],
759
+ **kw: Any,
760
+ ) -> TypeEngine[Any]:
761
+ """Dynamically adapt a range type to an abstract impl.
762
+
763
+ For example ``INT4RANGE().adapt(_Psycopg2NumericRange)`` should
764
+ produce a type that will have ``_Psycopg2NumericRange`` behaviors
765
+ and also render as ``INT4RANGE`` in SQL and DDL.
766
+
767
+ """
768
+ if (
769
+ issubclass(cls, (AbstractSingleRangeImpl, AbstractMultiRangeImpl))
770
+ and cls is not self.__class__
771
+ ):
772
+ # two ways to do this are: 1. create a new type on the fly
773
+ # or 2. have AbstractRangeImpl(visit_name) constructor and a
774
+ # visit_abstract_range_impl() method in the PG compiler.
775
+ # I'm choosing #1 as the resulting type object
776
+ # will then make use of the same mechanics
777
+ # as if we had made all these sub-types explicitly, and will
778
+ # also look more obvious under pdb etc.
779
+ # The adapt() operation here is cached per type-class-per-dialect,
780
+ # so is not much of a performance concern
781
+ visit_name = self.__visit_name__
782
+ return type( # type: ignore
783
+ f"{visit_name}RangeImpl",
784
+ (cls, self.__class__),
785
+ {"__visit_name__": visit_name},
786
+ )()
787
+ else:
788
+ return super().adapt(cls)
789
+
790
+ class comparator_factory(TypeEngine.Comparator[Range[Any]]):
791
+ """Define comparison operations for range types."""
792
+
793
+ def contains(self, other: Any, **kw: Any) -> ColumnElement[bool]:
794
+ """Boolean expression. Returns true if the right hand operand,
795
+ which can be an element or a range, is contained within the
796
+ column.
797
+
798
+ kwargs may be ignored by this operator but are required for API
799
+ conformance.
800
+ """
801
+ return self.expr.operate(CONTAINS, other)
802
+
803
+ def contained_by(self, other: Any) -> ColumnElement[bool]:
804
+ """Boolean expression. Returns true if the column is contained
805
+ within the right hand operand.
806
+ """
807
+ return self.expr.operate(CONTAINED_BY, other)
808
+
809
+ def overlaps(self, other: Any) -> ColumnElement[bool]:
810
+ """Boolean expression. Returns true if the column overlaps
811
+ (has points in common with) the right hand operand.
812
+ """
813
+ return self.expr.operate(OVERLAP, other)
814
+
815
+ def strictly_left_of(self, other: Any) -> ColumnElement[bool]:
816
+ """Boolean expression. Returns true if the column is strictly
817
+ left of the right hand operand.
818
+ """
819
+ return self.expr.operate(STRICTLY_LEFT_OF, other)
820
+
821
+ __lshift__ = strictly_left_of
822
+
823
+ def strictly_right_of(self, other: Any) -> ColumnElement[bool]:
824
+ """Boolean expression. Returns true if the column is strictly
825
+ right of the right hand operand.
826
+ """
827
+ return self.expr.operate(STRICTLY_RIGHT_OF, other)
828
+
829
+ __rshift__ = strictly_right_of
830
+
831
+ def not_extend_right_of(self, other: Any) -> ColumnElement[bool]:
832
+ """Boolean expression. Returns true if the range in the column
833
+ does not extend right of the range in the operand.
834
+ """
835
+ return self.expr.operate(NOT_EXTEND_RIGHT_OF, other)
836
+
837
+ def not_extend_left_of(self, other: Any) -> ColumnElement[bool]:
838
+ """Boolean expression. Returns true if the range in the column
839
+ does not extend left of the range in the operand.
840
+ """
841
+ return self.expr.operate(NOT_EXTEND_LEFT_OF, other)
842
+
843
+ def adjacent_to(self, other: Any) -> ColumnElement[bool]:
844
+ """Boolean expression. Returns true if the range in the column
845
+ is adjacent to the range in the operand.
846
+ """
847
+ return self.expr.operate(ADJACENT_TO, other)
848
+
849
+ def union(self, other: Any) -> ColumnElement[bool]:
850
+ """Range expression. Returns the union of the two ranges.
851
+ Will raise an exception if the resulting range is not
852
+ contiguous.
853
+ """
854
+ return self.expr.operate(operators.add, other)
855
+
856
+ def difference(self, other: Any) -> ColumnElement[bool]:
857
+ """Range expression. Returns the union of the two ranges.
858
+ Will raise an exception if the resulting range is not
859
+ contiguous.
860
+ """
861
+ return self.expr.operate(operators.sub, other)
862
+
863
+ def intersection(self, other: Any) -> ColumnElement[Range[_T]]:
864
+ """Range expression. Returns the intersection of the two ranges.
865
+ Will raise an exception if the resulting range is not
866
+ contiguous.
867
+ """
868
+ return self.expr.operate(operators.mul, other)
869
+
870
+
871
+ class AbstractSingleRange(AbstractRange[Range[_T]]):
872
+ """Base for PostgreSQL RANGE types.
873
+
874
+ These are types that return a single :class:`_postgresql.Range` object.
875
+
876
+ .. seealso::
877
+
878
+ `PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_
879
+
880
+ """ # noqa: E501
881
+
882
+ __abstract__ = True
883
+
884
+ def _resolve_for_literal(self, value: Range[Any]) -> Any:
885
+ spec = value.lower if value.lower is not None else value.upper
886
+
887
+ if isinstance(spec, int):
888
+ # pg is unreasonably picky here: the query
889
+ # "select 1::INTEGER <@ '[1, 4)'::INT8RANGE" raises
890
+ # "operator does not exist: integer <@ int8range" as of pg 16
891
+ if _is_int32(value):
892
+ return INT4RANGE()
893
+ else:
894
+ return INT8RANGE()
895
+ elif isinstance(spec, (Decimal, float)):
896
+ return NUMRANGE()
897
+ elif isinstance(spec, datetime):
898
+ return TSRANGE() if not spec.tzinfo else TSTZRANGE()
899
+ elif isinstance(spec, date):
900
+ return DATERANGE()
901
+ else:
902
+ # empty Range, SQL datatype can't be determined here
903
+ return sqltypes.NULLTYPE
904
+
905
+
906
+ class AbstractSingleRangeImpl(AbstractSingleRange[_T]):
907
+ """Marker for AbstractSingleRange that will apply a subclass-specific
908
+ adaptation"""
909
+
910
+
911
+ class AbstractMultiRange(AbstractRange[Sequence[Range[_T]]]):
912
+ """Base for PostgreSQL MULTIRANGE types.
913
+
914
+ these are types that return a sequence of :class:`_postgresql.Range`
915
+ objects.
916
+
917
+ """
918
+
919
+ __abstract__ = True
920
+
921
+ def _resolve_for_literal(self, value: Sequence[Range[Any]]) -> Any:
922
+ if not value:
923
+ # empty MultiRange, SQL datatype can't be determined here
924
+ return sqltypes.NULLTYPE
925
+ first = value[0]
926
+ spec = first.lower if first.lower is not None else first.upper
927
+
928
+ if isinstance(spec, int):
929
+ # pg is unreasonably picky here: the query
930
+ # "select 1::INTEGER <@ '{[1, 4),[6,19)}'::INT8MULTIRANGE" raises
931
+ # "operator does not exist: integer <@ int8multirange" as of pg 16
932
+ if all(_is_int32(r) for r in value):
933
+ return INT4MULTIRANGE()
934
+ else:
935
+ return INT8MULTIRANGE()
936
+ elif isinstance(spec, (Decimal, float)):
937
+ return NUMMULTIRANGE()
938
+ elif isinstance(spec, datetime):
939
+ return TSMULTIRANGE() if not spec.tzinfo else TSTZMULTIRANGE()
940
+ elif isinstance(spec, date):
941
+ return DATEMULTIRANGE()
942
+ else:
943
+ # empty Range, SQL datatype can't be determined here
944
+ return sqltypes.NULLTYPE
945
+
946
+
947
+ class AbstractMultiRangeImpl(AbstractMultiRange[_T]):
948
+ """Marker for AbstractMultiRange that will apply a subclass-specific
949
+ adaptation"""
950
+
951
+
952
+ class INT4RANGE(AbstractSingleRange[int]):
953
+ """Represent the PostgreSQL INT4RANGE type."""
954
+
955
+ __visit_name__ = "INT4RANGE"
956
+
957
+
958
+ class INT8RANGE(AbstractSingleRange[int]):
959
+ """Represent the PostgreSQL INT8RANGE type."""
960
+
961
+ __visit_name__ = "INT8RANGE"
962
+
963
+
964
+ class NUMRANGE(AbstractSingleRange[Decimal]):
965
+ """Represent the PostgreSQL NUMRANGE type."""
966
+
967
+ __visit_name__ = "NUMRANGE"
968
+
969
+
970
+ class DATERANGE(AbstractSingleRange[date]):
971
+ """Represent the PostgreSQL DATERANGE type."""
972
+
973
+ __visit_name__ = "DATERANGE"
974
+
975
+
976
+ class TSRANGE(AbstractSingleRange[datetime]):
977
+ """Represent the PostgreSQL TSRANGE type."""
978
+
979
+ __visit_name__ = "TSRANGE"
980
+
981
+
982
+ class TSTZRANGE(AbstractSingleRange[datetime]):
983
+ """Represent the PostgreSQL TSTZRANGE type."""
984
+
985
+ __visit_name__ = "TSTZRANGE"
986
+
987
+
988
+ class INT4MULTIRANGE(AbstractMultiRange[int]):
989
+ """Represent the PostgreSQL INT4MULTIRANGE type."""
990
+
991
+ __visit_name__ = "INT4MULTIRANGE"
992
+
993
+
994
+ class INT8MULTIRANGE(AbstractMultiRange[int]):
995
+ """Represent the PostgreSQL INT8MULTIRANGE type."""
996
+
997
+ __visit_name__ = "INT8MULTIRANGE"
998
+
999
+
1000
+ class NUMMULTIRANGE(AbstractMultiRange[Decimal]):
1001
+ """Represent the PostgreSQL NUMMULTIRANGE type."""
1002
+
1003
+ __visit_name__ = "NUMMULTIRANGE"
1004
+
1005
+
1006
+ class DATEMULTIRANGE(AbstractMultiRange[date]):
1007
+ """Represent the PostgreSQL DATEMULTIRANGE type."""
1008
+
1009
+ __visit_name__ = "DATEMULTIRANGE"
1010
+
1011
+
1012
+ class TSMULTIRANGE(AbstractMultiRange[datetime]):
1013
+ """Represent the PostgreSQL TSRANGE type."""
1014
+
1015
+ __visit_name__ = "TSMULTIRANGE"
1016
+
1017
+
1018
+ class TSTZMULTIRANGE(AbstractMultiRange[datetime]):
1019
+ """Represent the PostgreSQL TSTZRANGE type."""
1020
+
1021
+ __visit_name__ = "TSTZMULTIRANGE"
1022
+
1023
+
1024
+ _max_int_32 = 2**31 - 1
1025
+ _min_int_32 = -(2**31)
1026
+
1027
+
1028
+ def _is_int32(r: Range[int]) -> bool:
1029
+ return (r.lower is None or _min_int_32 <= r.lower <= _max_int_32) and (
1030
+ r.upper is None or _min_int_32 <= r.upper <= _max_int_32
1031
+ )