gremlinpython 3.7.4__tar.gz → 4.0.0b1__tar.gz
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.
- gremlinpython-4.0.0b1/NOTICE +5 -0
- {gremlinpython-3.7.4/gremlinpython.egg-info → gremlinpython-4.0.0b1}/PKG-INFO +1 -1
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/__version__.py +2 -2
- gremlinpython-4.0.0b1/gremlin_python/driver/aiohttp/transport.py +182 -0
- gremlinpython-4.0.0b1/gremlin_python/driver/auth.py +55 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/client.py +45 -70
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/connection.py +20 -18
- gremlinpython-4.0.0b1/gremlin_python/driver/driver_remote_connection.py +132 -0
- gremlinpython-4.0.0b1/gremlin_python/driver/protocol.py +175 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/remote_connection.py +3 -9
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/request.py +4 -2
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/resultset.py +1 -15
- gremlinpython-4.0.0b1/gremlin_python/driver/serializer.py +210 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/useragent.py +1 -1
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/process/anonymous_traversal.py +9 -12
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/process/graph_traversal.py +393 -424
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/process/strategies.py +38 -3
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/process/traversal.py +392 -150
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/statics.py +25 -10
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/structure/graph.py +0 -16
- gremlinpython-3.7.4/gremlin_python/structure/io/graphbinaryV1.py → gremlinpython-4.0.0b1/gremlin_python/structure/io/graphbinaryV4.py +129 -458
- gremlinpython-3.7.4/gremlin_python/structure/io/graphsonV3d0.py → gremlinpython-4.0.0b1/gremlin_python/structure/io/graphsonV4.py +71 -266
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/structure/io/util.py +38 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1/gremlinpython.egg-info}/PKG-INFO +1 -1
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlinpython.egg-info/SOURCES.txt +3 -4
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlinpython.egg-info/requires.txt +7 -1
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/setup.py +4 -2
- gremlinpython-3.7.4/NOTICE +0 -5
- gremlinpython-3.7.4/gremlin_python/driver/aiohttp/transport.py +0 -244
- gremlinpython-3.7.4/gremlin_python/driver/driver_remote_connection.py +0 -183
- gremlinpython-3.7.4/gremlin_python/driver/protocol.py +0 -259
- gremlinpython-3.7.4/gremlin_python/driver/serializer.py +0 -294
- gremlinpython-3.7.4/gremlin_python/process/translator.py +0 -297
- gremlinpython-3.7.4/gremlin_python/structure/io/graphsonV2d0.py +0 -663
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/LICENSE +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/MANIFEST.in +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/README.rst +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/__init__.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/__init__.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/aiohttp/__init__.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/driver/transport.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/process/__init__.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/structure/__init__.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlin_python/structure/io/__init__.py +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlinpython.egg-info/dependency_links.txt +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/gremlinpython.egg-info/top_level.txt +0 -0
- {gremlinpython-3.7.4 → gremlinpython-4.0.0b1}/setup.cfg +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
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 json
|
|
20
|
+
|
|
21
|
+
import aiohttp
|
|
22
|
+
import asyncio
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
if sys.version_info >= (3, 11):
|
|
26
|
+
import asyncio as async_timeout
|
|
27
|
+
else:
|
|
28
|
+
import async_timeout
|
|
29
|
+
from aiohttp import ClientPayloadError
|
|
30
|
+
from gremlin_python.driver.protocol import GremlinServerError
|
|
31
|
+
from gremlin_python.driver.transport import AbstractBaseTransport
|
|
32
|
+
|
|
33
|
+
__author__ = 'Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AiohttpHTTPTransport(AbstractBaseTransport):
|
|
37
|
+
nest_asyncio_applied = False
|
|
38
|
+
|
|
39
|
+
def __init__(self, call_from_event_loop=None, read_timeout=None, write_timeout=None, **kwargs):
|
|
40
|
+
if call_from_event_loop is not None and call_from_event_loop and not AiohttpHTTPTransport.nest_asyncio_applied:
|
|
41
|
+
"""
|
|
42
|
+
The AiohttpTransport implementation uses the asyncio event loop. Because of this, it cannot be called
|
|
43
|
+
within an event loop without nest_asyncio. If the code is ever refactored so that it can be called
|
|
44
|
+
within an event loop this import and call can be removed. Without this, applications which use the
|
|
45
|
+
event loop to call gremlin-python (such as Jupyter) will not work.
|
|
46
|
+
"""
|
|
47
|
+
import nest_asyncio
|
|
48
|
+
nest_asyncio.apply()
|
|
49
|
+
AiohttpHTTPTransport.nest_asyncio_applied = True
|
|
50
|
+
|
|
51
|
+
# Start event loop and initialize client session and response to None
|
|
52
|
+
self._loop = asyncio.new_event_loop()
|
|
53
|
+
self._client_session = None
|
|
54
|
+
self._http_req_resp = None
|
|
55
|
+
self._enable_ssl = False
|
|
56
|
+
self._url = None
|
|
57
|
+
|
|
58
|
+
# Set all inner variables to parameters passed in.
|
|
59
|
+
self._aiohttp_kwargs = kwargs
|
|
60
|
+
self._write_timeout = write_timeout
|
|
61
|
+
self._read_timeout = read_timeout
|
|
62
|
+
if "max_content_length" in self._aiohttp_kwargs:
|
|
63
|
+
self._max_content_len = self._aiohttp_kwargs.pop("max_content_length")
|
|
64
|
+
else:
|
|
65
|
+
self._max_content_len = 10 * 1024 * 1024
|
|
66
|
+
if "ssl_options" in self._aiohttp_kwargs:
|
|
67
|
+
self._ssl_context = self._aiohttp_kwargs.pop("ssl_options")
|
|
68
|
+
self._enable_ssl = True
|
|
69
|
+
|
|
70
|
+
def __del__(self):
|
|
71
|
+
# Close will only actually close if things are left open, so this is safe to call.
|
|
72
|
+
# Clean up any connection resources and close the event loop.
|
|
73
|
+
self.close()
|
|
74
|
+
|
|
75
|
+
def connect(self, url, headers=None):
|
|
76
|
+
self._url = url
|
|
77
|
+
# Inner function to perform async connect.
|
|
78
|
+
async def async_connect():
|
|
79
|
+
# Start client session and use it to send all HTTP requests. Headers can be set here.
|
|
80
|
+
if self._enable_ssl:
|
|
81
|
+
# ssl context is established through tcp connector
|
|
82
|
+
tcp_conn = aiohttp.TCPConnector(ssl_context=self._ssl_context)
|
|
83
|
+
self._client_session = aiohttp.ClientSession(connector=tcp_conn,
|
|
84
|
+
headers=headers, loop=self._loop)
|
|
85
|
+
else:
|
|
86
|
+
self._client_session = aiohttp.ClientSession(headers=headers, loop=self._loop)
|
|
87
|
+
|
|
88
|
+
# Execute the async connect synchronously.
|
|
89
|
+
self._loop.run_until_complete(async_connect())
|
|
90
|
+
|
|
91
|
+
def write(self, message):
|
|
92
|
+
# Inner function to perform async write.
|
|
93
|
+
async def async_write():
|
|
94
|
+
# To pass url into message for request authentication processing
|
|
95
|
+
message.update({'url': self._url})
|
|
96
|
+
if message['auth']:
|
|
97
|
+
message['auth'](message)
|
|
98
|
+
|
|
99
|
+
async with async_timeout.timeout(self._write_timeout):
|
|
100
|
+
self._http_req_resp = await self._client_session.post(url=self._url,
|
|
101
|
+
data=message['payload'],
|
|
102
|
+
headers=message['headers'],
|
|
103
|
+
**self._aiohttp_kwargs)
|
|
104
|
+
|
|
105
|
+
# Execute the async write synchronously.
|
|
106
|
+
self._loop.run_until_complete(async_write())
|
|
107
|
+
|
|
108
|
+
def read(self, stream_chunk=None):
|
|
109
|
+
if not stream_chunk:
|
|
110
|
+
'''
|
|
111
|
+
GraphSON does not support streaming deserialization, we are aggregating data and bypassing streamed
|
|
112
|
+
deserialization while GraphSON is enabled for testing. Remove after GraphSON is removed.
|
|
113
|
+
'''
|
|
114
|
+
async def async_read():
|
|
115
|
+
async with async_timeout.timeout(self._read_timeout):
|
|
116
|
+
data_buffer = b""
|
|
117
|
+
async for data, end_of_http_chunk in self._http_req_resp.content.iter_chunks():
|
|
118
|
+
try:
|
|
119
|
+
data_buffer += data
|
|
120
|
+
except ClientPayloadError:
|
|
121
|
+
# server disconnect during streaming will cause ClientPayLoadError from aiohttp
|
|
122
|
+
raise GremlinServerError({'code': 500,
|
|
123
|
+
'message': 'Server disconnected - please try to reconnect',
|
|
124
|
+
'exception': ClientPayloadError})
|
|
125
|
+
if self._max_content_len and len(
|
|
126
|
+
data_buffer) > self._max_content_len:
|
|
127
|
+
raise Exception(f'Response size {len(data_buffer)} exceeds limit {self._max_content_len} bytes')
|
|
128
|
+
if self._http_req_resp.headers.get('content-type') == 'application/json':
|
|
129
|
+
message = json.loads(data_buffer.decode('utf-8'))
|
|
130
|
+
err = message.get('message')
|
|
131
|
+
raise Exception(f'Server disconnected with error message: "{err}" - please try to reconnect')
|
|
132
|
+
return data_buffer
|
|
133
|
+
return self._loop.run_until_complete(async_read())
|
|
134
|
+
# raise Exception('missing handling of streamed responses to protocol')
|
|
135
|
+
|
|
136
|
+
# Inner function to perform async read.
|
|
137
|
+
async def async_read():
|
|
138
|
+
# TODO: potentially refactor to just use streaming and remove transport/protocol
|
|
139
|
+
async with async_timeout.timeout(self._read_timeout):
|
|
140
|
+
read_completed = False
|
|
141
|
+
# aiohttp streaming may not iterate through one whole chunk if it's too large, need to buffer it
|
|
142
|
+
data_buffer = b""
|
|
143
|
+
async for data, end_of_http_chunk in self._http_req_resp.content.iter_chunks():
|
|
144
|
+
try:
|
|
145
|
+
data_buffer += data
|
|
146
|
+
if end_of_http_chunk:
|
|
147
|
+
if self._max_content_len and len(
|
|
148
|
+
data_buffer) > self._max_content_len:
|
|
149
|
+
raise Exception( # TODO: do we need proper exception class for this?
|
|
150
|
+
f'Response size {len(data_buffer)} exceeds limit {self._max_content_len} bytes')
|
|
151
|
+
stream_chunk(data_buffer, read_completed, self._http_req_resp.ok)
|
|
152
|
+
data_buffer = b""
|
|
153
|
+
except ClientPayloadError:
|
|
154
|
+
# server disconnect during streaming will cause ClientPayLoadError from aiohttp
|
|
155
|
+
# TODO: double check during refactoring
|
|
156
|
+
raise GremlinServerError({'code': 500,
|
|
157
|
+
'message': 'Server disconnected - please try to reconnect',
|
|
158
|
+
'exception': ClientPayloadError})
|
|
159
|
+
read_completed = True
|
|
160
|
+
stream_chunk(data_buffer, read_completed, self._http_req_resp.ok)
|
|
161
|
+
|
|
162
|
+
return self._loop.run_until_complete(async_read())
|
|
163
|
+
|
|
164
|
+
def close(self):
|
|
165
|
+
# Inner function to perform async close.
|
|
166
|
+
async def async_close():
|
|
167
|
+
if self._client_session is not None and not self._client_session.closed:
|
|
168
|
+
await self._client_session.close()
|
|
169
|
+
self._client_session = None
|
|
170
|
+
|
|
171
|
+
# If the loop is not closed (connection hasn't already been closed)
|
|
172
|
+
if not self._loop.is_closed():
|
|
173
|
+
# Execute the async close synchronously.
|
|
174
|
+
self._loop.run_until_complete(async_close())
|
|
175
|
+
|
|
176
|
+
# Close the event loop.
|
|
177
|
+
self._loop.close()
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def closed(self):
|
|
181
|
+
# Connection is closed when client session is closed.
|
|
182
|
+
return self._client_session.closed
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
|
|
21
|
+
def basic(username, password):
|
|
22
|
+
from aiohttp import BasicAuth as aiohttpBasicAuth
|
|
23
|
+
|
|
24
|
+
def apply(request):
|
|
25
|
+
return request['headers'].update({'authorization': aiohttpBasicAuth(username, password).encode()})
|
|
26
|
+
|
|
27
|
+
return apply
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sigv4(region, service):
|
|
31
|
+
import os
|
|
32
|
+
from boto3 import Session
|
|
33
|
+
from botocore.auth import SigV4Auth
|
|
34
|
+
from botocore.awsrequest import AWSRequest
|
|
35
|
+
|
|
36
|
+
def apply(request):
|
|
37
|
+
access_key = os.environ.get('AWS_ACCESS_KEY_ID', '')
|
|
38
|
+
secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY', '')
|
|
39
|
+
session_token = os.environ.get('AWS_SESSION_TOKEN', '')
|
|
40
|
+
|
|
41
|
+
session = Session(
|
|
42
|
+
aws_access_key_id=access_key,
|
|
43
|
+
aws_secret_access_key=secret_key,
|
|
44
|
+
aws_session_token=session_token,
|
|
45
|
+
region_name=region
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
sigv4_request = AWSRequest(method="POST", url=request['url'], data=request['payload'])
|
|
49
|
+
SigV4Auth(session.get_credentials(), service, region).add_auth(sigv4_request)
|
|
50
|
+
request['headers'].update(sigv4_request.headers)
|
|
51
|
+
request['payload'] = sigv4_request.data
|
|
52
|
+
return request
|
|
53
|
+
|
|
54
|
+
return apply
|
|
55
|
+
|
|
@@ -19,11 +19,9 @@
|
|
|
19
19
|
import logging
|
|
20
20
|
import warnings
|
|
21
21
|
import queue
|
|
22
|
-
import re
|
|
23
22
|
from concurrent.futures import ThreadPoolExecutor
|
|
24
23
|
|
|
25
24
|
from gremlin_python.driver import connection, protocol, request, serializer
|
|
26
|
-
from gremlin_python.process import traversal
|
|
27
25
|
|
|
28
26
|
log = logging.getLogger("gremlinpython")
|
|
29
27
|
|
|
@@ -38,69 +36,51 @@ except ImportError:
|
|
|
38
36
|
__author__ = 'David M. Brown (davebshow@gmail.com), Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
39
37
|
|
|
40
38
|
|
|
39
|
+
# TODO: remove session, update connection pooling, etc.
|
|
41
40
|
class Client:
|
|
42
41
|
|
|
43
42
|
def __init__(self, url, traversal_source, protocol_factory=None,
|
|
44
43
|
transport_factory=None, pool_size=None, max_workers=None,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**transport_kwargs):
|
|
44
|
+
request_serializer=serializer.GraphBinarySerializersV4(),
|
|
45
|
+
response_serializer=None, interceptors=None, auth=None,
|
|
46
|
+
headers=None, enable_user_agent_on_connect=True,
|
|
47
|
+
bulk_results=False, **transport_kwargs):
|
|
49
48
|
log.info("Creating Client with url '%s'", url)
|
|
50
49
|
|
|
51
|
-
# check via url that we are using http protocol
|
|
52
|
-
self._use_http = re.search('^http', url)
|
|
53
|
-
|
|
54
50
|
self._closed = False
|
|
55
51
|
self._url = url
|
|
56
52
|
self._headers = headers
|
|
57
53
|
self._enable_user_agent_on_connect = enable_user_agent_on_connect
|
|
54
|
+
self._bulk_results = bulk_results
|
|
58
55
|
self._traversal_source = traversal_source
|
|
59
|
-
|
|
60
|
-
if not self._use_http and "max_content_length" not in transport_kwargs:
|
|
56
|
+
if "max_content_length" not in transport_kwargs:
|
|
61
57
|
transport_kwargs["max_content_length"] = 10 * 1024 * 1024
|
|
62
|
-
if
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
67
|
-
|
|
68
|
-
self._session = session
|
|
69
|
-
self._session_enabled = (session is not None and session != "")
|
|
58
|
+
if response_serializer is None:
|
|
59
|
+
response_serializer = serializer.GraphBinarySerializersV4()
|
|
60
|
+
|
|
61
|
+
self._auth = auth
|
|
62
|
+
self._response_serializer = response_serializer
|
|
63
|
+
|
|
70
64
|
if transport_factory is None:
|
|
71
65
|
try:
|
|
72
|
-
from gremlin_python.driver.aiohttp.transport import
|
|
73
|
-
AiohttpTransport, AiohttpHTTPTransport)
|
|
66
|
+
from gremlin_python.driver.aiohttp.transport import AiohttpHTTPTransport
|
|
74
67
|
except ImportError:
|
|
75
68
|
raise Exception("Please install AIOHTTP or pass "
|
|
76
69
|
"custom transport factory")
|
|
77
70
|
else:
|
|
78
71
|
def transport_factory():
|
|
79
|
-
if self.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return AiohttpTransport(enable_compression=enable_compression, **transport_kwargs)
|
|
72
|
+
if self._protocol_factory is None:
|
|
73
|
+
self._protocol_factory = protocol_factory
|
|
74
|
+
return AiohttpHTTPTransport(**transport_kwargs)
|
|
83
75
|
self._transport_factory = transport_factory
|
|
76
|
+
|
|
84
77
|
if protocol_factory is None:
|
|
85
78
|
def protocol_factory():
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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"])
|
|
79
|
+
return protocol.GremlinServerHTTPProtocol(
|
|
80
|
+
request_serializer, response_serializer, auth=self._auth,
|
|
81
|
+
interceptors=interceptors)
|
|
98
82
|
self._protocol_factory = protocol_factory
|
|
99
|
-
|
|
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!")
|
|
83
|
+
|
|
104
84
|
if pool_size is None:
|
|
105
85
|
pool_size = 8
|
|
106
86
|
self._pool_size = pool_size
|
|
@@ -117,6 +97,9 @@ class Client:
|
|
|
117
97
|
@property
|
|
118
98
|
def available_pool_size(self):
|
|
119
99
|
return self._pool.qsize()
|
|
100
|
+
|
|
101
|
+
def response_serializer(self):
|
|
102
|
+
return self._response_serializer
|
|
120
103
|
|
|
121
104
|
@property
|
|
122
105
|
def executor(self):
|
|
@@ -140,8 +123,6 @@ class Client:
|
|
|
140
123
|
if self._closed:
|
|
141
124
|
return
|
|
142
125
|
|
|
143
|
-
if self._session_enabled:
|
|
144
|
-
self._close_session()
|
|
145
126
|
log.info("Closing Client with url '%s'", self._url)
|
|
146
127
|
while not self._pool.empty():
|
|
147
128
|
conn = self._pool.get(True)
|
|
@@ -149,17 +130,6 @@ class Client:
|
|
|
149
130
|
self._executor.shutdown()
|
|
150
131
|
self._closed = True
|
|
151
132
|
|
|
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
133
|
def _get_connection(self):
|
|
164
134
|
protocol = self._protocol_factory()
|
|
165
135
|
return connection.Connection(
|
|
@@ -182,25 +152,30 @@ class Client:
|
|
|
182
152
|
raise Exception("Client is closed")
|
|
183
153
|
|
|
184
154
|
log.debug("message '%s'", str(message))
|
|
185
|
-
|
|
186
|
-
processor = ''
|
|
187
|
-
op = 'eval'
|
|
188
|
-
if isinstance(message, traversal.Bytecode):
|
|
189
|
-
op = 'bytecode'
|
|
190
|
-
processor = 'traversal'
|
|
155
|
+
fields = {'g': self._traversal_source}
|
|
191
156
|
|
|
157
|
+
# TODO: bindings is now part of request_options, evaluate the need to keep it separate in python.
|
|
158
|
+
# Note this bindings parameter only applies to string script submissions
|
|
192
159
|
if isinstance(message, str) and bindings:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if self._session_enabled:
|
|
196
|
-
args['session'] = str(self._session)
|
|
197
|
-
processor = 'session'
|
|
160
|
+
fields['bindings'] = bindings
|
|
198
161
|
|
|
199
|
-
if isinstance(message,
|
|
200
|
-
log.debug("
|
|
201
|
-
message = request.RequestMessage(
|
|
162
|
+
if isinstance(message, str):
|
|
163
|
+
log.debug("fields='%s', gremlin='%s'", str(fields), str(message))
|
|
164
|
+
message = request.RequestMessage(fields=fields, gremlin=message)
|
|
202
165
|
|
|
203
166
|
conn = self._pool.get(True)
|
|
204
167
|
if request_options:
|
|
205
|
-
message.
|
|
168
|
+
message.fields.update({token: request_options[token] for token in request.Tokens
|
|
169
|
+
if token in request_options and token != 'bindings'})
|
|
170
|
+
if 'bindings' in request_options:
|
|
171
|
+
if 'bindings' in message.fields:
|
|
172
|
+
message.fields['bindings'].update(request_options['bindings'])
|
|
173
|
+
else:
|
|
174
|
+
message.fields['bindings'] = request_options['bindings']
|
|
175
|
+
if 'params' in request_options:
|
|
176
|
+
if 'bindings' in message.fields:
|
|
177
|
+
message.fields['bindings'].update(request_options['params'])
|
|
178
|
+
else:
|
|
179
|
+
message.fields['bindings'] = request_options['params']
|
|
180
|
+
|
|
206
181
|
return conn.write(message)
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
# KIND, either express or implied. See the License for the
|
|
15
15
|
# specific language governing permissions and limitations
|
|
16
16
|
# under the License.
|
|
17
|
-
import uuid
|
|
18
17
|
import queue
|
|
19
18
|
from concurrent.futures import Future
|
|
20
19
|
|
|
@@ -26,7 +25,8 @@ __author__ = 'David M. Brown (davebshow@gmail.com)'
|
|
|
26
25
|
class Connection:
|
|
27
26
|
|
|
28
27
|
def __init__(self, url, traversal_source, protocol, transport_factory,
|
|
29
|
-
executor, pool, headers=None, enable_user_agent_on_connect=True
|
|
28
|
+
executor, pool, headers=None, enable_user_agent_on_connect=True,
|
|
29
|
+
bulk_results=False):
|
|
30
30
|
self._url = url
|
|
31
31
|
self._headers = headers
|
|
32
32
|
self._traversal_source = traversal_source
|
|
@@ -35,11 +35,14 @@ class Connection:
|
|
|
35
35
|
self._executor = executor
|
|
36
36
|
self._transport = None
|
|
37
37
|
self._pool = pool
|
|
38
|
-
self.
|
|
38
|
+
self._result_set = None
|
|
39
39
|
self._inited = False
|
|
40
40
|
self._enable_user_agent_on_connect = enable_user_agent_on_connect
|
|
41
41
|
if self._enable_user_agent_on_connect:
|
|
42
42
|
self.__add_header(useragent.userAgentHeader, useragent.userAgent)
|
|
43
|
+
self._bulk_results = bulk_results
|
|
44
|
+
if self._bulk_results:
|
|
45
|
+
self.__add_header("bulkResults", "true")
|
|
43
46
|
|
|
44
47
|
def connect(self):
|
|
45
48
|
if self._transport:
|
|
@@ -56,17 +59,11 @@ class Connection:
|
|
|
56
59
|
def write(self, request_message):
|
|
57
60
|
if not self._inited:
|
|
58
61
|
self.connect()
|
|
59
|
-
|
|
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
|
|
62
|
+
self._result_set = resultset.ResultSet(queue.Queue())
|
|
66
63
|
# Create write task
|
|
67
64
|
future = Future()
|
|
68
65
|
future_write = self._executor.submit(
|
|
69
|
-
self._protocol.write,
|
|
66
|
+
self._protocol.write, request_message)
|
|
70
67
|
|
|
71
68
|
def cb(f):
|
|
72
69
|
try:
|
|
@@ -77,22 +74,27 @@ class Connection:
|
|
|
77
74
|
else:
|
|
78
75
|
# Start receive task
|
|
79
76
|
done = self._executor.submit(self._receive)
|
|
80
|
-
|
|
81
|
-
future.set_result(
|
|
77
|
+
self._result_set.done = done
|
|
78
|
+
future.set_result(self._result_set)
|
|
82
79
|
|
|
83
80
|
future_write.add_done_callback(cb)
|
|
84
81
|
return future
|
|
85
82
|
|
|
86
83
|
def _receive(self):
|
|
87
84
|
try:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
'''
|
|
86
|
+
GraphSON does not support streaming deserialization, we are aggregating data and bypassing streamed
|
|
87
|
+
deserialization while GraphSON is enabled for testing. Remove after GraphSON is removed.
|
|
88
|
+
'''
|
|
89
|
+
self._protocol.data_received_aggregate(self._transport.read(), self._result_set)
|
|
90
|
+
# re-enable streaming after graphSON removal
|
|
91
|
+
# self._transport.read(self.stream_chunk)
|
|
93
92
|
finally:
|
|
94
93
|
self._pool.put_nowait(self)
|
|
95
94
|
|
|
95
|
+
def stream_chunk(self, chunk_data, read_completed=None, http_req_resp=None):
|
|
96
|
+
self._protocol.data_received(chunk_data, self._result_set, read_completed, http_req_resp)
|
|
97
|
+
|
|
96
98
|
def __add_header(self, key, value):
|
|
97
99
|
if self._headers is None:
|
|
98
100
|
self._headers = dict()
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
from concurrent.futures import Future
|
|
21
|
+
import warnings
|
|
22
|
+
|
|
23
|
+
from gremlin_python.driver import client, serializer
|
|
24
|
+
from gremlin_python.driver.remote_connection import RemoteConnection, RemoteTraversal
|
|
25
|
+
from gremlin_python.driver.request import Tokens
|
|
26
|
+
|
|
27
|
+
log = logging.getLogger("gremlinpython")
|
|
28
|
+
|
|
29
|
+
__author__ = 'David M. Brown (davebshow@gmail.com), Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DriverRemoteConnection(RemoteConnection):
|
|
33
|
+
|
|
34
|
+
def __init__(self, url, traversal_source="g", protocol_factory=None,
|
|
35
|
+
transport_factory=None, pool_size=None, max_workers=None,
|
|
36
|
+
request_serializer=serializer.GraphBinarySerializersV4(),
|
|
37
|
+
response_serializer=None, interceptors=None, auth=None,
|
|
38
|
+
headers=None, enable_user_agent_on_connect=True,
|
|
39
|
+
bulk_results=False, **transport_kwargs):
|
|
40
|
+
log.info("Creating DriverRemoteConnection with url '%s'", str(url))
|
|
41
|
+
self.__url = url
|
|
42
|
+
self.__traversal_source = traversal_source
|
|
43
|
+
self.__protocol_factory = protocol_factory
|
|
44
|
+
self.__transport_factory = transport_factory
|
|
45
|
+
self.__pool_size = pool_size
|
|
46
|
+
self.__max_workers = max_workers
|
|
47
|
+
self.__auth = auth
|
|
48
|
+
self.__headers = headers
|
|
49
|
+
self.__enable_user_agent_on_connect = enable_user_agent_on_connect
|
|
50
|
+
self.__bulk_results = bulk_results
|
|
51
|
+
self.__transport_kwargs = transport_kwargs
|
|
52
|
+
|
|
53
|
+
if response_serializer is None:
|
|
54
|
+
response_serializer = serializer.GraphBinarySerializersV4()
|
|
55
|
+
self._client = client.Client(url, traversal_source,
|
|
56
|
+
protocol_factory=protocol_factory,
|
|
57
|
+
transport_factory=transport_factory,
|
|
58
|
+
pool_size=pool_size,
|
|
59
|
+
max_workers=max_workers,
|
|
60
|
+
request_serializer=request_serializer,
|
|
61
|
+
response_serializer=response_serializer,
|
|
62
|
+
interceptors=interceptors, auth=auth,
|
|
63
|
+
headers=headers,
|
|
64
|
+
enable_user_agent_on_connect=enable_user_agent_on_connect,
|
|
65
|
+
bulk_results=bulk_results,
|
|
66
|
+
**transport_kwargs)
|
|
67
|
+
self._url = self._client._url
|
|
68
|
+
self._traversal_source = self._client._traversal_source
|
|
69
|
+
|
|
70
|
+
def close(self):
|
|
71
|
+
log.info("closing DriverRemoteConnection with url '%s'", str(self._url))
|
|
72
|
+
|
|
73
|
+
self._client.close()
|
|
74
|
+
|
|
75
|
+
def submit(self, gremlin_lang):
|
|
76
|
+
log.debug("submit with gremlin lang script '%s'", gremlin_lang.get_gremlin())
|
|
77
|
+
gremlin_lang.add_g(self._traversal_source)
|
|
78
|
+
result_set = self._client.submit(gremlin_lang.get_gremlin(),
|
|
79
|
+
request_options=self.extract_request_options(gremlin_lang))
|
|
80
|
+
results = result_set.all().result()
|
|
81
|
+
return RemoteTraversal(iter(results))
|
|
82
|
+
|
|
83
|
+
def submitAsync(self, message, bindings=None, request_options=None):
|
|
84
|
+
warnings.warn(
|
|
85
|
+
"gremlin_python.driver.driver_remote_connection.DriverRemoteConnection.submitAsync will be replaced by "
|
|
86
|
+
"gremlin_python.driver.driver_remote_connection.DriverRemoteConnection.submit_async.",
|
|
87
|
+
DeprecationWarning)
|
|
88
|
+
self.submit_async(message, bindings, request_options)
|
|
89
|
+
|
|
90
|
+
def submit_async(self, gremlin_lang):
|
|
91
|
+
log.debug("submit_async with gremlin lang script '%s'", gremlin_lang.get_gremlin())
|
|
92
|
+
future = Future()
|
|
93
|
+
gremlin_lang.add_g(self._traversal_source)
|
|
94
|
+
future_result_set = self._client.submit_async(gremlin_lang.get_gremlin(),
|
|
95
|
+
request_options=self.extract_request_options(gremlin_lang))
|
|
96
|
+
|
|
97
|
+
def cb(f):
|
|
98
|
+
try:
|
|
99
|
+
result_set = f.result()
|
|
100
|
+
results = result_set.all().result()
|
|
101
|
+
future.set_result(RemoteTraversal(iter(results)))
|
|
102
|
+
except Exception as e:
|
|
103
|
+
future.set_exception(e)
|
|
104
|
+
|
|
105
|
+
future_result_set.add_done_callback(cb)
|
|
106
|
+
return future
|
|
107
|
+
|
|
108
|
+
def is_closed(self):
|
|
109
|
+
return self._client.is_closed()
|
|
110
|
+
|
|
111
|
+
# TODO remove or update once HTTP transaction is implemented
|
|
112
|
+
# def commit(self):
|
|
113
|
+
# log.info("Submitting commit graph operation.")
|
|
114
|
+
# return self._client.submit(Bytecode.GraphOp.commit())
|
|
115
|
+
#
|
|
116
|
+
# def rollback(self):
|
|
117
|
+
# log.info("Submitting rollback graph operation.")
|
|
118
|
+
# return self._client.submit(Bytecode.GraphOp.rollback())
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def extract_request_options(gremlin_lang):
|
|
122
|
+
request_options = {}
|
|
123
|
+
for os in gremlin_lang.options_strategies:
|
|
124
|
+
request_options.update({token: os.configuration[token] for token in Tokens
|
|
125
|
+
if token in os.configuration})
|
|
126
|
+
# request the server to bulk results by default when using drc through request options
|
|
127
|
+
if 'bulkResults' not in request_options:
|
|
128
|
+
request_options['bulkResults'] = True
|
|
129
|
+
if gremlin_lang.parameters is not None and len(gremlin_lang.parameters) > 0:
|
|
130
|
+
request_options["params"] = gremlin_lang.parameters
|
|
131
|
+
|
|
132
|
+
return request_options
|