fakesnow 0.7.1__tar.gz → 0.8.0__tar.gz

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 (24) hide show
  1. {fakesnow-0.7.1/fakesnow.egg-info → fakesnow-0.8.0}/PKG-INFO +1 -1
  2. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/fakes.py +2 -5
  3. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/info_schema.py +1 -2
  4. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/transforms.py +8 -9
  5. {fakesnow-0.7.1 → fakesnow-0.8.0/fakesnow.egg-info}/PKG-INFO +1 -1
  6. {fakesnow-0.7.1 → fakesnow-0.8.0}/pyproject.toml +1 -1
  7. {fakesnow-0.7.1 → fakesnow-0.8.0}/tests/test_fakes.py +38 -13
  8. {fakesnow-0.7.1 → fakesnow-0.8.0}/tests/test_transforms.py +1 -1
  9. {fakesnow-0.7.1 → fakesnow-0.8.0}/LICENSE +0 -0
  10. {fakesnow-0.7.1 → fakesnow-0.8.0}/MANIFEST.in +0 -0
  11. {fakesnow-0.7.1 → fakesnow-0.8.0}/README.md +0 -0
  12. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/__init__.py +0 -0
  13. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/checks.py +0 -0
  14. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/expr.py +0 -0
  15. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/fixtures.py +0 -0
  16. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow/py.typed +0 -0
  17. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow.egg-info/SOURCES.txt +0 -0
  18. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow.egg-info/dependency_links.txt +0 -0
  19. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow.egg-info/requires.txt +0 -0
  20. {fakesnow-0.7.1 → fakesnow-0.8.0}/fakesnow.egg-info/top_level.txt +0 -0
  21. {fakesnow-0.7.1 → fakesnow-0.8.0}/setup.cfg +0 -0
  22. {fakesnow-0.7.1 → fakesnow-0.8.0}/tests/test_checks.py +0 -0
  23. {fakesnow-0.7.1 → fakesnow-0.8.0}/tests/test_expr.py +0 -0
  24. {fakesnow-0.7.1 → fakesnow-0.8.0}/tests/test_patch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.7.1
3
+ Version: 0.8.0
4
4
  Summary: Fake Snowflake Connector for Python. Run Snowflake DB locally.
5
5
  License: MIT License
6
6
 
@@ -322,13 +322,10 @@ class FakeSnowflakeCursor:
322
322
  return ResultMetadata(
323
323
  name=column_name, type_code=12, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # noqa: E501
324
324
  )
325
- elif column_type == "JSON[]":
326
- return ResultMetadata(
327
- name=column_name, type_code=10, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # noqa: E501
328
- )
329
325
  elif column_type == "JSON":
326
+ # TODO: correctly map OBJECT and ARRAY see https://github.com/tekumara/fakesnow/issues/26
330
327
  return ResultMetadata(
331
- name=column_name, type_code=9, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # noqa: E501
328
+ name=column_name, type_code=5, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # noqa: E501
332
329
  )
333
330
  else:
334
331
  # TODO handle more types
@@ -40,8 +40,7 @@ case when starts_with(data_type, 'DECIMAL') or data_type='BIGINT' then 'NUMBER'
40
40
  when data_type='DOUBLE' then 'FLOAT'
41
41
  when data_type='BLOB' then 'BINARY'
42
42
  when data_type='TIMESTAMP' then 'TIMESTAMP_NTZ'
43
- when data_type='JSON[]' then 'ARRAY'
44
- when data_type='JSON' then 'OBJECT'
43
+ when data_type='JSON' then 'VARIANT'
45
44
  else data_type end as data_type,
46
45
  ext_character_maximum_length as character_maximum_length, ext_character_octet_length as character_octet_length,
47
46
  case when data_type='BIGINT' then 38
@@ -578,15 +578,14 @@ def semi_structured_types(expression: exp.Expression) -> exp.Expression:
578
578
  exp.Expression: The transformed expression.
579
579
  """
580
580
 
581
- if isinstance(expression, exp.DataType):
582
- if expression.this in [exp.DataType.Type.OBJECT, exp.DataType.Type.VARIANT]:
583
- new = expression.copy()
584
- new.args["this"] = exp.DataType.Type.JSON
585
- return new
586
- elif expression.this == exp.DataType.Type.ARRAY:
587
- new = expression.copy()
588
- new.set("expressions", [exp.DataType(this=exp.DataType.Type.JSON)])
589
- return new
581
+ if isinstance(expression, exp.DataType) and expression.this in [
582
+ exp.DataType.Type.ARRAY,
583
+ exp.DataType.Type.OBJECT,
584
+ exp.DataType.Type.VARIANT,
585
+ ]:
586
+ new = expression.copy()
587
+ new.args["this"] = exp.DataType.Type.JSON
588
+ return new
590
589
 
591
590
  return expression
592
591
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.7.1
3
+ Version: 0.8.0
4
4
  Summary: Fake Snowflake Connector for Python. Run Snowflake DB locally.
5
5
  License: MIT License
6
6
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "fakesnow"
3
3
  description = "Fake Snowflake Connector for Python. Run Snowflake DB locally."
4
- version = "0.7.1"
4
+ version = "0.8.0"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
7
  classifiers = ["License :: OSI Approved :: MIT License"]
@@ -2,6 +2,7 @@
2
2
 
3
3
  import datetime
4
4
  import json
5
+ from collections.abc import Sequence
5
6
  from decimal import Decimal
6
7
 
7
8
  import pandas as pd
@@ -205,7 +206,7 @@ def test_describe(cur: snowflake.connector.cursor.SnowflakeCursor):
205
206
  XINT INT, XINTEGER INTEGER, XBIGINT BIGINT, XSMALLINT SMALLINT, XTINYINT TINYINT, XBYTEINT BYTEINT,
206
207
  XVARCHAR20 VARCHAR(20), XVARCHAR VARCHAR, XTEXT TEXT,
207
208
  XTIMESTAMP TIMESTAMP, XTIMESTAMP_NTZ9 TIMESTAMP_NTZ(9), XDATE DATE, XTIME TIME,
208
- XBINARY BINARY, XARRAY ARRAY, XOBJECT OBJECT
209
+ XBINARY BINARY, /* XARRAY ARRAY, XOBJECT OBJECT */ XVARIANT VARIANT
209
210
  )
210
211
  """
211
212
  )
@@ -233,8 +234,10 @@ def test_describe(cur: snowflake.connector.cursor.SnowflakeCursor):
233
234
  ResultMetadata(name='XDATE', type_code=3, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
234
235
  ResultMetadata(name='XTIME', type_code=12, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True),
235
236
  ResultMetadata(name='XBINARY', type_code=11, display_size=None, internal_size=8388608, precision=None, scale=None, is_nullable=True),
236
- ResultMetadata(name='XARRAY', type_code=10, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
237
- ResultMetadata(name='XOBJECT', type_code=9, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
237
+ # TODO: handle ARRAY and OBJECT see https://github.com/tekumara/fakesnow/issues/26
238
+ # ResultMetadata(name='XARRAY', type_code=10, display_size=None, internal_size=16777216, precision=None, scale=None, is_nullable=True),
239
+ # ResultMetadata(name='XOBJECT', type_code=9, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
240
+ ResultMetadata(name='XVARIANT', type_code=5, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
238
241
  ]
239
242
  # fmt: on
240
243
 
@@ -247,6 +250,19 @@ def test_describe(cur: snowflake.connector.cursor.SnowflakeCursor):
247
250
  cur.execute("select * from example where XNUMBER = %s", (1,))
248
251
  assert cur.description == expected_metadata
249
252
 
253
+ # test semi-structured ops return variant ie: type_code=5
254
+ # fmt: off
255
+ assert (
256
+ cur.describe("SELECT ['A', 'B'][0] as array_index, OBJECT_CONSTRUCT('k','v1')['k'] as object_key, ARRAY_CONSTRUCT('foo')::VARIANT[0] as variant_key")
257
+ == [
258
+ # NB: snowflake returns internal_size = 16777216 for all columns
259
+ ResultMetadata(name="ARRAY_INDEX", type_code=5, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
260
+ ResultMetadata(name="OBJECT_KEY", type_code=5, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True),
261
+ ResultMetadata(name="VARIANT_KEY", type_code=5, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True)
262
+ ]
263
+ )
264
+ # fmt: on
265
+
250
266
 
251
267
  def test_describe_info_schema_columns(cur: snowflake.connector.cursor.SnowflakeCursor):
252
268
  # test we can handle the column types returned from the info schema, which are created by duckdb
@@ -420,7 +436,7 @@ def test_information_schema_columns_other(cur: snowflake.connector.cursor.Snowfl
420
436
  """
421
437
  create or replace table example (
422
438
  XTIMESTAMP TIMESTAMP, XTIMESTAMP_NTZ9 TIMESTAMP_NTZ(9), XDATE DATE, XTIME TIME,
423
- XBINARY BINARY, XARRAY ARRAY, XOBJECT OBJECT
439
+ XBINARY BINARY, /* XARRAY ARRAY, XOBJECT OBJECT */ XVARIANT VARIANT
424
440
  )
425
441
  """
426
442
  )
@@ -438,8 +454,10 @@ def test_information_schema_columns_other(cur: snowflake.connector.cursor.Snowfl
438
454
  ("XDATE", "DATE"),
439
455
  ("XTIME", "TIME"),
440
456
  ("XBINARY", "BINARY"),
441
- ("XARRAY", "ARRAY"),
442
- ("XOBJECT", "OBJECT"),
457
+ # TODO: support these types https://github.com/tekumara/fakesnow/issues/27
458
+ # ("XARRAY", "ARRAY"),
459
+ # ("XOBJECT", "OBJECT"),
460
+ ("XVARIANT", "VARIANT"),
443
461
  ]
444
462
 
445
463
 
@@ -547,18 +565,25 @@ def test_schema_drop(cur: snowflake.connector.cursor.SnowflakeCursor):
547
565
 
548
566
 
549
567
  def test_semi_structured_types(cur: snowflake.connector.cursor.SnowflakeCursor):
550
- cur.execute("create table semis (emails array, name object, notes variant)")
568
+ def indent(rows: Sequence[tuple]) -> list[tuple]:
569
+ # indent duckdb json strings to match snowflake json strings
570
+ return [(json.dumps(json.loads(r[0]), indent=2), *r[1:]) for r in rows]
571
+
572
+ cur.execute("create or replace table semis (emails array, name object, notes variant)")
551
573
  cur.execute(
552
- """insert into semis(emails, name, notes) SELECT [1, 2], parse_json('{"k": "v1"}'), parse_json('["foo"]')"""
574
+ """insert into semis(emails, name, notes) SELECT ['A', 'B'], OBJECT_CONSTRUCT('k','v1'), ARRAY_CONSTRUCT('foo')::VARIANT"""
553
575
  )
554
576
  cur.execute(
555
- """insert into semis(emails, name, notes) VALUES ([3,4], parse_json('{"k": "v2"}'), parse_json('{"b": "ar"}'))"""
577
+ """insert into semis(emails, name, notes) SELECT ['C','D'], parse_json('{"k": "v2"}'), parse_json('{"b": "ar"}')"""
556
578
  )
557
579
 
558
580
  # results are returned as strings, because the underlying type is JSON (duckdb) / VARIANT (snowflake)
559
581
 
582
+ cur.execute("select emails from semis")
583
+ assert indent(cur.fetchall()) == [('[\n "A",\n "B"\n]',), ('[\n "C",\n "D"\n]',)] # type: ignore
584
+
560
585
  cur.execute("select emails[0] from semis")
561
- assert cur.fetchall() == [("1",), ("3",)]
586
+ assert cur.fetchall() == [('"A"',), ('"C"',)]
562
587
 
563
588
  cur.execute("select name['k'] from semis")
564
589
  assert cur.fetchall() == [('"v1"',), ('"v2"',)]
@@ -728,12 +753,12 @@ def test_values(conn: snowflake.connector.SnowflakeConnection):
728
753
 
729
754
  def test_write_pandas(conn: snowflake.connector.SnowflakeConnection):
730
755
  with conn.cursor() as cur:
731
- cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
756
+ cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar, ORDERS array)")
732
757
 
733
758
  df = pd.DataFrame.from_records(
734
759
  [
735
- {"ID": 1, "FIRST_NAME": "Jenny", "LAST_NAME": "P"},
736
- {"ID": 2, "FIRST_NAME": "Jasper", "LAST_NAME": "M"},
760
+ {"ID": 1, "FIRST_NAME": "Jenny", "LAST_NAME": "P", "ORDERS": ["A", "B"]},
761
+ {"ID": 2, "FIRST_NAME": "Jasper", "LAST_NAME": "M", "ORDERS": ["C", "D"]},
737
762
  ]
738
763
  )
739
764
  snowflake.connector.pandas_tools.write_pandas(conn, df, "customers")
@@ -163,7 +163,7 @@ def test_semi_structured_types() -> None:
163
163
 
164
164
  assert (
165
165
  sqlglot.parse_one("CREATE TABLE table1 (name array)").transform(semi_structured_types).sql(dialect="duckdb")
166
- == "CREATE TABLE table1 (name JSON[])"
166
+ == "CREATE TABLE table1 (name JSON)"
167
167
  )
168
168
 
169
169
  assert (
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes