polypheny 2.0.0__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.
- polypheny/__init__.py +85 -0
- polypheny/connection.py +327 -0
- polypheny/exceptions.py +52 -0
- polypheny/interval.py +37 -0
- polypheny/rpc.py +236 -0
- polypheny/serialize.py +126 -0
- polypheny-2.0.0.dist-info/METADATA +128 -0
- polypheny-2.0.0.dist-info/RECORD +11 -0
- polypheny-2.0.0.dist-info/WHEEL +5 -0
- polypheny-2.0.0.dist-info/licenses/LICENSE +177 -0
- polypheny-2.0.0.dist-info/top_level.txt +1 -0
polypheny/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copyright 2024 The Polypheny Project
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from polypheny.connection import Connection, Cursor
|
|
16
|
+
from polypheny.exceptions import *
|
|
17
|
+
|
|
18
|
+
import datetime
|
|
19
|
+
from typing import Union
|
|
20
|
+
|
|
21
|
+
# See https://peps.python.org/pep-0249/#globals
|
|
22
|
+
apilevel = '2.0'
|
|
23
|
+
threadsafety = 0
|
|
24
|
+
paramstyle = 'qmark'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def Date(year, month, day):
|
|
28
|
+
return datetime.date(year, month, day)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def Time(hour, minute, second):
|
|
32
|
+
return datetime.time(hour, minute, second)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def Timestamp(year, month, day, hour, minute, second):
|
|
36
|
+
return datetime.datetime(year, month, day, hour, minute, second)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# See PEP 249
|
|
40
|
+
import time
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def DateFromTicks(ticks):
|
|
44
|
+
return Date(*time.localtime(ticks)[:3]) # TODO: Really local time?
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def TimeFromTicks(ticks):
|
|
48
|
+
return Time(*time.localtime(ticks)[3:6])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def TimestampFromTicks(ticks):
|
|
52
|
+
return Timestamp(*time.localtime(ticks)[:6])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def Binary(string):
|
|
56
|
+
return string.encode('UTF-8')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Intentionally omitted, we always give type_code = None, like sqlite3
|
|
60
|
+
# STRING = 1
|
|
61
|
+
# BINARY = 2
|
|
62
|
+
# NUMBER = 3
|
|
63
|
+
# DATETIME = 4
|
|
64
|
+
# ROWID = 5
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def connect(address: Union[tuple[str, int], str] = None, *, username: str = None, password: str = None,
|
|
68
|
+
transport: str = None, **kwargs) -> Connection:
|
|
69
|
+
"""
|
|
70
|
+
Connect to a Polypheny instance with the given parameters. When
|
|
71
|
+
no parameters are given, the driver will connect via the ``unix``
|
|
72
|
+
transport to ``~/.polypheny/polypheny-prism.sock``.
|
|
73
|
+
|
|
74
|
+
:param address: A :py:class:`str` for ``unix`` transport or a (hostname, port) :py:class:`tuple` for ``plain`` transport.
|
|
75
|
+
:param username: username
|
|
76
|
+
:param password: password
|
|
77
|
+
:param transport: Either ``plain`` or ``unix``
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
if address is None and transport is None and username is None and password is None and len(kwargs) == 0:
|
|
81
|
+
transport = 'unix'
|
|
82
|
+
elif address is None or transport is None:
|
|
83
|
+
raise Error("Address and transport must be given")
|
|
84
|
+
|
|
85
|
+
return Connection(address, username, password, transport, kwargs)
|
polypheny/connection.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Copyright 2024 The Polypheny Project
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import List, Any
|
|
16
|
+
|
|
17
|
+
from org.polypheny.prism import relational_frame_pb2
|
|
18
|
+
from polypheny import rpc
|
|
19
|
+
from polypheny.exceptions import *
|
|
20
|
+
from polypheny.serialize import *
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Connection:
|
|
24
|
+
def __init__(self, address, username, password, transport, kwargs):
|
|
25
|
+
self.cursors = set()
|
|
26
|
+
self.con = None # Needed so destructor works
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
self.con = rpc.Connection(address, transport, kwargs)
|
|
30
|
+
except ConnectionRefusedError:
|
|
31
|
+
raise Error("Connection refused") from None
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
resp = self.con.connect(username, password, False)
|
|
35
|
+
if not resp.is_compatible:
|
|
36
|
+
raise Error(
|
|
37
|
+
f"Client ({rpc.POLYPHENY_API_MAJOR}.{rpc.POLYPHENY_API_MINOR}) is incompatible with Server version ({resp.major_api_version}.{resp.minor_api_version})")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
# This manual dance prevents the disconnect message from being sent
|
|
40
|
+
self.con.con.close()
|
|
41
|
+
self.con.con = None
|
|
42
|
+
self.con = None
|
|
43
|
+
raise Error(str(e))
|
|
44
|
+
|
|
45
|
+
def cursor(self):
|
|
46
|
+
if self.con is None:
|
|
47
|
+
raise ProgrammingError('Connection is closed')
|
|
48
|
+
cur = Cursor(self)
|
|
49
|
+
self.cursors.add(cur)
|
|
50
|
+
return cur
|
|
51
|
+
|
|
52
|
+
def commit(self):
|
|
53
|
+
"""
|
|
54
|
+
.. Note::
|
|
55
|
+
|
|
56
|
+
Performing a DDL automatically commits the transaction.
|
|
57
|
+
See the :py:meth:`rollback` method for an example what this
|
|
58
|
+
means.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
if self.con is None:
|
|
62
|
+
raise ProgrammingError('Connection is closed')
|
|
63
|
+
self.con.commit()
|
|
64
|
+
|
|
65
|
+
def rollback(self):
|
|
66
|
+
"""
|
|
67
|
+
.. Note::
|
|
68
|
+
|
|
69
|
+
It is not possible to rollback DDLs, as they commit automatically.
|
|
70
|
+
|
|
71
|
+
>>> cur.execute('SELECT * FROM fruits WHERE name = ?', ('Pear',))
|
|
72
|
+
>>> cur.fetchone()
|
|
73
|
+
>>> cur.execute('INSERT INTO fruits (id, name) VALUES (2, ?)', ('Pear',))
|
|
74
|
+
>>> cur.execute('CREATE TABLE demo(id INTEGER PRIMARY KEY)')
|
|
75
|
+
>>> # Implicit commit here because of DDL
|
|
76
|
+
>>> con.rollback()
|
|
77
|
+
>>> cur.execute('SELECT name FROM fruits WHERE name = ?', ('Pear',))
|
|
78
|
+
>>> print(cur.fetchone())
|
|
79
|
+
['Pear']
|
|
80
|
+
"""
|
|
81
|
+
if self.con is None:
|
|
82
|
+
raise ProgrammingError('Connection is closed')
|
|
83
|
+
self.con.rollback()
|
|
84
|
+
|
|
85
|
+
def __del__(self):
|
|
86
|
+
# TODO Thread-safety?
|
|
87
|
+
self.close()
|
|
88
|
+
|
|
89
|
+
def close(self):
|
|
90
|
+
if self.con is None:
|
|
91
|
+
assert len(self.cursors) == 0
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
for cur in list(self.cursors): # self.cursors is materialized because cur.close modifies it
|
|
95
|
+
cur.close()
|
|
96
|
+
assert len(self.cursors) == 0
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
self.rollback()
|
|
100
|
+
finally:
|
|
101
|
+
self.con.close()
|
|
102
|
+
self.con = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ResultCursor:
|
|
106
|
+
def __init__(self, con, statement_id, frame, fetch_size):
|
|
107
|
+
self.con = con
|
|
108
|
+
self.statement_id = statement_id
|
|
109
|
+
self.closed = False
|
|
110
|
+
self.frame = frame
|
|
111
|
+
self.fetch_size = fetch_size
|
|
112
|
+
if frame is not None:
|
|
113
|
+
restype = self.frame.WhichOneof('result')
|
|
114
|
+
assert restype is not None
|
|
115
|
+
if restype == 'relational_frame':
|
|
116
|
+
self.rows = iter(self.frame.relational_frame.rows)
|
|
117
|
+
elif restype == 'document_frame':
|
|
118
|
+
self.rows = iter(self.frame.document_frame.documents)
|
|
119
|
+
else:
|
|
120
|
+
self.closed = True
|
|
121
|
+
raise NotImplementedError(f'Resultset of type {restype} is not implemented')
|
|
122
|
+
|
|
123
|
+
def __del__(self):
|
|
124
|
+
assert self.closed
|
|
125
|
+
self.close()
|
|
126
|
+
|
|
127
|
+
def close(self):
|
|
128
|
+
if self.closed:
|
|
129
|
+
return
|
|
130
|
+
assert self.con.con is not None
|
|
131
|
+
try:
|
|
132
|
+
self.con.con.close_statement(self.statement_id)
|
|
133
|
+
finally:
|
|
134
|
+
self.con = None
|
|
135
|
+
self.closed = True
|
|
136
|
+
|
|
137
|
+
def __next__(self):
|
|
138
|
+
# frame is None when there were no results
|
|
139
|
+
if self.frame is None:
|
|
140
|
+
raise Error("Previous statement did not produce any results")
|
|
141
|
+
|
|
142
|
+
assert self.rows is not None
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
return next(self.rows)
|
|
146
|
+
except StopIteration:
|
|
147
|
+
if self.frame.is_last:
|
|
148
|
+
raise
|
|
149
|
+
return self.nextframe()
|
|
150
|
+
|
|
151
|
+
def nextframe(self):
|
|
152
|
+
self.frame = self.con.con.fetch(self.statement_id, self.fetch_size)
|
|
153
|
+
self.rows = iter(self.frame.relational_frame.rows) # TODO result must not be relational
|
|
154
|
+
return next(self.rows) # TODO: What happens if this returns StopIteration, but another frame could be fetched?
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class Cursor:
|
|
158
|
+
def __init__(self, con):
|
|
159
|
+
self.con = con
|
|
160
|
+
self.result = None
|
|
161
|
+
self.reset()
|
|
162
|
+
|
|
163
|
+
def reset(self):
|
|
164
|
+
if self.result is not None:
|
|
165
|
+
self.result.close()
|
|
166
|
+
self.description = None
|
|
167
|
+
self.rowcount = -1
|
|
168
|
+
self.arraysize = 1
|
|
169
|
+
self.result = None
|
|
170
|
+
|
|
171
|
+
# def callproc(self):
|
|
172
|
+
# optional
|
|
173
|
+
|
|
174
|
+
def __del__(self):
|
|
175
|
+
self.close()
|
|
176
|
+
|
|
177
|
+
def close(self):
|
|
178
|
+
# TODO: Error when already closed?
|
|
179
|
+
assert self.con is not None or self.result is None
|
|
180
|
+
if self.con is not None:
|
|
181
|
+
if self.result is not None:
|
|
182
|
+
self.result.close()
|
|
183
|
+
self.result = None
|
|
184
|
+
self.con.cursors.remove(self)
|
|
185
|
+
self.con = None
|
|
186
|
+
|
|
187
|
+
def __iter__(self):
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
def __next__(self):
|
|
191
|
+
n = self.fetchone()
|
|
192
|
+
if n is None:
|
|
193
|
+
raise StopIteration
|
|
194
|
+
return n
|
|
195
|
+
|
|
196
|
+
def derive_description(self, relframe):
|
|
197
|
+
self.description = []
|
|
198
|
+
for column in relframe.column_meta:
|
|
199
|
+
self.description.append(
|
|
200
|
+
(column.column_label, None, None, None, None, column.precision, column.scale, column.is_nullable))
|
|
201
|
+
|
|
202
|
+
def execute(self, query: str, params: List[Any] = None, *, fetch_size: int = None):
|
|
203
|
+
"""
|
|
204
|
+
Executes a SQL query.
|
|
205
|
+
"""
|
|
206
|
+
return self.executeany('sql', query, params, fetch_size=fetch_size)
|
|
207
|
+
|
|
208
|
+
def executemany(self, query: str, params: List[List[Any]]):
|
|
209
|
+
"""
|
|
210
|
+
Execute `query` once with each item in `params` as parameters.
|
|
211
|
+
"""
|
|
212
|
+
# TODO: Optimize, this is to exercise the execute code more
|
|
213
|
+
for param in params:
|
|
214
|
+
self.execute(query, param)
|
|
215
|
+
|
|
216
|
+
def executeany(self, lang: str, query: str, params: List[Any] = None, *,
|
|
217
|
+
fetch_size: int = None, namespace: str = None):
|
|
218
|
+
"""
|
|
219
|
+
This method is used to query Polypheny in any of the supported
|
|
220
|
+
languages. Dynamic parameter substitution is language
|
|
221
|
+
specific
|
|
222
|
+
|
|
223
|
+
:param lang:
|
|
224
|
+
:param query:
|
|
225
|
+
:param params:
|
|
226
|
+
:param namespace: Sets the default namespace for the query.
|
|
227
|
+
|
|
228
|
+
.. Note::
|
|
229
|
+
|
|
230
|
+
Queries returning graphs are not supported yet.
|
|
231
|
+
|
|
232
|
+
To query Polypheny using the MongoQL:
|
|
233
|
+
|
|
234
|
+
>>> cur.executeany('mongo', 'db.fruits.find({"id": 1})')
|
|
235
|
+
>>> print(cur.fetchone())
|
|
236
|
+
{'id': 1, 'name': 'Orange'}
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
if self.con is None:
|
|
240
|
+
raise Error("Cursor is closed")
|
|
241
|
+
|
|
242
|
+
self.reset()
|
|
243
|
+
|
|
244
|
+
if params is None: # Unparameterized query
|
|
245
|
+
r = self.con.con.execute_unparameterized_statement(lang, query, fetch_size, namespace)
|
|
246
|
+
assert r.HasField("result") # Is this always true?
|
|
247
|
+
statement_id = r.statement_id
|
|
248
|
+
result = r.result
|
|
249
|
+
elif type(params) == list or type(params) == tuple:
|
|
250
|
+
resp = self.con.con.prepare_indexed_statement(lang, query, namespace)
|
|
251
|
+
statement_id = resp.statement_id
|
|
252
|
+
result = self.con.con.execute_indexed_statement(statement_id, params, fetch_size)
|
|
253
|
+
elif type(params) == dict:
|
|
254
|
+
resp = self.con.con.prepare_named_statement(lang, query, namespace)
|
|
255
|
+
statement_id = resp.statement_id
|
|
256
|
+
result = self.con.con.execute_named_statement(statement_id, params, fetch_size)
|
|
257
|
+
else:
|
|
258
|
+
raise Error("Unexpected type for params " + str(type(params)))
|
|
259
|
+
|
|
260
|
+
if result.HasField(
|
|
261
|
+
"frame"): # TODO Better Error when one of the fetch* methods is invoked. Empty fake result?
|
|
262
|
+
self.rowcount = -1
|
|
263
|
+
if result.frame.WhichOneof('result') == 'relational_frame':
|
|
264
|
+
self.derive_description(result.frame.relational_frame)
|
|
265
|
+
frame = result.frame
|
|
266
|
+
else:
|
|
267
|
+
self.rowcount = result.scalar
|
|
268
|
+
frame = None
|
|
269
|
+
|
|
270
|
+
self.result = ResultCursor(self.con, statement_id, frame, fetch_size)
|
|
271
|
+
|
|
272
|
+
def fetchone(self):
|
|
273
|
+
if self.con is None:
|
|
274
|
+
raise ProgrammingError("Cursor is closed")
|
|
275
|
+
|
|
276
|
+
if self.result is None:
|
|
277
|
+
raise ProgrammingError("No statement was yet executed")
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
n = next(self.result)
|
|
281
|
+
except StopIteration:
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
if isinstance(n, relational_frame_pb2.Row):
|
|
285
|
+
v = []
|
|
286
|
+
for value in n.values:
|
|
287
|
+
v.append(proto2py(value))
|
|
288
|
+
return v
|
|
289
|
+
elif isinstance(n, value_pb2.ProtoDocument):
|
|
290
|
+
value = value_pb2.ProtoValue()
|
|
291
|
+
value.document.CopyFrom(n)
|
|
292
|
+
return proto2py(value)
|
|
293
|
+
else:
|
|
294
|
+
raise Error(f"Unknown result of type {type(n)}")
|
|
295
|
+
|
|
296
|
+
def fetchmany(self, size=None):
|
|
297
|
+
# TODO: Optimize, this is to exercise the fetch code more
|
|
298
|
+
if size is None:
|
|
299
|
+
size = self.arraysize
|
|
300
|
+
results = []
|
|
301
|
+
for _ in range(size):
|
|
302
|
+
row = self.fetchone()
|
|
303
|
+
if row is None:
|
|
304
|
+
break
|
|
305
|
+
results.append(row)
|
|
306
|
+
return results
|
|
307
|
+
|
|
308
|
+
def fetchall(self):
|
|
309
|
+
results = []
|
|
310
|
+
while True:
|
|
311
|
+
row = self.fetchone()
|
|
312
|
+
if row is None:
|
|
313
|
+
break
|
|
314
|
+
results.append(row)
|
|
315
|
+
return results
|
|
316
|
+
|
|
317
|
+
# optional
|
|
318
|
+
# def nextset(self):
|
|
319
|
+
# pass
|
|
320
|
+
|
|
321
|
+
def setinputsizes(self, sizes):
|
|
322
|
+
""" This method is a no-op. """
|
|
323
|
+
pass # We are free to do nothing
|
|
324
|
+
|
|
325
|
+
def setoutputsize(self, sizes, column=None):
|
|
326
|
+
""" This method is a no-op """
|
|
327
|
+
pass # We are free to do nothing
|
polypheny/exceptions.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright 2024 The Polypheny Project
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
class Warning(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Error(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class InterfaceError(Error):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DatabaseError(Error):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DataError(DatabaseError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OperationalError(DatabaseError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IntegrityError(DatabaseError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InternalError(DatabaseError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ProgrammingError(DatabaseError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NotSupportedError(DatabaseError):
|
|
52
|
+
pass
|
polypheny/interval.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright 2024 The Polypheny Project
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
def simple_plural(count, word):
|
|
16
|
+
if count == 1:
|
|
17
|
+
return word
|
|
18
|
+
else:
|
|
19
|
+
return word + "s"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def simple_str(count, word):
|
|
23
|
+
return f"{count} {simple_plural(count, word)}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IntervalMonthMilliseconds:
|
|
27
|
+
def __init__(self, months, milliseconds):
|
|
28
|
+
#: The number of months
|
|
29
|
+
self.months = months
|
|
30
|
+
self.milliseconds = milliseconds
|
|
31
|
+
|
|
32
|
+
def __eq__(self, other):
|
|
33
|
+
return isinstance(other,
|
|
34
|
+
IntervalMonthMilliseconds) and self.months == other.months and self.milliseconds == other.milliseconds
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return simple_str(self.months, "month") + " and " + simple_str(self.milliseconds, "millisecond")
|
polypheny/rpc.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Copyright 2024 The Polypheny Project
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import socket
|
|
17
|
+
|
|
18
|
+
from polypheny.exceptions import Error
|
|
19
|
+
from polypheny.serialize import *
|
|
20
|
+
from org.polypheny.prism import protointerface_pb2
|
|
21
|
+
from org.polypheny.prism import statement_requests_pb2
|
|
22
|
+
from org.polypheny.prism import transaction_requests_pb2
|
|
23
|
+
from org.polypheny.prism import connection_requests_pb2
|
|
24
|
+
from org.polypheny.prism import version
|
|
25
|
+
|
|
26
|
+
POLYPHENY_API_MAJOR = version.MAJOR_VERSION
|
|
27
|
+
POLYPHENY_API_MINOR = version.MINOR_VERSION
|
|
28
|
+
|
|
29
|
+
class PlainTransport:
|
|
30
|
+
VERSION = "plain-v1@polypheny.com"
|
|
31
|
+
|
|
32
|
+
def __init__(self, address):
|
|
33
|
+
self.con = socket.create_connection(address)
|
|
34
|
+
self.con.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
35
|
+
self.exchange_version(self.VERSION)
|
|
36
|
+
|
|
37
|
+
def exchange_version(self, version):
|
|
38
|
+
bl = self.con.recv(1)
|
|
39
|
+
if len(bl) != 1:
|
|
40
|
+
raise EOFError
|
|
41
|
+
n = int.from_bytes(bl, byteorder='little')
|
|
42
|
+
if n > 127:
|
|
43
|
+
raise Error("Invalid version length")
|
|
44
|
+
remote_version = self.con.recv(n)
|
|
45
|
+
if remote_version[-1] != 0x0a:
|
|
46
|
+
raise Error("Invalid version message")
|
|
47
|
+
|
|
48
|
+
if remote_version[0:-1] != version.encode():
|
|
49
|
+
raise Error(f"Unsupported version: {repr(remote_version[0:-1])} expected {version.encode()}")
|
|
50
|
+
|
|
51
|
+
self.con.sendall(bl + remote_version)
|
|
52
|
+
|
|
53
|
+
def send_msg(self, serialized):
|
|
54
|
+
n = len(serialized)
|
|
55
|
+
bl = n.to_bytes(length=8, byteorder='little')
|
|
56
|
+
self.con.sendall(bl + serialized)
|
|
57
|
+
|
|
58
|
+
def recv_msg(self):
|
|
59
|
+
bl = self.con.recv(8)
|
|
60
|
+
if len(bl) != 8:
|
|
61
|
+
raise EOFError
|
|
62
|
+
n = int.from_bytes(bl, 'little')
|
|
63
|
+
raw = self.con.recv(n)
|
|
64
|
+
if len(raw) != n:
|
|
65
|
+
raise EOFError
|
|
66
|
+
return raw
|
|
67
|
+
|
|
68
|
+
def close(self):
|
|
69
|
+
if self.con is not None:
|
|
70
|
+
self.con.close()
|
|
71
|
+
self.con = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class UnixTransport(PlainTransport):
|
|
75
|
+
VERSION = "unix-v1@polypheny.com"
|
|
76
|
+
|
|
77
|
+
def __init__(self, path):
|
|
78
|
+
self.con = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
79
|
+
if path is None:
|
|
80
|
+
path = os.path.expanduser("~/.polypheny/polypheny-prism.sock")
|
|
81
|
+
self.con.connect(path)
|
|
82
|
+
self.exchange_version(self.VERSION)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Connection:
|
|
86
|
+
def __init__(self, address, transport, kwargs):
|
|
87
|
+
if transport == "plain":
|
|
88
|
+
self.con = PlainTransport(address)
|
|
89
|
+
elif transport == "unix":
|
|
90
|
+
self.con = UnixTransport(address)
|
|
91
|
+
else:
|
|
92
|
+
raise Exception("Unknown transport: " + transport)
|
|
93
|
+
self.id = 1
|
|
94
|
+
|
|
95
|
+
def close(self):
|
|
96
|
+
if self.con is None:
|
|
97
|
+
return
|
|
98
|
+
try:
|
|
99
|
+
self.disconnect()
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
self.con.close()
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
self.con = None
|
|
108
|
+
|
|
109
|
+
def new_request(self):
|
|
110
|
+
msg = protointerface_pb2.Request()
|
|
111
|
+
msg.id = self.id
|
|
112
|
+
self.id += 1
|
|
113
|
+
return msg
|
|
114
|
+
|
|
115
|
+
def send_msg(self, msg):
|
|
116
|
+
self.con.send_msg(msg.SerializeToString())
|
|
117
|
+
|
|
118
|
+
def recv_msg(self):
|
|
119
|
+
r = protointerface_pb2.Response()
|
|
120
|
+
r.ParseFromString(self.con.recv_msg())
|
|
121
|
+
if r.WhichOneof('type') == 'error_response':
|
|
122
|
+
# TODO: Add to error_response something to decide if this is necessary
|
|
123
|
+
# self.con.close()
|
|
124
|
+
# self.con = None
|
|
125
|
+
raise Error(r.error_response.message)
|
|
126
|
+
return r
|
|
127
|
+
|
|
128
|
+
def call(self, msg):
|
|
129
|
+
self.send_msg(msg)
|
|
130
|
+
response = self.recv_msg()
|
|
131
|
+
assert response.id == msg.id
|
|
132
|
+
assert response.last
|
|
133
|
+
return response
|
|
134
|
+
|
|
135
|
+
def connect(self, username, password, auto_commit):
|
|
136
|
+
msg = self.new_request()
|
|
137
|
+
req = msg.connection_request
|
|
138
|
+
if username is not None:
|
|
139
|
+
req.username = username
|
|
140
|
+
if password is not None:
|
|
141
|
+
req.password = password
|
|
142
|
+
req.major_api_version = POLYPHENY_API_MAJOR
|
|
143
|
+
req.minor_api_version = POLYPHENY_API_MINOR
|
|
144
|
+
req.connection_properties.is_auto_commit = auto_commit
|
|
145
|
+
|
|
146
|
+
return self.call(msg).connection_response
|
|
147
|
+
|
|
148
|
+
def disconnect(self):
|
|
149
|
+
msg = self.new_request()
|
|
150
|
+
req = connection_requests_pb2.DisconnectRequest()
|
|
151
|
+
msg.disconnect_request.MergeFrom(req)
|
|
152
|
+
|
|
153
|
+
return self.call(msg).disconnect_response
|
|
154
|
+
|
|
155
|
+
def commit(self):
|
|
156
|
+
msg = self.new_request()
|
|
157
|
+
req = transaction_requests_pb2.CommitRequest()
|
|
158
|
+
msg.commit_request.MergeFrom(req)
|
|
159
|
+
|
|
160
|
+
return self.call(msg).commit_response
|
|
161
|
+
|
|
162
|
+
def rollback(self):
|
|
163
|
+
msg = self.new_request()
|
|
164
|
+
req = transaction_requests_pb2.RollbackRequest()
|
|
165
|
+
msg.rollback_request.MergeFrom(req)
|
|
166
|
+
return self.call(msg).rollback_response
|
|
167
|
+
|
|
168
|
+
def execute_unparameterized_statement(self, language_name, statement, fetch_size, namespace):
|
|
169
|
+
msg = self.new_request()
|
|
170
|
+
req = statement_requests_pb2.ExecuteUnparameterizedStatementRequest()
|
|
171
|
+
req.language_name = language_name
|
|
172
|
+
req.statement = statement
|
|
173
|
+
if fetch_size:
|
|
174
|
+
req.fetch_size = fetch_size
|
|
175
|
+
if namespace:
|
|
176
|
+
req.namespace_name = namespace
|
|
177
|
+
|
|
178
|
+
msg.execute_unparameterized_statement_request.MergeFrom(req)
|
|
179
|
+
|
|
180
|
+
self.send_msg(msg)
|
|
181
|
+
self.recv_msg()
|
|
182
|
+
r = self.recv_msg()
|
|
183
|
+
assert r.id == msg.id
|
|
184
|
+
assert r.last
|
|
185
|
+
return r.statement_response
|
|
186
|
+
|
|
187
|
+
def prepare_indexed_statement(self, language_name, statement, namespace):
|
|
188
|
+
msg = self.new_request()
|
|
189
|
+
req = msg.prepare_indexed_statement_request
|
|
190
|
+
req.language_name = language_name
|
|
191
|
+
req.statement = statement
|
|
192
|
+
if namespace:
|
|
193
|
+
req.namespace_name = namespace
|
|
194
|
+
return self.call(msg).prepared_statement_signature
|
|
195
|
+
|
|
196
|
+
def execute_indexed_statement(self, statement_id, params, fetch_size):
|
|
197
|
+
msg = self.new_request()
|
|
198
|
+
req = msg.execute_indexed_statement_request
|
|
199
|
+
req.statement_id = statement_id
|
|
200
|
+
req.parameters.parameters.extend(list(map(py2proto, params)))
|
|
201
|
+
if fetch_size:
|
|
202
|
+
req.fetch_size = fetch_size
|
|
203
|
+
return self.call(msg).statement_result
|
|
204
|
+
|
|
205
|
+
def prepare_named_statement(self, language_name, statement, namespace):
|
|
206
|
+
msg = self.new_request()
|
|
207
|
+
req = msg.prepare_named_statement_request
|
|
208
|
+
req.language_name = language_name
|
|
209
|
+
req.statement = statement
|
|
210
|
+
if namespace:
|
|
211
|
+
req.namespace_name = namespace
|
|
212
|
+
return self.call(msg).prepared_statement_signature
|
|
213
|
+
|
|
214
|
+
def execute_named_statement(self, statement_id, params, fetch_size):
|
|
215
|
+
msg = self.new_request()
|
|
216
|
+
req = msg.execute_named_statement_request
|
|
217
|
+
req.statement_id = statement_id
|
|
218
|
+
if fetch_size:
|
|
219
|
+
req.fetch_size = fetch_size
|
|
220
|
+
for k, v in params.items():
|
|
221
|
+
py2proto(v, req.parameters.parameters[k])
|
|
222
|
+
return self.call(msg).statement_result
|
|
223
|
+
|
|
224
|
+
def fetch(self, statement_id, fetch_size):
|
|
225
|
+
msg = self.new_request()
|
|
226
|
+
req = msg.fetch_request
|
|
227
|
+
req.statement_id = statement_id
|
|
228
|
+
if fetch_size:
|
|
229
|
+
req.fetch_size = fetch_size
|
|
230
|
+
|
|
231
|
+
return self.call(msg).frame
|
|
232
|
+
|
|
233
|
+
def close_statement(self, statement_id):
|
|
234
|
+
msg = self.new_request()
|
|
235
|
+
msg.close_statement_request.statement_id = statement_id
|
|
236
|
+
return self.call(msg).close_statement_response
|
polypheny/serialize.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Copyright 2024 The Polypheny Project
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import datetime
|
|
16
|
+
import decimal
|
|
17
|
+
from functools import reduce
|
|
18
|
+
|
|
19
|
+
import polypheny.interval as interval
|
|
20
|
+
from org.polypheny.prism import value_pb2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def serialize_big_decimal(v, value):
|
|
24
|
+
sign, digits, exponent = value.as_tuple()
|
|
25
|
+
sign = -2 * sign + 1
|
|
26
|
+
unscaled = sign * reduce(lambda r, d: r * 10 + d, digits)
|
|
27
|
+
l = unscaled.bit_length() + 1 # add sign bit
|
|
28
|
+
n = (l + 8) >> 3
|
|
29
|
+
v.big_decimal.unscaled_value = unscaled.to_bytes(n, byteorder='big', signed=True)
|
|
30
|
+
v.big_decimal.scale = -exponent
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# See ProtoValueDeserializer
|
|
34
|
+
def py2proto(value, v=None):
|
|
35
|
+
if v is None:
|
|
36
|
+
v = value_pb2.ProtoValue()
|
|
37
|
+
if type(value) is bool:
|
|
38
|
+
v.boolean.boolean = value
|
|
39
|
+
elif type(value) is int:
|
|
40
|
+
if -2 ** 31 <= value <= 2 ** 31 - 1:
|
|
41
|
+
v.integer.integer = value
|
|
42
|
+
elif -2 ** 63 <= value <= 2 ** 63 - 1:
|
|
43
|
+
v.long.long = value
|
|
44
|
+
else:
|
|
45
|
+
serialize_big_decimal(v, decimal.Decimal(value))
|
|
46
|
+
elif type(value) is float:
|
|
47
|
+
# TODO: Always use decimal?
|
|
48
|
+
v.double.double = value
|
|
49
|
+
elif type(value) is decimal.Decimal:
|
|
50
|
+
serialize_big_decimal(v, value)
|
|
51
|
+
elif type(value) is datetime.date:
|
|
52
|
+
diff = value - datetime.date(1970, 1, 1)
|
|
53
|
+
v.date.date = diff.days
|
|
54
|
+
elif type(value) is datetime.time:
|
|
55
|
+
v.time.time = (value.hour * 3600 + value.minute * 60 + value.second) * 1000 + value.microsecond * 10
|
|
56
|
+
elif type(value) is datetime.datetime:
|
|
57
|
+
v.timestamp.timestamp = int(value.timestamp() * 1000)
|
|
58
|
+
elif type(value) is str:
|
|
59
|
+
v.string.string = value
|
|
60
|
+
elif type(value) is bytes:
|
|
61
|
+
v.binary.binary = value
|
|
62
|
+
elif value is None:
|
|
63
|
+
v.null.CopyFrom(value_pb2.ProtoNull())
|
|
64
|
+
elif type(value) is list:
|
|
65
|
+
for element in value:
|
|
66
|
+
v.list.values.append(py2proto(element))
|
|
67
|
+
else:
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
return v
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def parse_big_decimal(value):
|
|
74
|
+
raw = value.unscaled_value
|
|
75
|
+
scale = value.scale
|
|
76
|
+
n = int.from_bytes(raw, byteorder='big', signed=True)
|
|
77
|
+
sign = 0
|
|
78
|
+
if n < 0:
|
|
79
|
+
sign = 1
|
|
80
|
+
n = -n
|
|
81
|
+
return decimal.Decimal((sign, tuple(map(int, str(n))), -scale))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def proto2py(value):
|
|
85
|
+
name = value.WhichOneof("value")
|
|
86
|
+
assert name is not None
|
|
87
|
+
if name == "boolean":
|
|
88
|
+
return value.boolean.boolean
|
|
89
|
+
elif name == "integer":
|
|
90
|
+
return value.integer.integer
|
|
91
|
+
elif name == "long":
|
|
92
|
+
return value.long.long
|
|
93
|
+
elif name == "big_decimal":
|
|
94
|
+
return parse_big_decimal(value.big_decimal)
|
|
95
|
+
elif name == "float":
|
|
96
|
+
return value.float.float
|
|
97
|
+
elif name == "double":
|
|
98
|
+
return value.double.double
|
|
99
|
+
elif name == "date":
|
|
100
|
+
return datetime.date(1970, 1, 1) + datetime.timedelta(days=value.date.date)
|
|
101
|
+
elif name == "time":
|
|
102
|
+
t = value.time.time
|
|
103
|
+
millis = t % 1000
|
|
104
|
+
t = t / 1000
|
|
105
|
+
hour = int(t / 3600)
|
|
106
|
+
t = t % 3600
|
|
107
|
+
minute = int(t / 60)
|
|
108
|
+
t = t % 60
|
|
109
|
+
second = int(t)
|
|
110
|
+
return datetime.time(hour, minute, second, microsecond=int(millis * 1000))
|
|
111
|
+
elif name == "timestamp":
|
|
112
|
+
return datetime.datetime.fromtimestamp(value.timestamp.timestamp / 1000, datetime.timezone.utc)
|
|
113
|
+
elif name == "interval":
|
|
114
|
+
return interval.IntervalMonthMilliseconds(value.interval.months, value.interval.milliseconds)
|
|
115
|
+
elif name == "string":
|
|
116
|
+
return value.string.string
|
|
117
|
+
elif name == "binary":
|
|
118
|
+
return value.binary.binary
|
|
119
|
+
elif name == "null":
|
|
120
|
+
return None
|
|
121
|
+
elif name == "list":
|
|
122
|
+
return list(map(lambda e: proto2py(e), value.list.values))
|
|
123
|
+
elif name == "document":
|
|
124
|
+
return {key: proto2py(entry) for key, entry in value.document.entries.items()}
|
|
125
|
+
else:
|
|
126
|
+
raise RuntimeError("Unhandled value type")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: polypheny
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Driver for Polypheny
|
|
5
|
+
Home-page: https://polypheny.com/
|
|
6
|
+
Author: The Polypheny Project
|
|
7
|
+
Author-email: mail@polypheny.org
|
|
8
|
+
License: Apache License, Version 2.0
|
|
9
|
+
Project-URL: Documentation, https://docs.polypheny.com/en/latest/drivers/python/overview
|
|
10
|
+
Project-URL: Code, https://github.com/polypheny/Polypheny-Connector-Python
|
|
11
|
+
Project-URL: Issue tracker, https://github.com/polypheny/Polypheny-DB/labels/A-python
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: polypheny-prism-api==2.2
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: project-url
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<a href="https://polypheny.org/">
|
|
30
|
+
<picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/polypheny/Admin/master/Logo/logo-white-text_cropped.png">
|
|
31
|
+
<img width='50%' alt="Light: 'Resume application project app icon' Dark: 'Resume application project app icon'" src="https://raw.githubusercontent.com/polypheny/Admin/master/Logo/logo-transparent_cropped.png">
|
|
32
|
+
</picture>
|
|
33
|
+
</a>
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Polypheny Connector for Python
|
|
38
|
+
|
|
39
|
+
This enables Python programs to access Polypheny databases, using an API that is compliant with the [Python Database API Specification v2.0 (PEP 249)](https://www.python.org/dev/peps/pep-0249/).
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
The recommended way to install the Python Connector for Polypheny is via pip:
|
|
45
|
+
```bash
|
|
46
|
+
pip install polypheny
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Alternatively, if you prefer to install the package manually you can also download the latest [release](https://github.com/polypheny/Polypheny-Connector-Python/releases/), extract the archive and install it manually:
|
|
50
|
+
```bash
|
|
51
|
+
cd /path/to/polyphney-connector-python-x.y.z/
|
|
52
|
+
python setup.py install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The connector uses the Prism 2.2 client definitions and connects to servers
|
|
56
|
+
that report a compatible Prism 2.x API. The minor versions do not have to match.
|
|
57
|
+
Document collection metadata requires server-side support introduced in Prism 2.2.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Getting Started
|
|
61
|
+
|
|
62
|
+
A few examples of the most common functionalities provided by the adapter:
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
```python3
|
|
66
|
+
import polypheny
|
|
67
|
+
|
|
68
|
+
# Connect to locally running Polypheny via UNIX sockets (on Linux, BSD and macOS)
|
|
69
|
+
con = polypheny.connect()
|
|
70
|
+
|
|
71
|
+
# Unencrypted over the network (all systems)
|
|
72
|
+
con = polypheny.connect(
|
|
73
|
+
('127.0.0.1', 20590),
|
|
74
|
+
username='pa',
|
|
75
|
+
password='',
|
|
76
|
+
transport='plain',
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Get a cursor
|
|
80
|
+
cursor = con.cursor()
|
|
81
|
+
|
|
82
|
+
# Create a new table
|
|
83
|
+
cursor.execute("CREATE TABLE dummy (id INT NOT NULL, text VARCHAR(2), num INT, PRIMARY KEY(id))")
|
|
84
|
+
|
|
85
|
+
# Insert values into table
|
|
86
|
+
cursor.execute("INSERT INTO dummy VALUES (407 , 'de', 93)")
|
|
87
|
+
con.commit()
|
|
88
|
+
|
|
89
|
+
# Execute a query
|
|
90
|
+
cursor.execute("SELECT * from dummy")
|
|
91
|
+
|
|
92
|
+
print("\nRelational results from SQL")
|
|
93
|
+
for row in cursor:
|
|
94
|
+
print("\t", row)
|
|
95
|
+
|
|
96
|
+
# Accessing data using MQL
|
|
97
|
+
cursor.executeany('mongo', 'db.dummy.find()', namespace='public')
|
|
98
|
+
|
|
99
|
+
print("\nDocument results from MQL (as Python dicts)")
|
|
100
|
+
for doc in cursor:
|
|
101
|
+
print("\t", doc)
|
|
102
|
+
|
|
103
|
+
cursor.execute("DROP TABLE dummy")
|
|
104
|
+
|
|
105
|
+
# Close the connection
|
|
106
|
+
con.close()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
An in-depth and more detailed documentation can be found [here](https://docs.polypheny.com/en/latest/drivers/python/overview).
|
|
110
|
+
|
|
111
|
+
## Tests
|
|
112
|
+
Run the tests with coverage report:
|
|
113
|
+
```
|
|
114
|
+
coverage run --source polypheny -m pytest && coverage report -m
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Roadmap
|
|
118
|
+
See the [open issues](https://github.com/polypheny/Polypheny-DB/labels/A-python) for a list of proposed features (and known issues).
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
We highly welcome your contributions to the _Polypheny Connector for Python_. If you would like to contribute, please fork the repository and submit your changes as a pull request. Please consult our [Admin Repository](https://github.com/polypheny/Admin) and our [Website](https://polypheny.org) for guidelines and additional information.
|
|
123
|
+
|
|
124
|
+
Please note that we have a [code of conduct](https://github.com/polypheny/Admin/blob/master/CODE_OF_CONDUCT.md). Please follow it in all your interactions with the project.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
The Apache 2.0 License
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
polypheny/__init__.py,sha256=Gy_a0Pl5xMERO1sdruPZyOAuDE2efp54dwU6P62X_6k,2506
|
|
2
|
+
polypheny/connection.py,sha256=VF_aGDUztO6967yiYKUsxjT2zRlDCirQVBvNmbR0HdA,10696
|
|
3
|
+
polypheny/exceptions.py,sha256=3IygiyicfIDhDkihKz0hTJEY_00SLeqgYAiAtradQac,1020
|
|
4
|
+
polypheny/interval.py,sha256=4j5O3SyxSl-gkHZxfE-WUIj4w10e2XDO3i5aH45O6lY,1275
|
|
5
|
+
polypheny/rpc.py,sha256=7MFPzpqsVHlFE8H8qAbwRZKEQ23sBYsKaBWWwINHbJc,7830
|
|
6
|
+
polypheny/serialize.py,sha256=6WA94_4e4cKXEinJtn7zYKi2s4tGni-94XOvbY87qkY,4313
|
|
7
|
+
polypheny-2.0.0.dist-info/licenses/LICENSE,sha256=DOwG4OVfvD3FzuT8qbYH9my49OTbzzs8ATWU3RVnMuk,10173
|
|
8
|
+
polypheny-2.0.0.dist-info/METADATA,sha256=6drRJOa33ea3FxqKWkaUWXyXVxAaKTnuMIL5tXhGJ1o,4219
|
|
9
|
+
polypheny-2.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
10
|
+
polypheny-2.0.0.dist-info/top_level.txt,sha256=-cr7O-g-bJyNLJiFl5yJZMLGHlsSlq3p_DmKYX3Oa5k,10
|
|
11
|
+
polypheny-2.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
polypheny
|