gremlinpython 4.0.0b2__py3-none-any.whl → 4.0.0.dev1__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.
@@ -16,10 +16,13 @@
16
16
  # specific language governing permissions and limitations
17
17
  # under the License.
18
18
  #
19
+ import queue
19
20
  from concurrent.futures import Future
20
21
 
21
22
  __author__ = 'David M. Brown (davebshow@gmail.com)'
22
23
 
24
+ _EXHAUSTED = object()
25
+
23
26
 
24
27
  class ResultSet:
25
28
 
@@ -45,7 +48,7 @@ class ResultSet:
45
48
 
46
49
  def __next__(self):
47
50
  result = self.one()
48
- if not result:
51
+ if result is _EXHAUSTED:
49
52
  raise StopIteration
50
53
  return result
51
54
 
@@ -62,11 +65,14 @@ class ResultSet:
62
65
 
63
66
  def one(self):
64
67
  while not self.done.done():
65
- if not self.stream.empty():
66
- return self.stream.get_nowait()
68
+ try:
69
+ return self.stream.get(timeout=0.1)
70
+ except queue.Empty:
71
+ pass
67
72
  if not self.stream.empty():
68
73
  return self.stream.get_nowait()
69
- return self.done.result()
74
+ self.done.result()
75
+ return _EXHAUSTED
70
76
 
71
77
  def all(self):
72
78
  future = Future()
@@ -79,7 +85,7 @@ class ResultSet:
79
85
  else:
80
86
  results = []
81
87
  while not self.stream.empty():
82
- results += self.stream.get_nowait()
88
+ results.append(self.stream.get_nowait())
83
89
  future.set_result(results)
84
90
 
85
91
  self.done.add_done_callback(cb)
@@ -17,81 +17,13 @@
17
17
  # under the License.
18
18
  #
19
19
 
20
- import base64
21
- import logging
22
- import struct
23
- import io
24
-
20
+ from gremlin_python.driver.connection import GremlinServerError
25
21
  from gremlin_python.process.traversal import Traverser
26
-
27
- try:
28
- import ujson as json
29
- if int(json.__version__[0]) < 2:
30
- logging.warning("Detected ujson version below 2.0.0. This is not recommended as precision may be lost.")
31
- except ImportError:
32
- import json
33
-
34
- from gremlin_python.structure.io import graphbinaryV4, graphsonV4
22
+ from gremlin_python.structure.io import graphbinaryV4
35
23
  from gremlin_python.structure.io.util import Marker
36
24
 
37
25
  __author__ = 'David M. Brown (davebshow@gmail.com), Lyndon Bauto (lyndonb@bitquilltech.com)'
38
26
 
39
- """
40
- GraphSONV4
41
- """
42
-
43
- class GraphSONSerializersV4(object):
44
- """
45
- Message serializer for GraphSON. Allow users to pass custom reader,
46
- writer, and version kwargs for custom serialization. Otherwise,
47
- use current GraphSON version as default.
48
- """
49
-
50
- # KEEP TRACK OF CURRENT DEFAULTS
51
- DEFAULT_READER_CLASS = graphsonV4.GraphSONReader
52
- DEFAULT_WRITER_CLASS = graphsonV4.GraphSONWriter
53
- DEFAULT_VERSION = b"application/vnd.gremlin-v4.0+json"
54
-
55
- def __init__(self, reader=None, writer=None, version=None):
56
- if not version:
57
- version = self.DEFAULT_VERSION
58
- self._version = version
59
- if not reader:
60
- reader = self.DEFAULT_READER_CLASS()
61
- self._graphson_reader = reader
62
- if not writer:
63
- writer = self.DEFAULT_WRITER_CLASS()
64
- self._graphson_writer = writer
65
-
66
- @property
67
- def version(self):
68
- """Read only property"""
69
- return self._version
70
-
71
- def serialize_message(self, request_message):
72
- message = self.build_message(request_message.fields, request_message.gremlin)
73
- return message
74
-
75
- def build_message(self, fields, gremlin):
76
- message = {
77
- 'gremlin': gremlin # gremlin wil always be a gremlin lang string script
78
- }
79
- for k, v in fields.items():
80
- message[k] = self._graphson_writer.to_dict(v)
81
- return self.finalize_message(message)
82
-
83
- def finalize_message(self, message):
84
- message = json.dumps(message)
85
- return message
86
-
87
- def deserialize_message(self, message, is_first_chunk=False):
88
- if is_first_chunk:
89
- msg = json.loads(message if isinstance(message, str) else message.decode('utf-8'))
90
- return self._graphson_reader.to_object(msg)
91
- else:
92
- # graphSON does not stream, all results are aggregated inside the first chunk
93
- return ""
94
-
95
27
  """
96
28
  GraphBinaryV4
97
29
  """
@@ -100,23 +32,27 @@ class GraphBinarySerializersV4(object):
100
32
  DEFAULT_WRITER_CLASS = graphbinaryV4.GraphBinaryWriter
101
33
  DEFAULT_VERSION = b"application/vnd.graphbinary-v4.0"
102
34
 
103
- max_int64 = 0xFFFFFFFFFFFFFFFF
104
- header_struct = struct.Struct('>b32sBQQ')
105
- header_pack = header_struct.pack
106
35
  int_pack = graphbinaryV4.int32_pack
107
- int32_unpack = struct.Struct(">i").unpack
108
36
 
109
- def __init__(self, reader=None, writer=None, version=None):
37
+ def __init__(self, reader=None, writer=None, version=None, pdt_registry=None):
110
38
  if not version:
111
39
  version = self.DEFAULT_VERSION
112
40
  self._version = version
113
41
  if not reader:
114
- reader = self.DEFAULT_READER_CLASS()
42
+ reader = self.DEFAULT_READER_CLASS(pdt_registry=pdt_registry)
115
43
  self._graphbinary_reader = reader
116
44
  if not writer:
117
45
  writer = self.DEFAULT_WRITER_CLASS()
118
46
  self._graphbinary_writer = writer
119
- self._bulked = False
47
+
48
+ def configure_pdt_registry(self, pdt_registry):
49
+ if self._graphbinary_reader.pdt_registry is None:
50
+ self._graphbinary_reader.pdt_registry = pdt_registry
51
+ else:
52
+ self._graphbinary_reader.pdt_registry._composite_adapters_by_name.update(pdt_registry._composite_adapters_by_name)
53
+ self._graphbinary_reader.pdt_registry._composite_adapters_by_class.update(pdt_registry._composite_adapters_by_class)
54
+ self._graphbinary_reader.pdt_registry._primitive_adapters_by_name.update(pdt_registry._primitive_adapters_by_name)
55
+ self._graphbinary_reader.pdt_registry._primitive_adapters_by_class.update(pdt_registry._primitive_adapters_by_class)
120
56
 
121
57
  @property
122
58
  def version(self):
@@ -153,58 +89,42 @@ class GraphBinarySerializersV4(object):
153
89
 
154
90
  return bytes(ba)
155
91
 
156
- def deserialize_message(self, message, is_first_chunk=False):
157
- if len(message) == 0:
158
- return {'status': {'code': 204},
159
- 'result': {'meta': {},
160
- 'data': []}}
161
-
162
- # for parsing string message via HTTP connections
163
- b = io.BytesIO(base64.b64decode(message) if isinstance(message, str) else message)
164
-
165
- if is_first_chunk:
166
- b.read(1) # version
167
- self._bulked = b.read(1)[0] == 0x01
168
-
169
- result, readable = self.read_payload(b)
170
- if not readable:
171
- return {
172
- 'result': {'meta': {},
173
- 'data': result}
174
- }
175
- status_code = self.int32_unpack(b.read(4))[0] # status code
176
- status_msg = self._graphbinary_reader.to_object(b, graphbinaryV4.DataType.string, nullable=True)
177
- status_ex = self._graphbinary_reader.to_object(b, graphbinaryV4.DataType.string, nullable=True)
178
- # meta_attrs = self._graphbinary_reader.to_object(b, graphbinaryV4.DataType.map, nullable=False)
179
-
180
- b.close()
181
-
182
- msg = {'status': {'code': status_code,
183
- 'message': status_msg,
184
- 'exception': status_ex},
185
- 'result': {'meta': {},
186
- 'data': result}}
187
-
188
- return msg
189
-
190
- def read_payload(self, buffer):
191
- results = []
192
- readable = True
193
- while buffer.readable(): # find method or way to access readable bytes without using buffer.getvalue()
194
- if buffer.tell() == len(buffer.getvalue()):
195
- readable = False
92
+ def deserialize_response_stream(self, stream):
93
+ """
94
+ Yield each result item from a GraphBinary response stream as it is read
95
+ off the wire, then raise ``GremlinServerError`` if the trailing status
96
+ is not a success code. ``Connection._receive`` dispatches through this
97
+ method so custom response serializers can replace GraphBinary parsing.
98
+ """
99
+ reader = self._graphbinary_reader
100
+
101
+ # Response header: version byte + flags byte
102
+ stream.read(1)
103
+ flags = stream.read(1)[0]
104
+ bulked = flags == 0x01
105
+
106
+ while True:
107
+ obj = reader.to_object(stream)
108
+ if obj == Marker.end_of_stream():
196
109
  break
197
- if self._bulked:
198
- item = self._graphbinary_reader.to_object(buffer)
199
- if item == Marker.end_of_stream():
200
- self._bulked = False # no more data expected, reset bulked flag
201
- break
202
- bulk = self._graphbinary_reader.to_object(buffer)
203
- results.append(Traverser(item, bulk))
110
+ if bulked:
111
+ bulk = reader.to_object(stream)
112
+ yield Traverser(obj, bulk)
204
113
  else:
205
- data = self._graphbinary_reader.to_object(buffer)
206
- if data == Marker.end_of_stream():
207
- break
208
- results.append(data)
209
-
210
- return results, readable
114
+ yield obj
115
+
116
+ # Trailer: status code, message, exception
117
+ status_code = graphbinaryV4.int32_unpack(stream.read(4))
118
+ msg_is_null = stream.read(1)[0] == 0x01
119
+ status_message = '' if msg_is_null else reader.to_object(
120
+ stream, graphbinaryV4.DataType.string, False)
121
+ exc_is_null = stream.read(1)[0] == 0x01
122
+ status_exception = '' if exc_is_null else reader.to_object(
123
+ stream, graphbinaryV4.DataType.string, False)
124
+
125
+ if status_code not in (200, 204):
126
+ raise GremlinServerError({
127
+ 'code': status_code,
128
+ 'message': status_message,
129
+ 'exception': status_exception
130
+ })
@@ -0,0 +1,178 @@
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
+
21
+ from gremlin_python.driver.remote_connection import RemoteConnection, RemoteTraversal
22
+
23
+ log = logging.getLogger("gremlinpython")
24
+
25
+
26
+ class Transaction:
27
+ """Controls an explicit remote transaction. A thin wrapper around a Client
28
+ that adds transaction lifecycle (begin/commit/rollback/close) and attaches
29
+ a transactionId to every request.
30
+
31
+ Created via Client.transact() or g.tx(). The traversal source (g alias)
32
+ is inherited from the Client and cannot be changed.
33
+
34
+ Transactions are short-lived and single-use. After commit or rollback,
35
+ the transaction ID is invalid and the object cannot be reused.
36
+
37
+ This class is NOT thread-safe.
38
+ """
39
+
40
+ def __init__(self, client):
41
+ self._client = client
42
+ self._transaction_id = None
43
+ self._is_open = False
44
+ self._failed = False
45
+
46
+ def begin(self):
47
+ """Starts the transaction and returns a transaction-bound GraphTraversalSource.
48
+
49
+ The returned GTS can be used to submit traversals within this transaction.
50
+ Users of the driver-level API (client.transact()) may ignore the return
51
+ value and use submit() directly instead.
52
+
53
+ begin() is idempotent: calling it while a transaction is already open does not
54
+ send a second begin to the server and does not raise - it reuses the existing
55
+ transaction ID and returns a source bound to the same transaction. A transaction
56
+ is single-use, so calling begin() after it has been closed raises.
57
+ """
58
+ if self._failed:
59
+ raise Exception("Transaction is closed and cannot be reused; begin a new transaction")
60
+
61
+ # idempotent: if a transaction is already open, reuse the existing transactionId without
62
+ # sending a second begin to the server, and return a source bound to the same transaction
63
+ if not self._is_open:
64
+ try:
65
+ result = self._client.submit("g.tx().begin()")
66
+ results = result.all().result()
67
+ except Exception:
68
+ self._failed = True
69
+ raise
70
+
71
+ if not results:
72
+ self._failed = True
73
+ raise Exception("Server did not return transaction ID")
74
+
75
+ result_map = results[0]
76
+ if isinstance(result_map, dict):
77
+ self._transaction_id = result_map.get('transactionId')
78
+ else:
79
+ self._failed = True
80
+ raise Exception("Server did not return transaction ID in expected format")
81
+
82
+ if not self._transaction_id:
83
+ self._failed = True
84
+ raise Exception("Server returned empty transaction ID")
85
+
86
+ self._is_open = True
87
+ self._client.track_transaction(self)
88
+
89
+ # Return a GraphTraversalSource bound to this transaction via
90
+ # TransactionRemoteConnection. Inline imports avoid circular dependencies
91
+ # between driver/ and process/ packages.
92
+ from gremlin_python.process.graph_traversal import GraphTraversalSource
93
+ from gremlin_python.process.traversal import TraversalStrategies, GremlinLang
94
+ from gremlin_python.driver.remote_connection import RemoteStrategy
95
+
96
+ tx_connection = TransactionRemoteConnection(self)
97
+ strategies = TraversalStrategies()
98
+ strategies.add_strategies([RemoteStrategy(tx_connection)])
99
+ return GraphTraversalSource(None, strategies, GremlinLang())
100
+
101
+ def submit(self, gremlin, parameters=None, request_options=None):
102
+ """Submits a gremlin-lang string within this transaction.
103
+
104
+ The transactionId is automatically attached. Has the same signature
105
+ as Client.submit() so both can be used interchangeably.
106
+ """
107
+ if not self._is_open:
108
+ raise Exception("Transaction is not open")
109
+ opts = {'transactionId': self._transaction_id}
110
+ if request_options:
111
+ opts.update(request_options)
112
+ return self._client.submit(gremlin, parameters=parameters, request_options=opts)
113
+
114
+ def commit(self):
115
+ """Commits the transaction."""
116
+ self._close_transaction("g.tx().commit()")
117
+
118
+ def rollback(self):
119
+ """Rolls back the transaction."""
120
+ self._close_transaction("g.tx().rollback()")
121
+
122
+ def _close_transaction(self, script):
123
+ if not self._is_open:
124
+ raise Exception("Transaction is not open")
125
+ self._client.submit(script, request_options={'transactionId': self._transaction_id}).all().result()
126
+ self._is_open = False
127
+ self._failed = True # Terminal state: transaction cannot be reused
128
+ self._client.untrack_transaction(self)
129
+
130
+ @property
131
+ def is_open(self):
132
+ return self._is_open
133
+
134
+ @property
135
+ def transaction_id(self):
136
+ return self._transaction_id
137
+
138
+ def close(self):
139
+ if self._is_open:
140
+ self.rollback()
141
+
142
+ def __enter__(self):
143
+ return self
144
+
145
+ def __exit__(self, exc_type, exc_val, exc_tb):
146
+ if self._is_open:
147
+ self.rollback()
148
+ return False
149
+
150
+
151
+ class TransactionRemoteConnection(RemoteConnection):
152
+ """A RemoteConnection that attaches transactionId to all requests submitted
153
+ through the traversal API (g.tx().begin() -> gtx.addV()...).
154
+
155
+ This bridges the traversal machinery (which calls RemoteConnection.submit)
156
+ to the Transaction (which injects the transactionId).
157
+ """
158
+
159
+ def __init__(self, transaction):
160
+ super().__init__(
161
+ transaction._client._url,
162
+ transaction._client._traversal_source)
163
+ self._transaction = transaction
164
+
165
+ def submit(self, gremlin_lang):
166
+ if not self._transaction.is_open:
167
+ raise Exception("Transaction is not open")
168
+
169
+ from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
170
+ request_options = DriverRemoteConnection.extract_request_options(gremlin_lang)
171
+ request_options['transactionId'] = self._transaction.transaction_id
172
+
173
+ result_set = self._transaction._client.submit(
174
+ gremlin_lang.get_gremlin(), request_options=request_options)
175
+ return RemoteTraversal(result_set)
176
+
177
+ def is_closed(self):
178
+ return not self._transaction.is_open
@@ -18,7 +18,7 @@
18
18
  #
19
19
  import platform
20
20
 
21
- gremlin_version = "4.0.0-beta.2" # DO NOT MODIFY - Configured automatically by Maven Replacer Plugin
21
+ gremlin_version = "4.0.0-beta.3" # DO NOT MODIFY - Configured automatically by Maven Replacer Plugin
22
22
 
23
23
  def _generate_user_agent():
24
24
  application_name = "NotAvailable"