python-datastore-sqlalchemy 0.0.1__py3-none-any.whl → 0.0.2__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.
- {python_datastore_sqlalchemy-0.0.1.dist-info → python_datastore_sqlalchemy-0.0.2.dist-info}/METADATA +14 -3
- python_datastore_sqlalchemy-0.0.2.dist-info/RECORD +12 -0
- {python_datastore_sqlalchemy-0.0.1.dist-info → python_datastore_sqlalchemy-0.0.2.dist-info}/WHEEL +1 -1
- sqlalchemy_datastore/__init__.py +2 -2
- sqlalchemy_datastore/_helpers.py +11 -9
- sqlalchemy_datastore/base.py +28 -60
- sqlalchemy_datastore/datastore_dbapi.py +20 -5
- sqlalchemy_datastore/parse_url.py +8 -0
- python_datastore_sqlalchemy-0.0.1.dist-info/RECORD +0 -12
- {python_datastore_sqlalchemy-0.0.1.dist-info → python_datastore_sqlalchemy-0.0.2.dist-info}/entry_points.txt +0 -0
- {python_datastore_sqlalchemy-0.0.1.dist-info → python_datastore_sqlalchemy-0.0.2.dist-info}/licenses/LICENSE +0 -0
- {python_datastore_sqlalchemy-0.0.1.dist-info → python_datastore_sqlalchemy-0.0.2.dist-info}/top_level.txt +0 -0
{python_datastore_sqlalchemy-0.0.1.dist-info → python_datastore_sqlalchemy-0.0.2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-datastore-sqlalchemy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: SQLAlchemy dialect for google cloud datastore
|
|
5
5
|
Home-page: https://github.com/splasky/python-datastore-sqlalchemy
|
|
6
6
|
Author: HY Chang(splasky)
|
|
@@ -21,6 +21,13 @@ Requires-Python: >=3.9
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: sqlalchemy<3.0.0,>=1.4.16
|
|
24
|
+
Requires-Dist: google-cloud-datastore>=2.21.0
|
|
25
|
+
Requires-Dist: google-cloud-firestore>=2.21.0
|
|
26
|
+
Requires-Dist: google-cloud-bigquery>=3.35.0
|
|
27
|
+
Requires-Dist: google-auth>=2.40.0
|
|
28
|
+
Requires-Dist: sqlglot>=28.0.0
|
|
29
|
+
Requires-Dist: pandas>=2.0.0
|
|
30
|
+
Requires-Dist: requests
|
|
24
31
|
Dynamic: author
|
|
25
32
|
Dynamic: author-email
|
|
26
33
|
Dynamic: classifier
|
|
@@ -41,14 +48,18 @@ How to install
|
|
|
41
48
|
```
|
|
42
49
|
python3 setup.py install
|
|
43
50
|
```
|
|
51
|
+
or
|
|
52
|
+
```
|
|
53
|
+
pip install python-datastore-sqlalchemy
|
|
54
|
+
```
|
|
44
55
|
How to use
|
|
45
56
|
```python
|
|
46
57
|
from sqlalchemy import *
|
|
47
58
|
from sqlalchemy.engine import create_engine
|
|
48
59
|
from sqlalchemy.schema import *
|
|
49
|
-
engine = create_engine(
|
|
60
|
+
engine = create_engine("datastore://my-project/?database=my-db", credentials='path/to/credentials.json')
|
|
50
61
|
conn = engine.connect()
|
|
51
|
-
result = conn.execute("SELECT *
|
|
62
|
+
result = conn.execute("SELECT * fro" test_table")
|
|
52
63
|
print(result.fetchall())
|
|
53
64
|
```
|
|
54
65
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
python_datastore_sqlalchemy-0.0.2.dist-info/licenses/LICENSE,sha256=vcsHP9P3NtGefy7ojTRCgi3w0QqF_igVlmFP5A47YVA,1093
|
|
2
|
+
sqlalchemy_datastore/__init__.py,sha256=UmMpE_Sn41BU88qdX5KVvJPH7wMD2SufDBDQ-2cCwNQ,1239
|
|
3
|
+
sqlalchemy_datastore/_helpers.py,sha256=bRvNeMz6QgJMW1M5K-dXksdnURYBZE-NIzt3tue92rg,4854
|
|
4
|
+
sqlalchemy_datastore/_types.py,sha256=zs-Gr1XPP-UnppzsuWHEmD-wshkuosXM7yGeUIt-VVY,5092
|
|
5
|
+
sqlalchemy_datastore/base.py,sha256=QpgQ19h9LA2h86TH14w4h_Ilg9_St76qrwD5r3Ln4bA,9322
|
|
6
|
+
sqlalchemy_datastore/datastore_dbapi.py,sha256=xJVZR0Ymzj1XjB6-UeWQRHRLLYLzlOrjaYOEv3-13lY,94404
|
|
7
|
+
sqlalchemy_datastore/parse_url.py,sha256=sTeNqBm-V4Dec97R4K264qvG05aUXG7iNFyKNCNkPJ8,10058
|
|
8
|
+
python_datastore_sqlalchemy-0.0.2.dist-info/METADATA,sha256=zQgLqKvD-e8nqx-WLy2wGXhDjcOFiUD5ibrASBsXZbs,2471
|
|
9
|
+
python_datastore_sqlalchemy-0.0.2.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
10
|
+
python_datastore_sqlalchemy-0.0.2.dist-info/entry_points.txt,sha256=h54O-P7HFeCKpoeQ-XSSyP_GVZ1FRAY3gFCS5lkDBIs,77
|
|
11
|
+
python_datastore_sqlalchemy-0.0.2.dist-info/top_level.txt,sha256=SE6uZfiwIQaJ1RDBfMm37dm7ZIZoqDcwQ1fDm32x0Rg,21
|
|
12
|
+
python_datastore_sqlalchemy-0.0.2.dist-info/RECORD,,
|
sqlalchemy_datastore/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2025 hychang <hychang.1997.tw@gmail.com>
|
|
1
|
+
# Copyright (c) 2025 hychang <hychang.1997.tw@gmail.com>
|
|
2
2
|
#
|
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
4
|
# this software and associated documentation files (the "Software"), to deal in
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
from .base import CloudDatastoreDialect
|
|
21
21
|
|
|
22
|
-
__version__ = "0.0.
|
|
22
|
+
__version__ = "0.0.2"
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
25
|
"__version__",
|
sqlalchemy_datastore/_helpers.py
CHANGED
|
@@ -60,21 +60,23 @@ def create_datastore_client(
|
|
|
60
60
|
user_agent: Optional[client_info.ClientInfo] = None,
|
|
61
61
|
database: Optional[str] = None
|
|
62
62
|
) -> Tuple[datastore.Client, Optional[Credentials]]:
|
|
63
|
-
"""Construct a
|
|
63
|
+
"""Construct a Datastore client object.
|
|
64
64
|
|
|
65
65
|
Args:
|
|
66
|
-
credentials_info Optional[dict]:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
credentials_info (Optional[dict]):
|
|
67
|
+
Service account info dictionary.
|
|
68
|
+
credentials_path (Optional[str]):
|
|
69
|
+
Path to a service account JSON key file.
|
|
70
|
+
credentials_base64 (Optional[str]):
|
|
71
|
+
Base64-encoded service account JSON key.
|
|
71
72
|
project_id (Optional[str]):
|
|
72
73
|
Project ID for the project which the client acts on behalf of.
|
|
73
74
|
user_agent (Optional[google.api_core.client_info.ClientInfo]):
|
|
74
75
|
The client info used to send a user-agent string along with API
|
|
75
|
-
requests. If ``None``, then default info will be used.
|
|
76
|
-
|
|
77
|
-
or
|
|
76
|
+
requests. If ``None``, then default info will be used.
|
|
77
|
+
database (Optional[str]):
|
|
78
|
+
Datastore database ID. ``None`` or ``"(default)"`` uses the
|
|
79
|
+
default database.
|
|
78
80
|
"""
|
|
79
81
|
|
|
80
82
|
default_project = None
|
sqlalchemy_datastore/base.py
CHANGED
|
@@ -17,20 +17,15 @@
|
|
|
17
17
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
18
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
19
19
|
import logging
|
|
20
|
-
from concurrent import futures
|
|
21
20
|
from typing import Any, List, Optional
|
|
22
21
|
|
|
23
22
|
from google.cloud import firestore_admin_v1
|
|
24
|
-
from google.cloud.firestore_admin_v1.types import Database
|
|
25
23
|
from google.oauth2 import service_account
|
|
26
24
|
from sqlalchemy import exc
|
|
27
25
|
from sqlalchemy.engine import Connection, default
|
|
28
26
|
from sqlalchemy.engine.interfaces import (
|
|
29
|
-
# DBAPICursor,
|
|
30
|
-
# _DBAPISingleExecuteParams,
|
|
31
27
|
ExecutionContext,
|
|
32
28
|
)
|
|
33
|
-
from sqlalchemy.sql import Select
|
|
34
29
|
|
|
35
30
|
from . import _types, datastore_dbapi
|
|
36
31
|
from ._helpers import create_datastore_client
|
|
@@ -88,6 +83,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
88
83
|
self.location = location
|
|
89
84
|
self.identifier_preparer = self.preparer(self)
|
|
90
85
|
self.dataset_id = None
|
|
86
|
+
self.database_id = None
|
|
91
87
|
self.list_tables_page_size = list_tables_page_size
|
|
92
88
|
self._client = None
|
|
93
89
|
|
|
@@ -129,6 +125,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
129
125
|
provided_job_config,
|
|
130
126
|
list_tables_page_size,
|
|
131
127
|
user_supplied_client,
|
|
128
|
+
database_id,
|
|
132
129
|
) = parse_url(url)
|
|
133
130
|
|
|
134
131
|
self.arraysize = arraysize or self.arraysize
|
|
@@ -138,6 +135,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
138
135
|
self.credentials_path = credentials_path
|
|
139
136
|
self.credentials_base64 = credentials_base64 or self.credentials_base64
|
|
140
137
|
self.dataset_id = dataset_id
|
|
138
|
+
self.database_id = database_id
|
|
141
139
|
self.billing_project_id = self.billing_project_id or self.project_id
|
|
142
140
|
|
|
143
141
|
if user_supplied_client:
|
|
@@ -148,7 +146,7 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
148
146
|
credentials_info=self.credentials_info,
|
|
149
147
|
credentials_base64=self.credentials_base64,
|
|
150
148
|
project_id=self.billing_project_id,
|
|
151
|
-
database=
|
|
149
|
+
database=self.database_id,
|
|
152
150
|
)
|
|
153
151
|
self.project_id = self.project_id if self.project_id else client.project
|
|
154
152
|
self.billing_project_id = (
|
|
@@ -162,10 +160,10 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
162
160
|
|
|
163
161
|
self._client = client
|
|
164
162
|
self.credentials = credentials
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
self._client.credentials_path = self.credentials_path
|
|
164
|
+
self._client.credentials_info = self.credentials_info
|
|
165
|
+
self._client.credentials_base64 = self.credentials_base64
|
|
166
|
+
self._client.scoped_credentials = credentials
|
|
169
167
|
return ([], {"client": client})
|
|
170
168
|
|
|
171
169
|
def get_schema_names(self, connection: Connection, **kw) -> List[str]:
|
|
@@ -182,18 +180,11 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
182
180
|
|
|
183
181
|
try:
|
|
184
182
|
list_database_resp = client.list_databases(parent=parent)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if full_name
|
|
189
|
-
|
|
190
|
-
return None
|
|
191
|
-
|
|
192
|
-
with futures.ThreadPoolExecutor() as executor:
|
|
193
|
-
schemas = list(
|
|
194
|
-
executor.map(get_database_short_name, list_database_resp.databases)
|
|
195
|
-
)
|
|
196
|
-
|
|
183
|
+
schemas = [
|
|
184
|
+
full_name.split("/")[-1]
|
|
185
|
+
for db in list_database_resp.databases
|
|
186
|
+
if (full_name := db.name)
|
|
187
|
+
]
|
|
197
188
|
return schemas
|
|
198
189
|
except Exception as e:
|
|
199
190
|
logging.error(e)
|
|
@@ -206,57 +197,34 @@ class CloudDatastoreDialect(default.DefaultDialect):
|
|
|
206
197
|
query = client.query(kind="__kind__")
|
|
207
198
|
kinds = list(query.fetch())
|
|
208
199
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
with futures.ThreadPoolExecutor() as executor:
|
|
220
|
-
result = list(executor.map(get_kind_name, kinds))
|
|
221
|
-
|
|
222
|
-
return [t for t in result if t is not None]
|
|
200
|
+
return [
|
|
201
|
+
name
|
|
202
|
+
for kind in kinds
|
|
203
|
+
if (name := getattr(getattr(kind, "key", None), "name", None))
|
|
204
|
+
is not None
|
|
205
|
+
and isinstance(name, str)
|
|
206
|
+
and not name.startswith("__")
|
|
207
|
+
]
|
|
223
208
|
|
|
224
209
|
def get_columns(
|
|
225
210
|
self, connection: Connection, table_name: str, schema: str | None = None, **kw
|
|
226
211
|
):
|
|
227
|
-
"""Retrieve column information from the database
|
|
212
|
+
"""Retrieve column information from the database."""
|
|
228
213
|
client = self._client
|
|
229
214
|
query = client.query(kind="__Stat_PropertyType_PropertyName_Kind__")
|
|
230
215
|
query.add_filter("kind_name", "=", table_name)
|
|
231
216
|
properties = list(query.fetch())
|
|
232
217
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"name":
|
|
236
|
-
"type": _types._property_type[
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
"name": prop["property_name"],
|
|
221
|
+
"type": _types._property_type[prop["property_type"]],
|
|
237
222
|
"nullable": True,
|
|
238
223
|
"comment": "",
|
|
239
224
|
"default": None,
|
|
240
225
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
columns = list(executor.map(process_property, properties))
|
|
244
|
-
return columns
|
|
245
|
-
|
|
246
|
-
def _contains_select_subquery(self, node) -> bool:
|
|
247
|
-
"""
|
|
248
|
-
Check the AST node contains select subquery
|
|
249
|
-
"""
|
|
250
|
-
if isinstance(node, Select):
|
|
251
|
-
for child in node.get_children():
|
|
252
|
-
if isinstance(child, Select) or self._contains_select_subquery(child):
|
|
253
|
-
return True
|
|
254
|
-
|
|
255
|
-
for child in node.get_children():
|
|
256
|
-
if isinstance(child, Select) or self._contains_select_subquery(child):
|
|
257
|
-
return True
|
|
258
|
-
|
|
259
|
-
return False
|
|
226
|
+
for prop in properties
|
|
227
|
+
]
|
|
260
228
|
|
|
261
229
|
def do_execute(
|
|
262
230
|
self,
|
|
@@ -228,7 +228,7 @@ class Cursor:
|
|
|
228
228
|
|
|
229
229
|
except Exception as e:
|
|
230
230
|
logging.error(f"INSERT failed: {e}")
|
|
231
|
-
raise ProgrammingError(f"INSERT failed: {e}")
|
|
231
|
+
raise ProgrammingError(f"INSERT failed: {e}") from e
|
|
232
232
|
|
|
233
233
|
def _execute_update(self, statement: str, parameters=None):
|
|
234
234
|
"""Execute an UPDATE statement using Datastore client."""
|
|
@@ -454,6 +454,8 @@ class Cursor:
|
|
|
454
454
|
r"\bCOUNT_UP_TO\s*\(",
|
|
455
455
|
r"\bSUM\s*\(",
|
|
456
456
|
r"\bAVG\s*\(",
|
|
457
|
+
r"\bMIN\s*\(",
|
|
458
|
+
r"\bMAX\s*\(",
|
|
457
459
|
]
|
|
458
460
|
for pattern in agg_patterns:
|
|
459
461
|
if re.search(pattern, upper):
|
|
@@ -931,9 +933,14 @@ class Cursor:
|
|
|
931
933
|
# This is a simplified evaluator for common patterns
|
|
932
934
|
try:
|
|
933
935
|
return self._eval_condition(context, where_clause)
|
|
934
|
-
except Exception:
|
|
935
|
-
|
|
936
|
-
|
|
936
|
+
except Exception as e:
|
|
937
|
+
logging.warning(
|
|
938
|
+
"Client-side WHERE evaluation failed for clause '%s': %s. "
|
|
939
|
+
"Row will be excluded (fail closed).",
|
|
940
|
+
where_clause,
|
|
941
|
+
e,
|
|
942
|
+
)
|
|
943
|
+
return False
|
|
937
944
|
|
|
938
945
|
def _eval_condition(self, context: Dict[str, Any], condition: str) -> bool:
|
|
939
946
|
"""Evaluate a single condition or compound condition."""
|
|
@@ -1566,10 +1573,12 @@ class Cursor:
|
|
|
1566
1573
|
self.description = list(agg_fields.values())
|
|
1567
1574
|
|
|
1568
1575
|
def execute_orm(
|
|
1569
|
-
self, statement: str, parameters=None, tokens: List[tokens.Token] =
|
|
1576
|
+
self, statement: str, parameters=None, tokens: Optional[List[tokens.Token]] = None
|
|
1570
1577
|
):
|
|
1571
1578
|
if parameters is None:
|
|
1572
1579
|
parameters = {}
|
|
1580
|
+
if tokens is None:
|
|
1581
|
+
tokens = []
|
|
1573
1582
|
|
|
1574
1583
|
logging.debug(
|
|
1575
1584
|
f"[DataStore DBAPI] Executing ORM query: {statement} with parameters: {parameters}"
|
|
@@ -1812,11 +1821,15 @@ class Cursor:
|
|
|
1812
1821
|
def fetchall(self):
|
|
1813
1822
|
if self._closed:
|
|
1814
1823
|
raise Error("Cursor is closed.")
|
|
1824
|
+
if self._query_rows is None:
|
|
1825
|
+
raise ProgrammingError("No query has been executed.")
|
|
1815
1826
|
return list(self._query_rows)
|
|
1816
1827
|
|
|
1817
1828
|
def fetchmany(self, size=None):
|
|
1818
1829
|
if self._closed:
|
|
1819
1830
|
raise Error("Cursor is closed.")
|
|
1831
|
+
if self._query_rows is None:
|
|
1832
|
+
raise ProgrammingError("No query has been executed.")
|
|
1820
1833
|
if size is None:
|
|
1821
1834
|
size = self.arraysize or 1
|
|
1822
1835
|
results = []
|
|
@@ -1830,6 +1843,8 @@ class Cursor:
|
|
|
1830
1843
|
def fetchone(self):
|
|
1831
1844
|
if self._closed:
|
|
1832
1845
|
raise Error("Cursor is closed.")
|
|
1846
|
+
if self._query_rows is None:
|
|
1847
|
+
raise ProgrammingError("No query has been executed.")
|
|
1833
1848
|
try:
|
|
1834
1849
|
return next(self._query_rows)
|
|
1835
1850
|
except StopIteration:
|
|
@@ -72,6 +72,11 @@ def parse_url(url): # noqa: C901
|
|
|
72
72
|
credentials_base64 = None
|
|
73
73
|
list_tables_page_size = None
|
|
74
74
|
user_supplied_client = False
|
|
75
|
+
database = None
|
|
76
|
+
|
|
77
|
+
# database
|
|
78
|
+
if "database" in query:
|
|
79
|
+
database = query.pop("database")
|
|
75
80
|
|
|
76
81
|
# location
|
|
77
82
|
if "location" in query:
|
|
@@ -122,6 +127,7 @@ def parse_url(url): # noqa: C901
|
|
|
122
127
|
QueryJobConfig(),
|
|
123
128
|
list_tables_page_size,
|
|
124
129
|
user_supplied_client,
|
|
130
|
+
database,
|
|
125
131
|
)
|
|
126
132
|
else:
|
|
127
133
|
return (
|
|
@@ -134,6 +140,7 @@ def parse_url(url): # noqa: C901
|
|
|
134
140
|
None,
|
|
135
141
|
list_tables_page_size,
|
|
136
142
|
user_supplied_client,
|
|
143
|
+
database,
|
|
137
144
|
)
|
|
138
145
|
|
|
139
146
|
job_config = QueryJobConfig()
|
|
@@ -284,4 +291,5 @@ def parse_url(url): # noqa: C901
|
|
|
284
291
|
job_config,
|
|
285
292
|
list_tables_page_size,
|
|
286
293
|
user_supplied_client,
|
|
294
|
+
database,
|
|
287
295
|
)
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
python_datastore_sqlalchemy-0.0.1.dist-info/licenses/LICENSE,sha256=vcsHP9P3NtGefy7ojTRCgi3w0QqF_igVlmFP5A47YVA,1093
|
|
2
|
-
sqlalchemy_datastore/__init__.py,sha256=IUWv-9bXoRdJckJOLqNpkibKLTPD5GNpeck2HZTE3wU,1240
|
|
3
|
-
sqlalchemy_datastore/_helpers.py,sha256=4rtRSCBhc0WMDimIWtD7vhxl4n3k-l8DzGR8Q6DiUaw,4769
|
|
4
|
-
sqlalchemy_datastore/_types.py,sha256=zs-Gr1XPP-UnppzsuWHEmD-wshkuosXM7yGeUIt-VVY,5092
|
|
5
|
-
sqlalchemy_datastore/base.py,sha256=DvYYV_qrGb86x0SwWMcY5_ZHXLXxzuVZ3JPEukF0tYQ,10610
|
|
6
|
-
sqlalchemy_datastore/datastore_dbapi.py,sha256=wyR2wCyCYUoksmivAgB6mDh_XMxAj-KBIiaIJ5XxRVo,93805
|
|
7
|
-
sqlalchemy_datastore/parse_url.py,sha256=Q8UO-No5he1_-iozbYoIrlnwm2Aw5ePGEBjP_4U1eyM,9883
|
|
8
|
-
python_datastore_sqlalchemy-0.0.1.dist-info/METADATA,sha256=W9Q5j0Z90WhVIZACeaPgWsl0zqOFj_D_LASVI3jW4So,2147
|
|
9
|
-
python_datastore_sqlalchemy-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
-
python_datastore_sqlalchemy-0.0.1.dist-info/entry_points.txt,sha256=h54O-P7HFeCKpoeQ-XSSyP_GVZ1FRAY3gFCS5lkDBIs,77
|
|
11
|
-
python_datastore_sqlalchemy-0.0.1.dist-info/top_level.txt,sha256=SE6uZfiwIQaJ1RDBfMm37dm7ZIZoqDcwQ1fDm32x0Rg,21
|
|
12
|
-
python_datastore_sqlalchemy-0.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|