fakesnow 0.9.12__tar.gz → 0.9.13__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 (33) hide show
  1. {fakesnow-0.9.12 → fakesnow-0.9.13}/PKG-INFO +2 -2
  2. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/__init__.py +6 -1
  3. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/fakes.py +7 -2
  4. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/transforms.py +2 -4
  5. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow.egg-info/PKG-INFO +2 -2
  6. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow.egg-info/SOURCES.txt +2 -1
  7. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow.egg-info/requires.txt +1 -1
  8. {fakesnow-0.9.12 → fakesnow-0.9.13}/pyproject.toml +2 -2
  9. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_fakes.py +7 -175
  10. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_transforms.py +4 -0
  11. fakesnow-0.9.13/tests/test_write_pandas.py +164 -0
  12. {fakesnow-0.9.12 → fakesnow-0.9.13}/LICENSE +0 -0
  13. {fakesnow-0.9.12 → fakesnow-0.9.13}/README.md +0 -0
  14. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/__main__.py +0 -0
  15. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/checks.py +0 -0
  16. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/cli.py +0 -0
  17. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/expr.py +0 -0
  18. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/fixtures.py +0 -0
  19. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/global_database.py +0 -0
  20. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/info_schema.py +0 -0
  21. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/macros.py +0 -0
  22. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow/py.typed +0 -0
  23. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow.egg-info/dependency_links.txt +0 -0
  24. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow.egg-info/entry_points.txt +0 -0
  25. {fakesnow-0.9.12 → fakesnow-0.9.13}/fakesnow.egg-info/top_level.txt +0 -0
  26. {fakesnow-0.9.12 → fakesnow-0.9.13}/setup.cfg +0 -0
  27. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_checks.py +0 -0
  28. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_cli.py +0 -0
  29. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_expr.py +0 -0
  30. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_info_schema.py +0 -0
  31. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_patch.py +0 -0
  32. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_sqlalchemy.py +0 -0
  33. {fakesnow-0.9.12 → fakesnow-0.9.13}/tests/test_users.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.12
3
+ Version: 0.9.13
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
@@ -213,7 +213,7 @@ License-File: LICENSE
213
213
  Requires-Dist: duckdb~=0.10.0
214
214
  Requires-Dist: pyarrow
215
215
  Requires-Dist: snowflake-connector-python
216
- Requires-Dist: sqlglot~=23.12.2
216
+ Requires-Dist: sqlglot~=23.14.0
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build~=1.0; extra == "dev"
219
219
  Requires-Dist: pandas-stubs; extra == "dev"
@@ -21,6 +21,7 @@ def patch(
21
21
  create_database_on_connect: bool = True,
22
22
  create_schema_on_connect: bool = True,
23
23
  db_path: str | os.PathLike | None = None,
24
+ nop_regexes: list[str] | None = None,
24
25
  ) -> Iterator[None]:
25
26
  """Patch snowflake targets with fakes.
26
27
 
@@ -36,8 +37,11 @@ def patch(
36
37
 
37
38
  create_database_on_connect (bool, optional): Create database if provided in connection. Defaults to True.
38
39
  create_schema_on_connect (bool, optional): Create schema if provided in connection. Defaults to True.
39
- db_path (str | os.PathLike | None, optional): _description_. Use existing database files from this path
40
+ db_path (str | os.PathLike | None, optional): Use existing database files from this path
40
41
  or create them here if they don't already exist. If None databases are in-memory. Defaults to None.
42
+ nop_regexes (list[str] | None, optional): SQL statements matching these regexes (case-insensitive) will return
43
+ the success response without being run. Useful to skip over SQL commands that aren't implemented yet.
44
+ Defaults to None.
41
45
 
42
46
  Yields:
43
47
  Iterator[None]: None.
@@ -57,6 +61,7 @@ def patch(
57
61
  create_database=create_database_on_connect,
58
62
  create_schema=create_schema_on_connect,
59
63
  db_path=db_path,
64
+ nop_regexes=nop_regexes,
60
65
  **kwargs,
61
66
  ),
62
67
  snowflake.connector.pandas_tools.write_pandas: fakes.write_pandas,
@@ -135,8 +135,11 @@ class FakeSnowflakeCursor:
135
135
  print(f"{command};{params=}" if params else f"{command};", file=sys.stderr)
136
136
 
137
137
  command, params = self._rewrite_with_params(command, params)
138
- expression = parse_one(command, read="snowflake")
139
- transformed = self._transform(expression)
138
+ if self._conn.nop_regexes and any(re.match(p, command, re.IGNORECASE) for p in self._conn.nop_regexes):
139
+ transformed = transforms.SUCCESS_NOP
140
+ else:
141
+ expression = parse_one(command, read="snowflake")
142
+ transformed = self._transform(expression)
140
143
  return self._execute(transformed, params)
141
144
  except snowflake.connector.errors.ProgrammingError as e:
142
145
  self._sqlstate = e.sqlstate
@@ -502,6 +505,7 @@ class FakeSnowflakeConnection:
502
505
  create_database: bool = True,
503
506
  create_schema: bool = True,
504
507
  db_path: str | os.PathLike | None = None,
508
+ nop_regexes: list[str] | None = None,
505
509
  *args: Any,
506
510
  **kwargs: Any,
507
511
  ):
@@ -513,6 +517,7 @@ class FakeSnowflakeConnection:
513
517
  self.database_set = False
514
518
  self.schema_set = False
515
519
  self.db_path = db_path
520
+ self.nop_regexes = nop_regexes
516
521
  self._paramstyle = snowflake.connector.paramstyle
517
522
 
518
523
  create_global_database(duck_conn)
@@ -1122,12 +1122,10 @@ def timestamp_ntz_ns(expression: exp.Expression) -> exp.Expression:
1122
1122
 
1123
1123
  if (
1124
1124
  isinstance(expression, exp.DataType)
1125
- and expression.this == exp.DataType.Type.TIMESTAMP
1125
+ and expression.this == exp.DataType.Type.TIMESTAMPNTZ
1126
1126
  and exp.DataTypeParam(this=exp.Literal(this="9", is_string=False)) in expression.expressions
1127
1127
  ):
1128
- new = expression.copy()
1129
- del new.args["expressions"]
1130
- return new
1128
+ return exp.DataType(this=exp.DataType.Type.TIMESTAMP)
1131
1129
 
1132
1130
  return expression
1133
1131
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.12
3
+ Version: 0.9.13
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
@@ -213,7 +213,7 @@ License-File: LICENSE
213
213
  Requires-Dist: duckdb~=0.10.0
214
214
  Requires-Dist: pyarrow
215
215
  Requires-Dist: snowflake-connector-python
216
- Requires-Dist: sqlglot~=23.12.2
216
+ Requires-Dist: sqlglot~=23.14.0
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build~=1.0; extra == "dev"
219
219
  Requires-Dist: pandas-stubs; extra == "dev"
@@ -27,4 +27,5 @@ tests/test_info_schema.py
27
27
  tests/test_patch.py
28
28
  tests/test_sqlalchemy.py
29
29
  tests/test_transforms.py
30
- tests/test_users.py
30
+ tests/test_users.py
31
+ tests/test_write_pandas.py
@@ -1,7 +1,7 @@
1
1
  duckdb~=0.10.0
2
2
  pyarrow
3
3
  snowflake-connector-python
4
- sqlglot~=23.12.2
4
+ sqlglot~=23.14.0
5
5
 
6
6
  [dev]
7
7
  build~=1.0
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "fakesnow"
3
3
  description = "Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally."
4
- version = "0.9.12"
4
+ version = "0.9.13"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
7
  classifiers = ["License :: OSI Approved :: MIT License"]
@@ -11,7 +11,7 @@ dependencies = [
11
11
  "duckdb~=0.10.0",
12
12
  "pyarrow",
13
13
  "snowflake-connector-python",
14
- "sqlglot~=23.12.2",
14
+ "sqlglot~=23.14.0",
15
15
  ]
16
16
 
17
17
  [project.urls]
@@ -5,9 +5,7 @@ from __future__ import annotations
5
5
  import datetime
6
6
  import json
7
7
  import tempfile
8
- from collections.abc import Sequence
9
8
  from decimal import Decimal
10
- from typing import cast
11
9
 
12
10
  import pandas as pd
13
11
  import pytest
@@ -19,6 +17,7 @@ from pandas.testing import assert_frame_equal
19
17
  from snowflake.connector.cursor import ResultMetadata
20
18
 
21
19
  import fakesnow
20
+ from tests.utils import dindent, indent
22
21
 
23
22
 
24
23
  def test_alter_table(cur: snowflake.connector.cursor.SnowflakeCursor):
@@ -871,6 +870,12 @@ def test_identifier(cur: snowflake.connector.cursor.SnowflakeCursor):
871
870
  assert cur.fetchall() == [(1,)]
872
871
 
873
872
 
873
+ def test_nop_regexes():
874
+ with fakesnow.patch(nop_regexes=["^CALL.*"]), snowflake.connector.connect() as conn, conn.cursor() as cur:
875
+ cur.execute("call this_procedure_does_not_exist('foo', 'bar);")
876
+ assert cur.fetchall() == [("Statement executed successfully.",)]
877
+
878
+
874
879
  def test_non_existent_table_throws_snowflake_exception(cur: snowflake.connector.cursor.SnowflakeCursor):
875
880
  with pytest.raises(snowflake.connector.errors.ProgrammingError) as _:
876
881
  cur.execute("select * from this_table_does_not_exist")
@@ -1500,176 +1505,3 @@ def test_json_extract_cast_as_varchar(dcur: snowflake.connector.cursor.DictCurso
1500
1505
 
1501
1506
  dcur.execute("SELECT j:str::number as c_str_number, j:number::number as c_num_number FROM example")
1502
1507
  assert dcur.fetchall() == [{"C_STR_NUMBER": 100, "C_NUM_NUMBER": 100}]
1503
-
1504
-
1505
- def test_write_pandas_auto_create(conn: snowflake.connector.SnowflakeConnection):
1506
- with conn.cursor() as cur:
1507
- df = pd.DataFrame.from_records(
1508
- [
1509
- {"ID": 1, "FIRST_NAME": "Jenny"},
1510
- {"ID": 2, "FIRST_NAME": "Jasper"},
1511
- ]
1512
- )
1513
- snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS", auto_create_table=True)
1514
-
1515
- cur.execute("select id, first_name from customers")
1516
-
1517
- assert cur.fetchall() == [(1, "Jenny"), (2, "Jasper")]
1518
-
1519
-
1520
- def test_write_pandas_quoted_column_names(conn: snowflake.connector.SnowflakeConnection):
1521
- with conn.cursor(snowflake.connector.cursor.DictCursor) as dcur:
1522
- # colunmn names with spaces
1523
- dcur.execute('create table customers (id int, "first name" varchar)')
1524
- df = pd.DataFrame.from_records(
1525
- [
1526
- {"ID": 1, "first name": "Jenny"},
1527
- {"ID": 2, "first name": "Jasper"},
1528
- ]
1529
- )
1530
- snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
1531
-
1532
- dcur.execute("select * from customers")
1533
-
1534
- assert dcur.fetchall() == [
1535
- {"ID": 1, "first name": "Jenny"},
1536
- {"ID": 2, "first name": "Jasper"},
1537
- ]
1538
-
1539
-
1540
- def test_write_pandas_array(conn: snowflake.connector.SnowflakeConnection):
1541
- with conn.cursor() as cur:
1542
- cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar, ORDERS array)")
1543
-
1544
- df = pd.DataFrame.from_records(
1545
- [
1546
- {"ID": 1, "FIRST_NAME": "Jenny", "LAST_NAME": "P", "ORDERS": ["A", "B"]},
1547
- {"ID": 2, "FIRST_NAME": "Jasper", "LAST_NAME": "M", "ORDERS": ["C", "D"]},
1548
- ]
1549
- )
1550
- snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
1551
-
1552
- cur.execute("select * from customers")
1553
-
1554
- assert indent(cur.fetchall()) == [
1555
- (1, "Jenny", "P", '[\n "A",\n "B"\n]'),
1556
- (2, "Jasper", "M", '[\n "C",\n "D"\n]'),
1557
- ]
1558
-
1559
-
1560
- def test_write_pandas_timestamp_ntz(conn: snowflake.connector.SnowflakeConnection):
1561
- # compensate for https://github.com/duckdb/duckdb/issues/7980
1562
- with conn.cursor() as cur:
1563
- cur.execute("create table example (UPDATE_AT_NTZ timestamp_ntz(9))")
1564
- # cur.execute("create table example (UPDATE_AT_NTZ timestamp)")
1565
-
1566
- now_utc = datetime.datetime.now(pytz.utc)
1567
- df = pd.DataFrame([(now_utc,)], columns=["UPDATE_AT_NTZ"])
1568
- snowflake.connector.pandas_tools.write_pandas(conn, df, "EXAMPLE")
1569
-
1570
- cur.execute("select * from example")
1571
-
1572
- assert cur.fetchall() == [(now_utc.replace(tzinfo=None),)]
1573
-
1574
-
1575
- def test_write_pandas_partial_columns(conn: snowflake.connector.SnowflakeConnection):
1576
- with conn.cursor() as cur:
1577
- cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
1578
-
1579
- df = pd.DataFrame.from_records(
1580
- [
1581
- {"ID": 1, "FIRST_NAME": "Jenny"},
1582
- {"ID": 2, "FIRST_NAME": "Jasper"},
1583
- ]
1584
- )
1585
- snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
1586
-
1587
- cur.execute("select id, first_name, last_name from customers")
1588
-
1589
- # columns not in dataframe will receive their default value
1590
- assert cur.fetchall() == [(1, "Jenny", None), (2, "Jasper", None)]
1591
-
1592
-
1593
- def test_write_pandas_dict_as_varchar(conn: snowflake.connector.SnowflakeConnection):
1594
- with conn.cursor() as cur:
1595
- cur.execute("create or replace table example (vc varchar, o object)")
1596
-
1597
- df = pd.DataFrame([({"kind": "vc", "count": 1}, {"kind": "obj", "amount": 2})], columns=["VC", "O"])
1598
- snowflake.connector.pandas_tools.write_pandas(conn, df, "EXAMPLE")
1599
-
1600
- cur.execute("select * from example")
1601
-
1602
- # returned values are valid json strings
1603
- # NB: snowflake orders object keys alphabetically, we don't
1604
- r = cur.fetchall()
1605
- assert [(sort_keys(r[0][0], indent=None), sort_keys(r[0][1], indent=2))] == [
1606
- ('{"count":1,"kind":"vc"}', '{\n "amount": 2,\n "kind": "obj"\n}')
1607
- ]
1608
-
1609
-
1610
- def test_write_pandas_dict_different_keys(conn: snowflake.connector.SnowflakeConnection):
1611
- with conn.cursor() as cur:
1612
- cur.execute("create or replace table customers (notes variant)")
1613
-
1614
- df = pd.DataFrame.from_records(
1615
- [
1616
- # rows have dicts with unique keys and values
1617
- {"NOTES": {"k": "v1"}},
1618
- # test single and double quoting
1619
- {"NOTES": {"k2": ["v'2", 'v"3']}},
1620
- ]
1621
- )
1622
- snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
1623
-
1624
- cur.execute("select * from customers")
1625
-
1626
- assert indent(cur.fetchall()) == [('{\n "k": "v1"\n}',), ('{\n "k2": [\n "v\'2",\n "v\\"3"\n ]\n}',)]
1627
-
1628
-
1629
- def test_write_pandas_db_schema(conn: snowflake.connector.SnowflakeConnection):
1630
- with conn.cursor() as cur:
1631
- cur.execute("create database db2")
1632
- cur.execute("create schema db2.schema2")
1633
- cur.execute("create or replace table db2.schema2.customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
1634
-
1635
- df = pd.DataFrame.from_records(
1636
- [
1637
- {"ID": 1, "FIRST_NAME": "Jenny"},
1638
- {"ID": 2, "FIRST_NAME": "Jasper"},
1639
- ]
1640
- )
1641
- snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS", "DB2", "SCHEMA2")
1642
-
1643
- cur.execute("select id, first_name, last_name from db2.schema2.customers")
1644
-
1645
- # columns not in dataframe will receive their default value
1646
- assert cur.fetchall() == [(1, "Jenny", None), (2, "Jasper", None)]
1647
-
1648
-
1649
- def indent(rows: Sequence[tuple] | Sequence[dict]) -> list[tuple]:
1650
- # indent duckdb json strings tuple values to match snowflake json strings
1651
- assert isinstance(rows[0], tuple)
1652
- return [
1653
- (*[json.dumps(json.loads(c), indent=2) if (isinstance(c, str) and c.startswith(("[", "{"))) else c for c in r],)
1654
- for r in rows
1655
- ]
1656
-
1657
-
1658
- def dindent(rows: Sequence[tuple] | Sequence[dict]) -> list[dict]:
1659
- # indent duckdb json strings dict values to match snowflake json strings
1660
- assert isinstance(rows[0], dict)
1661
- return [
1662
- {
1663
- k: json.dumps(json.loads(v), indent=2) if (isinstance(v, str) and v.startswith(("[", "{"))) else v
1664
- for k, v in cast(dict, r).items()
1665
- }
1666
- for r in rows
1667
- ]
1668
-
1669
-
1670
- def sort_keys(sdict: str, indent: int | None = 2) -> str:
1671
- return json.dumps(
1672
- json.loads(sdict, object_pairs_hook=lambda x: dict(sorted(x))),
1673
- indent=indent,
1674
- separators=None if indent else (",", ":"),
1675
- )
@@ -594,6 +594,10 @@ def test_show_schemas() -> None:
594
594
 
595
595
  def test_tag() -> None:
596
596
  assert sqlglot.parse_one("ALTER TABLE table1 SET TAG foo='bar'", read="snowflake").transform(tag) == SUCCESS_NOP
597
+ assert (
598
+ sqlglot.parse_one("ALTER TABLE db1.schema1.table1 SET TAG foo.bar='baz'", read="snowflake").transform(tag)
599
+ == SUCCESS_NOP
600
+ )
597
601
 
598
602
 
599
603
  def test_timestamp_ntz_ns() -> None:
@@ -0,0 +1,164 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import json
5
+
6
+ import pandas as pd
7
+ import pytz
8
+ import snowflake.connector
9
+ import snowflake.connector.cursor
10
+ import snowflake.connector.pandas_tools
11
+
12
+ from tests.utils import indent
13
+
14
+
15
+ def test_write_pandas_auto_create(conn: snowflake.connector.SnowflakeConnection):
16
+ with conn.cursor() as cur:
17
+ df = pd.DataFrame.from_records(
18
+ [
19
+ {"ID": 1, "FIRST_NAME": "Jenny"},
20
+ {"ID": 2, "FIRST_NAME": "Jasper"},
21
+ ]
22
+ )
23
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS", auto_create_table=True)
24
+
25
+ cur.execute("select id, first_name from customers")
26
+
27
+ assert cur.fetchall() == [(1, "Jenny"), (2, "Jasper")]
28
+
29
+
30
+ def test_write_pandas_quoted_column_names(conn: snowflake.connector.SnowflakeConnection):
31
+ with conn.cursor(snowflake.connector.cursor.DictCursor) as dcur:
32
+ # colunmn names with spaces
33
+ dcur.execute('create table customers (id int, "first name" varchar)')
34
+ df = pd.DataFrame.from_records(
35
+ [
36
+ {"ID": 1, "first name": "Jenny"},
37
+ {"ID": 2, "first name": "Jasper"},
38
+ ]
39
+ )
40
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
41
+
42
+ dcur.execute("select * from customers")
43
+
44
+ assert dcur.fetchall() == [
45
+ {"ID": 1, "first name": "Jenny"},
46
+ {"ID": 2, "first name": "Jasper"},
47
+ ]
48
+
49
+
50
+ def test_write_pandas_array(conn: snowflake.connector.SnowflakeConnection):
51
+ with conn.cursor() as cur:
52
+ cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar, ORDERS array)")
53
+
54
+ df = pd.DataFrame.from_records(
55
+ [
56
+ {"ID": 1, "FIRST_NAME": "Jenny", "LAST_NAME": "P", "ORDERS": ["A", "B"]},
57
+ {"ID": 2, "FIRST_NAME": "Jasper", "LAST_NAME": "M", "ORDERS": ["C", "D"]},
58
+ ]
59
+ )
60
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
61
+
62
+ cur.execute("select * from customers")
63
+
64
+ assert indent(cur.fetchall()) == [
65
+ (1, "Jenny", "P", '[\n "A",\n "B"\n]'),
66
+ (2, "Jasper", "M", '[\n "C",\n "D"\n]'),
67
+ ]
68
+
69
+
70
+ def test_write_pandas_timestamp_ntz(conn: snowflake.connector.SnowflakeConnection):
71
+ # compensate for https://github.com/duckdb/duckdb/issues/7980
72
+ with conn.cursor() as cur:
73
+ cur.execute("create table example (UPDATE_AT_NTZ timestamp_ntz(9))")
74
+ # cur.execute("create table example (UPDATE_AT_NTZ timestamp)")
75
+
76
+ now_utc = datetime.datetime.now(pytz.utc)
77
+ df = pd.DataFrame([(now_utc,)], columns=["UPDATE_AT_NTZ"])
78
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "EXAMPLE")
79
+
80
+ cur.execute("select * from example")
81
+
82
+ assert cur.fetchall() == [(now_utc.replace(tzinfo=None),)]
83
+
84
+
85
+ def test_write_pandas_partial_columns(conn: snowflake.connector.SnowflakeConnection):
86
+ with conn.cursor() as cur:
87
+ cur.execute("create table customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
88
+
89
+ df = pd.DataFrame.from_records(
90
+ [
91
+ {"ID": 1, "FIRST_NAME": "Jenny"},
92
+ {"ID": 2, "FIRST_NAME": "Jasper"},
93
+ ]
94
+ )
95
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
96
+
97
+ cur.execute("select id, first_name, last_name from customers")
98
+
99
+ # columns not in dataframe will receive their default value
100
+ assert cur.fetchall() == [(1, "Jenny", None), (2, "Jasper", None)]
101
+
102
+
103
+ def test_write_pandas_dict_as_varchar(conn: snowflake.connector.SnowflakeConnection):
104
+ with conn.cursor() as cur:
105
+ cur.execute("create or replace table example (vc varchar, o object)")
106
+
107
+ df = pd.DataFrame([({"kind": "vc", "count": 1}, {"kind": "obj", "amount": 2})], columns=["VC", "O"])
108
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "EXAMPLE")
109
+
110
+ cur.execute("select * from example")
111
+
112
+ # returned values are valid json strings
113
+ # NB: snowflake orders object keys alphabetically, we don't
114
+ r = cur.fetchall()
115
+ assert [(sort_keys(r[0][0], indent=None), sort_keys(r[0][1], indent=2))] == [
116
+ ('{"count":1,"kind":"vc"}', '{\n "amount": 2,\n "kind": "obj"\n}')
117
+ ]
118
+
119
+
120
+ def test_write_pandas_dict_different_keys(conn: snowflake.connector.SnowflakeConnection):
121
+ with conn.cursor() as cur:
122
+ cur.execute("create or replace table customers (notes variant)")
123
+
124
+ df = pd.DataFrame.from_records(
125
+ [
126
+ # rows have dicts with unique keys and values
127
+ {"NOTES": {"k": "v1"}},
128
+ # test single and double quoting
129
+ {"NOTES": {"k2": ["v'2", 'v"3']}},
130
+ ]
131
+ )
132
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS")
133
+
134
+ cur.execute("select * from customers")
135
+
136
+ assert indent(cur.fetchall()) == [('{\n "k": "v1"\n}',), ('{\n "k2": [\n "v\'2",\n "v\\"3"\n ]\n}',)]
137
+
138
+
139
+ def test_write_pandas_db_schema(conn: snowflake.connector.SnowflakeConnection):
140
+ with conn.cursor() as cur:
141
+ cur.execute("create database db2")
142
+ cur.execute("create schema db2.schema2")
143
+ cur.execute("create or replace table db2.schema2.customers (ID int, FIRST_NAME varchar, LAST_NAME varchar)")
144
+
145
+ df = pd.DataFrame.from_records(
146
+ [
147
+ {"ID": 1, "FIRST_NAME": "Jenny"},
148
+ {"ID": 2, "FIRST_NAME": "Jasper"},
149
+ ]
150
+ )
151
+ snowflake.connector.pandas_tools.write_pandas(conn, df, "CUSTOMERS", "DB2", "SCHEMA2")
152
+
153
+ cur.execute("select id, first_name, last_name from db2.schema2.customers")
154
+
155
+ # columns not in dataframe will receive their default value
156
+ assert cur.fetchall() == [(1, "Jenny", None), (2, "Jasper", None)]
157
+
158
+
159
+ def sort_keys(sdict: str, indent: int | None = 2) -> str:
160
+ return json.dumps(
161
+ json.loads(sdict, object_pairs_hook=lambda x: dict(sorted(x))),
162
+ indent=indent,
163
+ separators=None if indent else (",", ":"),
164
+ )
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