snowflake-sqlalchemy 1.7.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. snowflake/sqlalchemy/__init__.py +162 -0
  2. snowflake/sqlalchemy/_constants.py +14 -0
  3. snowflake/sqlalchemy/base.py +1188 -0
  4. snowflake/sqlalchemy/compat.py +36 -0
  5. snowflake/sqlalchemy/custom_commands.py +627 -0
  6. snowflake/sqlalchemy/custom_types.py +155 -0
  7. snowflake/sqlalchemy/exc.py +82 -0
  8. snowflake/sqlalchemy/functions.py +16 -0
  9. snowflake/sqlalchemy/parser/custom_type_parser.py +245 -0
  10. snowflake/sqlalchemy/provision.py +12 -0
  11. snowflake/sqlalchemy/requirements.py +313 -0
  12. snowflake/sqlalchemy/snowdialect.py +1029 -0
  13. snowflake/sqlalchemy/sql/__init__.py +3 -0
  14. snowflake/sqlalchemy/sql/custom_schema/__init__.py +9 -0
  15. snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +37 -0
  16. snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +127 -0
  17. snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +13 -0
  18. snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +117 -0
  19. snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +63 -0
  20. snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +102 -0
  21. snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +33 -0
  22. snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +63 -0
  23. snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +58 -0
  24. snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +63 -0
  25. snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +25 -0
  26. snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +65 -0
  27. snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +14 -0
  28. snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +67 -0
  29. snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +84 -0
  30. snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +94 -0
  31. snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +70 -0
  32. snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +54 -0
  33. snowflake/sqlalchemy/util.py +344 -0
  34. snowflake/sqlalchemy/version.py +6 -0
  35. snowflake_sqlalchemy-1.7.3.dist-info/METADATA +737 -0
  36. snowflake_sqlalchemy-1.7.3.dist-info/RECORD +39 -0
  37. snowflake_sqlalchemy-1.7.3.dist-info/WHEEL +4 -0
  38. snowflake_sqlalchemy-1.7.3.dist-info/entry_points.txt +2 -0
  39. snowflake_sqlalchemy-1.7.3.dist-info/licenses/LICENSE.txt +202 -0
@@ -0,0 +1,344 @@
1
+ #
2
+ # Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
3
+ #
4
+
5
+ import re
6
+ from itertools import chain
7
+ from typing import Any
8
+ from urllib.parse import quote_plus
9
+
10
+ from sqlalchemy import exc, inspection, sql
11
+ from sqlalchemy.exc import NoForeignKeysError
12
+ from sqlalchemy.orm.interfaces import MapperProperty
13
+ from sqlalchemy.orm.util import _ORMJoin as sa_orm_util_ORMJoin
14
+ from sqlalchemy.orm.util import attributes
15
+ from sqlalchemy.sql import util as sql_util
16
+ from sqlalchemy.sql.base import _expand_cloned, _from_objects
17
+ from sqlalchemy.sql.elements import _find_columns
18
+ from sqlalchemy.sql.selectable import Join, Lateral, coercions, operators, roles
19
+
20
+ from snowflake.connector.compat import IS_STR
21
+ from snowflake.connector.connection import SnowflakeConnection
22
+ from snowflake.sqlalchemy import compat
23
+
24
+ from ._constants import (
25
+ APPLICATION_NAME,
26
+ PARAM_APPLICATION,
27
+ PARAM_INTERNAL_APPLICATION_NAME,
28
+ PARAM_INTERNAL_APPLICATION_VERSION,
29
+ SNOWFLAKE_SQLALCHEMY_VERSION,
30
+ )
31
+
32
+
33
+ def _rfc_1738_quote(text):
34
+ return re.sub(r"[:@/]", lambda m: "%%%X" % ord(m.group(0)), text)
35
+
36
+
37
+ def _url(**db_parameters):
38
+ """
39
+ Composes a SQLAlchemy connect string from the given database connection
40
+ parameters.
41
+
42
+ Password containing special characters (e.g., '@', '%') need to be encoded to be parsed correctly.
43
+ Unescaped password containing special characters might lead to authentication failure.
44
+ Please follow the instructions to encode the password:
45
+ https://github.com/snowflakedb/snowflake-sqlalchemy#escaping-special-characters-such-as---signs-in-passwords
46
+ """
47
+ specified_parameters = []
48
+ if "account" not in db_parameters:
49
+ raise exc.ArgumentError("account parameter must be specified.")
50
+
51
+ if "host" in db_parameters:
52
+ ret = "snowflake://{user}:{password}@{host}:{port}/".format(
53
+ user=db_parameters.get("user", ""),
54
+ password=_rfc_1738_quote(db_parameters.get("password", "")),
55
+ host=db_parameters["host"],
56
+ port=db_parameters["port"] if "port" in db_parameters else 443,
57
+ )
58
+ specified_parameters += ["user", "password", "host", "port"]
59
+ elif "region" not in db_parameters:
60
+ ret = "snowflake://{user}:{password}@{account}/".format(
61
+ account=db_parameters["account"],
62
+ user=db_parameters.get("user", ""),
63
+ password=_rfc_1738_quote(db_parameters.get("password", "")),
64
+ )
65
+ specified_parameters += ["user", "password", "account"]
66
+ else:
67
+ ret = "snowflake://{user}:{password}@{account}.{region}/".format(
68
+ account=db_parameters["account"],
69
+ user=db_parameters.get("user", ""),
70
+ password=_rfc_1738_quote(db_parameters.get("password", "")),
71
+ region=db_parameters["region"],
72
+ )
73
+ specified_parameters += ["user", "password", "account", "region"]
74
+
75
+ if "database" in db_parameters:
76
+ ret += quote_plus(db_parameters["database"])
77
+ specified_parameters += ["database"]
78
+ if "schema" in db_parameters:
79
+ ret += "/" + quote_plus(db_parameters["schema"])
80
+ specified_parameters += ["schema"]
81
+ elif "schema" in db_parameters:
82
+ raise exc.ArgumentError("schema cannot be specified without database")
83
+
84
+ def sep(is_first_parameter):
85
+ return "?" if is_first_parameter else "&"
86
+
87
+ is_first_parameter = True
88
+ for p in sorted(db_parameters.keys()):
89
+ v = db_parameters[p]
90
+ if p not in specified_parameters:
91
+ encoded_value = quote_plus(v) if IS_STR(v) else str(v)
92
+ ret += sep(is_first_parameter) + p + "=" + encoded_value
93
+ is_first_parameter = False
94
+ return ret
95
+
96
+
97
+ def _set_connection_interpolate_empty_sequences(
98
+ dbapi_connection: SnowflakeConnection, flag: bool
99
+ ) -> None:
100
+ """set the _interpolate_empty_sequences config of the underlying connection"""
101
+ if hasattr(dbapi_connection, "driver_connection"):
102
+ # _dbapi_connection is a _ConnectionFairy which proxies raw SnowflakeConnection
103
+ dbapi_connection.driver_connection._interpolate_empty_sequences = flag
104
+ else:
105
+ # _dbapi_connection is a raw SnowflakeConnection
106
+ dbapi_connection._interpolate_empty_sequences = flag
107
+
108
+
109
+ def _update_connection_application_name(**conn_kwargs: Any) -> Any:
110
+ if PARAM_APPLICATION not in conn_kwargs:
111
+ conn_kwargs[PARAM_APPLICATION] = APPLICATION_NAME
112
+ if PARAM_INTERNAL_APPLICATION_NAME not in conn_kwargs:
113
+ conn_kwargs[PARAM_INTERNAL_APPLICATION_NAME] = APPLICATION_NAME
114
+ if PARAM_INTERNAL_APPLICATION_VERSION not in conn_kwargs:
115
+ conn_kwargs[PARAM_INTERNAL_APPLICATION_VERSION] = SNOWFLAKE_SQLALCHEMY_VERSION
116
+ return conn_kwargs
117
+
118
+
119
+ def parse_url_boolean(value: str) -> bool:
120
+ if value == "True":
121
+ return True
122
+ elif value == "False":
123
+ return False
124
+ else:
125
+ raise ValueError(f"Invalid boolean value detected: '{value}'")
126
+
127
+
128
+ def parse_url_integer(value: str) -> int:
129
+ try:
130
+ return int(value)
131
+ except ValueError as e:
132
+ raise ValueError(f"Invalid int value detected: '{value}") from e
133
+
134
+
135
+ # handle Snowflake BCR bcr-1057
136
+ # the BCR impacts sqlalchemy.orm.context.ORMSelectCompileState and sqlalchemy.sql.selectable.SelectState
137
+ # which used the 'sqlalchemy.util.preloaded.sql_util.find_left_clause_to_join_from' method that
138
+ # can not handle the BCR change, we implement it in a way that lateral join does not need onclause
139
+ def _find_left_clause_to_join_from(clauses, join_to, onclause):
140
+ """Given a list of FROM clauses, a selectable,
141
+ and optional ON clause, return a list of integer indexes from the
142
+ clauses list indicating the clauses that can be joined from.
143
+
144
+ The presence of an "onclause" indicates that at least one clause can
145
+ definitely be joined from; if the list of clauses is of length one
146
+ and the onclause is given, returns that index. If the list of clauses
147
+ is more than length one, and the onclause is given, attempts to locate
148
+ which clauses contain the same columns.
149
+
150
+ """
151
+ idx = []
152
+ selectables = set(_from_objects(join_to))
153
+
154
+ # if we are given more than one target clause to join
155
+ # from, use the onclause to provide a more specific answer.
156
+ # otherwise, don't try to limit, after all, "ON TRUE" is a valid
157
+ # on clause
158
+ if len(clauses) > 1 and onclause is not None:
159
+ resolve_ambiguity = True
160
+ cols_in_onclause = _find_columns(onclause)
161
+ else:
162
+ resolve_ambiguity = False
163
+ cols_in_onclause = None
164
+
165
+ for i, f in enumerate(clauses):
166
+ for s in selectables.difference([f]):
167
+ if resolve_ambiguity:
168
+ if set(f.c).union(s.c).issuperset(cols_in_onclause):
169
+ idx.append(i)
170
+ break
171
+ elif onclause is not None or Join._can_join(f, s):
172
+ idx.append(i)
173
+ break
174
+ elif onclause is None and isinstance(s, Lateral):
175
+ # in snowflake, onclause is not accepted for lateral due to BCR change:
176
+ # https://docs.snowflake.com/en/release-notes/bcr-bundles/2023_04/bcr-1057
177
+ # sqlalchemy only allows join with on condition.
178
+ # to adapt to snowflake syntax change,
179
+ # we make the change such that when oncaluse is None and the right part is
180
+ # Lateral, we append the index indicating Lateral clause can be joined from with without onclause.
181
+ idx.append(i)
182
+ break
183
+
184
+ if len(idx) > 1:
185
+ # this is the same "hide froms" logic from
186
+ # Selectable._get_display_froms
187
+ toremove = set(chain(*[_expand_cloned(f._hide_froms) for f in clauses]))
188
+ idx = [i for i in idx if clauses[i] not in toremove]
189
+
190
+ # onclause was given and none of them resolved, so assume
191
+ # all indexes can match
192
+ if not idx and onclause is not None:
193
+ return range(len(clauses))
194
+ else:
195
+ return idx
196
+
197
+
198
+ class _Snowflake_ORMJoin(sa_orm_util_ORMJoin):
199
+ def __init__(
200
+ self,
201
+ left,
202
+ right,
203
+ onclause=None,
204
+ isouter=False,
205
+ full=False,
206
+ _left_memo=None,
207
+ _right_memo=None,
208
+ _extra_criteria=(),
209
+ ):
210
+ left_info = inspection.inspect(left)
211
+
212
+ right_info = inspection.inspect(right)
213
+ adapt_to = right_info.selectable
214
+
215
+ # used by joined eager loader
216
+ self._left_memo = _left_memo
217
+ self._right_memo = _right_memo
218
+
219
+ # legacy, for string attr name ON clause. if that's removed
220
+ # then the "_joined_from_info" concept can go
221
+ left_orm_info = getattr(left, "_joined_from_info", left_info)
222
+ self._joined_from_info = right_info
223
+ if isinstance(onclause, compat.string_types):
224
+ onclause = getattr(left_orm_info.entity, onclause)
225
+ # ####
226
+
227
+ if isinstance(onclause, attributes.QueryableAttribute):
228
+ on_selectable = onclause.comparator._source_selectable()
229
+ prop = onclause.property
230
+ _extra_criteria += onclause._extra_criteria
231
+ elif isinstance(onclause, MapperProperty):
232
+ # used internally by joined eager loader...possibly not ideal
233
+ prop = onclause
234
+ on_selectable = prop.parent.selectable
235
+ else:
236
+ prop = None
237
+
238
+ if prop:
239
+ left_selectable = left_info.selectable
240
+
241
+ if sql_util.clause_is_present(on_selectable, left_selectable):
242
+ adapt_from = on_selectable
243
+ else:
244
+ adapt_from = left_selectable
245
+
246
+ (
247
+ pj,
248
+ sj,
249
+ source,
250
+ dest,
251
+ secondary,
252
+ target_adapter,
253
+ ) = prop._create_joins(
254
+ source_selectable=adapt_from,
255
+ dest_selectable=adapt_to,
256
+ source_polymorphic=True,
257
+ of_type_entity=right_info,
258
+ alias_secondary=True,
259
+ extra_criteria=_extra_criteria,
260
+ )
261
+
262
+ if sj is not None:
263
+ if isouter:
264
+ # note this is an inner join from secondary->right
265
+ right = sql.join(secondary, right, sj)
266
+ onclause = pj
267
+ else:
268
+ left = sql.join(left, secondary, pj, isouter)
269
+ onclause = sj
270
+ else:
271
+ onclause = pj
272
+
273
+ self._target_adapter = target_adapter
274
+
275
+ # we don't use the normal coercions logic for _ORMJoin
276
+ # (probably should), so do some gymnastics to get the entity.
277
+ # logic here is for #8721, which was a major bug in 1.4
278
+ # for almost two years, not reported/fixed until 1.4.43 (!)
279
+ if left_info.is_selectable:
280
+ parententity = left_selectable._annotations.get("parententity", None)
281
+ elif left_info.is_mapper or left_info.is_aliased_class:
282
+ parententity = left_info
283
+ else:
284
+ parententity = None
285
+
286
+ if parententity is not None:
287
+ self._annotations = self._annotations.union(
288
+ {"parententity": parententity}
289
+ )
290
+
291
+ augment_onclause = onclause is None and _extra_criteria
292
+ # handle Snowflake BCR bcr-1057
293
+ _Snowflake_Selectable_Join.__init__(self, left, right, onclause, isouter, full)
294
+
295
+ if augment_onclause:
296
+ self.onclause &= sql.and_(*_extra_criteria)
297
+
298
+ if (
299
+ not prop
300
+ and getattr(right_info, "mapper", None)
301
+ and right_info.mapper.single
302
+ ):
303
+ # if single inheritance target and we are using a manual
304
+ # or implicit ON clause, augment it the same way we'd augment the
305
+ # WHERE.
306
+ single_crit = right_info.mapper._single_table_criterion
307
+ if single_crit is not None:
308
+ if right_info.is_aliased_class:
309
+ single_crit = right_info._adapter.traverse(single_crit)
310
+ self.onclause = self.onclause & single_crit
311
+
312
+
313
+ class _Snowflake_Selectable_Join(Join):
314
+ def __init__(self, left, right, onclause=None, isouter=False, full=False):
315
+ """Construct a new :class:`_expression.Join`.
316
+
317
+ The usual entrypoint here is the :func:`_expression.join`
318
+ function or the :meth:`_expression.FromClause.join` method of any
319
+ :class:`_expression.FromClause` object.
320
+
321
+ """
322
+ self.left = coercions.expect(roles.FromClauseRole, left, deannotate=True)
323
+ self.right = coercions.expect(
324
+ roles.FromClauseRole, right, deannotate=True
325
+ ).self_group()
326
+
327
+ if onclause is None:
328
+ try:
329
+ self.onclause = self._match_primaries(self.left, self.right)
330
+ except NoForeignKeysError:
331
+ # handle Snowflake BCR bcr-1057
332
+ if isinstance(self.right, Lateral):
333
+ self.onclause = None
334
+ else:
335
+ raise
336
+ else:
337
+ # note: taken from If91f61527236fd4d7ae3cad1f24c38be921c90ba
338
+ # not merged yet
339
+ self.onclause = coercions.expect(roles.OnClauseRole, onclause).self_group(
340
+ against=operators._asbool
341
+ )
342
+
343
+ self.isouter = isouter
344
+ self.full = full
@@ -0,0 +1,6 @@
1
+ #
2
+ # Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
3
+ #
4
+ # Update this for the versions
5
+ # Don't change the forth version number from None
6
+ VERSION = "1.7.3"