fakesnow 0.9.18__py3-none-any.whl → 0.9.20__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.
fakesnow/__init__.py CHANGED
@@ -13,6 +13,7 @@ import snowflake.connector
13
13
  import snowflake.connector.pandas_tools
14
14
 
15
15
  import fakesnow.fakes as fakes
16
+ from fakesnow.global_database import create_global_database
16
17
 
17
18
 
18
19
  @contextmanager
@@ -52,6 +53,7 @@ def patch(
52
53
  assert not isinstance(snowflake.connector.connect, mock.MagicMock), "Snowflake connector is already patched"
53
54
 
54
55
  duck_conn = duckdb.connect(database=":memory:")
56
+ create_global_database(duck_conn)
55
57
 
56
58
  fake_fns = {
57
59
  # every time we connect, create a new cursor (ie: connection) so we can isolate each connection's
fakesnow/fakes.py CHANGED
@@ -32,7 +32,7 @@ import fakesnow.expr as expr
32
32
  import fakesnow.info_schema as info_schema
33
33
  import fakesnow.macros as macros
34
34
  import fakesnow.transforms as transforms
35
- from fakesnow.global_database import create_global_database
35
+ from fakesnow.variables import Variables
36
36
 
37
37
  SCHEMA_UNSET = "schema_unset"
38
38
  SQL_SUCCESS = "SELECT 'Statement executed successfully.' as 'status'"
@@ -134,6 +134,7 @@ class FakeSnowflakeCursor:
134
134
  if os.environ.get("FAKESNOW_DEBUG") == "snowflake":
135
135
  print(f"{command};{params=}" if params else f"{command};", file=sys.stderr)
136
136
 
137
+ command = self._inline_variables(command)
137
138
  command, params = self._rewrite_with_params(command, params)
138
139
  if self._conn.nop_regexes and any(re.match(p, command, re.IGNORECASE) for p in self._conn.nop_regexes):
139
140
  transformed = transforms.SUCCESS_NOP
@@ -148,6 +149,7 @@ class FakeSnowflakeCursor:
148
149
  def _transform(self, expression: exp.Expression) -> exp.Expression:
149
150
  return (
150
151
  expression.transform(transforms.upper_case_unquoted_identifiers)
152
+ .transform(transforms.update_variables, variables=self._conn.variables)
151
153
  .transform(transforms.set_schema, current_database=self._conn.database)
152
154
  .transform(transforms.create_database, db_path=self._conn.db_path)
153
155
  .transform(transforms.extract_comment_on_table)
@@ -501,6 +503,9 @@ class FakeSnowflakeCursor:
501
503
 
502
504
  return command, params
503
505
 
506
+ def _inline_variables(self, sql: str) -> str:
507
+ return self._conn.variables.inline_variables(sql)
508
+
504
509
 
505
510
  class FakeSnowflakeConnection:
506
511
  def __init__(
@@ -525,8 +530,7 @@ class FakeSnowflakeConnection:
525
530
  self.db_path = Path(db_path) if db_path else None
526
531
  self.nop_regexes = nop_regexes
527
532
  self._paramstyle = snowflake.connector.paramstyle
528
-
529
- create_global_database(duck_conn)
533
+ self.variables = Variables()
530
534
 
531
535
  # create database if needed
532
536
  if (
fakesnow/transforms.py CHANGED
@@ -8,6 +8,7 @@ import sqlglot
8
8
  from sqlglot import exp
9
9
 
10
10
  from fakesnow.global_database import USERS_TABLE_FQ_NAME
11
+ from fakesnow.variables import Variables
11
12
 
12
13
  MISSING_DATABASE = "missing_database"
13
14
  SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.'")
@@ -1401,12 +1402,27 @@ def show_keys(
1401
1402
 
1402
1403
  if schema:
1403
1404
  statement += f"AND schema_name = '{schema}' "
1405
+ elif scope_kind == "TABLE":
1406
+ if not table:
1407
+ raise ValueError(f"SHOW PRIMARY KEYS with {scope_kind} scope requires a table")
1408
+
1409
+ statement += f"AND table_name = '{table.name}' "
1404
1410
  else:
1405
1411
  raise NotImplementedError(f"SHOW PRIMARY KEYS with {scope_kind} not yet supported")
1406
1412
  return sqlglot.parse_one(statement)
1407
1413
  return expression
1408
1414
 
1409
1415
 
1416
+ def update_variables(
1417
+ expression: exp.Expression,
1418
+ variables: Variables,
1419
+ ) -> exp.Expression:
1420
+ if Variables.is_variable_modifier(expression):
1421
+ variables.update_variables(expression)
1422
+ return SUCCESS_NOP # Nothing further to do if its a SET/UNSET operation.
1423
+ return expression
1424
+
1425
+
1410
1426
  class SHA256(exp.Func):
1411
1427
  _sql_names: ClassVar = ["SHA256"]
1412
1428
  arg_types: ClassVar = {"this": True}
fakesnow/variables.py ADDED
@@ -0,0 +1,56 @@
1
+ import re
2
+
3
+ import snowflake.connector.errors
4
+ from sqlglot import exp
5
+
6
+
7
+ # Implements snowflake variables: https://docs.snowflake.com/en/sql-reference/session-variables#using-variables-in-sql
8
+ class Variables:
9
+ @classmethod
10
+ def is_variable_modifier(cls, expr: exp.Expression) -> bool:
11
+ return isinstance(expr, exp.Set) or cls._is_unset_expression(expr)
12
+
13
+ @classmethod
14
+ def _is_unset_expression(cls, expr: exp.Expression) -> bool:
15
+ if isinstance(expr, exp.Alias):
16
+ this_expr = expr.this.args.get("this")
17
+ return isinstance(this_expr, exp.Expression) and this_expr.this == "UNSET"
18
+ return False
19
+
20
+ def __init__(self) -> None:
21
+ self._variables = {}
22
+
23
+ def update_variables(self, expr: exp.Expression) -> None:
24
+ if isinstance(expr, exp.Set):
25
+ unset = expr.args.get("unset")
26
+ if not unset: # SET varname = value;
27
+ unset_expressions = expr.args.get("expressions")
28
+ assert unset_expressions, "SET without values in expression(s) is unexpected."
29
+ eq = unset_expressions[0].this
30
+ name = eq.this.sql()
31
+ value = eq.args.get("expression").sql()
32
+ self._set(name, value)
33
+ else:
34
+ # Haven't been able to produce this in tests yet due to UNSET being parsed as an Alias expression.
35
+ raise NotImplementedError("UNSET not supported yet")
36
+ elif self._is_unset_expression(expr): # Unfortunately UNSET varname; is parsed as an Alias expression :(
37
+ alias = expr.args.get("alias")
38
+ assert alias, "UNSET without value in alias attribute is unexpected."
39
+ name = alias.this
40
+ self._unset(name)
41
+
42
+ def _set(self, name: str, value: str) -> None:
43
+ self._variables[name] = value
44
+
45
+ def _unset(self, name: str) -> None:
46
+ self._variables.pop(name)
47
+
48
+ def inline_variables(self, sql: str) -> str:
49
+ for name, value in self._variables.items():
50
+ sql = re.sub(rf"\${name}", value, sql, flags=re.IGNORECASE)
51
+
52
+ if remaining_variables := re.search(r"(?<!\$)\$\w+", sql):
53
+ raise snowflake.connector.errors.ProgrammingError(
54
+ msg=f"Session variable '{remaining_variables.group().upper()}' does not exist"
55
+ )
56
+ return sql
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.18
3
+ Version: 0.9.20
4
4
  Summary: Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -1,18 +1,19 @@
1
- fakesnow/__init__.py,sha256=7040jPjD-bNJDU4Lel42CD_DU47oeu1IsY9Fx_DlslQ,3799
1
+ fakesnow/__init__.py,sha256=gEo6Jg6f8tJUwRTsrfb9q-FpDOufelf8Y3XeWtEJ9wg,3897
2
2
  fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
3
3
  fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
4
4
  fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
5
5
  fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
6
- fakesnow/fakes.py,sha256=u2EDQ_fXQFFx5bq5pgLrUXV33Flgg5U82L7LIvFiqGw,30556
6
+ fakesnow/fakes.py,sha256=MYzj3XqVokPbOdOcU4WFep1CRsuXlBIwaz9EWaNCGo4,30778
7
7
  fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
8
8
  fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
9
9
  fakesnow/info_schema.py,sha256=CdIcGXHEQ_kmEAzdQKvA-PX41LA6wlK-4p1J45qgKYA,6266
10
10
  fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
11
11
  fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
12
- fakesnow/transforms.py,sha256=1xok38tft_VowwSm8kKA3P3OEZEeGF09hqfpawxXNaY,52648
13
- fakesnow-0.9.18.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
14
- fakesnow-0.9.18.dist-info/METADATA,sha256=cmS6VqDOHlkSZgfhT7biB03DuSktq4DNtv3EYbFDrrg,17839
15
- fakesnow-0.9.18.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
- fakesnow-0.9.18.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
17
- fakesnow-0.9.18.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
18
- fakesnow-0.9.18.dist-info/RECORD,,
12
+ fakesnow/transforms.py,sha256=7XS42Ehv3SRciQLpAL4swCFCkNpOTwarXEIX6jUzO4Y,53230
13
+ fakesnow/variables.py,sha256=iE8fnyaMnIoWh_ftT2qUq39fCNglwtQoGbC7MkTqHGU,2341
14
+ fakesnow-0.9.20.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
15
+ fakesnow-0.9.20.dist-info/METADATA,sha256=RSk43Zbbb8LQsG3G03DwUO8nVbaxs5qNd5VKTHqg4Xw,17839
16
+ fakesnow-0.9.20.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
17
+ fakesnow-0.9.20.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
18
+ fakesnow-0.9.20.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
19
+ fakesnow-0.9.20.dist-info/RECORD,,