fakesnow 0.9.6__py3-none-any.whl → 0.9.7__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 CHANGED
@@ -173,7 +173,7 @@ class FakeSnowflakeCursor:
173
173
  .transform(transforms.drop_schema_cascade)
174
174
  .transform(transforms.tag)
175
175
  .transform(transforms.semi_structured_types)
176
- .transform(transforms.parse_json)
176
+ .transform(transforms.try_parse_json)
177
177
  # indices_to_json_extract must be before regex_substr
178
178
  .transform(transforms.indices_to_json_extract)
179
179
  .transform(transforms.json_extract_cast_as_varchar)
@@ -185,6 +185,7 @@ class FakeSnowflakeCursor:
185
185
  .transform(transforms.values_columns)
186
186
  .transform(transforms.to_date)
187
187
  .transform(transforms.to_decimal)
188
+ .transform(transforms.try_to_decimal)
188
189
  .transform(transforms.to_timestamp_ntz)
189
190
  .transform(transforms.to_timestamp)
190
191
  .transform(transforms.object_construct)
@@ -196,6 +197,11 @@ class FakeSnowflakeCursor:
196
197
  .transform(transforms.array_size)
197
198
  .transform(transforms.random)
198
199
  .transform(transforms.identifier)
200
+ .transform(transforms.array_agg_within_group)
201
+ .transform(transforms.array_agg_to_json)
202
+ .transform(transforms.dateadd_date_cast)
203
+ .transform(transforms.dateadd_string_literal_timestamp_cast)
204
+ .transform(transforms.datediff_string_literal_timestamp_cast)
199
205
  .transform(lambda e: transforms.show_schemas(e, self._conn.database))
200
206
  .transform(lambda e: transforms.show_objects_tables(e, self._conn.database))
201
207
  # TODO collapse into a single show_keys function
@@ -204,6 +210,7 @@ class FakeSnowflakeCursor:
204
210
  .transform(lambda e: transforms.show_keys(e, self._conn.database, kind="FOREIGN"))
205
211
  .transform(transforms.show_users)
206
212
  .transform(transforms.create_user)
213
+ .transform(transforms.sha256)
207
214
  )
208
215
  sql = transformed.sql(dialect="duckdb")
209
216
  result_sql = None
@@ -617,7 +624,9 @@ class FakeSnowflakeConnection:
617
624
  # don't jsonify string
618
625
  df[col] = df[col].apply(lambda x: json.dumps(x) if isinstance(x, (dict, list)) else x)
619
626
 
620
- self._duck_conn.execute(f"INSERT INTO {table_name}({','.join(df.columns.to_list())}) SELECT * FROM df")
627
+ escaped_cols = ",".join(f'"{col}"' for col in df.columns.to_list())
628
+ self._duck_conn.execute(f"INSERT INTO {table_name}({escaped_cols}) SELECT * FROM df")
629
+
621
630
  return self._duck_conn.fetchall()[0][0]
622
631
 
623
632
 
fakesnow/transforms.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
4
  from string import Template
5
- from typing import Literal, cast
5
+ from typing import ClassVar, Literal, cast
6
6
 
7
7
  import sqlglot
8
8
  from sqlglot import exp
@@ -22,6 +22,39 @@ def array_size(expression: exp.Expression) -> exp.Expression:
22
22
  return expression
23
23
 
24
24
 
25
+ def array_agg_to_json(expression: exp.Expression) -> exp.Expression:
26
+ if isinstance(expression, exp.ArrayAgg):
27
+ return exp.Anonymous(this="TO_JSON", expressions=[expression])
28
+
29
+ return expression
30
+
31
+
32
+ def array_agg_within_group(expression: exp.Expression) -> exp.Expression:
33
+ """Convert ARRAY_AGG(<expr>) WITHIN GROUP (<order-by-clause>) to ARRAY_AGG( <expr> <order-by-clause> )
34
+ Snowflake uses ARRAY_AGG(<expr>) WITHIN GROUP (ORDER BY <order-by-clause>)
35
+ to order the array, but DuckDB uses ARRAY_AGG( <expr> <order-by-clause> ).
36
+ See;
37
+ - https://docs.snowflake.com/en/sql-reference/functions/array_agg
38
+ - https://duckdb.org/docs/sql/aggregates.html#order-by-clause-in-aggregate-functions
39
+ Note; Snowflake has following restriction;
40
+ If you specify DISTINCT and WITHIN GROUP, both must refer to the same column.
41
+ Transformation does not handle this restriction.
42
+ """
43
+ if (
44
+ isinstance(expression, exp.WithinGroup)
45
+ and (agg := expression.find(exp.ArrayAgg))
46
+ and (order := expression.expression)
47
+ ):
48
+ return exp.ArrayAgg(
49
+ this=exp.Order(
50
+ this=agg.this,
51
+ expressions=order.expressions,
52
+ )
53
+ )
54
+
55
+ return expression
56
+
57
+
25
58
  # TODO: move this into a Dialect as a transpilation
26
59
  def create_database(expression: exp.Expression, db_path: Path | None = None) -> exp.Expression:
27
60
  """Transform create database to attach database.
@@ -136,6 +169,98 @@ def drop_schema_cascade(expression: exp.Expression) -> exp.Expression:
136
169
  return new
137
170
 
138
171
 
172
+ def dateadd_date_cast(expression: exp.Expression) -> exp.Expression:
173
+ """Cast result of DATEADD to DATE if the given expression is a cast to DATE
174
+ and unit is either DAY, WEEK, MONTH or YEAR to mimic Snowflake's DATEADD
175
+ behaviour.
176
+
177
+ Snowflake;
178
+ SELECT DATEADD(DAY, 3, '2023-03-03'::DATE) as D;
179
+ D: 2023-03-06 (DATE)
180
+ DuckDB;
181
+ SELECT CAST('2023-03-03' AS DATE) + INTERVAL 3 DAY AS D
182
+ D: 2023-03-06 00:00:00 (TIMESTAMP)
183
+ """
184
+
185
+ if not isinstance(expression, exp.DateAdd):
186
+ return expression
187
+
188
+ if expression.unit is None:
189
+ return expression
190
+
191
+ if not isinstance(expression.unit.this, str):
192
+ return expression
193
+
194
+ if (unit := expression.unit.this.upper()) and unit.upper() not in {"DAY", "WEEK", "MONTH", "YEAR"}:
195
+ return expression
196
+
197
+ if not isinstance(expression.this, exp.Cast):
198
+ return expression
199
+
200
+ if expression.this.to.this != exp.DataType.Type.DATE:
201
+ return expression
202
+
203
+ return exp.Cast(
204
+ this=expression,
205
+ to=exp.DataType(this=exp.DataType.Type.DATE, nested=False, prefix=False),
206
+ )
207
+
208
+
209
+ def dateadd_string_literal_timestamp_cast(expression: exp.Expression) -> exp.Expression:
210
+ """Snowflake's DATEADD function implicitly casts string literals to
211
+ timestamps regardless of unit.
212
+ """
213
+ if not isinstance(expression, exp.DateAdd):
214
+ return expression
215
+
216
+ if not isinstance(expression.this, exp.Literal) or not expression.this.is_string:
217
+ return expression
218
+
219
+ new_dateadd = expression.copy()
220
+ new_dateadd.set(
221
+ "this",
222
+ exp.Cast(
223
+ this=expression.this,
224
+ # TODO: support TIMESTAMP_TYPE_MAPPING of TIMESTAMP_LTZ/TZ
225
+ to=exp.DataType(this=exp.DataType.Type.TIMESTAMP, nested=False, prefix=False),
226
+ ),
227
+ )
228
+
229
+ return new_dateadd
230
+
231
+
232
+ def datediff_string_literal_timestamp_cast(expression: exp.Expression) -> exp.Expression:
233
+ """Snowflake's DATEDIFF function implicitly casts string literals to
234
+ timestamps regardless of unit.
235
+ """
236
+
237
+ if not isinstance(expression, exp.DateDiff):
238
+ return expression
239
+
240
+ op1 = expression.this.copy()
241
+ op2 = expression.expression.copy()
242
+
243
+ if isinstance(op1, exp.Literal) and op1.is_string:
244
+ op1 = exp.Cast(
245
+ this=op1,
246
+ # TODO: support TIMESTAMP_TYPE_MAPPING of TIMESTAMP_LTZ/TZ
247
+ to=exp.DataType(this=exp.DataType.Type.TIMESTAMP, nested=False, prefix=False),
248
+ )
249
+
250
+ if isinstance(op2, exp.Literal) and op2.is_string:
251
+ op2 = exp.Cast(
252
+ this=op2,
253
+ # TODO: support TIMESTAMP_TYPE_MAPPING of TIMESTAMP_LTZ/TZ
254
+ to=exp.DataType(this=exp.DataType.Type.TIMESTAMP, nested=False, prefix=False),
255
+ )
256
+
257
+ new_datediff = expression.copy()
258
+ new_datediff.set("this", op1)
259
+ new_datediff.set("expression", op2)
260
+
261
+ return new_datediff
262
+
263
+
139
264
  def extract_comment_on_columns(expression: exp.Expression) -> exp.Expression:
140
265
  """Extract column comments, removing it from the Expression.
141
266
 
@@ -508,38 +633,26 @@ def object_construct(expression: exp.Expression) -> exp.Expression:
508
633
  """
509
634
 
510
635
  if isinstance(expression, exp.Struct):
511
- # remove expressions containing NULL
512
- for enull in expression.find_all(exp.Null):
513
- if enull.parent:
514
- enull.parent.pop()
515
-
516
- return exp.Anonymous(this="TO_JSON", expressions=[expression])
636
+ non_null_expressions = []
637
+ for e in expression.expressions:
638
+ if not (isinstance(e, exp.PropertyEQ)):
639
+ non_null_expressions.append(e)
640
+ continue
517
641
 
518
- return expression
642
+ left = e.left
643
+ right = e.right
519
644
 
645
+ left_is_null = isinstance(left, exp.Null)
646
+ right_is_null = isinstance(right, exp.Null)
520
647
 
521
- def parse_json(expression: exp.Expression) -> exp.Expression:
522
- """Convert parse_json() to json().
648
+ if left_is_null or right_is_null:
649
+ continue
523
650
 
524
- Example:
525
- >>> import sqlglot
526
- >>> sqlglot.parse_one("insert into table1 (name) select parse_json('{}')").transform(parse_json).sql()
527
- "CREATE TABLE table1 (name JSON)"
528
- Args:
529
- expression (exp.Expression): the expression that will be transformed.
651
+ non_null_expressions.append(e)
530
652
 
531
- Returns:
532
- exp.Expression: The transformed expression.
533
- """
534
-
535
- if (
536
- isinstance(expression, exp.Anonymous)
537
- and isinstance(expression.this, str)
538
- and expression.this.upper() == "PARSE_JSON"
539
- ):
540
- new = expression.copy()
541
- new.args["this"] = "JSON"
542
- return new
653
+ new_struct = expression.copy()
654
+ new_struct.set("expressions", non_null_expressions)
655
+ return exp.Anonymous(this="TO_JSON", expressions=[new_struct])
543
656
 
544
657
  return expression
545
658
 
@@ -829,30 +942,107 @@ def to_date(expression: exp.Expression) -> exp.Expression:
829
942
  return expression
830
943
 
831
944
 
945
+ def _get_to_number_args(e: exp.ToNumber) -> tuple[exp.Expression | None, exp.Expression | None, exp.Expression | None]:
946
+ arg_format = e.args.get("format")
947
+ arg_precision = e.args.get("precision")
948
+ arg_scale = e.args.get("scale")
949
+
950
+ _format = None
951
+ _precision = None
952
+ _scale = None
953
+
954
+ # to_number(value, <format>, <precision>, <scale>)
955
+ if arg_format:
956
+ if arg_format.is_string:
957
+ # to_number('100', 'TM9' ...)
958
+ _format = arg_format
959
+
960
+ # to_number('100', 'TM9', 10 ...)
961
+ if arg_precision:
962
+ _precision = arg_precision
963
+
964
+ # to_number('100', 'TM9', 10, 2)
965
+ if arg_scale:
966
+ _scale = arg_scale
967
+ else:
968
+ pass
969
+ else:
970
+ # to_number('100', 10, ...)
971
+ # arg_format is not a string, so it must be precision.
972
+ _precision = arg_format
973
+
974
+ # to_number('100', 10, 2)
975
+ # And arg_precision must be scale
976
+ if arg_precision:
977
+ _scale = arg_precision
978
+ else:
979
+ # If format is not provided, just check for precision and scale directly
980
+ if arg_precision:
981
+ _precision = arg_precision
982
+ if arg_scale:
983
+ _scale = arg_scale
984
+
985
+ return _format, _precision, _scale
986
+
987
+
988
+ def _to_decimal(expression: exp.Expression, cast_node: type[exp.Cast]) -> exp.Expression:
989
+ expressions: list[exp.Expression] = expression.expressions
990
+
991
+ if len(expressions) > 1 and expressions[1].is_string:
992
+ # see https://docs.snowflake.com/en/sql-reference/functions/to_decimal#arguments
993
+ raise NotImplementedError(f"{expression.this} with format argument")
994
+
995
+ precision = expressions[1] if len(expressions) > 1 else exp.Literal(this="38", is_string=False)
996
+ scale = expressions[2] if len(expressions) > 2 else exp.Literal(this="0", is_string=False)
997
+
998
+ return cast_node(
999
+ this=expressions[0],
1000
+ to=exp.DataType(this=exp.DataType.Type.DECIMAL, expressions=[precision, scale], nested=False, prefix=False),
1001
+ )
1002
+
1003
+
832
1004
  def to_decimal(expression: exp.Expression) -> exp.Expression:
833
1005
  """Transform to_decimal, to_number, to_numeric expressions from snowflake to duckdb.
834
1006
 
835
1007
  See https://docs.snowflake.com/en/sql-reference/functions/to_decimal
836
1008
  """
837
1009
 
1010
+ if isinstance(expression, exp.ToNumber):
1011
+ format_, precision, scale = _get_to_number_args(expression)
1012
+ if format_:
1013
+ raise NotImplementedError(f"{expression.this} with format argument")
1014
+
1015
+ if not precision:
1016
+ precision = exp.Literal(this="38", is_string=False)
1017
+ if not scale:
1018
+ scale = exp.Literal(this="0", is_string=False)
1019
+
1020
+ return exp.Cast(
1021
+ this=expression.this,
1022
+ to=exp.DataType(this=exp.DataType.Type.DECIMAL, expressions=[precision, scale], nested=False, prefix=False),
1023
+ )
1024
+
838
1025
  if (
839
1026
  isinstance(expression, exp.Anonymous)
840
1027
  and isinstance(expression.this, str)
841
- and expression.this.upper() in ["TO_DECIMAL", "TO_NUMBER", "TO_NUMERIC"]
1028
+ and expression.this.upper() in ["TO_DECIMAL", "TO_NUMERIC"]
842
1029
  ):
843
- expressions: list[exp.Expression] = expression.expressions
1030
+ return _to_decimal(expression, exp.Cast)
844
1031
 
845
- if len(expressions) > 1 and expressions[1].is_string:
846
- # see https://docs.snowflake.com/en/sql-reference/functions/to_decimal#arguments
847
- raise NotImplementedError(f"{expression.this} with format argument")
1032
+ return expression
848
1033
 
849
- precision = expressions[1] if len(expressions) > 1 else exp.Literal(this="38", is_string=False)
850
- scale = expressions[2] if len(expressions) > 2 else exp.Literal(this="0", is_string=False)
851
1034
 
852
- return exp.Cast(
853
- this=expressions[0],
854
- to=exp.DataType(this=exp.DataType.Type.DECIMAL, expressions=[precision, scale], nested=False, prefix=False),
855
- )
1035
+ def try_to_decimal(expression: exp.Expression) -> exp.Expression:
1036
+ """Transform try_to_decimal, try_to_number, try_to_numeric expressions from snowflake to duckdb.
1037
+ See https://docs.snowflake.com/en/sql-reference/functions/try_to_decimal
1038
+ """
1039
+
1040
+ if (
1041
+ isinstance(expression, exp.Anonymous)
1042
+ and isinstance(expression.this, str)
1043
+ and expression.this.upper() in ["TRY_TO_DECIMAL", "TRY_TO_NUMBER", "TRY_TO_NUMERIC"]
1044
+ ):
1045
+ return _to_decimal(expression, exp.TryCast)
856
1046
 
857
1047
  return expression
858
1048
 
@@ -905,6 +1095,34 @@ def timestamp_ntz_ns(expression: exp.Expression) -> exp.Expression:
905
1095
  return expression
906
1096
 
907
1097
 
1098
+ def try_parse_json(expression: exp.Expression) -> exp.Expression:
1099
+ """Convert TRY_PARSE_JSON() to TRY_CAST(... as JSON).
1100
+
1101
+ Example:
1102
+ >>> import sqlglot
1103
+ >>> sqlglot.parse_one("select try_parse_json('{}')").transform(parse_json).sql()
1104
+ "SELECT TRY_CAST('{}' AS JSON)"
1105
+ Args:
1106
+ expression (exp.Expression): the expression that will be transformed.
1107
+
1108
+ Returns:
1109
+ exp.Expression: The transformed expression.
1110
+ """
1111
+
1112
+ if (
1113
+ isinstance(expression, exp.Anonymous)
1114
+ and isinstance(expression.this, str)
1115
+ and expression.this.upper() == "TRY_PARSE_JSON"
1116
+ ):
1117
+ expressions = expression.expressions
1118
+ return exp.TryCast(
1119
+ this=expressions[0],
1120
+ to=exp.DataType(this=exp.DataType.Type.JSON, nested=False),
1121
+ )
1122
+
1123
+ return expression
1124
+
1125
+
908
1126
  # sqlglot.parse_one("create table example(date TIMESTAMP_NTZ(9));", read="snowflake")
909
1127
  def semi_structured_types(expression: exp.Expression) -> exp.Expression:
910
1128
  """Convert OBJECT, ARRAY, and VARIANT types to duckdb compatible types.
@@ -1087,3 +1305,48 @@ def show_keys(
1087
1305
  raise NotImplementedError(f"SHOW PRIMARY KEYS with {scope_kind} not yet supported")
1088
1306
  return sqlglot.parse_one(statement)
1089
1307
  return expression
1308
+
1309
+
1310
+ class SHA256(exp.Func):
1311
+ _sql_names: ClassVar = ["SHA256"]
1312
+ arg_types: ClassVar = {"this": True}
1313
+
1314
+
1315
+ def sha256(expression: exp.Expression) -> exp.Expression:
1316
+ """Convert sha2() or sha2_hex() to sha256().
1317
+
1318
+ Convert sha2_binary() to unhex(sha256()).
1319
+
1320
+ Example:
1321
+ >>> import sqlglot
1322
+ >>> sqlglot.parse_one("insert into table1 (name) select sha2('foo')").transform(sha256).sql()
1323
+ "INSERT INTO table1 (name) SELECT SHA256('foo')"
1324
+ Args:
1325
+ expression (exp.Expression): the expression that will be transformed.
1326
+
1327
+ Returns:
1328
+ exp.Expression: The transformed expression.
1329
+ """
1330
+
1331
+ if isinstance(expression, exp.SHA2) and expression.args.get("length", exp.Literal.number(256)).this == "256":
1332
+ return SHA256(this=expression.this)
1333
+ elif (
1334
+ isinstance(expression, exp.Anonymous)
1335
+ and expression.this.upper() == "SHA2_HEX"
1336
+ and (
1337
+ len(expression.expressions) == 1
1338
+ or (len(expression.expressions) == 2 and expression.expressions[1].this == "256")
1339
+ )
1340
+ ):
1341
+ return SHA256(this=expression.expressions[0])
1342
+ elif (
1343
+ isinstance(expression, exp.Anonymous)
1344
+ and expression.this.upper() == "SHA2_BINARY"
1345
+ and (
1346
+ len(expression.expressions) == 1
1347
+ or (len(expression.expressions) == 2 and expression.expressions[1].this == "256")
1348
+ )
1349
+ ):
1350
+ return exp.Unhex(this=SHA256(this=expression.expressions[0]))
1351
+
1352
+ return expression
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.6
3
+ Version: 0.9.7
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 ~=21.2.0
216
+ Requires-Dist: sqlglot ~=23.3.0
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build ~=1.0 ; extra == 'dev'
219
219
  Requires-Dist: pandas-stubs ; extra == 'dev'
@@ -233,8 +233,7 @@ Requires-Dist: jupysql ; extra == 'notebook'
233
233
  [![ci](https://github.com/tekumara/fakesnow/actions/workflows/ci.yml/badge.svg)](https://github.com/tekumara/fakesnow/actions/workflows/ci.yml)
234
234
  [![release](https://github.com/tekumara/fakesnow/actions/workflows/release.yml/badge.svg)](https://github.com/tekumara/fakesnow/actions/workflows/release.yml)
235
235
  [![PyPI](https://img.shields.io/pypi/v/fakesnow?color=violet)](https://pypi.org/project/fakesnow/)
236
-
237
- [![ci](../../actions/workflows/ci.yml/badge.svg)](../../actions/workflows/ci.yml)
236
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/fakesnow?color=violet)](https://pypi.org/project/fakesnow/)
238
237
 
239
238
  Fake [Snowflake Connector for Python](https://docs.snowflake.com/en/user-guide/python-connector). Run and mock Snowflake DB locally.
240
239
 
@@ -3,16 +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=5Fq_Qk-Iqxxzl-1XkcMyGDw2NY5hzE_50e5xii-jxhA,28463
6
+ fakesnow/fakes.py,sha256=3tTPaAC1vBaTLmSG92o51QA0AzIT9XDieYiZsMzvY9M,28929
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=4cGfNbcd-X1l0UEGFudjoRejTjxSwWo-E0NXGpdVlaE,40109
13
- fakesnow-0.9.6.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
14
- fakesnow-0.9.6.dist-info/METADATA,sha256=PBc3zlOgUpHFpxAJoTHnZaKcgZz8J6LqfVnP_yMytRw,17802
15
- fakesnow-0.9.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
- fakesnow-0.9.6.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
17
- fakesnow-0.9.6.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
18
- fakesnow-0.9.6.dist-info/RECORD,,
12
+ fakesnow/transforms.py,sha256=77hqWLWsZNvi6fLrn-JhIIeDy8CgiJ-zlNIAm8rQLf0,48818
13
+ fakesnow-0.9.7.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
14
+ fakesnow-0.9.7.dist-info/METADATA,sha256=ISDnq1yQPohGORq0isidKp11g_vWYt37rdtWz2vaoKE,17831
15
+ fakesnow-0.9.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
+ fakesnow-0.9.7.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
17
+ fakesnow-0.9.7.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
18
+ fakesnow-0.9.7.dist-info/RECORD,,