fakesnow 0.9.34__py3-none-any.whl → 0.9.35__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/converter.py CHANGED
@@ -4,7 +4,7 @@ import binascii
4
4
  import datetime
5
5
  from datetime import date, time, timezone
6
6
 
7
- # convert bindings provided as strings to the server into python types
7
+ # convert server bindings from strings into python types
8
8
 
9
9
 
10
10
  def from_binding(binding: dict[str, str]) -> int | bytes | bool | date | time | datetime.datetime | str:
fakesnow/cursor.py CHANGED
@@ -166,6 +166,9 @@ class FakeSnowflakeCursor:
166
166
  # strip highlight for better readability, TODO: show pointer to start of error
167
167
  msg = str(e).replace("\x1b[4m", "").replace("\x1b[0m", "")
168
168
  raise snowflake.connector.errors.ProgrammingError(msg=msg, errno=1003, sqlstate="42000") from None
169
+ except NotImplementedError as e:
170
+ msg = f"{e} not implemented. Please raise an issue via https://github.com/tekumara/fakesnow/issues/new"
171
+ raise snowflake.connector.errors.ProgrammingError(msg=msg, errno=9999, sqlstate="99999") from e
169
172
 
170
173
  def check_db_and_schema(self, expression: exp.Expression) -> None:
171
174
  no_database, no_schema = checks.is_unqualified_table_expression(expression)
@@ -238,7 +241,7 @@ class FakeSnowflakeCursor:
238
241
  .transform(transforms.show_procedures)
239
242
  .transform(transforms.show_warehouses)
240
243
  .transform(lambda e: transforms.show_schemas(e, self._conn.database))
241
- .transform(lambda e: transforms.show_objects_tables(e, self._conn.database))
244
+ .transform(lambda e: transforms.show_tables_etc(e, self._conn.database))
242
245
  .transform(lambda e: transforms.show_columns(e, self._conn.database))
243
246
  # TODO collapse into a single show_keys function
244
247
  .transform(lambda e: transforms.show_keys(e, self._conn.database, kind="PRIMARY"))
@@ -403,7 +406,7 @@ class FakeSnowflakeCursor:
403
406
  ) -> FakeSnowflakeCursor:
404
407
  if isinstance(seqparams, dict):
405
408
  # see https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-api
406
- raise NotImplementedError("dict params not supported yet")
409
+ raise NotImplementedError("executemany dict params")
407
410
 
408
411
  # TODO: support insert optimisations
409
412
  # the snowflake connector will optimise inserts into a single query
fakesnow/info_schema.py CHANGED
@@ -34,8 +34,8 @@ create table if not exists _fs_global._fs_information_schema._fs_columns_ext (
34
34
  """
35
35
 
36
36
  # replicates the output structure of https://docs.snowflake.com/en/sql-reference/sql/show-users
37
- SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_USERS_TABLE_EXT = """
38
- create table if not exists _fs_global._fs_information_schema._fs_users_ext (
37
+ SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_USERS_TABLE = """
38
+ create table if not exists _fs_global._fs_information_schema._fs_users (
39
39
  name varchar,
40
40
  created_on TIMESTAMPTZ,
41
41
  login_name varchar,
@@ -196,13 +196,13 @@ def per_db_creation_sql(catalog: str) -> str:
196
196
  """
197
197
 
198
198
 
199
- def fs_global_creation_sql(catalog: str) -> str:
199
+ def fs_global_creation_sql() -> str:
200
200
  return f"""
201
201
  {SQL_CREATE_GLOBAL_FS_INFORMATION_SCHEMA};
202
202
  {SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_TABLES_EXT};
203
203
  {SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_COLUMNS_EXT};
204
204
  {SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_COLUMNS_VIEW};
205
- {SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_USERS_TABLE_EXT};
205
+ {SQL_CREATE_GLOBAL_INFORMATION_SCHEMA_USERS_TABLE};
206
206
  """
207
207
 
208
208
 
fakesnow/instance.py CHANGED
@@ -7,6 +7,7 @@ import duckdb
7
7
 
8
8
  import fakesnow.fakes as fakes
9
9
  from fakesnow import info_schema
10
+ from fakesnow.transforms import show
10
11
 
11
12
  GLOBAL_DATABASE_NAME = "_fs_global"
12
13
 
@@ -28,8 +29,9 @@ class FakeSnow:
28
29
 
29
30
  # create a "global" database for storing objects which span databases.
30
31
  self.duck_conn.execute(f"ATTACH IF NOT EXISTS ':memory:' AS {GLOBAL_DATABASE_NAME}")
31
- # create the info schema extensions
32
- self.duck_conn.execute(info_schema.fs_global_creation_sql(GLOBAL_DATABASE_NAME))
32
+ # create the info schema extensions and show views
33
+ self.duck_conn.execute(info_schema.fs_global_creation_sql())
34
+ self.duck_conn.execute(show.fs_global_creation_sql())
33
35
 
34
36
  # use UTC instead of local time zone for consistent testing
35
37
  self.duck_conn.execute("SET GLOBAL TimeZone = 'UTC'")
fakesnow/server.py CHANGED
@@ -78,12 +78,12 @@ async def query_request(request: Request) -> JSONResponse:
78
78
 
79
79
  sql_text = body_json["sqlText"]
80
80
 
81
- params = None
82
-
83
81
  if bindings := body_json.get("bindings"):
84
- # Convert parameters like {'1': {'type': 'FIXED', 'value': '1'}, ...} to tuple (1, ...)
82
+ # Convert parameters like {'1': {'type': 'FIXED', 'value': '10'}, ...} to tuple (10, ...)
85
83
  params = tuple(from_binding(bindings[str(pos)]) for pos in range(1, len(bindings) + 1))
86
84
  logger.debug(f"Bindings: {params}")
85
+ else:
86
+ params = None
87
87
 
88
88
  try:
89
89
  # only a single sql statement is sent at a time by the python snowflake connector
@@ -121,6 +121,9 @@ async def query_request(request: Request) -> JSONResponse:
121
121
  return JSONResponse(
122
122
  {
123
123
  "data": {
124
+ "parameters": [
125
+ {"name": "TIMEZONE", "value": "Etc/UTC"},
126
+ ],
124
127
  "rowtype": rowtype,
125
128
  "rowsetBase64": rowset_b64,
126
129
  "total": cur._rowcount, # noqa: SLF001
@@ -13,9 +13,9 @@ from fakesnow.transforms.show import (
13
13
  show_databases as show_databases,
14
14
  show_functions as show_functions,
15
15
  show_keys as show_keys,
16
- show_objects_tables as show_objects_tables,
17
16
  show_procedures as show_procedures,
18
17
  show_schemas as show_schemas,
18
+ show_tables_etc as show_tables_etc,
19
19
  show_users as show_users,
20
20
  show_warehouses as show_warehouses,
21
21
  )
@@ -837,7 +837,7 @@ def regex_replace(expression: exp.Expression) -> exp.Expression:
837
837
  if len(expression.args) > 3:
838
838
  # see https://docs.snowflake.com/en/sql-reference/functions/regexp_replace
839
839
  raise NotImplementedError(
840
- "REGEXP_REPLACE with additional parameters (eg: <position>, <occurrence>, <parameters>) not supported"
840
+ "REGEXP_REPLACE with additional parameters (eg: <position>, <occurrence>, <parameters>)"
841
841
  )
842
842
 
843
843
  # pattern: snowflake requires escaping backslashes in single-quoted string constants, but duckdb doesn't
@@ -1316,9 +1316,9 @@ def create_user(expression: exp.Expression) -> exp.Expression:
1316
1316
  if sub_exp.upper().startswith("USER"):
1317
1317
  _, name, *ignored = sub_exp.split(" ")
1318
1318
  if ignored:
1319
- raise NotImplementedError(f"`CREATE USER` with {ignored} not yet supported")
1319
+ raise NotImplementedError(f"`CREATE USER` with {ignored}")
1320
1320
  return sqlglot.parse_one(
1321
- f"INSERT INTO _fs_global._fs_information_schema._fs_users_ext (name) VALUES ('{name}')", read="duckdb"
1321
+ f"INSERT INTO _fs_global._fs_information_schema._fs_users (name) VALUES ('{name}')", read="duckdb"
1322
1322
  )
1323
1323
 
1324
1324
  return expression
@@ -6,6 +6,14 @@ import sqlglot
6
6
  from sqlglot import exp
7
7
 
8
8
 
9
+ def fs_global_creation_sql() -> str:
10
+ return f"""
11
+ {SQL_CREATE_VIEW_SHOW_OBJECTS};
12
+ {SQL_CREATE_VIEW_SHOW_TABLES};
13
+ {SQL_CREATE_VIEW_SHOW_VIEWS};
14
+ """
15
+
16
+
9
17
  def show_columns(
10
18
  expression: exp.Expression, current_database: str | None = None, current_schema: str | None = None
11
19
  ) -> exp.Expression:
@@ -283,22 +291,99 @@ def show_keys(
283
291
 
284
292
  statement += f"AND table_name = '{table.name}' "
285
293
  else:
286
- raise NotImplementedError(f"SHOW PRIMARY KEYS with {scope_kind} not yet supported")
294
+ raise NotImplementedError(f"SHOW PRIMARY KEYS with {scope_kind}")
287
295
  return sqlglot.parse_one(statement)
288
296
  return expression
289
297
 
290
298
 
291
- def show_objects_tables(expression: exp.Expression, current_database: str | None = None) -> exp.Expression:
292
- """Transform SHOW OBJECTS/TABLES to a query against the information_schema.tables table.
299
+ # see https://docs.snowflake.com/en/sql-reference/sql/show-objects
300
+ SQL_CREATE_VIEW_SHOW_OBJECTS = """
301
+ create view if not exists _fs_global._fs_information_schema._fs_show_objects as
302
+ select
303
+ to_timestamp(0)::timestamptz as created_on,
304
+ table_name as name,
305
+ case when table_type='BASE TABLE' then 'TABLE' else table_type end as 'kind',
306
+ table_catalog as database_name,
307
+ table_schema as schema_name,
308
+ '' as comment,
309
+ '' as cluster_by,
310
+ -- TODO: implement rows and bytes as rows * 1024
311
+ 0 as rows,
312
+ 0 as bytes,
313
+ 'SYSADMIN' as owner,
314
+ 1 as retention_time,
315
+ 'ROLE' as owner_role_type,
316
+ null as budget,
317
+ 'N' as is_hybrid,
318
+ 'N' as is_dynamic
319
+ from information_schema.tables
320
+ where not (table_schema == '_fs_information_schema')
321
+ """
293
322
 
294
- See https://docs.snowflake.com/en/sql-reference/sql/show-objects
295
- https://docs.snowflake.com/en/sql-reference/sql/show-tables
296
- """
323
+ # see https://docs.snowflake.com/en/sql-reference/sql/show-tables
324
+ SQL_CREATE_VIEW_SHOW_TABLES = """
325
+ create view if not exists _fs_global._fs_information_schema._fs_show_tables as
326
+ select
327
+ to_timestamp(0)::timestamptz as created_on,
328
+ table_name as name,
329
+ 'TABLE' as kind,
330
+ table_catalog as database_name,
331
+ table_schema as schema_name,
332
+ '' as comment,
333
+ '' as cluster_by,
334
+ -- TODO: implement rows and bytes as rows * 1024
335
+ 0 as rows,
336
+ 0 as bytes,
337
+ 'SYSADMIN' as owner,
338
+ 1 as retention_time,
339
+ 'OFF' as automatic_clustering,
340
+ 'OFF' as change_tracking,
341
+ 'OFF' as search_optimization,
342
+ null as search_optimization_progress,
343
+ null as search_optimization_bytes,
344
+ 'N' as is_external,
345
+ 'N' as enable_schema_evolution,
346
+ 'ROLE' as owner_role_type,
347
+ 'N' as is_event,
348
+ null as budget,
349
+ 'N' as is_hybrid,
350
+ 'N' as is_iceberg,
351
+ 'N' as is_dynamic,
352
+ 'N' as is_immutable
353
+ from information_schema.tables
354
+ where not (table_schema == '_fs_information_schema')
355
+ and table_type = 'BASE TABLE'
356
+ """
357
+
358
+ # see https://docs.snowflake.com/en/sql-reference/sql/show-views
359
+ SQL_CREATE_VIEW_SHOW_VIEWS = """
360
+ create view if not exists _fs_global._fs_information_schema._fs_show_views as
361
+ select
362
+ to_timestamp(0)::timestamptz as created_on,
363
+ table_name as name,
364
+ '' as reserved,
365
+ table_catalog as database_name,
366
+ table_schema as schema_name,
367
+ 'SYSADMIN' as owner,
368
+ '' as comment,
369
+ view_definition as text,
370
+ false as is_secure,
371
+ false as is_materialized,
372
+ 'ROLE' as owner_role_type,
373
+ 'OFF' as change_tracking
374
+ from information_schema.views
375
+ where not table_catalog in ('system')
376
+ and not table_schema in ('main', '_fs_information_schema')
377
+ """
378
+
379
+
380
+ def show_tables_etc(expression: exp.Expression, current_database: str | None = None) -> exp.Expression:
381
+ """Transform SHOW OBJECTS/TABLES/VIEWS to a query against the _fs_information_schema views."""
297
382
  if not (
298
383
  isinstance(expression, exp.Show)
299
384
  and isinstance(expression.this, str)
300
385
  and (show := expression.this.upper())
301
- and show in {"OBJECTS", "TABLES"}
386
+ and show in {"OBJECTS", "TABLES", "VIEWS"}
302
387
  ):
303
388
  return expression
304
389
 
@@ -316,76 +401,28 @@ def show_objects_tables(expression: exp.Expression, current_database: str | None
316
401
  catalog = None
317
402
  schema = None
318
403
 
319
- columns = [
320
- "to_timestamp(0)::timestamptz as 'created_on'",
321
- "table_name as 'name'",
322
- "case when table_type='BASE TABLE' then 'TABLE' else table_type end as 'kind'",
323
- "table_catalog as 'database_name'",
324
- "table_schema as 'schema_name'",
325
- ]
326
- if not expression.args["terse"]:
327
- if show == "OBJECTS":
328
- columns.extend(
329
- [
330
- "'' as 'comment'",
331
- "'' as 'cluster_by'",
332
- # TODO: implement rows and bytes as rows * 1024
333
- "0 as 'rows'",
334
- "0 as 'bytes'",
335
- "'SYSADMIN' as 'owner'",
336
- "1 as 'retention_time'",
337
- "'ROLE' as 'owner_role_type'",
338
- "null as 'budget'",
339
- "'N' as 'is_hybrid'",
340
- "'N' as 'is_dynamic'",
341
- ]
342
- )
343
- else:
344
- # show == "TABLES"
345
- columns.extend(
346
- [
347
- "'' as 'comment'",
348
- "'' as 'cluster_by'",
349
- # TODO: implement rows and bytes as rows * 1024
350
- "0 as 'rows'",
351
- "0 as 'bytes'",
352
- "'SYSADMIN' as 'owner'",
353
- "1 as 'retention_time'",
354
- "'OFF' as 'automatic_clustering'",
355
- "'OFF' as 'change_tracking'",
356
- "'OFF' as 'search_optimization'",
357
- "null as 'search_optimization_progress'",
358
- "null as 'search_optimization_bytes'",
359
- "'N' as 'is_external'",
360
- "'N' as 'enable_schema_evolution'",
361
- "'ROLE' as 'owner_role_type'",
362
- "'N' as 'is_event'",
363
- "null as 'budget'",
364
- "'N' as 'is_hybrid'",
365
- "'N' as 'is_iceberg'",
366
- "'N' as 'is_dynamic'",
367
- "'N' as 'is_immutable'",
368
- ]
369
- )
370
-
404
+ if expression.args["terse"] and show == "VIEWS":
405
+ columns = ["created_on, name, 'VIEW' as kind, database_name, schema_name"]
406
+ elif expression.args["terse"]:
407
+ columns = ["created_on, name, kind, database_name, schema_name"]
408
+ else:
409
+ columns = ["*"]
371
410
  columns_clause = ", ".join(columns)
372
411
 
373
- where = ["not (table_schema == '_fs_information_schema')"] # exclude fakesnow's internal schemas
374
- if show == "TABLES":
375
- where.append("table_type = 'BASE TABLE'")
412
+ where = ["1=1"]
376
413
  if catalog:
377
- where.append(f"table_catalog = '{catalog}'")
414
+ where.append(f"database_name = '{catalog}'")
378
415
  if schema:
379
- where.append(f"table_schema = '{schema}'")
416
+ where.append(f"schema_name = '{schema}'")
380
417
  if (like := expression.args.get("like")) and isinstance(like, exp.Expression):
381
- where.append(f"table_name ilike {like.sql()}")
418
+ where.append(f"name ilike {like.sql()}")
382
419
  where_clause = " AND ".join(where)
383
420
 
384
421
  limit = limit.sql() if (limit := expression.args.get("limit")) and isinstance(limit, exp.Expression) else ""
385
422
 
386
423
  query = f"""
387
424
  SELECT {columns_clause}
388
- from information_schema.tables
425
+ from _fs_global._fs_information_schema._fs_show_{show.lower()}
389
426
  where {where_clause}
390
427
  {limit}
391
428
  """
@@ -471,6 +508,6 @@ def show_users(expression: exp.Expression) -> exp.Expression:
471
508
  https://docs.snowflake.com/en/sql-reference/sql/show-users
472
509
  """
473
510
  if isinstance(expression, exp.Show) and isinstance(expression.this, str) and expression.this.upper() == "USERS":
474
- return sqlglot.parse_one("SELECT * FROM _fs_global._fs_information_schema._fs_users_ext", read="duckdb")
511
+ return sqlglot.parse_one("SELECT * FROM _fs_global._fs_information_schema._fs_users", read="duckdb")
475
512
 
476
513
  return expression
fakesnow/variables.py CHANGED
@@ -45,7 +45,7 @@ class Variables:
45
45
  self._set(name, value)
46
46
  else:
47
47
  # Haven't been able to produce this in tests yet due to UNSET being parsed as an Alias expression.
48
- raise NotImplementedError("UNSET not supported yet")
48
+ raise NotImplementedError("UNSET")
49
49
  elif self._is_unset_expression(expr): # Unfortunately UNSET varname; is parsed as an Alias expression :(
50
50
  alias = expr.args.get("alias")
51
51
  assert alias, "UNSET without value in alias attribute is unexpected."
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fakesnow
3
- Version: 0.9.34
3
+ Version: 0.9.35
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
@@ -4,26 +4,26 @@ fakesnow/arrow.py,sha256=XjTpFyLrD9jULWOtPgpr0RyNMmO6a5yi82y6ivi2CCI,4884
4
4
  fakesnow/checks.py,sha256=be-xo0oMoAUVhlMDCu1_Rkoh_L8p_p8qo9P6reJSHIQ,2874
5
5
  fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
6
6
  fakesnow/conn.py,sha256=2WClMmUgfQkQA2hFQjfMP3R-85TbTbZh_8Y1tCdcerA,6053
7
- fakesnow/converter.py,sha256=7YlASaMomzchMZoorTH3KtVmgBakaHrF5fAl5VP747I,1635
8
- fakesnow/cursor.py,sha256=mK4nC1iucON1MohicTugJqUOfRsx5c8ToUJgnCfUSbs,21813
7
+ fakesnow/converter.py,sha256=xoBFnfBbGWQyUQAVr6zi-RyglU8A7A3GSlwLPkH1dzI,1621
8
+ fakesnow/cursor.py,sha256=e-nY-w25xmLs1jdhIbO6mwzBHVIUKYIkzR37Wnf3Cfc,22068
9
9
  fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
10
10
  fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
11
11
  fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
12
- fakesnow/info_schema.py,sha256=xDhGy07fpc8bcy_VTfh54UzwNIaB4ZhGmjgJeoiZ0hQ,8744
13
- fakesnow/instance.py,sha256=VsFbhVfy6EAJdEKykgavJwkMtrig01NehorptT51Jh8,2020
12
+ fakesnow/info_schema.py,sha256=AYmTIHxk5Y6xdMTgttgBL1V0VO8qiM2T1-gKwkLmWDs,8720
13
+ fakesnow/instance.py,sha256=OKoYXwaI6kL9HQpnHx44yzpON_xNfuIT_F4oJNF_XXQ,2114
14
14
  fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
15
15
  fakesnow/pandas_tools.py,sha256=wI203UQHC8JvDzxE_VjE1NeV4rThek2P-u52oTg2foo,3481
16
16
  fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
17
17
  fakesnow/rowtype.py,sha256=QUp8EaXD5LT0Xv8BXk5ze4WseEn52xoJ6R05pJjs5mM,2729
18
- fakesnow/server.py,sha256=4DgZUTd-G_usjSqy6NdUqd2fWUw2a-wHSSeJt3cdneA,6375
19
- fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
20
- fakesnow/transforms/__init__.py,sha256=xFrpw28DaHvMt6LGaRMsPqTo8PWogg10JgEu3oa6jdA,49515
18
+ fakesnow/server.py,sha256=6EMdNMxL0DgfMNXfFBqRFYmRThbuEVFFpa_1sCW-rZ4,6519
19
+ fakesnow/variables.py,sha256=C3y_9u7LuVtARkpcim3ihgVWg6KKdz1hSVeW4YI7oL4,3014
20
+ fakesnow/transforms/__init__.py,sha256=eJ_XH9pKO4Mxb7MKVgJuo6Wea-LYahF1dc_py5lPHAY,49471
21
21
  fakesnow/transforms/merge.py,sha256=Pg7_rwbAT_vr1U4ocBofUSyqaK8_e3qdIz_2SDm2S3s,8320
22
- fakesnow/transforms/show.py,sha256=2qfK3Fi0RLylqTnkwSVgv5JIorXYb1y0fnf5oErRZ2o,16839
23
- fakesnow-0.9.34.dist-info/licenses/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
22
+ fakesnow/transforms/show.py,sha256=0NjuLQjodrukfUw8mcxcAmtBkV_6r02mA3nuE3ad3rE,17458
23
+ fakesnow-0.9.35.dist-info/licenses/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
24
24
  tools/decode.py,sha256=kC5kUvLQxdCkMRsnH6BqCajlKxKeN77w6rwCKsY6gqU,1781
25
- fakesnow-0.9.34.dist-info/METADATA,sha256=Hqkb8CT1-QTNzQqRMVhEHWGwx3gJORv9YHy7wGSoBgQ,18128
26
- fakesnow-0.9.34.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
27
- fakesnow-0.9.34.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
28
- fakesnow-0.9.34.dist-info/top_level.txt,sha256=Yos7YveA3f03xVYuURqnBsfMV2DePXfu_yGcsj3pPzI,30
29
- fakesnow-0.9.34.dist-info/RECORD,,
25
+ fakesnow-0.9.35.dist-info/METADATA,sha256=D2aDpQUAIuSHEf6W5f1m84IG3BBDEvvMN5fU_FiWl3c,18128
26
+ fakesnow-0.9.35.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
27
+ fakesnow-0.9.35.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
28
+ fakesnow-0.9.35.dist-info/top_level.txt,sha256=Yos7YveA3f03xVYuURqnBsfMV2DePXfu_yGcsj3pPzI,30
29
+ fakesnow-0.9.35.dist-info/RECORD,,