fakesnow 0.9.21__py3-none-any.whl → 0.9.23__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/arrow.py +38 -3
- fakesnow/conn.py +175 -0
- fakesnow/cursor.py +463 -0
- fakesnow/fakes.py +3 -747
- fakesnow/info_schema.py +1 -0
- fakesnow/pandas_tools.py +77 -0
- fakesnow/server.py +6 -11
- fakesnow/transforms.py +30 -7
- fakesnow/types.py +89 -0
- {fakesnow-0.9.21.dist-info → fakesnow-0.9.23.dist-info}/METADATA +19 -18
- fakesnow-0.9.23.dist-info/RECORD +25 -0
- {fakesnow-0.9.21.dist-info → fakesnow-0.9.23.dist-info}/WHEEL +1 -1
- fakesnow-0.9.21.dist-info/RECORD +0 -21
- {fakesnow-0.9.21.dist-info → fakesnow-0.9.23.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.21.dist-info → fakesnow-0.9.23.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.21.dist-info → fakesnow-0.9.23.dist-info}/top_level.txt +0 -0
fakesnow/info_schema.py
CHANGED
fakesnow/pandas_tools.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Sequence
|
4
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
import pandas as pd
|
10
|
+
|
11
|
+
from fakesnow.conn import FakeSnowflakeConnection
|
12
|
+
|
13
|
+
CopyResult = tuple[
|
14
|
+
str,
|
15
|
+
str,
|
16
|
+
int,
|
17
|
+
int,
|
18
|
+
int,
|
19
|
+
int,
|
20
|
+
Optional[str],
|
21
|
+
Optional[int],
|
22
|
+
Optional[int],
|
23
|
+
Optional[str],
|
24
|
+
]
|
25
|
+
|
26
|
+
WritePandasResult = tuple[
|
27
|
+
bool,
|
28
|
+
int,
|
29
|
+
int,
|
30
|
+
Sequence[CopyResult],
|
31
|
+
]
|
32
|
+
|
33
|
+
|
34
|
+
def sql_type(dtype: np.dtype) -> str:
|
35
|
+
if str(dtype) == "int64":
|
36
|
+
return "NUMBER"
|
37
|
+
elif str(dtype) == "object":
|
38
|
+
return "VARCHAR"
|
39
|
+
else:
|
40
|
+
raise NotImplementedError(f"sql_type {dtype=}")
|
41
|
+
|
42
|
+
|
43
|
+
def write_pandas(
|
44
|
+
conn: FakeSnowflakeConnection,
|
45
|
+
df: pd.DataFrame,
|
46
|
+
table_name: str,
|
47
|
+
database: str | None = None,
|
48
|
+
schema: str | None = None,
|
49
|
+
chunk_size: int | None = None,
|
50
|
+
compression: str = "gzip",
|
51
|
+
on_error: str = "abort_statement",
|
52
|
+
parallel: int = 4,
|
53
|
+
quote_identifiers: bool = True,
|
54
|
+
auto_create_table: bool = False,
|
55
|
+
create_temp_table: bool = False,
|
56
|
+
overwrite: bool = False,
|
57
|
+
table_type: Literal["", "temp", "temporary", "transient"] = "",
|
58
|
+
**kwargs: Any,
|
59
|
+
) -> WritePandasResult:
|
60
|
+
name = table_name
|
61
|
+
if schema:
|
62
|
+
name = f"{schema}.{name}"
|
63
|
+
if database:
|
64
|
+
name = f"{database}.{name}"
|
65
|
+
|
66
|
+
if auto_create_table:
|
67
|
+
cols = [f"{c} {sql_type(t)}" for c, t in df.dtypes.to_dict().items()]
|
68
|
+
|
69
|
+
conn.cursor().execute(f"CREATE TABLE IF NOT EXISTS {name} ({','.join(cols)})")
|
70
|
+
|
71
|
+
count = conn._insert_df(df, name) # noqa: SLF001
|
72
|
+
|
73
|
+
# mocks https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#output
|
74
|
+
mock_copy_results = [("fakesnow/file0.txt", "LOADED", count, count, 1, 0, None, None, None, None)]
|
75
|
+
|
76
|
+
# return success
|
77
|
+
return (True, len(mock_copy_results), count, mock_copy_results)
|
fakesnow/server.py
CHANGED
@@ -12,7 +12,7 @@ from starlette.requests import Request
|
|
12
12
|
from starlette.responses import JSONResponse
|
13
13
|
from starlette.routing import Route
|
14
14
|
|
15
|
-
from fakesnow.arrow import to_ipc
|
15
|
+
from fakesnow.arrow import to_ipc, to_rowtype, with_sf_metadata
|
16
16
|
from fakesnow.fakes import FakeSnowflakeConnection
|
17
17
|
from fakesnow.instance import FakeSnow
|
18
18
|
|
@@ -52,19 +52,13 @@ async def query_request(request: Request) -> JSONResponse:
|
|
52
52
|
batch_bytes = to_ipc(cur._arrow_table) # noqa: SLF001
|
53
53
|
rowset_b64 = b64encode(batch_bytes).decode("utf-8")
|
54
54
|
|
55
|
+
# TODO: avoid calling with_sf_metadata twice
|
56
|
+
rowtype = to_rowtype(with_sf_metadata(cur._arrow_table.schema)) # noqa: SLF001
|
57
|
+
|
55
58
|
return JSONResponse(
|
56
59
|
{
|
57
60
|
"data": {
|
58
|
-
"rowtype":
|
59
|
-
{
|
60
|
-
"name": "'HELLO WORLD'",
|
61
|
-
"nullable": False,
|
62
|
-
"type": "text",
|
63
|
-
"length": 11,
|
64
|
-
"scale": None,
|
65
|
-
"precision": None,
|
66
|
-
}
|
67
|
-
],
|
61
|
+
"rowtype": rowtype,
|
68
62
|
"rowsetBase64": rowset_b64,
|
69
63
|
"total": 1,
|
70
64
|
"queryResultFormat": "arrow",
|
@@ -103,6 +97,7 @@ routes = [
|
|
103
97
|
query_request,
|
104
98
|
methods=["POST"],
|
105
99
|
),
|
100
|
+
Route("/queries/v1/abort-request", lambda _: JSONResponse({"success": True}), methods=["POST"]),
|
106
101
|
]
|
107
102
|
|
108
103
|
app = Starlette(debug=True, routes=routes)
|
fakesnow/transforms.py
CHANGED
@@ -159,22 +159,41 @@ SELECT
|
|
159
159
|
column_default AS "default",
|
160
160
|
'N' AS "primary key",
|
161
161
|
'N' AS "unique key",
|
162
|
-
NULL AS "check",
|
163
|
-
NULL AS "expression",
|
164
|
-
NULL AS "comment",
|
165
|
-
NULL AS "policy name",
|
166
|
-
NULL AS "privacy domain",
|
162
|
+
NULL::VARCHAR AS "check",
|
163
|
+
NULL::VARCHAR AS "expression",
|
164
|
+
NULL::VARCHAR AS "comment",
|
165
|
+
NULL::VARCHAR AS "policy name",
|
166
|
+
NULL::JSON AS "privacy domain",
|
167
167
|
FROM information_schema._fs_columns_snowflake
|
168
168
|
WHERE table_catalog = '${catalog}' AND table_schema = '${schema}' AND table_name = '${table}'
|
169
169
|
ORDER BY ordinal_position
|
170
170
|
"""
|
171
171
|
)
|
172
172
|
|
173
|
+
SQL_DESCRIBE_INFO_SCHEMA = Template(
|
174
|
+
"""
|
175
|
+
SELECT
|
176
|
+
column_name AS "name",
|
177
|
+
column_type as "type",
|
178
|
+
'COLUMN' AS "kind",
|
179
|
+
CASE WHEN "null" = 'YES' THEN 'Y' ELSE 'N' END AS "null?",
|
180
|
+
NULL::VARCHAR AS "default",
|
181
|
+
'N' AS "primary key",
|
182
|
+
'N' AS "unique key",
|
183
|
+
NULL::VARCHAR AS "check",
|
184
|
+
NULL::VARCHAR AS "expression",
|
185
|
+
NULL::VARCHAR AS "comment",
|
186
|
+
NULL::VARCHAR AS "policy name",
|
187
|
+
NULL::JSON AS "privacy domain",
|
188
|
+
FROM (DESCRIBE information_schema.${view})
|
189
|
+
"""
|
190
|
+
)
|
191
|
+
|
173
192
|
|
174
193
|
def describe_table(
|
175
194
|
expression: exp.Expression, current_database: str | None = None, current_schema: str | None = None
|
176
195
|
) -> exp.Expression:
|
177
|
-
"""Redirect to the information_schema.
|
196
|
+
"""Redirect to the information_schema._fs_columns_snowflake to match snowflake.
|
178
197
|
|
179
198
|
See https://docs.snowflake.com/en/sql-reference/sql/desc-table
|
180
199
|
"""
|
@@ -183,12 +202,16 @@ def describe_table(
|
|
183
202
|
isinstance(expression, exp.Describe)
|
184
203
|
and (kind := expression.args.get("kind"))
|
185
204
|
and isinstance(kind, str)
|
186
|
-
and kind.upper()
|
205
|
+
and kind.upper() in ("TABLE", "VIEW")
|
187
206
|
and (table := expression.find(exp.Table))
|
188
207
|
):
|
189
208
|
catalog = table.catalog or current_database
|
190
209
|
schema = table.db or current_schema
|
191
210
|
|
211
|
+
if schema and schema.upper() == "INFORMATION_SCHEMA":
|
212
|
+
# information schema views don't exist in _fs_columns_snowflake
|
213
|
+
return sqlglot.parse_one(SQL_DESCRIBE_INFO_SCHEMA.substitute(view=table.name), read="duckdb")
|
214
|
+
|
192
215
|
return sqlglot.parse_one(
|
193
216
|
SQL_DESCRIBE_TABLE.substitute(catalog=catalog, schema=schema, table=table.name),
|
194
217
|
read="duckdb",
|
fakesnow/types.py
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Optional, TypedDict
|
3
|
+
|
4
|
+
from snowflake.connector.cursor import ResultMetadata
|
5
|
+
|
6
|
+
|
7
|
+
class ColumnInfo(TypedDict):
|
8
|
+
name: str
|
9
|
+
database: str
|
10
|
+
schema: str
|
11
|
+
table: str
|
12
|
+
nullable: bool
|
13
|
+
type: str
|
14
|
+
byteLength: Optional[int]
|
15
|
+
length: Optional[int]
|
16
|
+
scale: Optional[int]
|
17
|
+
precision: Optional[int]
|
18
|
+
collation: Optional[str]
|
19
|
+
|
20
|
+
|
21
|
+
duckdb_to_sf_type = {
|
22
|
+
"BIGINT": "fixed",
|
23
|
+
"BLOB": "binary",
|
24
|
+
"BOOLEAN": "boolean",
|
25
|
+
"DATE": "date",
|
26
|
+
"DECIMAL": "fixed",
|
27
|
+
"DOUBLE": "real",
|
28
|
+
"INTEGER": "fixed",
|
29
|
+
"JSON": "variant",
|
30
|
+
"TIME": "time",
|
31
|
+
"TIMESTAMP WITH TIME ZONE": "timestamp_tz",
|
32
|
+
"TIMESTAMP_NS": "timestamp_ntz",
|
33
|
+
"TIMESTAMP": "timestamp_ntz",
|
34
|
+
"VARCHAR": "text",
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
def describe_as_rowtype(describe_results: list) -> list[ColumnInfo]:
|
39
|
+
"""Convert duckdb column type to snowflake rowtype returned by the API."""
|
40
|
+
|
41
|
+
def as_column_info(column_name: str, column_type: str) -> ColumnInfo:
|
42
|
+
if not (sf_type := duckdb_to_sf_type.get("DECIMAL" if column_type.startswith("DECIMAL") else column_type)):
|
43
|
+
raise NotImplementedError(f"for column type {column_type}")
|
44
|
+
|
45
|
+
info: ColumnInfo = {
|
46
|
+
"name": column_name,
|
47
|
+
# TODO
|
48
|
+
"database": "",
|
49
|
+
"schema": "",
|
50
|
+
"table": "",
|
51
|
+
# TODO
|
52
|
+
"nullable": True,
|
53
|
+
"type": sf_type,
|
54
|
+
"byteLength": None,
|
55
|
+
"length": None,
|
56
|
+
"scale": None,
|
57
|
+
"precision": None,
|
58
|
+
"collation": None,
|
59
|
+
}
|
60
|
+
|
61
|
+
if column_type.startswith("DECIMAL"):
|
62
|
+
match = re.search(r"\((\d+),(\d+)\)", column_type)
|
63
|
+
info["precision"] = int(match[1]) if match else 38
|
64
|
+
info["scale"] = int(match[2]) if match else 0
|
65
|
+
elif sf_type == "fixed":
|
66
|
+
info["precision"] = 38
|
67
|
+
info["scale"] = 0
|
68
|
+
elif sf_type == "text":
|
69
|
+
# TODO: fetch actual varchar size
|
70
|
+
info["byteLength"] = 16777216
|
71
|
+
info["length"] = 16777216
|
72
|
+
elif sf_type.startswith("time"):
|
73
|
+
info["precision"] = 0
|
74
|
+
info["scale"] = 9
|
75
|
+
elif sf_type == "binary":
|
76
|
+
info["byteLength"] = 8388608
|
77
|
+
info["length"] = 8388608
|
78
|
+
|
79
|
+
return info
|
80
|
+
|
81
|
+
column_infos = [
|
82
|
+
as_column_info(column_name, column_type)
|
83
|
+
for (column_name, column_type, _null, _key, _default, _extra) in describe_results
|
84
|
+
]
|
85
|
+
return column_infos
|
86
|
+
|
87
|
+
|
88
|
+
def describe_as_result_metadata(describe_results: list) -> list[ResultMetadata]:
|
89
|
+
return [ResultMetadata.from_column(c) for c in describe_as_rowtype(describe_results)] # pyright: ignore[reportArgumentType]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fakesnow
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.23
|
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,28 +210,29 @@ 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
|
213
|
+
Requires-Dist: duckdb~=1.0.0
|
214
214
|
Requires-Dist: pyarrow
|
215
215
|
Requires-Dist: snowflake-connector-python
|
216
|
-
Requires-Dist: sqlglot
|
216
|
+
Requires-Dist: sqlglot~=25.9.0
|
217
217
|
Provides-Extra: dev
|
218
|
-
Requires-Dist: build
|
219
|
-
Requires-Dist:
|
220
|
-
Requires-Dist:
|
221
|
-
Requires-Dist:
|
222
|
-
Requires-Dist:
|
223
|
-
Requires-Dist:
|
224
|
-
Requires-Dist: pytest
|
225
|
-
Requires-Dist:
|
226
|
-
Requires-Dist:
|
227
|
-
Requires-Dist:
|
218
|
+
Requires-Dist: build~=1.0; extra == "dev"
|
219
|
+
Requires-Dist: dirty-equals; extra == "dev"
|
220
|
+
Requires-Dist: pandas-stubs; extra == "dev"
|
221
|
+
Requires-Dist: snowflake-connector-python[pandas,secure-local-storage]; extra == "dev"
|
222
|
+
Requires-Dist: pre-commit~=3.4; extra == "dev"
|
223
|
+
Requires-Dist: pyarrow-stubs; extra == "dev"
|
224
|
+
Requires-Dist: pytest~=8.0; extra == "dev"
|
225
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
226
|
+
Requires-Dist: ruff~=0.5.1; extra == "dev"
|
227
|
+
Requires-Dist: twine~=5.0; extra == "dev"
|
228
|
+
Requires-Dist: snowflake-sqlalchemy~=1.5.0; extra == "dev"
|
228
229
|
Provides-Extra: notebook
|
229
|
-
Requires-Dist: duckdb-engine
|
230
|
-
Requires-Dist: ipykernel
|
231
|
-
Requires-Dist: jupysql
|
230
|
+
Requires-Dist: duckdb-engine; extra == "notebook"
|
231
|
+
Requires-Dist: ipykernel; extra == "notebook"
|
232
|
+
Requires-Dist: jupysql; extra == "notebook"
|
232
233
|
Provides-Extra: server
|
233
|
-
Requires-Dist: starlette
|
234
|
-
Requires-Dist: uvicorn
|
234
|
+
Requires-Dist: starlette; extra == "server"
|
235
|
+
Requires-Dist: uvicorn; extra == "server"
|
235
236
|
|
236
237
|
# fakesnow ❄️
|
237
238
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
fakesnow/__init__.py,sha256=9tFJJKvowKNW3vfnlmza6hOLN1I52DwChgNc5Ew6CcA,3499
|
2
|
+
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
|
+
fakesnow/arrow.py,sha256=WLkr1nEiNxUcPdzadKSM33sRAiQJsN6LvuzTVIsi3D0,2766
|
4
|
+
fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
|
5
|
+
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
6
|
+
fakesnow/conn.py,sha256=yR9SMGSKkLvdPfi5fDwj9PQggOrPcKkdBBnQy2y_Bak,6921
|
7
|
+
fakesnow/cursor.py,sha256=lITuMMy_hA9_riC121Sv9bFDFbU9BP9NlOLdHlP0ahY,19371
|
8
|
+
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
9
|
+
fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
|
10
|
+
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
11
|
+
fakesnow/info_schema.py,sha256=DObVOrhzppAFHsdtj4YI9oRISn9SkJUG6ONjVleQQ_Y,6303
|
12
|
+
fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
|
13
|
+
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
14
|
+
fakesnow/pandas_tools.py,sha256=ecL0kxIVU5o2--P3bRLWWVhxXqq6Km4trFr36txukMg,1897
|
15
|
+
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
16
|
+
fakesnow/server.py,sha256=8dzaLUUXPzCMm6-ESn0CBws6XSwwOpnUuHQAZJ-4SwU,3011
|
17
|
+
fakesnow/transforms.py,sha256=ellcY5OBc7mqgL9ChNolrqcCLWXF9RH21Jt88FcFl-I,54419
|
18
|
+
fakesnow/types.py,sha256=9Tt83Z7ctc9_v6SYyayXYz4MEI4RZo4zq_uqdj4g3Dk,2681
|
19
|
+
fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
|
20
|
+
fakesnow-0.9.23.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
21
|
+
fakesnow-0.9.23.dist-info/METADATA,sha256=90vwAf40lg9ipw2urbu9nMq7cEARbtENE0aKd7qSpjo,18064
|
22
|
+
fakesnow-0.9.23.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
23
|
+
fakesnow-0.9.23.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
24
|
+
fakesnow-0.9.23.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
25
|
+
fakesnow-0.9.23.dist-info/RECORD,,
|
fakesnow-0.9.21.dist-info/RECORD
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
fakesnow/__init__.py,sha256=9tFJJKvowKNW3vfnlmza6hOLN1I52DwChgNc5Ew6CcA,3499
|
2
|
-
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
|
-
fakesnow/arrow.py,sha256=1ypCsf-r2Ven6CuSm-bTLoeq1G31kBD6JnaLvDxpwhU,1218
|
4
|
-
fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
|
5
|
-
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
6
|
-
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
7
|
-
fakesnow/fakes.py,sha256=wLSjKrNI8wxe3MuUAa97jpUHd5vZTzvrlF1-Hf0FC0M,31208
|
8
|
-
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
9
|
-
fakesnow/info_schema.py,sha256=LjS_-8YXBtCSvkdU5uL0aJdFcZEsBa6o5zf-Q_aV9i0,6302
|
10
|
-
fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
|
11
|
-
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
12
|
-
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
13
|
-
fakesnow/server.py,sha256=HwCAZ5AhU4nRbFGIqDBs2rdwoK70dYZDyw1XfE1cHqU,3082
|
14
|
-
fakesnow/transforms.py,sha256=GCpczoFdvnffQMvSG59PNiiTVTld9kROnnR5dWNXQvY,53624
|
15
|
-
fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
|
16
|
-
fakesnow-0.9.21.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
17
|
-
fakesnow-0.9.21.dist-info/METADATA,sha256=6EzLZbGBTS0VEYCHdG4sNNZT-w5SiespKcitL_XEtM4,18043
|
18
|
-
fakesnow-0.9.21.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
19
|
-
fakesnow-0.9.21.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
20
|
-
fakesnow-0.9.21.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
21
|
-
fakesnow-0.9.21.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|