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