gremlinpython 3.6.8__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.
- gremlin_python/__init__.py +20 -0
- gremlin_python/__version__.py +20 -0
- gremlin_python/driver/__init__.py +20 -0
- gremlin_python/driver/aiohttp/__init__.py +18 -0
- gremlin_python/driver/aiohttp/transport.py +242 -0
- gremlin_python/driver/client.py +206 -0
- gremlin_python/driver/connection.py +106 -0
- gremlin_python/driver/driver_remote_connection.py +183 -0
- gremlin_python/driver/protocol.py +259 -0
- gremlin_python/driver/remote_connection.py +84 -0
- gremlin_python/driver/request.py +25 -0
- gremlin_python/driver/resultset.py +100 -0
- gremlin_python/driver/serializer.py +294 -0
- gremlin_python/driver/transport.py +45 -0
- gremlin_python/driver/useragent.py +37 -0
- gremlin_python/process/__init__.py +20 -0
- gremlin_python/process/anonymous_traversal.py +64 -0
- gremlin_python/process/graph_traversal.py +2301 -0
- gremlin_python/process/strategies.py +239 -0
- gremlin_python/process/translator.py +297 -0
- gremlin_python/process/traversal.py +875 -0
- gremlin_python/statics.py +117 -0
- gremlin_python/structure/__init__.py +20 -0
- gremlin_python/structure/graph.py +134 -0
- gremlin_python/structure/io/__init__.py +20 -0
- gremlin_python/structure/io/graphbinaryV1.py +1153 -0
- gremlin_python/structure/io/graphsonV2d0.py +646 -0
- gremlin_python/structure/io/graphsonV3d0.py +766 -0
- gremlin_python/structure/io/util.py +60 -0
- gremlinpython-3.6.8.data/data/LICENSE +202 -0
- gremlinpython-3.6.8.data/data/NOTICE +5 -0
- gremlinpython-3.6.8.dist-info/LICENSE +202 -0
- gremlinpython-3.6.8.dist-info/METADATA +147 -0
- gremlinpython-3.6.8.dist-info/NOTICE +5 -0
- gremlinpython-3.6.8.dist-info/RECORD +37 -0
- gremlinpython-3.6.8.dist-info/WHEEL +5 -0
- gremlinpython-3.6.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
or more contributor license agreements. See the NOTICE file
|
|
4
|
+
distributed with this work for additional information
|
|
5
|
+
regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
to you under the Apache License, Version 2.0 (the
|
|
7
|
+
"License"); you may not use this file except in compliance
|
|
8
|
+
with the License. You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Unless required by applicable law or agreed to in writing,
|
|
13
|
+
software distributed under the License is distributed on an
|
|
14
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
KIND, either express or implied. See the License for the
|
|
16
|
+
specific language governing permissions and limitations
|
|
17
|
+
under the License.
|
|
18
|
+
'''
|
|
19
|
+
version = '3.6.8'
|
|
20
|
+
timestamp = 1730222094
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
#
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
#
|
|
19
|
+
import aiohttp
|
|
20
|
+
import asyncio
|
|
21
|
+
import async_timeout
|
|
22
|
+
from aiohttp import ClientResponseError
|
|
23
|
+
|
|
24
|
+
from gremlin_python.driver.transport import AbstractBaseTransport
|
|
25
|
+
|
|
26
|
+
__author__ = 'Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AiohttpTransport(AbstractBaseTransport):
|
|
30
|
+
nest_asyncio_applied = False
|
|
31
|
+
|
|
32
|
+
def __init__(self, call_from_event_loop=None, read_timeout=None, write_timeout=None, enable_compression=False,
|
|
33
|
+
**kwargs):
|
|
34
|
+
if call_from_event_loop is not None and call_from_event_loop and not AiohttpTransport.nest_asyncio_applied:
|
|
35
|
+
"""
|
|
36
|
+
The AiohttpTransport implementation uses the asyncio event loop. Because of this, it cannot be called
|
|
37
|
+
within an event loop without nest_asyncio. If the code is ever refactored so that it can be called
|
|
38
|
+
within an event loop this import and call can be removed. Without this, applications which use the
|
|
39
|
+
event loop to call gremlin-python (such as Jupyter) will not work.
|
|
40
|
+
"""
|
|
41
|
+
import nest_asyncio
|
|
42
|
+
nest_asyncio.apply()
|
|
43
|
+
AiohttpTransport.nest_asyncio_applied = True
|
|
44
|
+
|
|
45
|
+
# Start event loop and initialize websocket and client to None
|
|
46
|
+
self._loop = asyncio.new_event_loop()
|
|
47
|
+
self._websocket = None
|
|
48
|
+
self._client_session = None
|
|
49
|
+
|
|
50
|
+
# Set all inner variables to parameters passed in.
|
|
51
|
+
self._aiohttp_kwargs = kwargs
|
|
52
|
+
self._write_timeout = write_timeout
|
|
53
|
+
self._read_timeout = read_timeout
|
|
54
|
+
self._enable_compression = enable_compression
|
|
55
|
+
if "max_content_length" in self._aiohttp_kwargs:
|
|
56
|
+
self._aiohttp_kwargs["max_msg_size"] = self._aiohttp_kwargs.pop("max_content_length")
|
|
57
|
+
if "ssl_options" in self._aiohttp_kwargs:
|
|
58
|
+
self._aiohttp_kwargs["ssl"] = self._aiohttp_kwargs.pop("ssl_options")
|
|
59
|
+
if self._enable_compression and "compress" not in self._aiohttp_kwargs:
|
|
60
|
+
self._aiohttp_kwargs["compress"] = 15 # enable per-message deflate compression with 32k sliding window size
|
|
61
|
+
|
|
62
|
+
def __del__(self):
|
|
63
|
+
# Close will only actually close if things are left open, so this is safe to call.
|
|
64
|
+
# Clean up any connection resources and close the event loop.
|
|
65
|
+
self.close()
|
|
66
|
+
|
|
67
|
+
def connect(self, url, headers=None):
|
|
68
|
+
# Inner function to perform async connect.
|
|
69
|
+
async def async_connect():
|
|
70
|
+
# Start client session and use it to create a websocket with all the connection options provided.
|
|
71
|
+
self._client_session = aiohttp.ClientSession(loop=self._loop)
|
|
72
|
+
try:
|
|
73
|
+
self._websocket = await self._client_session.ws_connect(url, **self._aiohttp_kwargs, headers=headers)
|
|
74
|
+
except ClientResponseError as err:
|
|
75
|
+
# If 403, just send forbidden because in some cases this prints out a huge verbose message
|
|
76
|
+
# that includes credentials.
|
|
77
|
+
if err.status == 403:
|
|
78
|
+
raise Exception('Failed to connect to server: HTTP Error code 403 - Forbidden.')
|
|
79
|
+
else:
|
|
80
|
+
raise
|
|
81
|
+
|
|
82
|
+
# Execute the async connect synchronously.
|
|
83
|
+
self._loop.run_until_complete(async_connect())
|
|
84
|
+
|
|
85
|
+
def write(self, message):
|
|
86
|
+
# Inner function to perform async write.
|
|
87
|
+
async def async_write():
|
|
88
|
+
async with async_timeout.timeout(self._write_timeout):
|
|
89
|
+
await self._websocket.send_bytes(message)
|
|
90
|
+
|
|
91
|
+
# Execute the async write synchronously.
|
|
92
|
+
self._loop.run_until_complete(async_write())
|
|
93
|
+
|
|
94
|
+
def read(self):
|
|
95
|
+
# Inner function to perform async read.
|
|
96
|
+
async def async_read():
|
|
97
|
+
async with async_timeout.timeout(self._read_timeout):
|
|
98
|
+
return await self._websocket.receive()
|
|
99
|
+
|
|
100
|
+
# Execute the async read synchronously.
|
|
101
|
+
msg = self._loop.run_until_complete(async_read())
|
|
102
|
+
|
|
103
|
+
# Need to handle multiple potential message types.
|
|
104
|
+
if msg.type == aiohttp.WSMsgType.close:
|
|
105
|
+
# Server is closing connection, shutdown and throw exception.
|
|
106
|
+
self.close()
|
|
107
|
+
raise RuntimeError("Connection was closed by server.")
|
|
108
|
+
elif msg.type == aiohttp.WSMsgType.closed:
|
|
109
|
+
# Should not be possible since our loop and socket would be closed.
|
|
110
|
+
raise RuntimeError("Connection was already closed.")
|
|
111
|
+
elif msg.type == aiohttp.WSMsgType.error:
|
|
112
|
+
# Error on connection, try to convert message to a string in error.
|
|
113
|
+
raise RuntimeError("Received error on read: '" + str(msg.data) + "'")
|
|
114
|
+
elif msg.type == aiohttp.WSMsgType.text:
|
|
115
|
+
# Convert message to bytes.
|
|
116
|
+
data = msg.data.strip().encode('utf-8')
|
|
117
|
+
else:
|
|
118
|
+
# General handle, return byte data.
|
|
119
|
+
data = msg.data
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
def close(self):
|
|
123
|
+
# Inner function to perform async close.
|
|
124
|
+
async def async_close():
|
|
125
|
+
if self._websocket is not None and not self._websocket.closed:
|
|
126
|
+
await self._websocket.close()
|
|
127
|
+
self._websocket = None
|
|
128
|
+
|
|
129
|
+
if self._client_session is not None and not self._client_session.closed:
|
|
130
|
+
await self._client_session.close()
|
|
131
|
+
self._client_session = None
|
|
132
|
+
|
|
133
|
+
# If the loop is not closed (connection hasn't already been closed)
|
|
134
|
+
if not self._loop.is_closed():
|
|
135
|
+
# Execute the async close synchronously.
|
|
136
|
+
self._loop.run_until_complete(async_close())
|
|
137
|
+
|
|
138
|
+
# Close the event loop.
|
|
139
|
+
self._loop.close()
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def closed(self):
|
|
143
|
+
# Connection is closed if either the websocket or the client session is closed.
|
|
144
|
+
return self._websocket.closed or self._client_session.closed
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class AiohttpHTTPTransport(AbstractBaseTransport):
|
|
148
|
+
nest_asyncio_applied = False
|
|
149
|
+
|
|
150
|
+
def __init__(self, call_from_event_loop=None, read_timeout=None, write_timeout=None, **kwargs):
|
|
151
|
+
if call_from_event_loop is not None and call_from_event_loop and not AiohttpTransport.nest_asyncio_applied:
|
|
152
|
+
"""
|
|
153
|
+
The AiohttpTransport implementation uses the asyncio event loop. Because of this, it cannot be called
|
|
154
|
+
within an event loop without nest_asyncio. If the code is ever refactored so that it can be called
|
|
155
|
+
within an event loop this import and call can be removed. Without this, applications which use the
|
|
156
|
+
event loop to call gremlin-python (such as Jupyter) will not work.
|
|
157
|
+
"""
|
|
158
|
+
import nest_asyncio
|
|
159
|
+
nest_asyncio.apply()
|
|
160
|
+
AiohttpTransport.nest_asyncio_applied = True
|
|
161
|
+
|
|
162
|
+
# Start event loop and initialize client session and response to None
|
|
163
|
+
self._loop = asyncio.new_event_loop()
|
|
164
|
+
self._client_session = None
|
|
165
|
+
self._http_req_resp = None
|
|
166
|
+
self._enable_ssl = False
|
|
167
|
+
|
|
168
|
+
# Set all inner variables to parameters passed in.
|
|
169
|
+
self._aiohttp_kwargs = kwargs
|
|
170
|
+
self._write_timeout = write_timeout
|
|
171
|
+
self._read_timeout = read_timeout
|
|
172
|
+
if "ssl_options" in self._aiohttp_kwargs:
|
|
173
|
+
self._ssl_context = self._aiohttp_kwargs.pop("ssl_options")
|
|
174
|
+
self._enable_ssl = True
|
|
175
|
+
|
|
176
|
+
def __del__(self):
|
|
177
|
+
# Close will only actually close if things are left open, so this is safe to call.
|
|
178
|
+
# Clean up any connection resources and close the event loop.
|
|
179
|
+
self.close()
|
|
180
|
+
|
|
181
|
+
def connect(self, url, headers=None):
|
|
182
|
+
# Inner function to perform async connect.
|
|
183
|
+
async def async_connect():
|
|
184
|
+
# Start client session and use it to send all HTTP requests. Base url is the endpoint, headers are set here
|
|
185
|
+
# Base url can only parse basic url with no path, see https://github.com/aio-libs/aiohttp/issues/6647
|
|
186
|
+
if self._enable_ssl:
|
|
187
|
+
# ssl context is established through tcp connector
|
|
188
|
+
tcp_conn = aiohttp.TCPConnector(ssl_context=self._ssl_context)
|
|
189
|
+
self._client_session = aiohttp.ClientSession(connector=tcp_conn,
|
|
190
|
+
base_url=url, headers=headers, loop=self._loop)
|
|
191
|
+
else:
|
|
192
|
+
self._client_session = aiohttp.ClientSession(base_url=url, headers=headers, loop=self._loop)
|
|
193
|
+
|
|
194
|
+
# Execute the async connect synchronously.
|
|
195
|
+
self._loop.run_until_complete(async_connect())
|
|
196
|
+
|
|
197
|
+
def write(self, message):
|
|
198
|
+
# Inner function to perform async write.
|
|
199
|
+
async def async_write():
|
|
200
|
+
basic_auth = None
|
|
201
|
+
# basic password authentication for https connections
|
|
202
|
+
if message['auth']:
|
|
203
|
+
basic_auth = aiohttp.BasicAuth(message['auth']['username'], message['auth']['password'])
|
|
204
|
+
async with async_timeout.timeout(self._write_timeout):
|
|
205
|
+
self._http_req_resp = await self._client_session.post(url="/gremlin",
|
|
206
|
+
auth=basic_auth,
|
|
207
|
+
data=message['payload'],
|
|
208
|
+
headers=message['headers'],
|
|
209
|
+
**self._aiohttp_kwargs)
|
|
210
|
+
|
|
211
|
+
# Execute the async write synchronously.
|
|
212
|
+
self._loop.run_until_complete(async_write())
|
|
213
|
+
|
|
214
|
+
def read(self):
|
|
215
|
+
# Inner function to perform async read.
|
|
216
|
+
async def async_read():
|
|
217
|
+
async with async_timeout.timeout(self._read_timeout):
|
|
218
|
+
return {"content": await self._http_req_resp.read(),
|
|
219
|
+
"ok": self._http_req_resp.ok,
|
|
220
|
+
"status": self._http_req_resp.status}
|
|
221
|
+
|
|
222
|
+
return self._loop.run_until_complete(async_read())
|
|
223
|
+
|
|
224
|
+
def close(self):
|
|
225
|
+
# Inner function to perform async close.
|
|
226
|
+
async def async_close():
|
|
227
|
+
if self._client_session is not None and not self._client_session.closed:
|
|
228
|
+
await self._client_session.close()
|
|
229
|
+
self._client_session = None
|
|
230
|
+
|
|
231
|
+
# If the loop is not closed (connection hasn't already been closed)
|
|
232
|
+
if not self._loop.is_closed():
|
|
233
|
+
# Execute the async close synchronously.
|
|
234
|
+
self._loop.run_until_complete(async_close())
|
|
235
|
+
|
|
236
|
+
# Close the event loop.
|
|
237
|
+
self._loop.close()
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def closed(self):
|
|
241
|
+
# Connection is closed when client session is closed.
|
|
242
|
+
return self._client_session.closed
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
# or more contributor license agreements. See the NOTICE file
|
|
4
|
+
# distributed with this work for additional information
|
|
5
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
# to you under the Apache License, Version 2.0 (the
|
|
7
|
+
# "License"); you may not use this file except in compliance
|
|
8
|
+
# with the License. You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing,
|
|
13
|
+
# software distributed under the License is distributed on an
|
|
14
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
# KIND, either express or implied. See the License for the
|
|
16
|
+
# specific language governing permissions and limitations
|
|
17
|
+
# under the License.
|
|
18
|
+
#
|
|
19
|
+
import logging
|
|
20
|
+
import warnings
|
|
21
|
+
import queue
|
|
22
|
+
import re
|
|
23
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
24
|
+
|
|
25
|
+
from gremlin_python.driver import connection, protocol, request, serializer
|
|
26
|
+
from gremlin_python.process import traversal
|
|
27
|
+
|
|
28
|
+
log = logging.getLogger("gremlinpython")
|
|
29
|
+
|
|
30
|
+
# This is until concurrent.futures backport 3.1.0 release
|
|
31
|
+
try:
|
|
32
|
+
from multiprocessing import cpu_count
|
|
33
|
+
except ImportError:
|
|
34
|
+
# some platforms don't have multiprocessing
|
|
35
|
+
def cpu_count():
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
__author__ = 'David M. Brown (davebshow@gmail.com), Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Client:
|
|
42
|
+
|
|
43
|
+
def __init__(self, url, traversal_source, protocol_factory=None,
|
|
44
|
+
transport_factory=None, pool_size=None, max_workers=None,
|
|
45
|
+
message_serializer=None, username="", password="",
|
|
46
|
+
kerberized_service="", headers=None, session=None,
|
|
47
|
+
enable_user_agent_on_connect=True, enable_compression=False,
|
|
48
|
+
**transport_kwargs):
|
|
49
|
+
log.info("Creating Client with url '%s'", url)
|
|
50
|
+
|
|
51
|
+
# check via url that we are using http protocol
|
|
52
|
+
self._use_http = re.search('^http', url)
|
|
53
|
+
|
|
54
|
+
self._closed = False
|
|
55
|
+
self._url = url
|
|
56
|
+
self._headers = headers
|
|
57
|
+
self._enable_user_agent_on_connect = enable_user_agent_on_connect
|
|
58
|
+
self._traversal_source = traversal_source
|
|
59
|
+
self._enable_compression = enable_compression
|
|
60
|
+
if not self._use_http and "max_content_length" not in transport_kwargs:
|
|
61
|
+
transport_kwargs["max_content_length"] = 10 * 1024 * 1024
|
|
62
|
+
if message_serializer is None:
|
|
63
|
+
message_serializer = serializer.GraphBinarySerializersV1()
|
|
64
|
+
|
|
65
|
+
self._message_serializer = message_serializer
|
|
66
|
+
self._username = username
|
|
67
|
+
self._password = password
|
|
68
|
+
self._session = session
|
|
69
|
+
self._session_enabled = (session is not None and session != "")
|
|
70
|
+
if transport_factory is None:
|
|
71
|
+
try:
|
|
72
|
+
from gremlin_python.driver.aiohttp.transport import (
|
|
73
|
+
AiohttpTransport, AiohttpHTTPTransport)
|
|
74
|
+
except ImportError:
|
|
75
|
+
raise Exception("Please install AIOHTTP or pass "
|
|
76
|
+
"custom transport factory")
|
|
77
|
+
else:
|
|
78
|
+
def transport_factory():
|
|
79
|
+
if self._use_http:
|
|
80
|
+
return AiohttpHTTPTransport(**transport_kwargs)
|
|
81
|
+
else:
|
|
82
|
+
return AiohttpTransport(enable_compression=enable_compression, **transport_kwargs)
|
|
83
|
+
self._transport_factory = transport_factory
|
|
84
|
+
if protocol_factory is None:
|
|
85
|
+
def protocol_factory():
|
|
86
|
+
if self._use_http:
|
|
87
|
+
return protocol.GremlinServerHTTPProtocol(
|
|
88
|
+
self._message_serializer,
|
|
89
|
+
username=self._username,
|
|
90
|
+
password=self._password)
|
|
91
|
+
else:
|
|
92
|
+
return protocol.GremlinServerWSProtocol(
|
|
93
|
+
self._message_serializer,
|
|
94
|
+
username=self._username,
|
|
95
|
+
password=self._password,
|
|
96
|
+
kerberized_service=kerberized_service,
|
|
97
|
+
max_content_length=transport_kwargs["max_content_length"])
|
|
98
|
+
self._protocol_factory = protocol_factory
|
|
99
|
+
if self._session_enabled:
|
|
100
|
+
if pool_size is None:
|
|
101
|
+
pool_size = 1
|
|
102
|
+
elif pool_size != 1:
|
|
103
|
+
raise Exception("PoolSize must be 1 on session mode!")
|
|
104
|
+
if pool_size is None:
|
|
105
|
+
pool_size = 8
|
|
106
|
+
self._pool_size = pool_size
|
|
107
|
+
# This is until concurrent.futures backport 3.1.0 release
|
|
108
|
+
if max_workers is None:
|
|
109
|
+
# If your application is overlapping Gremlin I/O on multiple threads
|
|
110
|
+
# consider passing kwarg max_workers = (cpu_count() or 1) * 5
|
|
111
|
+
max_workers = pool_size
|
|
112
|
+
self._executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
113
|
+
# Threadsafe queue
|
|
114
|
+
self._pool = queue.Queue()
|
|
115
|
+
self._fill_pool()
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def available_pool_size(self):
|
|
119
|
+
return self._pool.qsize()
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def executor(self):
|
|
123
|
+
return self._executor
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def traversal_source(self):
|
|
127
|
+
return self._traversal_source
|
|
128
|
+
|
|
129
|
+
def _fill_pool(self):
|
|
130
|
+
for i in range(self._pool_size):
|
|
131
|
+
conn = self._get_connection()
|
|
132
|
+
self._pool.put_nowait(conn)
|
|
133
|
+
|
|
134
|
+
def is_closed(self):
|
|
135
|
+
return self._closed
|
|
136
|
+
|
|
137
|
+
def close(self):
|
|
138
|
+
# prevent the Client from being closed more than once. it raises errors if new jobby jobs
|
|
139
|
+
# get submitted to the executor when it is shutdown
|
|
140
|
+
if self._closed:
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if self._session_enabled:
|
|
144
|
+
self._close_session()
|
|
145
|
+
log.info("Closing Client with url '%s'", self._url)
|
|
146
|
+
while not self._pool.empty():
|
|
147
|
+
conn = self._pool.get(True)
|
|
148
|
+
conn.close()
|
|
149
|
+
self._executor.shutdown()
|
|
150
|
+
self._closed = True
|
|
151
|
+
|
|
152
|
+
def _close_session(self):
|
|
153
|
+
message = request.RequestMessage(
|
|
154
|
+
processor='session', op='close',
|
|
155
|
+
args={'session': str(self._session)})
|
|
156
|
+
conn = self._pool.get(True)
|
|
157
|
+
try:
|
|
158
|
+
write_result_set = conn.write(message).result()
|
|
159
|
+
return write_result_set.all().result() # wait for _receive() to finish
|
|
160
|
+
except protocol.GremlinServerError:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
def _get_connection(self):
|
|
164
|
+
protocol = self._protocol_factory()
|
|
165
|
+
return connection.Connection(
|
|
166
|
+
self._url, self._traversal_source, protocol,
|
|
167
|
+
self._transport_factory, self._executor, self._pool,
|
|
168
|
+
headers=self._headers, enable_user_agent_on_connect=self._enable_user_agent_on_connect)
|
|
169
|
+
|
|
170
|
+
def submit(self, message, bindings=None, request_options=None):
|
|
171
|
+
return self.submit_async(message, bindings=bindings, request_options=request_options).result()
|
|
172
|
+
|
|
173
|
+
def submitAsync(self, message, bindings=None, request_options=None):
|
|
174
|
+
warnings.warn(
|
|
175
|
+
"gremlin_python.driver.client.Client.submitAsync will be replaced by "
|
|
176
|
+
"gremlin_python.driver.client.Client.submit_async.",
|
|
177
|
+
DeprecationWarning)
|
|
178
|
+
return self.submit_async(message, bindings, request_options)
|
|
179
|
+
|
|
180
|
+
def submit_async(self, message, bindings=None, request_options=None):
|
|
181
|
+
if self.is_closed():
|
|
182
|
+
raise Exception("Client is closed")
|
|
183
|
+
|
|
184
|
+
log.debug("message '%s'", str(message))
|
|
185
|
+
args = {'gremlin': message, 'aliases': {'g': self._traversal_source}}
|
|
186
|
+
processor = ''
|
|
187
|
+
op = 'eval'
|
|
188
|
+
if isinstance(message, traversal.Bytecode):
|
|
189
|
+
op = 'bytecode'
|
|
190
|
+
processor = 'traversal'
|
|
191
|
+
|
|
192
|
+
if isinstance(message, str) and bindings:
|
|
193
|
+
args['bindings'] = bindings
|
|
194
|
+
|
|
195
|
+
if self._session_enabled:
|
|
196
|
+
args['session'] = str(self._session)
|
|
197
|
+
processor = 'session'
|
|
198
|
+
|
|
199
|
+
if isinstance(message, traversal.Bytecode) or isinstance(message, str):
|
|
200
|
+
log.debug("processor='%s', op='%s', args='%s'", str(processor), str(op), str(args))
|
|
201
|
+
message = request.RequestMessage(processor=processor, op=op, args=args)
|
|
202
|
+
|
|
203
|
+
conn = self._pool.get(True)
|
|
204
|
+
if request_options:
|
|
205
|
+
message.args.update(request_options)
|
|
206
|
+
return conn.write(message)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
# or more contributor license agreements. See the NOTICE file
|
|
3
|
+
# distributed with this work for additional information
|
|
4
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
# to you under the Apache License, Version 2.0 (the
|
|
6
|
+
# "License"); you may not use this file except in compliance
|
|
7
|
+
# with the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
|
12
|
+
# software distributed under the License is distributed on an
|
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
# KIND, either express or implied. See the License for the
|
|
15
|
+
# specific language governing permissions and limitations
|
|
16
|
+
# under the License.
|
|
17
|
+
import uuid
|
|
18
|
+
import queue
|
|
19
|
+
from concurrent.futures import Future
|
|
20
|
+
|
|
21
|
+
from gremlin_python.driver import resultset, useragent
|
|
22
|
+
|
|
23
|
+
__author__ = 'David M. Brown (davebshow@gmail.com)'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Connection:
|
|
27
|
+
|
|
28
|
+
def __init__(self, url, traversal_source, protocol, transport_factory,
|
|
29
|
+
executor, pool, headers=None, enable_user_agent_on_connect=True):
|
|
30
|
+
self._url = url
|
|
31
|
+
self._headers = headers
|
|
32
|
+
self._traversal_source = traversal_source
|
|
33
|
+
self._protocol = protocol
|
|
34
|
+
self._transport_factory = transport_factory
|
|
35
|
+
self._executor = executor
|
|
36
|
+
self._transport = None
|
|
37
|
+
self._pool = pool
|
|
38
|
+
self._results = {}
|
|
39
|
+
self._inited = False
|
|
40
|
+
self._enable_user_agent_on_connect = enable_user_agent_on_connect
|
|
41
|
+
if self._enable_user_agent_on_connect:
|
|
42
|
+
self.__add_header(useragent.userAgentHeader, useragent.userAgent)
|
|
43
|
+
|
|
44
|
+
def connect(self):
|
|
45
|
+
if self._transport:
|
|
46
|
+
self._transport.close()
|
|
47
|
+
self._transport = self._transport_factory()
|
|
48
|
+
self._transport.connect(self._url, self._headers)
|
|
49
|
+
self._protocol.connection_made(self._transport)
|
|
50
|
+
self._inited = True
|
|
51
|
+
|
|
52
|
+
def close(self):
|
|
53
|
+
if self._inited:
|
|
54
|
+
self._transport.close()
|
|
55
|
+
|
|
56
|
+
def write(self, request_message):
|
|
57
|
+
if not self._inited:
|
|
58
|
+
self.connect()
|
|
59
|
+
if request_message.args.get("requestId"):
|
|
60
|
+
request_id = str(request_message.args.get("requestId"))
|
|
61
|
+
uuid.UUID(request_id) # Checks for proper UUID or else server will return an error.
|
|
62
|
+
else:
|
|
63
|
+
request_id = str(uuid.uuid4())
|
|
64
|
+
result_set = resultset.ResultSet(queue.Queue(), request_id)
|
|
65
|
+
self._results[request_id] = result_set
|
|
66
|
+
# Create write task
|
|
67
|
+
future = Future()
|
|
68
|
+
future_write = self._executor.submit(
|
|
69
|
+
self._protocol.write, request_id, request_message)
|
|
70
|
+
|
|
71
|
+
def cb(f):
|
|
72
|
+
try:
|
|
73
|
+
f.result()
|
|
74
|
+
except Exception as e:
|
|
75
|
+
future.set_exception(e)
|
|
76
|
+
self._pool.put_nowait(self)
|
|
77
|
+
else:
|
|
78
|
+
# Start receive task
|
|
79
|
+
done = self._executor.submit(self._receive)
|
|
80
|
+
result_set.done = done
|
|
81
|
+
future.set_result(result_set)
|
|
82
|
+
|
|
83
|
+
future_write.add_done_callback(cb)
|
|
84
|
+
return future
|
|
85
|
+
|
|
86
|
+
def _receive(self):
|
|
87
|
+
try:
|
|
88
|
+
while True:
|
|
89
|
+
data = self._transport.read()
|
|
90
|
+
status_code = self._protocol.data_received(data, self._results)
|
|
91
|
+
if status_code != 206:
|
|
92
|
+
break
|
|
93
|
+
finally:
|
|
94
|
+
self._pool.put_nowait(self)
|
|
95
|
+
|
|
96
|
+
def __add_header(self, key, value):
|
|
97
|
+
if self._headers is None:
|
|
98
|
+
self._headers = dict()
|
|
99
|
+
# Headers may be a list of pairs
|
|
100
|
+
if isinstance(self._headers, list):
|
|
101
|
+
for pair in self._headers:
|
|
102
|
+
if pair[0] == key:
|
|
103
|
+
self._headers.remove(pair)
|
|
104
|
+
self._headers.append((key, value))
|
|
105
|
+
else:
|
|
106
|
+
self._headers[key] = value
|