fakesnow 0.9.1__py3-none-any.whl → 0.9.2__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/fakes.py +5 -0
- fakesnow/global_database.py +46 -0
- fakesnow/info_schema.py +2 -1
- fakesnow/transforms.py +39 -15
- {fakesnow-0.9.1.dist-info → fakesnow-0.9.2.dist-info}/METADATA +4 -3
- {fakesnow-0.9.1.dist-info → fakesnow-0.9.2.dist-info}/RECORD +10 -9
- {fakesnow-0.9.1.dist-info → fakesnow-0.9.2.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.1.dist-info → fakesnow-0.9.2.dist-info}/WHEEL +0 -0
- {fakesnow-0.9.1.dist-info → fakesnow-0.9.2.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.1.dist-info → fakesnow-0.9.2.dist-info}/top_level.txt +0 -0
fakesnow/fakes.py
CHANGED
@@ -30,6 +30,7 @@ import fakesnow.expr as expr
|
|
30
30
|
import fakesnow.info_schema as info_schema
|
31
31
|
import fakesnow.macros as macros
|
32
32
|
import fakesnow.transforms as transforms
|
33
|
+
from fakesnow.global_database import create_global_database
|
33
34
|
|
34
35
|
SCHEMA_UNSET = "schema_unset"
|
35
36
|
SQL_SUCCESS = "SELECT 'Statement executed successfully.' as 'status'"
|
@@ -195,6 +196,8 @@ class FakeSnowflakeCursor:
|
|
195
196
|
.transform(transforms.identifier)
|
196
197
|
.transform(lambda e: transforms.show_schemas(e, self._conn.database))
|
197
198
|
.transform(lambda e: transforms.show_objects_tables(e, self._conn.database))
|
199
|
+
.transform(transforms.show_users)
|
200
|
+
.transform(transforms.create_user)
|
198
201
|
)
|
199
202
|
sql = transformed.sql(dialect="duckdb")
|
200
203
|
result_sql = None
|
@@ -485,6 +488,8 @@ class FakeSnowflakeConnection:
|
|
485
488
|
self.db_path = db_path
|
486
489
|
self._paramstyle = "pyformat"
|
487
490
|
|
491
|
+
create_global_database(duck_conn)
|
492
|
+
|
488
493
|
# create database if needed
|
489
494
|
if (
|
490
495
|
create_database
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from duckdb import DuckDBPyConnection
|
2
|
+
|
3
|
+
GLOBAL_DATABASE_NAME = "_fs_global"
|
4
|
+
USERS_TABLE_FQ_NAME = f"{GLOBAL_DATABASE_NAME}._fs_users_ext"
|
5
|
+
|
6
|
+
# replicates the output structure of https://docs.snowflake.com/en/sql-reference/sql/show-users
|
7
|
+
SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT = f"""
|
8
|
+
create table if not exists {USERS_TABLE_FQ_NAME} (
|
9
|
+
name varchar,
|
10
|
+
created_on TIMESTAMPTZ,
|
11
|
+
login_name varchar,
|
12
|
+
display_name varchar,
|
13
|
+
first_name varchar,
|
14
|
+
last_name varchar,
|
15
|
+
email varchar,
|
16
|
+
mins_to_unlock varchar,
|
17
|
+
days_to_expiry varchar,
|
18
|
+
comment varchar,
|
19
|
+
disabled varchar,
|
20
|
+
must_change_password varchar,
|
21
|
+
snowflake_lock varchar,
|
22
|
+
default_warehouse varchar,
|
23
|
+
default_namespace varchar,
|
24
|
+
default_role varchar,
|
25
|
+
default_secondary_roles varchar,
|
26
|
+
ext_authn_duo varchar,
|
27
|
+
ext_authn_uid varchar,
|
28
|
+
mins_to_bypass_mfa varchar,
|
29
|
+
owner varchar,
|
30
|
+
last_success_login TIMESTAMPTZ,
|
31
|
+
expires_at_time TIMESTAMPTZ,
|
32
|
+
locked_until_time TIMESTAMPTZ,
|
33
|
+
has_password varchar,
|
34
|
+
has_rsa_public_key varchar,
|
35
|
+
)
|
36
|
+
"""
|
37
|
+
|
38
|
+
|
39
|
+
def create_global_database(conn: DuckDBPyConnection) -> None:
|
40
|
+
"""Create a "global" database for storing objects which span database.
|
41
|
+
|
42
|
+
Including (but not limited to):
|
43
|
+
- Users
|
44
|
+
"""
|
45
|
+
conn.execute(f"ATTACH IF NOT EXISTS ':memory:' AS {GLOBAL_DATABASE_NAME}")
|
46
|
+
conn.execute(SQL_CREATE_INFORMATION_SCHEMA_USERS_TABLE_EXT)
|
fakesnow/info_schema.py
CHANGED
@@ -74,7 +74,8 @@ select
|
|
74
74
|
1 as retention_time,
|
75
75
|
'STANDARD' as type
|
76
76
|
from information_schema.schemata
|
77
|
-
where catalog_name not in ('memory', 'system', 'temp'
|
77
|
+
where catalog_name not in ('memory', 'system', 'temp', '_fs_global')
|
78
|
+
and schema_name = 'information_schema'
|
78
79
|
"""
|
79
80
|
)
|
80
81
|
|
fakesnow/transforms.py
CHANGED
@@ -7,6 +7,8 @@ from typing import cast
|
|
7
7
|
import sqlglot
|
8
8
|
from sqlglot import exp
|
9
9
|
|
10
|
+
from fakesnow.global_database import USERS_TABLE_FQ_NAME
|
11
|
+
|
10
12
|
MISSING_DATABASE = "missing_database"
|
11
13
|
SUCCESS_NOP = sqlglot.parse_one("SELECT 'Statement executed successfully.'")
|
12
14
|
|
@@ -271,22 +273,15 @@ def flatten(expression: exp.Expression) -> exp.Expression:
|
|
271
273
|
return exp.Lateral(
|
272
274
|
this=exp.Unnest(
|
273
275
|
expressions=[
|
274
|
-
exp.
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
to=exp.DataType(
|
282
|
-
this=exp.DataType.Type.ARRAY,
|
283
|
-
expressions=[exp.DataType(this=exp.DataType.Type.JSON, nested=False, prefix=False)],
|
284
|
-
nested=True,
|
285
|
-
),
|
286
|
-
)
|
287
|
-
],
|
276
|
+
exp.Cast(
|
277
|
+
this=explode_expression,
|
278
|
+
to=exp.DataType(
|
279
|
+
this=exp.DataType.Type.ARRAY,
|
280
|
+
expressions=[exp.DataType(this=exp.DataType.Type.JSON, nested=False, prefix=False)],
|
281
|
+
nested=True,
|
282
|
+
),
|
288
283
|
)
|
289
|
-
]
|
284
|
+
],
|
290
285
|
),
|
291
286
|
alias=exp.TableAlias(this=alias.this, columns=[exp.Identifier(this="VALUE", quoted=False)]),
|
292
287
|
)
|
@@ -963,3 +958,32 @@ def values_columns(expression: exp.Expression) -> exp.Expression:
|
|
963
958
|
expression.set("alias", exp.TableAlias(this=exp.Identifier(this="_", quoted=False), columns=columns))
|
964
959
|
|
965
960
|
return expression
|
961
|
+
|
962
|
+
|
963
|
+
def show_users(expression: exp.Expression) -> exp.Expression:
|
964
|
+
"""Transform SHOW USERS to a query against the global database's information_schema._fs_users table.
|
965
|
+
|
966
|
+
https://docs.snowflake.com/en/sql-reference/sql/show-users
|
967
|
+
"""
|
968
|
+
if isinstance(expression, exp.Show) and isinstance(expression.this, str) and expression.this.upper() == "USERS":
|
969
|
+
return sqlglot.parse_one(f"SELECT * FROM {USERS_TABLE_FQ_NAME}", read="duckdb")
|
970
|
+
|
971
|
+
return expression
|
972
|
+
|
973
|
+
|
974
|
+
def create_user(expression: exp.Expression) -> exp.Expression:
|
975
|
+
"""Transform CREATE USER to a query against the global database's information_schema._fs_users table.
|
976
|
+
|
977
|
+
https://docs.snowflake.com/en/sql-reference/sql/create-user
|
978
|
+
"""
|
979
|
+
# XXX: this is a placeholder. We need to implement the full CREATE USER syntax, but
|
980
|
+
# sqlglot doesnt yet support Create for snowflake.
|
981
|
+
if isinstance(expression, exp.Command) and expression.this == "CREATE":
|
982
|
+
sub_exp = expression.expression.strip()
|
983
|
+
if sub_exp.upper().startswith("USER"):
|
984
|
+
_, name, *ignored = sub_exp.split(" ")
|
985
|
+
if ignored:
|
986
|
+
raise NotImplementedError(f"`CREATE USER` with {ignored} not yet supported")
|
987
|
+
return sqlglot.parse_one(f"INSERT INTO {USERS_TABLE_FQ_NAME} (name) VALUES ('{name}')", read="duckdb")
|
988
|
+
|
989
|
+
return expression
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fakesnow
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.2
|
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
|
@@ -210,10 +210,10 @@ Classifier: License :: OSI Approved :: MIT License
|
|
210
210
|
Requires-Python: >=3.9
|
211
211
|
Description-Content-Type: text/markdown
|
212
212
|
License-File: LICENSE
|
213
|
-
Requires-Dist: duckdb ~=0.
|
213
|
+
Requires-Dist: duckdb ~=0.10.0
|
214
214
|
Requires-Dist: pyarrow
|
215
215
|
Requires-Dist: snowflake-connector-python
|
216
|
-
Requires-Dist: sqlglot ~=21.0
|
216
|
+
Requires-Dist: sqlglot ~=21.1.0
|
217
217
|
Provides-Extra: dev
|
218
218
|
Requires-Dist: black ~=23.9 ; extra == 'dev'
|
219
219
|
Requires-Dist: build ~=1.0 ; extra == 'dev'
|
@@ -349,6 +349,7 @@ Partial support
|
|
349
349
|
- [x] regex functions
|
350
350
|
- [x] semi-structured data
|
351
351
|
- [x] tags
|
352
|
+
- [x] user management (See [tests/test_users.py](tests/test_users.py))
|
352
353
|
|
353
354
|
For more detail see [tests/test_fakes.py](tests/test_fakes.py)
|
354
355
|
|
@@ -3,15 +3,16 @@ 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=
|
6
|
+
fakesnow/fakes.py,sha256=iF9IN7nG74DNMDNTZ9CBUGqsYsqicZ7PgRNe4B_zAFs,27931
|
7
7
|
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
8
|
-
fakesnow/
|
8
|
+
fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
|
9
|
+
fakesnow/info_schema.py,sha256=eiKEVBbaSGfsuPD8USuhHF9BDC8pU9sPiDHgy0orTN8,4701
|
9
10
|
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
10
11
|
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
11
|
-
fakesnow/transforms.py,sha256=
|
12
|
-
fakesnow-0.9.
|
13
|
-
fakesnow-0.9.
|
14
|
-
fakesnow-0.9.
|
15
|
-
fakesnow-0.9.
|
16
|
-
fakesnow-0.9.
|
17
|
-
fakesnow-0.9.
|
12
|
+
fakesnow/transforms.py,sha256=z9tZDOtvQDAoYmQDvDegUOwxgBFvjnSfKCo7kTc-ryA,36300
|
13
|
+
fakesnow-0.9.2.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
14
|
+
fakesnow-0.9.2.dist-info/METADATA,sha256=7rSjgiC1nVjW_38DFx5I6OfyFz71f_TbjapFAIZ-kkI,17724
|
15
|
+
fakesnow-0.9.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
16
|
+
fakesnow-0.9.2.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
17
|
+
fakesnow-0.9.2.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
18
|
+
fakesnow-0.9.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|