SQLAlchemy 2.0.36__cp313-cp313-win32.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 (273) hide show
  1. SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
  2. SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
  3. SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
  4. SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
  5. SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
  6. sqlalchemy/__init__.py +294 -0
  7. sqlalchemy/connectors/__init__.py +18 -0
  8. sqlalchemy/connectors/aioodbc.py +174 -0
  9. sqlalchemy/connectors/asyncio.py +213 -0
  10. sqlalchemy/connectors/pyodbc.py +249 -0
  11. sqlalchemy/cyextension/__init__.py +6 -0
  12. sqlalchemy/cyextension/collections.cp313-win32.pyd +0 -0
  13. sqlalchemy/cyextension/collections.pyx +409 -0
  14. sqlalchemy/cyextension/immutabledict.cp313-win32.pyd +0 -0
  15. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  16. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  17. sqlalchemy/cyextension/processors.cp313-win32.pyd +0 -0
  18. sqlalchemy/cyextension/processors.pyx +68 -0
  19. sqlalchemy/cyextension/resultproxy.cp313-win32.pyd +0 -0
  20. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  21. sqlalchemy/cyextension/util.cp313-win32.pyd +0 -0
  22. sqlalchemy/cyextension/util.pyx +91 -0
  23. sqlalchemy/dialects/__init__.py +61 -0
  24. sqlalchemy/dialects/_typing.py +25 -0
  25. sqlalchemy/dialects/mssql/__init__.py +88 -0
  26. sqlalchemy/dialects/mssql/aioodbc.py +64 -0
  27. sqlalchemy/dialects/mssql/base.py +4010 -0
  28. sqlalchemy/dialects/mssql/information_schema.py +254 -0
  29. sqlalchemy/dialects/mssql/json.py +133 -0
  30. sqlalchemy/dialects/mssql/provision.py +162 -0
  31. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  32. sqlalchemy/dialects/mssql/pyodbc.py +745 -0
  33. sqlalchemy/dialects/mysql/__init__.py +101 -0
  34. sqlalchemy/dialects/mysql/aiomysql.py +333 -0
  35. sqlalchemy/dialects/mysql/asyncmy.py +337 -0
  36. sqlalchemy/dialects/mysql/base.py +3494 -0
  37. sqlalchemy/dialects/mysql/cymysql.py +84 -0
  38. sqlalchemy/dialects/mysql/dml.py +219 -0
  39. sqlalchemy/dialects/mysql/enumerated.py +244 -0
  40. sqlalchemy/dialects/mysql/expression.py +141 -0
  41. sqlalchemy/dialects/mysql/json.py +81 -0
  42. sqlalchemy/dialects/mysql/mariadb.py +32 -0
  43. sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
  44. sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
  45. sqlalchemy/dialects/mysql/mysqldb.py +303 -0
  46. sqlalchemy/dialects/mysql/provision.py +110 -0
  47. sqlalchemy/dialects/mysql/pymysql.py +137 -0
  48. sqlalchemy/dialects/mysql/pyodbc.py +138 -0
  49. sqlalchemy/dialects/mysql/reflection.py +677 -0
  50. sqlalchemy/dialects/mysql/reserved_words.py +571 -0
  51. sqlalchemy/dialects/mysql/types.py +774 -0
  52. sqlalchemy/dialects/oracle/__init__.py +67 -0
  53. sqlalchemy/dialects/oracle/base.py +3271 -0
  54. sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
  55. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  56. sqlalchemy/dialects/oracle/oracledb.py +431 -0
  57. sqlalchemy/dialects/oracle/provision.py +220 -0
  58. sqlalchemy/dialects/oracle/types.py +287 -0
  59. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  60. sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
  61. sqlalchemy/dialects/postgresql/array.py +425 -0
  62. sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
  63. sqlalchemy/dialects/postgresql/base.py +5008 -0
  64. sqlalchemy/dialects/postgresql/dml.py +310 -0
  65. sqlalchemy/dialects/postgresql/ext.py +496 -0
  66. sqlalchemy/dialects/postgresql/hstore.py +397 -0
  67. sqlalchemy/dialects/postgresql/json.py +333 -0
  68. sqlalchemy/dialects/postgresql/named_types.py +509 -0
  69. sqlalchemy/dialects/postgresql/operators.py +129 -0
  70. sqlalchemy/dialects/postgresql/pg8000.py +662 -0
  71. sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
  72. sqlalchemy/dialects/postgresql/provision.py +175 -0
  73. sqlalchemy/dialects/postgresql/psycopg.py +772 -0
  74. sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
  75. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  76. sqlalchemy/dialects/postgresql/ranges.py +1029 -0
  77. sqlalchemy/dialects/postgresql/types.py +303 -0
  78. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  79. sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
  80. sqlalchemy/dialects/sqlite/base.py +2805 -0
  81. sqlalchemy/dialects/sqlite/dml.py +240 -0
  82. sqlalchemy/dialects/sqlite/json.py +92 -0
  83. sqlalchemy/dialects/sqlite/provision.py +198 -0
  84. sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
  85. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  86. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  87. sqlalchemy/engine/__init__.py +62 -0
  88. sqlalchemy/engine/_py_processors.py +136 -0
  89. sqlalchemy/engine/_py_row.py +128 -0
  90. sqlalchemy/engine/_py_util.py +74 -0
  91. sqlalchemy/engine/base.py +3375 -0
  92. sqlalchemy/engine/characteristics.py +155 -0
  93. sqlalchemy/engine/create.py +875 -0
  94. sqlalchemy/engine/cursor.py +2181 -0
  95. sqlalchemy/engine/default.py +2365 -0
  96. sqlalchemy/engine/events.py +951 -0
  97. sqlalchemy/engine/interfaces.py +3403 -0
  98. sqlalchemy/engine/mock.py +131 -0
  99. sqlalchemy/engine/processors.py +61 -0
  100. sqlalchemy/engine/reflection.py +2098 -0
  101. sqlalchemy/engine/result.py +2382 -0
  102. sqlalchemy/engine/row.py +401 -0
  103. sqlalchemy/engine/strategies.py +19 -0
  104. sqlalchemy/engine/url.py +910 -0
  105. sqlalchemy/engine/util.py +167 -0
  106. sqlalchemy/event/__init__.py +25 -0
  107. sqlalchemy/event/api.py +225 -0
  108. sqlalchemy/event/attr.py +655 -0
  109. sqlalchemy/event/base.py +470 -0
  110. sqlalchemy/event/legacy.py +246 -0
  111. sqlalchemy/event/registry.py +386 -0
  112. sqlalchemy/events.py +17 -0
  113. sqlalchemy/exc.py +830 -0
  114. sqlalchemy/ext/__init__.py +11 -0
  115. sqlalchemy/ext/associationproxy.py +2013 -0
  116. sqlalchemy/ext/asyncio/__init__.py +25 -0
  117. sqlalchemy/ext/asyncio/base.py +279 -0
  118. sqlalchemy/ext/asyncio/engine.py +1466 -0
  119. sqlalchemy/ext/asyncio/exc.py +21 -0
  120. sqlalchemy/ext/asyncio/result.py +961 -0
  121. sqlalchemy/ext/asyncio/scoping.py +1614 -0
  122. sqlalchemy/ext/asyncio/session.py +1936 -0
  123. sqlalchemy/ext/automap.py +1691 -0
  124. sqlalchemy/ext/baked.py +574 -0
  125. sqlalchemy/ext/compiler.py +570 -0
  126. sqlalchemy/ext/declarative/__init__.py +65 -0
  127. sqlalchemy/ext/declarative/extensions.py +548 -0
  128. sqlalchemy/ext/horizontal_shard.py +481 -0
  129. sqlalchemy/ext/hybrid.py +1514 -0
  130. sqlalchemy/ext/indexable.py +341 -0
  131. sqlalchemy/ext/instrumentation.py +450 -0
  132. sqlalchemy/ext/mutable.py +1073 -0
  133. sqlalchemy/ext/mypy/__init__.py +6 -0
  134. sqlalchemy/ext/mypy/apply.py +320 -0
  135. sqlalchemy/ext/mypy/decl_class.py +515 -0
  136. sqlalchemy/ext/mypy/infer.py +590 -0
  137. sqlalchemy/ext/mypy/names.py +335 -0
  138. sqlalchemy/ext/mypy/plugin.py +303 -0
  139. sqlalchemy/ext/mypy/util.py +357 -0
  140. sqlalchemy/ext/orderinglist.py +416 -0
  141. sqlalchemy/ext/serializer.py +181 -0
  142. sqlalchemy/future/__init__.py +16 -0
  143. sqlalchemy/future/engine.py +15 -0
  144. sqlalchemy/inspection.py +174 -0
  145. sqlalchemy/log.py +288 -0
  146. sqlalchemy/orm/__init__.py +170 -0
  147. sqlalchemy/orm/_orm_constructors.py +2571 -0
  148. sqlalchemy/orm/_typing.py +179 -0
  149. sqlalchemy/orm/attributes.py +2835 -0
  150. sqlalchemy/orm/base.py +973 -0
  151. sqlalchemy/orm/bulk_persistence.py +2123 -0
  152. sqlalchemy/orm/clsregistry.py +571 -0
  153. sqlalchemy/orm/collections.py +1620 -0
  154. sqlalchemy/orm/context.py +3268 -0
  155. sqlalchemy/orm/decl_api.py +1883 -0
  156. sqlalchemy/orm/decl_base.py +2190 -0
  157. sqlalchemy/orm/dependency.py +1304 -0
  158. sqlalchemy/orm/descriptor_props.py +1076 -0
  159. sqlalchemy/orm/dynamic.py +300 -0
  160. sqlalchemy/orm/evaluator.py +379 -0
  161. sqlalchemy/orm/events.py +3261 -0
  162. sqlalchemy/orm/exc.py +228 -0
  163. sqlalchemy/orm/identity.py +302 -0
  164. sqlalchemy/orm/instrumentation.py +754 -0
  165. sqlalchemy/orm/interfaces.py +1474 -0
  166. sqlalchemy/orm/loading.py +1682 -0
  167. sqlalchemy/orm/mapped_collection.py +557 -0
  168. sqlalchemy/orm/mapper.py +4432 -0
  169. sqlalchemy/orm/path_registry.py +811 -0
  170. sqlalchemy/orm/persistence.py +1782 -0
  171. sqlalchemy/orm/properties.py +886 -0
  172. sqlalchemy/orm/query.py +3396 -0
  173. sqlalchemy/orm/relationships.py +3500 -0
  174. sqlalchemy/orm/scoping.py +2165 -0
  175. sqlalchemy/orm/session.py +5301 -0
  176. sqlalchemy/orm/state.py +1143 -0
  177. sqlalchemy/orm/state_changes.py +198 -0
  178. sqlalchemy/orm/strategies.py +3473 -0
  179. sqlalchemy/orm/strategy_options.py +2569 -0
  180. sqlalchemy/orm/sync.py +164 -0
  181. sqlalchemy/orm/unitofwork.py +796 -0
  182. sqlalchemy/orm/util.py +2424 -0
  183. sqlalchemy/orm/writeonly.py +678 -0
  184. sqlalchemy/pool/__init__.py +44 -0
  185. sqlalchemy/pool/base.py +1515 -0
  186. sqlalchemy/pool/events.py +370 -0
  187. sqlalchemy/pool/impl.py +581 -0
  188. sqlalchemy/py.typed +0 -0
  189. sqlalchemy/schema.py +70 -0
  190. sqlalchemy/sql/__init__.py +145 -0
  191. sqlalchemy/sql/_dml_constructors.py +140 -0
  192. sqlalchemy/sql/_elements_constructors.py +1850 -0
  193. sqlalchemy/sql/_orm_types.py +20 -0
  194. sqlalchemy/sql/_py_util.py +75 -0
  195. sqlalchemy/sql/_selectable_constructors.py +635 -0
  196. sqlalchemy/sql/_typing.py +460 -0
  197. sqlalchemy/sql/annotation.py +585 -0
  198. sqlalchemy/sql/base.py +2185 -0
  199. sqlalchemy/sql/cache_key.py +1057 -0
  200. sqlalchemy/sql/coercions.py +1405 -0
  201. sqlalchemy/sql/compiler.py +7818 -0
  202. sqlalchemy/sql/crud.py +1669 -0
  203. sqlalchemy/sql/ddl.py +1378 -0
  204. sqlalchemy/sql/default_comparator.py +552 -0
  205. sqlalchemy/sql/dml.py +1817 -0
  206. sqlalchemy/sql/elements.py +5499 -0
  207. sqlalchemy/sql/events.py +455 -0
  208. sqlalchemy/sql/expression.py +162 -0
  209. sqlalchemy/sql/functions.py +2055 -0
  210. sqlalchemy/sql/lambdas.py +1449 -0
  211. sqlalchemy/sql/naming.py +212 -0
  212. sqlalchemy/sql/operators.py +2579 -0
  213. sqlalchemy/sql/roles.py +323 -0
  214. sqlalchemy/sql/schema.py +6158 -0
  215. sqlalchemy/sql/selectable.py +7004 -0
  216. sqlalchemy/sql/sqltypes.py +3827 -0
  217. sqlalchemy/sql/traversals.py +1024 -0
  218. sqlalchemy/sql/type_api.py +2339 -0
  219. sqlalchemy/sql/util.py +1486 -0
  220. sqlalchemy/sql/visitors.py +1165 -0
  221. sqlalchemy/testing/__init__.py +96 -0
  222. sqlalchemy/testing/assertions.py +989 -0
  223. sqlalchemy/testing/assertsql.py +516 -0
  224. sqlalchemy/testing/asyncio.py +135 -0
  225. sqlalchemy/testing/config.py +427 -0
  226. sqlalchemy/testing/engines.py +472 -0
  227. sqlalchemy/testing/entities.py +117 -0
  228. sqlalchemy/testing/exclusions.py +435 -0
  229. sqlalchemy/testing/fixtures/__init__.py +28 -0
  230. sqlalchemy/testing/fixtures/base.py +366 -0
  231. sqlalchemy/testing/fixtures/mypy.py +312 -0
  232. sqlalchemy/testing/fixtures/orm.py +227 -0
  233. sqlalchemy/testing/fixtures/sql.py +503 -0
  234. sqlalchemy/testing/pickleable.py +155 -0
  235. sqlalchemy/testing/plugin/__init__.py +6 -0
  236. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  237. sqlalchemy/testing/plugin/plugin_base.py +779 -0
  238. sqlalchemy/testing/plugin/pytestplugin.py +868 -0
  239. sqlalchemy/testing/profiling.py +324 -0
  240. sqlalchemy/testing/provision.py +496 -0
  241. sqlalchemy/testing/requirements.py +1818 -0
  242. sqlalchemy/testing/schema.py +224 -0
  243. sqlalchemy/testing/suite/__init__.py +19 -0
  244. sqlalchemy/testing/suite/test_cte.py +211 -0
  245. sqlalchemy/testing/suite/test_ddl.py +389 -0
  246. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  247. sqlalchemy/testing/suite/test_dialect.py +740 -0
  248. sqlalchemy/testing/suite/test_insert.py +630 -0
  249. sqlalchemy/testing/suite/test_reflection.py +3225 -0
  250. sqlalchemy/testing/suite/test_results.py +502 -0
  251. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  252. sqlalchemy/testing/suite/test_select.py +1999 -0
  253. sqlalchemy/testing/suite/test_sequence.py +317 -0
  254. sqlalchemy/testing/suite/test_types.py +2141 -0
  255. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  256. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  257. sqlalchemy/testing/util.py +537 -0
  258. sqlalchemy/testing/warnings.py +52 -0
  259. sqlalchemy/types.py +76 -0
  260. sqlalchemy/util/__init__.py +160 -0
  261. sqlalchemy/util/_collections.py +715 -0
  262. sqlalchemy/util/_concurrency_py3k.py +288 -0
  263. sqlalchemy/util/_has_cy.py +40 -0
  264. sqlalchemy/util/_py_collections.py +541 -0
  265. sqlalchemy/util/compat.py +301 -0
  266. sqlalchemy/util/concurrency.py +108 -0
  267. sqlalchemy/util/deprecations.py +401 -0
  268. sqlalchemy/util/langhelpers.py +2218 -0
  269. sqlalchemy/util/preloaded.py +150 -0
  270. sqlalchemy/util/queue.py +322 -0
  271. sqlalchemy/util/tool_support.py +201 -0
  272. sqlalchemy/util/topological.py +120 -0
  273. sqlalchemy/util/typing.py +629 -0
@@ -0,0 +1,341 @@
1
+ # ext/indexable.py
2
+ # Copyright (C) 2005-2024 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
+ # mypy: ignore-errors
8
+
9
+ """Define attributes on ORM-mapped classes that have "index" attributes for
10
+ columns with :class:`_types.Indexable` types.
11
+
12
+ "index" means the attribute is associated with an element of an
13
+ :class:`_types.Indexable` column with the predefined index to access it.
14
+ The :class:`_types.Indexable` types include types such as
15
+ :class:`_types.ARRAY`, :class:`_types.JSON` and
16
+ :class:`_postgresql.HSTORE`.
17
+
18
+
19
+
20
+ The :mod:`~sqlalchemy.ext.indexable` extension provides
21
+ :class:`_schema.Column`-like interface for any element of an
22
+ :class:`_types.Indexable` typed column. In simple cases, it can be
23
+ treated as a :class:`_schema.Column` - mapped attribute.
24
+
25
+ Synopsis
26
+ ========
27
+
28
+ Given ``Person`` as a model with a primary key and JSON data field.
29
+ While this field may have any number of elements encoded within it,
30
+ we would like to refer to the element called ``name`` individually
31
+ as a dedicated attribute which behaves like a standalone column::
32
+
33
+ from sqlalchemy import Column, JSON, Integer
34
+ from sqlalchemy.ext.declarative import declarative_base
35
+ from sqlalchemy.ext.indexable import index_property
36
+
37
+ Base = declarative_base()
38
+
39
+ class Person(Base):
40
+ __tablename__ = 'person'
41
+
42
+ id = Column(Integer, primary_key=True)
43
+ data = Column(JSON)
44
+
45
+ name = index_property('data', 'name')
46
+
47
+
48
+ Above, the ``name`` attribute now behaves like a mapped column. We
49
+ can compose a new ``Person`` and set the value of ``name``::
50
+
51
+ >>> person = Person(name='Alchemist')
52
+
53
+ The value is now accessible::
54
+
55
+ >>> person.name
56
+ 'Alchemist'
57
+
58
+ Behind the scenes, the JSON field was initialized to a new blank dictionary
59
+ and the field was set::
60
+
61
+ >>> person.data
62
+ {"name": "Alchemist'}
63
+
64
+ The field is mutable in place::
65
+
66
+ >>> person.name = 'Renamed'
67
+ >>> person.name
68
+ 'Renamed'
69
+ >>> person.data
70
+ {'name': 'Renamed'}
71
+
72
+ When using :class:`.index_property`, the change that we make to the indexable
73
+ structure is also automatically tracked as history; we no longer need
74
+ to use :class:`~.mutable.MutableDict` in order to track this change
75
+ for the unit of work.
76
+
77
+ Deletions work normally as well::
78
+
79
+ >>> del person.name
80
+ >>> person.data
81
+ {}
82
+
83
+ Above, deletion of ``person.name`` deletes the value from the dictionary,
84
+ but not the dictionary itself.
85
+
86
+ A missing key will produce ``AttributeError``::
87
+
88
+ >>> person = Person()
89
+ >>> person.name
90
+ ...
91
+ AttributeError: 'name'
92
+
93
+ Unless you set a default value::
94
+
95
+ >>> class Person(Base):
96
+ >>> __tablename__ = 'person'
97
+ >>>
98
+ >>> id = Column(Integer, primary_key=True)
99
+ >>> data = Column(JSON)
100
+ >>>
101
+ >>> name = index_property('data', 'name', default=None) # See default
102
+
103
+ >>> person = Person()
104
+ >>> print(person.name)
105
+ None
106
+
107
+
108
+ The attributes are also accessible at the class level.
109
+ Below, we illustrate ``Person.name`` used to generate
110
+ an indexed SQL criteria::
111
+
112
+ >>> from sqlalchemy.orm import Session
113
+ >>> session = Session()
114
+ >>> query = session.query(Person).filter(Person.name == 'Alchemist')
115
+
116
+ The above query is equivalent to::
117
+
118
+ >>> query = session.query(Person).filter(Person.data['name'] == 'Alchemist')
119
+
120
+ Multiple :class:`.index_property` objects can be chained to produce
121
+ multiple levels of indexing::
122
+
123
+ from sqlalchemy import Column, JSON, Integer
124
+ from sqlalchemy.ext.declarative import declarative_base
125
+ from sqlalchemy.ext.indexable import index_property
126
+
127
+ Base = declarative_base()
128
+
129
+ class Person(Base):
130
+ __tablename__ = 'person'
131
+
132
+ id = Column(Integer, primary_key=True)
133
+ data = Column(JSON)
134
+
135
+ birthday = index_property('data', 'birthday')
136
+ year = index_property('birthday', 'year')
137
+ month = index_property('birthday', 'month')
138
+ day = index_property('birthday', 'day')
139
+
140
+ Above, a query such as::
141
+
142
+ q = session.query(Person).filter(Person.year == '1980')
143
+
144
+ On a PostgreSQL backend, the above query will render as::
145
+
146
+ SELECT person.id, person.data
147
+ FROM person
148
+ WHERE person.data -> %(data_1)s -> %(param_1)s = %(param_2)s
149
+
150
+ Default Values
151
+ ==============
152
+
153
+ :class:`.index_property` includes special behaviors for when the indexed
154
+ data structure does not exist, and a set operation is called:
155
+
156
+ * For an :class:`.index_property` that is given an integer index value,
157
+ the default data structure will be a Python list of ``None`` values,
158
+ at least as long as the index value; the value is then set at its
159
+ place in the list. This means for an index value of zero, the list
160
+ will be initialized to ``[None]`` before setting the given value,
161
+ and for an index value of five, the list will be initialized to
162
+ ``[None, None, None, None, None]`` before setting the fifth element
163
+ to the given value. Note that an existing list is **not** extended
164
+ in place to receive a value.
165
+
166
+ * for an :class:`.index_property` that is given any other kind of index
167
+ value (e.g. strings usually), a Python dictionary is used as the
168
+ default data structure.
169
+
170
+ * The default data structure can be set to any Python callable using the
171
+ :paramref:`.index_property.datatype` parameter, overriding the previous
172
+ rules.
173
+
174
+
175
+ Subclassing
176
+ ===========
177
+
178
+ :class:`.index_property` can be subclassed, in particular for the common
179
+ use case of providing coercion of values or SQL expressions as they are
180
+ accessed. Below is a common recipe for use with a PostgreSQL JSON type,
181
+ where we want to also include automatic casting plus ``astext()``::
182
+
183
+ class pg_json_property(index_property):
184
+ def __init__(self, attr_name, index, cast_type):
185
+ super(pg_json_property, self).__init__(attr_name, index)
186
+ self.cast_type = cast_type
187
+
188
+ def expr(self, model):
189
+ expr = super(pg_json_property, self).expr(model)
190
+ return expr.astext.cast(self.cast_type)
191
+
192
+ The above subclass can be used with the PostgreSQL-specific
193
+ version of :class:`_postgresql.JSON`::
194
+
195
+ from sqlalchemy import Column, Integer
196
+ from sqlalchemy.ext.declarative import declarative_base
197
+ from sqlalchemy.dialects.postgresql import JSON
198
+
199
+ Base = declarative_base()
200
+
201
+ class Person(Base):
202
+ __tablename__ = 'person'
203
+
204
+ id = Column(Integer, primary_key=True)
205
+ data = Column(JSON)
206
+
207
+ age = pg_json_property('data', 'age', Integer)
208
+
209
+ The ``age`` attribute at the instance level works as before; however
210
+ when rendering SQL, PostgreSQL's ``->>`` operator will be used
211
+ for indexed access, instead of the usual index operator of ``->``::
212
+
213
+ >>> query = session.query(Person).filter(Person.age < 20)
214
+
215
+ The above query will render::
216
+
217
+ SELECT person.id, person.data
218
+ FROM person
219
+ WHERE CAST(person.data ->> %(data_1)s AS INTEGER) < %(param_1)s
220
+
221
+ """ # noqa
222
+ from .. import inspect
223
+ from ..ext.hybrid import hybrid_property
224
+ from ..orm.attributes import flag_modified
225
+
226
+
227
+ __all__ = ["index_property"]
228
+
229
+
230
+ class index_property(hybrid_property): # noqa
231
+ """A property generator. The generated property describes an object
232
+ attribute that corresponds to an :class:`_types.Indexable`
233
+ column.
234
+
235
+ .. seealso::
236
+
237
+ :mod:`sqlalchemy.ext.indexable`
238
+
239
+ """
240
+
241
+ _NO_DEFAULT_ARGUMENT = object()
242
+
243
+ def __init__(
244
+ self,
245
+ attr_name,
246
+ index,
247
+ default=_NO_DEFAULT_ARGUMENT,
248
+ datatype=None,
249
+ mutable=True,
250
+ onebased=True,
251
+ ):
252
+ """Create a new :class:`.index_property`.
253
+
254
+ :param attr_name:
255
+ An attribute name of an `Indexable` typed column, or other
256
+ attribute that returns an indexable structure.
257
+ :param index:
258
+ The index to be used for getting and setting this value. This
259
+ should be the Python-side index value for integers.
260
+ :param default:
261
+ A value which will be returned instead of `AttributeError`
262
+ when there is not a value at given index.
263
+ :param datatype: default datatype to use when the field is empty.
264
+ By default, this is derived from the type of index used; a
265
+ Python list for an integer index, or a Python dictionary for
266
+ any other style of index. For a list, the list will be
267
+ initialized to a list of None values that is at least
268
+ ``index`` elements long.
269
+ :param mutable: if False, writes and deletes to the attribute will
270
+ be disallowed.
271
+ :param onebased: assume the SQL representation of this value is
272
+ one-based; that is, the first index in SQL is 1, not zero.
273
+ """
274
+
275
+ if mutable:
276
+ super().__init__(self.fget, self.fset, self.fdel, self.expr)
277
+ else:
278
+ super().__init__(self.fget, None, None, self.expr)
279
+ self.attr_name = attr_name
280
+ self.index = index
281
+ self.default = default
282
+ is_numeric = isinstance(index, int)
283
+ onebased = is_numeric and onebased
284
+
285
+ if datatype is not None:
286
+ self.datatype = datatype
287
+ else:
288
+ if is_numeric:
289
+ self.datatype = lambda: [None for x in range(index + 1)]
290
+ else:
291
+ self.datatype = dict
292
+ self.onebased = onebased
293
+
294
+ def _fget_default(self, err=None):
295
+ if self.default == self._NO_DEFAULT_ARGUMENT:
296
+ raise AttributeError(self.attr_name) from err
297
+ else:
298
+ return self.default
299
+
300
+ def fget(self, instance):
301
+ attr_name = self.attr_name
302
+ column_value = getattr(instance, attr_name)
303
+ if column_value is None:
304
+ return self._fget_default()
305
+ try:
306
+ value = column_value[self.index]
307
+ except (KeyError, IndexError) as err:
308
+ return self._fget_default(err)
309
+ else:
310
+ return value
311
+
312
+ def fset(self, instance, value):
313
+ attr_name = self.attr_name
314
+ column_value = getattr(instance, attr_name, None)
315
+ if column_value is None:
316
+ column_value = self.datatype()
317
+ setattr(instance, attr_name, column_value)
318
+ column_value[self.index] = value
319
+ setattr(instance, attr_name, column_value)
320
+ if attr_name in inspect(instance).mapper.attrs:
321
+ flag_modified(instance, attr_name)
322
+
323
+ def fdel(self, instance):
324
+ attr_name = self.attr_name
325
+ column_value = getattr(instance, attr_name)
326
+ if column_value is None:
327
+ raise AttributeError(self.attr_name)
328
+ try:
329
+ del column_value[self.index]
330
+ except KeyError as err:
331
+ raise AttributeError(self.attr_name) from err
332
+ else:
333
+ setattr(instance, attr_name, column_value)
334
+ flag_modified(instance, attr_name)
335
+
336
+ def expr(self, model):
337
+ column = getattr(model, self.attr_name)
338
+ index = self.index
339
+ if self.onebased:
340
+ index += 1
341
+ return column[index]