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,183 @@
|
|
|
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 (
|
|
25
|
+
RemoteConnection, RemoteTraversal)
|
|
26
|
+
from gremlin_python.process.strategies import OptionsStrategy
|
|
27
|
+
from gremlin_python.process.traversal import Bytecode
|
|
28
|
+
import uuid
|
|
29
|
+
|
|
30
|
+
log = logging.getLogger("gremlinpython")
|
|
31
|
+
|
|
32
|
+
__author__ = 'David M. Brown (davebshow@gmail.com), Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DriverRemoteConnection(RemoteConnection):
|
|
36
|
+
|
|
37
|
+
def __init__(self, url, traversal_source="g", protocol_factory=None,
|
|
38
|
+
transport_factory=None, pool_size=None, max_workers=None,
|
|
39
|
+
username="", password="", kerberized_service='',
|
|
40
|
+
message_serializer=None, graphson_reader=None,
|
|
41
|
+
graphson_writer=None, headers=None, session=None,
|
|
42
|
+
enable_user_agent_on_connect=True, enable_compression=False, **transport_kwargs):
|
|
43
|
+
log.info("Creating DriverRemoteConnection with url '%s'", str(url))
|
|
44
|
+
self.__url = url
|
|
45
|
+
self.__traversal_source = traversal_source
|
|
46
|
+
self.__protocol_factory = protocol_factory
|
|
47
|
+
self.__transport_factory = transport_factory
|
|
48
|
+
self.__pool_size = pool_size
|
|
49
|
+
self.__max_workers = max_workers
|
|
50
|
+
self.__username = username
|
|
51
|
+
self.__password = password
|
|
52
|
+
self.__kerberized_service = kerberized_service
|
|
53
|
+
self.__message_serializer = message_serializer
|
|
54
|
+
self.__graphson_reader = graphson_reader
|
|
55
|
+
self.__graphson_writer = graphson_writer
|
|
56
|
+
self.__headers = headers
|
|
57
|
+
self.__session = session
|
|
58
|
+
self.__enable_user_agent_on_connect = enable_user_agent_on_connect
|
|
59
|
+
self.__enable_compression = enable_compression
|
|
60
|
+
self.__transport_kwargs = transport_kwargs
|
|
61
|
+
|
|
62
|
+
# keeps a list of sessions that have been spawned from this DriverRemoteConnection
|
|
63
|
+
# so that they can be closed if this parent session is closed.
|
|
64
|
+
self.__spawned_sessions = []
|
|
65
|
+
|
|
66
|
+
if message_serializer is None and graphson_reader is not None and graphson_writer is not None:
|
|
67
|
+
message_serializer = serializer.GraphSONMessageSerializer(
|
|
68
|
+
reader=graphson_reader,
|
|
69
|
+
writer=graphson_writer)
|
|
70
|
+
self._client = client.Client(url, traversal_source,
|
|
71
|
+
protocol_factory=protocol_factory,
|
|
72
|
+
transport_factory=transport_factory,
|
|
73
|
+
pool_size=pool_size,
|
|
74
|
+
max_workers=max_workers,
|
|
75
|
+
message_serializer=message_serializer,
|
|
76
|
+
username=username,
|
|
77
|
+
password=password,
|
|
78
|
+
kerberized_service=kerberized_service,
|
|
79
|
+
headers=headers,
|
|
80
|
+
session=session,
|
|
81
|
+
enable_user_agent_on_connect=enable_user_agent_on_connect,
|
|
82
|
+
enable_compression=enable_compression,
|
|
83
|
+
**transport_kwargs)
|
|
84
|
+
self._url = self._client._url
|
|
85
|
+
self._traversal_source = self._client._traversal_source
|
|
86
|
+
|
|
87
|
+
def close(self):
|
|
88
|
+
# close this client and any DriverRemoteConnection instances spawned from this one
|
|
89
|
+
# for a session
|
|
90
|
+
if len(self.__spawned_sessions) > 0:
|
|
91
|
+
log.info("closing spawned sessions from DriverRemoteConnection with url '%s'", str(self._url))
|
|
92
|
+
for spawned_session in self.__spawned_sessions:
|
|
93
|
+
spawned_session.close()
|
|
94
|
+
self.__spawned_sessions.clear()
|
|
95
|
+
|
|
96
|
+
if self.__session:
|
|
97
|
+
log.info("closing DriverRemoteConnection with url '%s' with session '%s'",
|
|
98
|
+
str(self._url), str(self.__session))
|
|
99
|
+
else:
|
|
100
|
+
log.info("closing DriverRemoteConnection with url '%s'", str(self._url))
|
|
101
|
+
|
|
102
|
+
self._client.close()
|
|
103
|
+
|
|
104
|
+
def submit(self, bytecode):
|
|
105
|
+
log.debug("submit with bytecode '%s'", str(bytecode))
|
|
106
|
+
result_set = self._client.submit(bytecode, request_options=self._extract_request_options(bytecode))
|
|
107
|
+
results = result_set.all().result()
|
|
108
|
+
return RemoteTraversal(iter(results))
|
|
109
|
+
|
|
110
|
+
def submitAsync(self, message, bindings=None, request_options=None):
|
|
111
|
+
warnings.warn(
|
|
112
|
+
"gremlin_python.driver.driver_remote_connection.DriverRemoteConnection.submitAsync will be replaced by "
|
|
113
|
+
"gremlin_python.driver.driver_remote_connection.DriverRemoteConnection.submit_async.",
|
|
114
|
+
DeprecationWarning)
|
|
115
|
+
self.submit_async(message, bindings, request_options)
|
|
116
|
+
|
|
117
|
+
def submit_async(self, bytecode):
|
|
118
|
+
log.debug("submit_async with bytecode '%s'", str(bytecode))
|
|
119
|
+
future = Future()
|
|
120
|
+
future_result_set = self._client.submit_async(bytecode, request_options=self._extract_request_options(bytecode))
|
|
121
|
+
|
|
122
|
+
def cb(f):
|
|
123
|
+
try:
|
|
124
|
+
result_set = f.result()
|
|
125
|
+
results = result_set.all().result()
|
|
126
|
+
future.set_result(RemoteTraversal(iter(results)))
|
|
127
|
+
except Exception as e:
|
|
128
|
+
future.set_exception(e)
|
|
129
|
+
|
|
130
|
+
future_result_set.add_done_callback(cb)
|
|
131
|
+
return future
|
|
132
|
+
|
|
133
|
+
def is_closed(self):
|
|
134
|
+
return self._client.is_closed()
|
|
135
|
+
|
|
136
|
+
def is_session_bound(self):
|
|
137
|
+
return self.__session is not None
|
|
138
|
+
|
|
139
|
+
def create_session(self):
|
|
140
|
+
log.info("Creating session based connection")
|
|
141
|
+
if self.is_session_bound():
|
|
142
|
+
raise Exception('Connection is already bound to a session - child sessions are not allowed')
|
|
143
|
+
conn = DriverRemoteConnection(self.__url,
|
|
144
|
+
traversal_source=self.__traversal_source,
|
|
145
|
+
protocol_factory=self.__protocol_factory,
|
|
146
|
+
transport_factory=self.__transport_factory,
|
|
147
|
+
pool_size=self.__pool_size,
|
|
148
|
+
max_workers=self.__max_workers,
|
|
149
|
+
username=self.__username,
|
|
150
|
+
password=self.__password,
|
|
151
|
+
kerberized_service=self.__kerberized_service,
|
|
152
|
+
message_serializer=self.__message_serializer,
|
|
153
|
+
graphson_reader=self.__graphson_reader,
|
|
154
|
+
graphson_writer=self.__graphson_writer,
|
|
155
|
+
headers=self.__headers,
|
|
156
|
+
session=uuid.uuid4(),
|
|
157
|
+
enable_user_agent_on_connect=self.__enable_user_agent_on_connect,
|
|
158
|
+
**self.__transport_kwargs)
|
|
159
|
+
self.__spawned_sessions.append(conn)
|
|
160
|
+
return conn
|
|
161
|
+
|
|
162
|
+
def remove_session(self, session_based_connection):
|
|
163
|
+
session_based_connection.close()
|
|
164
|
+
self.__spawned_sessions.remove(session_based_connection)
|
|
165
|
+
|
|
166
|
+
def commit(self):
|
|
167
|
+
log.info("Submitting commit graph operation.")
|
|
168
|
+
return self._client.submit(Bytecode.GraphOp.commit())
|
|
169
|
+
|
|
170
|
+
def rollback(self):
|
|
171
|
+
log.info("Submitting rollback graph operation.")
|
|
172
|
+
return self._client.submit(Bytecode.GraphOp.rollback())
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _extract_request_options(bytecode):
|
|
176
|
+
options_strategy = next((x for x in bytecode.source_instructions
|
|
177
|
+
if x[0] == "withStrategies" and type(x[1]) is OptionsStrategy), None)
|
|
178
|
+
request_options = None
|
|
179
|
+
if options_strategy:
|
|
180
|
+
allowed_keys = ['evaluationTimeout', 'scriptEvaluationTimeout', 'batchSize', 'requestId', 'userAgent']
|
|
181
|
+
request_options = {allowed: options_strategy[1].configuration[allowed] for allowed in allowed_keys
|
|
182
|
+
if allowed in options_strategy[1].configuration}
|
|
183
|
+
return request_options
|
|
@@ -0,0 +1,259 @@
|
|
|
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
|
+
import logging
|
|
21
|
+
import abc
|
|
22
|
+
import base64
|
|
23
|
+
import struct
|
|
24
|
+
|
|
25
|
+
# import kerberos Optional dependency imported in relevant codeblock
|
|
26
|
+
|
|
27
|
+
from gremlin_python.driver import request
|
|
28
|
+
from gremlin_python.driver.resultset import ResultSet
|
|
29
|
+
from gremlin_python.process.translator import Translator
|
|
30
|
+
from gremlin_python.process.traversal import Bytecode
|
|
31
|
+
|
|
32
|
+
log = logging.getLogger("gremlinpython")
|
|
33
|
+
|
|
34
|
+
__author__ = 'David M. Brown (davebshow@gmail.com)'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GremlinServerError(Exception):
|
|
38
|
+
def __init__(self, status):
|
|
39
|
+
super(GremlinServerError, self).__init__('{0}: {1}'.format(status['code'], status['message']))
|
|
40
|
+
self._status_attributes = status['attributes']
|
|
41
|
+
self.status_code = status['code']
|
|
42
|
+
self.status_message = status['message']
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def status_attributes(self):
|
|
46
|
+
return self._status_attributes
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConfigurationError(Exception):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AbstractBaseProtocol(metaclass=abc.ABCMeta):
|
|
54
|
+
|
|
55
|
+
@abc.abstractmethod
|
|
56
|
+
def connection_made(self, transport):
|
|
57
|
+
self._transport = transport
|
|
58
|
+
|
|
59
|
+
@abc.abstractmethod
|
|
60
|
+
def data_received(self, message, results_dict):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abc.abstractmethod
|
|
64
|
+
def write(self, request_id, request_message):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class GremlinServerWSProtocol(AbstractBaseProtocol):
|
|
69
|
+
QOP_AUTH_BIT = 1
|
|
70
|
+
_kerberos_context = None
|
|
71
|
+
_max_content_length = 10 * 1024 * 1024
|
|
72
|
+
|
|
73
|
+
def __init__(self,
|
|
74
|
+
message_serializer,
|
|
75
|
+
username='', password='',
|
|
76
|
+
kerberized_service='',
|
|
77
|
+
max_content_length=10 * 1024 * 1024):
|
|
78
|
+
self._message_serializer = message_serializer
|
|
79
|
+
self._username = username
|
|
80
|
+
self._password = password
|
|
81
|
+
self._kerberized_service = kerberized_service
|
|
82
|
+
self._max_content_length = max_content_length
|
|
83
|
+
|
|
84
|
+
def connection_made(self, transport):
|
|
85
|
+
super(GremlinServerWSProtocol, self).connection_made(transport)
|
|
86
|
+
|
|
87
|
+
def write(self, request_id, request_message):
|
|
88
|
+
message = self._message_serializer.serialize_message(request_id, request_message)
|
|
89
|
+
self._transport.write(message)
|
|
90
|
+
|
|
91
|
+
def data_received(self, message, results_dict):
|
|
92
|
+
# if Gremlin Server cuts off then we get a None for the message
|
|
93
|
+
if message is None:
|
|
94
|
+
log.error("Received empty message from server.")
|
|
95
|
+
raise GremlinServerError({'code': 500,
|
|
96
|
+
'message': 'Server disconnected - please try to reconnect', 'attributes': {}})
|
|
97
|
+
|
|
98
|
+
message = self._message_serializer.deserialize_message(message)
|
|
99
|
+
request_id = message['requestId']
|
|
100
|
+
result_set = results_dict[request_id] if request_id in results_dict else ResultSet(None, None)
|
|
101
|
+
status_code = message['status']['code']
|
|
102
|
+
aggregate_to = message['result']['meta'].get('aggregateTo', 'list')
|
|
103
|
+
data = message['result']['data']
|
|
104
|
+
result_set.aggregate_to = aggregate_to
|
|
105
|
+
if status_code == 407:
|
|
106
|
+
if self._username and self._password:
|
|
107
|
+
auth_bytes = b''.join([b'\x00', self._username.encode('utf-8'),
|
|
108
|
+
b'\x00', self._password.encode('utf-8')])
|
|
109
|
+
auth = base64.b64encode(auth_bytes)
|
|
110
|
+
request_message = request.RequestMessage(
|
|
111
|
+
'traversal', 'authentication', {'sasl': auth.decode()})
|
|
112
|
+
elif self._kerberized_service:
|
|
113
|
+
request_message = self._kerberos_received(message)
|
|
114
|
+
else:
|
|
115
|
+
error_message = 'Gremlin server requires authentication credentials in DriverRemoteConnection. ' \
|
|
116
|
+
'For basic authentication provide username and password. ' \
|
|
117
|
+
'For kerberos authentication provide the kerberized_service parameter.'
|
|
118
|
+
log.error(error_message)
|
|
119
|
+
raise ConfigurationError(error_message)
|
|
120
|
+
self.write(request_id, request_message)
|
|
121
|
+
data = self._transport.read()
|
|
122
|
+
# Allow for auth handshake with multiple steps
|
|
123
|
+
return self.data_received(data, results_dict)
|
|
124
|
+
elif status_code == 204:
|
|
125
|
+
result_set.stream.put_nowait([])
|
|
126
|
+
del results_dict[request_id]
|
|
127
|
+
return status_code
|
|
128
|
+
elif status_code in [200, 206]:
|
|
129
|
+
result_set.stream.put_nowait(data)
|
|
130
|
+
if status_code == 200:
|
|
131
|
+
result_set.status_attributes = message['status']['attributes']
|
|
132
|
+
del results_dict[request_id]
|
|
133
|
+
return status_code
|
|
134
|
+
else:
|
|
135
|
+
# This message is going to be huge and kind of hard to read, but in the event of an error,
|
|
136
|
+
# it can provide invaluable info, so space it out appropriately.
|
|
137
|
+
log.error("\r\nReceived error message '%s'\r\n\r\nWith results dictionary '%s'",
|
|
138
|
+
str(message), str(results_dict))
|
|
139
|
+
del results_dict[request_id]
|
|
140
|
+
raise GremlinServerError(message['status'])
|
|
141
|
+
|
|
142
|
+
def _kerberos_received(self, message):
|
|
143
|
+
# Inspired by: https://github.com/thobbs/pure-sasl/blob/0.6.2/puresasl/mechanisms.py
|
|
144
|
+
# https://github.com/thobbs/pure-sasl/blob/0.6.2/LICENSE
|
|
145
|
+
try:
|
|
146
|
+
import kerberos
|
|
147
|
+
except ImportError:
|
|
148
|
+
raise ImportError('Please install gremlinpython[kerberos].')
|
|
149
|
+
|
|
150
|
+
# First pass: get service granting ticket and return it to gremlin-server
|
|
151
|
+
if not self._kerberos_context:
|
|
152
|
+
try:
|
|
153
|
+
_, kerberos_context = kerberos.authGSSClientInit(
|
|
154
|
+
self._kerberized_service, gssflags=kerberos.GSS_C_MUTUAL_FLAG)
|
|
155
|
+
kerberos.authGSSClientStep(kerberos_context, '')
|
|
156
|
+
auth = kerberos.authGSSClientResponse(kerberos_context)
|
|
157
|
+
self._kerberos_context = kerberos_context
|
|
158
|
+
except kerberos.KrbError as e:
|
|
159
|
+
raise ConfigurationError(
|
|
160
|
+
'Kerberos authentication requires a valid service name in DriverRemoteConnection, '
|
|
161
|
+
'as well as a valid tgt (export KRB5CCNAME) or keytab (export KRB5_KTNAME): ' + str(e))
|
|
162
|
+
return request.RequestMessage('', 'authentication', {'sasl': auth})
|
|
163
|
+
|
|
164
|
+
# Second pass: completion of authentication
|
|
165
|
+
sasl_response = message['status']['attributes']['sasl']
|
|
166
|
+
if not self._username:
|
|
167
|
+
result_code = kerberos.authGSSClientStep(self._kerberos_context, sasl_response)
|
|
168
|
+
if result_code == kerberos.AUTH_GSS_COMPLETE:
|
|
169
|
+
self._username = kerberos.authGSSClientUserName(self._kerberos_context)
|
|
170
|
+
return request.RequestMessage('', 'authentication', {'sasl': ''})
|
|
171
|
+
|
|
172
|
+
# Third pass: sasl quality of protection (qop) handshake
|
|
173
|
+
|
|
174
|
+
# Gremlin-server Krb5Authenticator only supports qop=QOP_AUTH; use ssl for confidentiality.
|
|
175
|
+
# Handshake content format:
|
|
176
|
+
# byte 0: the selected qop. 1==auth, 2==auth-int, 4==auth-conf
|
|
177
|
+
# byte 1-3: the max length for any buffer sent back and forth on this connection. (big endian)
|
|
178
|
+
# the rest of the buffer: the authorization user name in UTF-8 - not null terminated.
|
|
179
|
+
kerberos.authGSSClientUnwrap(self._kerberos_context, sasl_response)
|
|
180
|
+
data = kerberos.authGSSClientResponse(self._kerberos_context)
|
|
181
|
+
plaintext_data = base64.b64decode(data)
|
|
182
|
+
assert len(plaintext_data) == 4, "Unexpected response from gremlin server sasl handshake"
|
|
183
|
+
word, = struct.unpack('!I', plaintext_data)
|
|
184
|
+
qop_bits = word >> 24
|
|
185
|
+
assert self.QOP_AUTH_BIT & qop_bits, "Unexpected sasl qop level received from gremlin server"
|
|
186
|
+
|
|
187
|
+
name_length = len(self._username)
|
|
188
|
+
fmt = '!I' + str(name_length) + 's'
|
|
189
|
+
word = self.QOP_AUTH_BIT << 24 | self._max_content_length
|
|
190
|
+
out = struct.pack(fmt, word, self._username.encode("utf-8"), )
|
|
191
|
+
encoded = base64.b64encode(out).decode('ascii')
|
|
192
|
+
kerberos.authGSSClientWrap(self._kerberos_context, encoded)
|
|
193
|
+
auth = kerberos.authGSSClientResponse(self._kerberos_context)
|
|
194
|
+
return request.RequestMessage('', 'authentication', {'sasl': auth})
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class GremlinServerHTTPProtocol(AbstractBaseProtocol):
|
|
198
|
+
|
|
199
|
+
def __init__(self,
|
|
200
|
+
message_serializer,
|
|
201
|
+
username='', password=''):
|
|
202
|
+
self._message_serializer = message_serializer
|
|
203
|
+
self._username = username
|
|
204
|
+
self._password = password
|
|
205
|
+
|
|
206
|
+
def connection_made(self, transport):
|
|
207
|
+
super(GremlinServerHTTPProtocol, self).connection_made(transport)
|
|
208
|
+
|
|
209
|
+
def write(self, request_id, request_message):
|
|
210
|
+
|
|
211
|
+
basic_auth = {}
|
|
212
|
+
if self._username and self._password:
|
|
213
|
+
basic_auth['username'] = self._username
|
|
214
|
+
basic_auth['password'] = self._password
|
|
215
|
+
|
|
216
|
+
content_type = str(self._message_serializer.version, encoding='utf-8')
|
|
217
|
+
message = {
|
|
218
|
+
'headers': {'CONTENT-TYPE': content_type,
|
|
219
|
+
'ACCEPT': content_type},
|
|
220
|
+
'payload': self._message_serializer.serialize_message(request_id, request_message),
|
|
221
|
+
'auth': basic_auth
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
self._transport.write(message)
|
|
225
|
+
|
|
226
|
+
def data_received(self, response, results_dict):
|
|
227
|
+
# if Gremlin Server cuts off then we get a None for the message
|
|
228
|
+
if response is None:
|
|
229
|
+
log.error("Received empty message from server.")
|
|
230
|
+
raise GremlinServerError({'code': 500,
|
|
231
|
+
'message': 'Server disconnected - please try to reconnect', 'attributes': {}})
|
|
232
|
+
|
|
233
|
+
if response['ok']:
|
|
234
|
+
message = self._message_serializer.deserialize_message(response['content'])
|
|
235
|
+
request_id = message['requestId']
|
|
236
|
+
result_set = results_dict[request_id] if request_id in results_dict else ResultSet(None, None)
|
|
237
|
+
status_code = message['status']['code']
|
|
238
|
+
aggregate_to = message['result']['meta'].get('aggregateTo', 'list')
|
|
239
|
+
data = message['result']['data']
|
|
240
|
+
result_set.aggregate_to = aggregate_to
|
|
241
|
+
|
|
242
|
+
if status_code == 204:
|
|
243
|
+
result_set.stream.put_nowait([])
|
|
244
|
+
del results_dict[request_id]
|
|
245
|
+
return status_code
|
|
246
|
+
elif status_code in [200, 206]:
|
|
247
|
+
result_set.stream.put_nowait(data)
|
|
248
|
+
if status_code == 200:
|
|
249
|
+
result_set.status_attributes = message['status']['attributes']
|
|
250
|
+
del results_dict[request_id]
|
|
251
|
+
return status_code
|
|
252
|
+
else:
|
|
253
|
+
# This message is going to be huge and kind of hard to read, but in the event of an error,
|
|
254
|
+
# it can provide invaluable info, so space it out appropriately.
|
|
255
|
+
log.error("\r\nReceived error message '%s'\r\n\r\nWith results dictionary '%s'",
|
|
256
|
+
str(response['content']), str(results_dict))
|
|
257
|
+
body = json.loads(response['content'])
|
|
258
|
+
del results_dict[body['requestId']]
|
|
259
|
+
raise GremlinServerError({'code': response['status'], 'message': body['message'], 'attributes': {}})
|
|
@@ -0,0 +1,84 @@
|
|
|
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 abc
|
|
20
|
+
|
|
21
|
+
from gremlin_python.process import traversal
|
|
22
|
+
|
|
23
|
+
__author__ = 'Marko A. Rodriguez (http://markorodriguez.com), Lyndon Bauto (lyndonb@bitquilltech.com)'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RemoteConnection(object, metaclass=abc.ABCMeta):
|
|
27
|
+
def __init__(self, url, traversal_source):
|
|
28
|
+
self._url = url
|
|
29
|
+
self._traversal_source = traversal_source
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def url(self):
|
|
33
|
+
return self._url
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def traversal_source(self):
|
|
37
|
+
return self._traversal_source
|
|
38
|
+
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
def submit(self, bytecode):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def is_closed(self):
|
|
44
|
+
raise Exception('is_closed() must be implemented')
|
|
45
|
+
|
|
46
|
+
def is_session_bound(self):
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
def create_session(self):
|
|
50
|
+
raise Exception('createSession() must be implemented');
|
|
51
|
+
|
|
52
|
+
def commit(self):
|
|
53
|
+
raise Exception('commit() must be implemented')
|
|
54
|
+
|
|
55
|
+
def rollback(self):
|
|
56
|
+
raise Exception('rollback() must be implemented')
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
return "remoteconnection[" + self._url + "," + self._traversal_source + "]"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class RemoteTraversal(traversal.Traversal):
|
|
63
|
+
def __init__(self, traversers):
|
|
64
|
+
super(RemoteTraversal, self).__init__(None, None, None)
|
|
65
|
+
self.traversers = traversers
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RemoteStrategy(traversal.TraversalStrategy):
|
|
69
|
+
def __init__(self, remote_connection):
|
|
70
|
+
# Gave this a fqcn that has a local "py:" prefix since this strategy isn't sent as bytecode to the server.
|
|
71
|
+
# this is a sort of local-only strategy that actually executes client side. not sure if this prefix is the
|
|
72
|
+
# right way to name this or not, but it should have a name to identify it.
|
|
73
|
+
traversal.TraversalStrategy.__init__(self, fqcn="py:RemoteStrategy")
|
|
74
|
+
self.remote_connection = remote_connection
|
|
75
|
+
|
|
76
|
+
def apply(self, traversal):
|
|
77
|
+
if traversal.traversers is None:
|
|
78
|
+
remote_traversal = self.remote_connection.submit(traversal.bytecode)
|
|
79
|
+
traversal.remote_results = remote_traversal
|
|
80
|
+
traversal.traversers = remote_traversal.traversers
|
|
81
|
+
|
|
82
|
+
def apply_async(self, traversal):
|
|
83
|
+
if traversal.traversers is None:
|
|
84
|
+
traversal.remote_results = self.remote_connection.submit_async(traversal.bytecode)
|
|
@@ -0,0 +1,25 @@
|
|
|
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 collections
|
|
20
|
+
|
|
21
|
+
__author__ = 'David M. Brown (davebshow@gmail.com)'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
RequestMessage = collections.namedtuple(
|
|
25
|
+
'RequestMessage', ['processor', 'op', 'args'])
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
from concurrent.futures import Future
|
|
20
|
+
|
|
21
|
+
__author__ = 'David M. Brown (davebshow@gmail.com)'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ResultSet:
|
|
25
|
+
|
|
26
|
+
def __init__(self, stream, request_id):
|
|
27
|
+
self._stream = stream
|
|
28
|
+
self._request_id = request_id
|
|
29
|
+
self._done = None
|
|
30
|
+
self._aggregate_to = None
|
|
31
|
+
self._status_attributes = {}
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def aggregate_to(self):
|
|
35
|
+
return self._aggregate_to
|
|
36
|
+
|
|
37
|
+
@aggregate_to.setter
|
|
38
|
+
def aggregate_to(self, val):
|
|
39
|
+
self._aggregate_to = val
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def status_attributes(self):
|
|
43
|
+
return self._status_attributes
|
|
44
|
+
|
|
45
|
+
@status_attributes.setter
|
|
46
|
+
def status_attributes(self, val):
|
|
47
|
+
self._status_attributes = val
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def request_id(self):
|
|
51
|
+
return self._request_id
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def stream(self):
|
|
55
|
+
return self._stream
|
|
56
|
+
|
|
57
|
+
def __iter__(self):
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __next__(self):
|
|
61
|
+
result = self.one()
|
|
62
|
+
if not result:
|
|
63
|
+
raise StopIteration
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
def next(self):
|
|
67
|
+
return self.__next__()
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def done(self):
|
|
71
|
+
return self._done
|
|
72
|
+
|
|
73
|
+
@done.setter
|
|
74
|
+
def done(self, future):
|
|
75
|
+
self._done = future
|
|
76
|
+
|
|
77
|
+
def one(self):
|
|
78
|
+
while not self.done.done():
|
|
79
|
+
if not self.stream.empty():
|
|
80
|
+
return self.stream.get_nowait()
|
|
81
|
+
if not self.stream.empty():
|
|
82
|
+
return self.stream.get_nowait()
|
|
83
|
+
return self.done.result()
|
|
84
|
+
|
|
85
|
+
def all(self):
|
|
86
|
+
future = Future()
|
|
87
|
+
|
|
88
|
+
def cb(f):
|
|
89
|
+
try:
|
|
90
|
+
f.result()
|
|
91
|
+
except Exception as e:
|
|
92
|
+
future.set_exception(e)
|
|
93
|
+
else:
|
|
94
|
+
results = []
|
|
95
|
+
while not self.stream.empty():
|
|
96
|
+
results += self.stream.get_nowait()
|
|
97
|
+
future.set_result(results)
|
|
98
|
+
|
|
99
|
+
self.done.add_done_callback(cb)
|
|
100
|
+
return future
|