From c17584336291ca83d8eca68367d2a42dad7e73f6 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Tue, 5 Jul 2016 19:51:13 +0530 Subject: [PATCH 001/435] First cut of functional client with testcases --- .gitignore | 3 + pydgraph/LICENSE | 201 +++++++++++++++++++++ pydgraph/__init__.py | 0 pydgraph/client.py | 40 +++++ pydgraph/graphresponse_pb2.py | 330 ++++++++++++++++++++++++++++++++++ pydgraph/test/__init__.py | 0 pydgraph/test/test_queries.py | 108 +++++++++++ requirements.txt | 1 + 8 files changed, 683 insertions(+) create mode 100644 .gitignore create mode 100644 pydgraph/LICENSE create mode 100644 pydgraph/__init__.py create mode 100644 pydgraph/client.py create mode 100644 pydgraph/graphresponse_pb2.py create mode 100644 pydgraph/test/__init__.py create mode 100644 pydgraph/test/test_queries.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a71936b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +.idea + diff --git a/pydgraph/LICENSE b/pydgraph/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/pydgraph/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydgraph/client.py b/pydgraph/client.py new file mode 100644 index 00000000..1aa759b0 --- /dev/null +++ b/pydgraph/client.py @@ -0,0 +1,40 @@ +# +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This module contains the main user-facing methods for interacting with the +Dgraph server over gRPC. +""" + +__author__ = 'Mohit Ranka ' +__maintainer__ = 'Mohit Ranka ' +__version__ = '0.3' +__status__ = 'development' + +from grpc.beta import implementations +import graphresponse_pb2 + + +class DgraphClient(object): + + def __init__(self, host, port): + self.channel = implementations.insecure_channel(host, port) + self.stub = graphresponse_pb2.beta_create_Dgraph_stub(self.channel) + + def query(self, q, timeout=None): + request = graphresponse_pb2.Request(query=q) + response = self.stub.Query(request, timeout) + return response + diff --git a/pydgraph/graphresponse_pb2.py b/pydgraph/graphresponse_pb2.py new file mode 100644 index 00000000..50c0dac4 --- /dev/null +++ b/pydgraph/graphresponse_pb2.py @@ -0,0 +1,330 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: graphresponse.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='graphresponse.proto', + package='graph', + syntax='proto3', + serialized_pb=_b('\n\x13graphresponse.proto\x12\x05graph\"\x18\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"%\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x0b\n\x03val\x18\x02 \x01(\x0c\"w\n\x04Node\x12\x0b\n\x03uid\x18\x01 \x01(\x04\x12\x0b\n\x03xid\x18\x02 \x01(\t\x12\x11\n\tattribute\x18\x03 \x01(\t\x12#\n\nproperties\x18\x04 \x03(\x0b\x32\x0f.graph.Property\x12\x1d\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0b.graph.Node\"=\n\x08Response\x12\x16\n\x01n\x18\x01 \x01(\x0b\x32\x0b.graph.Node\x12\x19\n\x01l\x18\x02 \x01(\x0b\x32\x0e.graph.Latency24\n\x06\x44graph\x12*\n\x05Query\x12\x0e.graph.Request\x1a\x0f.graph.Response\"\x00\x62\x06proto3') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_REQUEST = _descriptor.Descriptor( + name='Request', + full_name='graph.Request', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='query', full_name='graph.Request.query', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=30, + serialized_end=54, +) + + +_LATENCY = _descriptor.Descriptor( + name='Latency', + full_name='graph.Latency', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='parsing', full_name='graph.Latency.parsing', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='processing', full_name='graph.Latency.processing', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='pb', full_name='graph.Latency.pb', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=56, + serialized_end=114, +) + + +_PROPERTY = _descriptor.Descriptor( + name='Property', + full_name='graph.Property', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prop', full_name='graph.Property.prop', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='val', full_name='graph.Property.val', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=116, + serialized_end=153, +) + + +_NODE = _descriptor.Descriptor( + name='Node', + full_name='graph.Node', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='uid', full_name='graph.Node.uid', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='xid', full_name='graph.Node.xid', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='attribute', full_name='graph.Node.attribute', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='properties', full_name='graph.Node.properties', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='children', full_name='graph.Node.children', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=155, + serialized_end=274, +) + + +_RESPONSE = _descriptor.Descriptor( + name='Response', + full_name='graph.Response', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='n', full_name='graph.Response.n', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='l', full_name='graph.Response.l', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=276, + serialized_end=337, +) + +_NODE.fields_by_name['properties'].message_type = _PROPERTY +_NODE.fields_by_name['children'].message_type = _NODE +_RESPONSE.fields_by_name['n'].message_type = _NODE +_RESPONSE.fields_by_name['l'].message_type = _LATENCY +DESCRIPTOR.message_types_by_name['Request'] = _REQUEST +DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY +DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY +DESCRIPTOR.message_types_by_name['Node'] = _NODE +DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE + +Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( + DESCRIPTOR = _REQUEST, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Request) + )) +_sym_db.RegisterMessage(Request) + +Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( + DESCRIPTOR = _LATENCY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Latency) + )) +_sym_db.RegisterMessage(Latency) + +Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( + DESCRIPTOR = _PROPERTY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Property) + )) +_sym_db.RegisterMessage(Property) + +Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( + DESCRIPTOR = _NODE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Node) + )) +_sym_db.RegisterMessage(Node) + +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( + DESCRIPTOR = _RESPONSE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Response) + )) +_sym_db.RegisterMessage(Response) + + +import abc +import six +from grpc.beta import implementations as beta_implementations +from grpc.beta import interfaces as beta_interfaces +from grpc.framework.common import cardinality +from grpc.framework.interfaces.face import utilities as face_utilities + +class BetaDgraphServicer(object): + def Query(self, request, context): + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + +class BetaDgraphStub(object): + def Query(self, request, timeout): + raise NotImplementedError() + Query.future = None + +def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): + import graphresponse_pb2 + import graphresponse_pb2 + request_deserializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.FromString, + } + response_serializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.SerializeToString, + } + method_implementations = { + ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), + } + server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) + return beta_implementations.server(method_implementations, options=server_options) + +def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): + import graphresponse_pb2 + import graphresponse_pb2 + request_serializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.SerializeToString, + } + response_deserializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.FromString, + } + cardinalities = { + 'Query': cardinality.Cardinality.UNARY_UNARY, + } + stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) + return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) +# @@protoc_insertion_point(module_scope) diff --git a/pydgraph/test/__init__.py b/pydgraph/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydgraph/test/test_queries.py b/pydgraph/test/test_queries.py new file mode 100644 index 00000000..6e694750 --- /dev/null +++ b/pydgraph/test/test_queries.py @@ -0,0 +1,108 @@ +# +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Mohit Ranka ' +__maintainer__ = 'Mohit Ranka ' + +import unittest +from client import DgraphClient + + +class DgraphClientTestCases(unittest.TestCase): + TEST_HOSTNAME = 'localhost' + TEST_PORT = 8081 + + def setUp(self): + self.client = DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) + + def test_mutation_and_query(self): + query_string = """ + mutation + { + set + { + \"Alice\" . + \"Greg\" . + . + } + } + + query + { + me(_xid_: alice) + { + follows + { + name _xid_ + } + } + } + """ + response = self.client.query(query_string) + self.assertEqual(response.n.children[0].xid, "greg") + self.assertEqual(response.n.children[0].attribute, "follows") + self.assertEqual(response.n.children[0].properties[0].prop, "name") + self.assertEqual(response.n.children[0].properties[0].val, "Greg") + self.assertTrue(isinstance(response.l.parsing, basestring), 'Parsing latency is not available') + self.assertTrue(isinstance(response.l.pb, basestring), 'Protocol buffers latency is not available') + self.assertTrue(isinstance(response.l.processing, basestring), 'Processing latency is not available') + + def test_mutation_and_query_two_levels(self): + query_string = """ + mutation + { + set + { + \"Bob\" . + \"Josh\" . + \"Rose\" . + . + . + } + } + + query + { + me(_xid_: bob) + { + name _xid_ follows + { + name _xid_ follows + { + name _xid_ + } + } + } + } + """ + response = self.client.query(query_string) + + self.assertEqual(len(response.n.children), 1) + self.assertEqual(response.n.children[0].xid, "josh") + self.assertEqual(response.n.children[0].attribute, "follows") + self.assertEqual(response.n.children[0].properties[0].prop, "name") + self.assertEqual(response.n.children[0].properties[0].val, "Josh") + + self.assertEqual(len(response.n.children[0].children), 1) + self.assertEqual(response.n.children[0].children[0].xid, "rose") + self.assertEqual(response.n.children[0].children[0].attribute, "follows") + self.assertEqual(response.n.children[0].children[0].properties[0].prop, "name") + self.assertEqual(response.n.children[0].children[0].properties[0].val, "Rose") + + + self.assertTrue(isinstance(response.l.parsing, basestring), 'Parsing latency is not available') + self.assertTrue(isinstance(response.l.pb, basestring), 'Protocol buffers latency is not available') + self.assertTrue(isinstance(response.l.processing, basestring), 'Processing latency is not available') + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d459dbf2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +grpcio==0.15.0 From b5fc7f272b8f3054c0bd334d65b4a58d2539b40c Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 14:36:44 +0530 Subject: [PATCH 002/435] Added setup.py to pydgraph --- .gitignore | 5 ++- pydgraph/LICENSE => LICENSE | 0 README.md | 3 ++ pydgraph/__init__.py | 1 + pydgraph/client.py | 6 ++-- setup.py | 41 ++++++++++++++++++++++++ {pydgraph/test => tests}/__init__.py | 0 {pydgraph/test => tests}/test_queries.py | 2 +- 8 files changed, 52 insertions(+), 6 deletions(-) rename pydgraph/LICENSE => LICENSE (100%) create mode 100644 setup.py rename {pydgraph/test => tests}/__init__.py (100%) rename {pydgraph/test => tests}/test_queries.py (98%) diff --git a/.gitignore b/.gitignore index a71936b2..329829b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.pyc .idea - +build/* +dist/* +pydgraph.egg-info/* +.eggs diff --git a/pydgraph/LICENSE b/LICENSE similarity index 100% rename from pydgraph/LICENSE rename to LICENSE diff --git a/README.md b/README.md index b2444bc5..9a702ee5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # pydgraph Python client for Dgraph + +## Documentation +Please refer the [wiki](https://wiki.dgraph.io/Clients#Python) for **Installation** and **How-To**s. diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py index e69de29b..98f6d1ef 100644 --- a/pydgraph/__init__.py +++ b/pydgraph/__init__.py @@ -0,0 +1 @@ +__all__ = ['client'] diff --git a/pydgraph/client.py b/pydgraph/client.py index 1aa759b0..2e8ec346 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -17,18 +17,17 @@ This module contains the main user-facing methods for interacting with the Dgraph server over gRPC. """ +from grpc.beta import implementations +import graphresponse_pb2 __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' __version__ = '0.3' __status__ = 'development' -from grpc.beta import implementations -import graphresponse_pb2 class DgraphClient(object): - def __init__(self, host, port): self.channel = implementations.insecure_channel(host, port) self.stub = graphresponse_pb2.beta_create_Dgraph_stub(self.channel) @@ -37,4 +36,3 @@ def query(self, q, timeout=None): request = graphresponse_pb2.Request(query=q) response = self.stub.Query(request, timeout) return response - diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..68a81f34 --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +# +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +setup(name="pydgraph", + version=0.3, + description="Dgraph driver for Python", + license="Apache License, Version 2.0", + author="Mohit Ranka", + author_email="mohitranka@gmail.com", + url="https://github.com/dgraph-io/pydgraph", + classifiers=[ + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: Database", + "Topic :: Software Development", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + ], + packages=["pydgraph"], + install_requires=open('requirements.txt').readlines(), + test_suite='tests', + ) diff --git a/pydgraph/test/__init__.py b/tests/__init__.py similarity index 100% rename from pydgraph/test/__init__.py rename to tests/__init__.py diff --git a/pydgraph/test/test_queries.py b/tests/test_queries.py similarity index 98% rename from pydgraph/test/test_queries.py rename to tests/test_queries.py index 6e694750..5cd19862 100644 --- a/pydgraph/test/test_queries.py +++ b/tests/test_queries.py @@ -17,7 +17,7 @@ __maintainer__ = 'Mohit Ranka ' import unittest -from client import DgraphClient +from pydgraph.client import DgraphClient class DgraphClientTestCases(unittest.TestCase): From 532e557b2d423dc2b7412590f6f00de7cfb9747b Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 14:40:25 +0530 Subject: [PATCH 003/435] Release 0.3 --- .gitignore | 6 + LICENSE | 201 +++++++++++++++++++++ README.md | 3 + pydgraph/__init__.py | 1 + pydgraph/client.py | 38 ++++ pydgraph/graphresponse_pb2.py | 330 ++++++++++++++++++++++++++++++++++ requirements.txt | 1 + setup.py | 41 +++++ tests/__init__.py | 0 tests/test_queries.py | 108 +++++++++++ 10 files changed, 729 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 pydgraph/__init__.py create mode 100644 pydgraph/client.py create mode 100644 pydgraph/graphresponse_pb2.py create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_queries.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..329829b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc +.idea +build/* +dist/* +pydgraph.egg-info/* +.eggs diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index b2444bc5..9a702ee5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # pydgraph Python client for Dgraph + +## Documentation +Please refer the [wiki](https://wiki.dgraph.io/Clients#Python) for **Installation** and **How-To**s. diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py new file mode 100644 index 00000000..98f6d1ef --- /dev/null +++ b/pydgraph/__init__.py @@ -0,0 +1 @@ +__all__ = ['client'] diff --git a/pydgraph/client.py b/pydgraph/client.py new file mode 100644 index 00000000..2e8ec346 --- /dev/null +++ b/pydgraph/client.py @@ -0,0 +1,38 @@ +# +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This module contains the main user-facing methods for interacting with the +Dgraph server over gRPC. +""" +from grpc.beta import implementations +import graphresponse_pb2 + +__author__ = 'Mohit Ranka ' +__maintainer__ = 'Mohit Ranka ' +__version__ = '0.3' +__status__ = 'development' + + + +class DgraphClient(object): + def __init__(self, host, port): + self.channel = implementations.insecure_channel(host, port) + self.stub = graphresponse_pb2.beta_create_Dgraph_stub(self.channel) + + def query(self, q, timeout=None): + request = graphresponse_pb2.Request(query=q) + response = self.stub.Query(request, timeout) + return response diff --git a/pydgraph/graphresponse_pb2.py b/pydgraph/graphresponse_pb2.py new file mode 100644 index 00000000..50c0dac4 --- /dev/null +++ b/pydgraph/graphresponse_pb2.py @@ -0,0 +1,330 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: graphresponse.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='graphresponse.proto', + package='graph', + syntax='proto3', + serialized_pb=_b('\n\x13graphresponse.proto\x12\x05graph\"\x18\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"%\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x0b\n\x03val\x18\x02 \x01(\x0c\"w\n\x04Node\x12\x0b\n\x03uid\x18\x01 \x01(\x04\x12\x0b\n\x03xid\x18\x02 \x01(\t\x12\x11\n\tattribute\x18\x03 \x01(\t\x12#\n\nproperties\x18\x04 \x03(\x0b\x32\x0f.graph.Property\x12\x1d\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0b.graph.Node\"=\n\x08Response\x12\x16\n\x01n\x18\x01 \x01(\x0b\x32\x0b.graph.Node\x12\x19\n\x01l\x18\x02 \x01(\x0b\x32\x0e.graph.Latency24\n\x06\x44graph\x12*\n\x05Query\x12\x0e.graph.Request\x1a\x0f.graph.Response\"\x00\x62\x06proto3') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_REQUEST = _descriptor.Descriptor( + name='Request', + full_name='graph.Request', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='query', full_name='graph.Request.query', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=30, + serialized_end=54, +) + + +_LATENCY = _descriptor.Descriptor( + name='Latency', + full_name='graph.Latency', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='parsing', full_name='graph.Latency.parsing', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='processing', full_name='graph.Latency.processing', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='pb', full_name='graph.Latency.pb', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=56, + serialized_end=114, +) + + +_PROPERTY = _descriptor.Descriptor( + name='Property', + full_name='graph.Property', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prop', full_name='graph.Property.prop', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='val', full_name='graph.Property.val', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=116, + serialized_end=153, +) + + +_NODE = _descriptor.Descriptor( + name='Node', + full_name='graph.Node', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='uid', full_name='graph.Node.uid', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='xid', full_name='graph.Node.xid', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='attribute', full_name='graph.Node.attribute', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='properties', full_name='graph.Node.properties', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='children', full_name='graph.Node.children', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=155, + serialized_end=274, +) + + +_RESPONSE = _descriptor.Descriptor( + name='Response', + full_name='graph.Response', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='n', full_name='graph.Response.n', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='l', full_name='graph.Response.l', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=276, + serialized_end=337, +) + +_NODE.fields_by_name['properties'].message_type = _PROPERTY +_NODE.fields_by_name['children'].message_type = _NODE +_RESPONSE.fields_by_name['n'].message_type = _NODE +_RESPONSE.fields_by_name['l'].message_type = _LATENCY +DESCRIPTOR.message_types_by_name['Request'] = _REQUEST +DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY +DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY +DESCRIPTOR.message_types_by_name['Node'] = _NODE +DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE + +Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( + DESCRIPTOR = _REQUEST, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Request) + )) +_sym_db.RegisterMessage(Request) + +Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( + DESCRIPTOR = _LATENCY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Latency) + )) +_sym_db.RegisterMessage(Latency) + +Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( + DESCRIPTOR = _PROPERTY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Property) + )) +_sym_db.RegisterMessage(Property) + +Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( + DESCRIPTOR = _NODE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Node) + )) +_sym_db.RegisterMessage(Node) + +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( + DESCRIPTOR = _RESPONSE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Response) + )) +_sym_db.RegisterMessage(Response) + + +import abc +import six +from grpc.beta import implementations as beta_implementations +from grpc.beta import interfaces as beta_interfaces +from grpc.framework.common import cardinality +from grpc.framework.interfaces.face import utilities as face_utilities + +class BetaDgraphServicer(object): + def Query(self, request, context): + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + +class BetaDgraphStub(object): + def Query(self, request, timeout): + raise NotImplementedError() + Query.future = None + +def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): + import graphresponse_pb2 + import graphresponse_pb2 + request_deserializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.FromString, + } + response_serializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.SerializeToString, + } + method_implementations = { + ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), + } + server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) + return beta_implementations.server(method_implementations, options=server_options) + +def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): + import graphresponse_pb2 + import graphresponse_pb2 + request_serializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.SerializeToString, + } + response_deserializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.FromString, + } + cardinalities = { + 'Query': cardinality.Cardinality.UNARY_UNARY, + } + stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) + return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) +# @@protoc_insertion_point(module_scope) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d459dbf2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +grpcio==0.15.0 diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..68a81f34 --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +# +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +setup(name="pydgraph", + version=0.3, + description="Dgraph driver for Python", + license="Apache License, Version 2.0", + author="Mohit Ranka", + author_email="mohitranka@gmail.com", + url="https://github.com/dgraph-io/pydgraph", + classifiers=[ + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: Database", + "Topic :: Software Development", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + ], + packages=["pydgraph"], + install_requires=open('requirements.txt').readlines(), + test_suite='tests', + ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_queries.py b/tests/test_queries.py new file mode 100644 index 00000000..5cd19862 --- /dev/null +++ b/tests/test_queries.py @@ -0,0 +1,108 @@ +# +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Mohit Ranka ' +__maintainer__ = 'Mohit Ranka ' + +import unittest +from pydgraph.client import DgraphClient + + +class DgraphClientTestCases(unittest.TestCase): + TEST_HOSTNAME = 'localhost' + TEST_PORT = 8081 + + def setUp(self): + self.client = DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) + + def test_mutation_and_query(self): + query_string = """ + mutation + { + set + { + \"Alice\" . + \"Greg\" . + . + } + } + + query + { + me(_xid_: alice) + { + follows + { + name _xid_ + } + } + } + """ + response = self.client.query(query_string) + self.assertEqual(response.n.children[0].xid, "greg") + self.assertEqual(response.n.children[0].attribute, "follows") + self.assertEqual(response.n.children[0].properties[0].prop, "name") + self.assertEqual(response.n.children[0].properties[0].val, "Greg") + self.assertTrue(isinstance(response.l.parsing, basestring), 'Parsing latency is not available') + self.assertTrue(isinstance(response.l.pb, basestring), 'Protocol buffers latency is not available') + self.assertTrue(isinstance(response.l.processing, basestring), 'Processing latency is not available') + + def test_mutation_and_query_two_levels(self): + query_string = """ + mutation + { + set + { + \"Bob\" . + \"Josh\" . + \"Rose\" . + . + . + } + } + + query + { + me(_xid_: bob) + { + name _xid_ follows + { + name _xid_ follows + { + name _xid_ + } + } + } + } + """ + response = self.client.query(query_string) + + self.assertEqual(len(response.n.children), 1) + self.assertEqual(response.n.children[0].xid, "josh") + self.assertEqual(response.n.children[0].attribute, "follows") + self.assertEqual(response.n.children[0].properties[0].prop, "name") + self.assertEqual(response.n.children[0].properties[0].val, "Josh") + + self.assertEqual(len(response.n.children[0].children), 1) + self.assertEqual(response.n.children[0].children[0].xid, "rose") + self.assertEqual(response.n.children[0].children[0].attribute, "follows") + self.assertEqual(response.n.children[0].children[0].properties[0].prop, "name") + self.assertEqual(response.n.children[0].children[0].properties[0].val, "Rose") + + + self.assertTrue(isinstance(response.l.parsing, basestring), 'Parsing latency is not available') + self.assertTrue(isinstance(response.l.pb, basestring), 'Protocol buffers latency is not available') + self.assertTrue(isinstance(response.l.processing, basestring), 'Processing latency is not available') + From 049758f0395c1952834a5ccd1abe81f49531f21f Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 15:58:17 +0530 Subject: [PATCH 004/435] Make it Pip installable --- MANIFEST.in | 1 + pydgraph/client.py | 2 +- setup.py | 9 +++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..ead34ff6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include *.txt LICENSE README.md diff --git a/pydgraph/client.py b/pydgraph/client.py index 2e8ec346..3cefeb4d 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -22,7 +22,7 @@ __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' -__version__ = '0.3' +__version__ = '0.3.1' __status__ = 'development' diff --git a/setup.py b/setup.py index 68a81f34..4686ecb6 100644 --- a/setup.py +++ b/setup.py @@ -12,14 +12,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import sys try: from setuptools import setup except ImportError: from distutils.core import setup +if sys.version_info >= (2, 7, 9): + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + setup(name="pydgraph", - version=0.3, + version="0.3.1", description="Dgraph driver for Python", license="Apache License, Version 2.0", author="Mohit Ranka", @@ -38,4 +42,5 @@ packages=["pydgraph"], install_requires=open('requirements.txt').readlines(), test_suite='tests', + data_files=[('.', ['requirements.txt', 'LICENSE'])] ) From 2d4cf2500705b0a0c61c923c351e70fa6b194d1d Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:00:31 +0530 Subject: [PATCH 005/435] Release 0.3.1 --- MANIFEST.in | 1 + pydgraph/client.py | 2 +- setup.py | 9 +++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..ead34ff6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include *.txt LICENSE README.md diff --git a/pydgraph/client.py b/pydgraph/client.py index 2e8ec346..3cefeb4d 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -22,7 +22,7 @@ __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' -__version__ = '0.3' +__version__ = '0.3.1' __status__ = 'development' diff --git a/setup.py b/setup.py index 68a81f34..4686ecb6 100644 --- a/setup.py +++ b/setup.py @@ -12,14 +12,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import sys try: from setuptools import setup except ImportError: from distutils.core import setup +if sys.version_info >= (2, 7, 9): + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + setup(name="pydgraph", - version=0.3, + version="0.3.1", description="Dgraph driver for Python", license="Apache License, Version 2.0", author="Mohit Ranka", @@ -38,4 +42,5 @@ packages=["pydgraph"], install_requires=open('requirements.txt').readlines(), test_suite='tests', + data_files=[('.', ['requirements.txt', 'LICENSE'])] ) From 87aebac05a43ecef37491d807213d85ff7b4aea2 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:02:45 +0530 Subject: [PATCH 006/435] Removed unnecessary data_files list from setup.py --- pydgraph/client.py | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 3cefeb4d..b730bd5d 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -22,7 +22,7 @@ __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' -__version__ = '0.3.1' +__version__ = '0.3.2' __status__ = 'development' diff --git a/setup.py b/setup.py index 4686ecb6..465c821a 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ ssl._create_default_https_context = ssl._create_unverified_context setup(name="pydgraph", - version="0.3.1", + version="0.3.2", description="Dgraph driver for Python", license="Apache License, Version 2.0", author="Mohit Ranka", @@ -42,5 +42,4 @@ packages=["pydgraph"], install_requires=open('requirements.txt').readlines(), test_suite='tests', - data_files=[('.', ['requirements.txt', 'LICENSE'])] ) From 71c6889f4a106488f69d9f81bd62672efa8bc593 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:04:23 +0530 Subject: [PATCH 007/435] Release 0.3.2 --- pydgraph/client.py | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 3cefeb4d..b730bd5d 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -22,7 +22,7 @@ __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' -__version__ = '0.3.1' +__version__ = '0.3.2' __status__ = 'development' diff --git a/setup.py b/setup.py index 4686ecb6..465c821a 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ ssl._create_default_https_context = ssl._create_unverified_context setup(name="pydgraph", - version="0.3.1", + version="0.3.2", description="Dgraph driver for Python", license="Apache License, Version 2.0", author="Mohit Ranka", @@ -42,5 +42,4 @@ packages=["pydgraph"], install_requires=open('requirements.txt').readlines(), test_suite='tests', - data_files=[('.', ['requirements.txt', 'LICENSE'])] ) From 52522dd7c89ef27dc2f7bb619669f962cf8c56a2 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:09:34 +0530 Subject: [PATCH 008/435] Minor refactoring --- pydgraph/client.py | 5 +++-- pydgraph/{ => utils}/graphresponse_pb2.py | 7 +------ setup.py | 4 +++- 3 files changed, 7 insertions(+), 9 deletions(-) rename pydgraph/{ => utils}/graphresponse_pb2.py (98%) diff --git a/pydgraph/client.py b/pydgraph/client.py index b730bd5d..44b77142 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -18,11 +18,12 @@ Dgraph server over gRPC. """ from grpc.beta import implementations -import graphresponse_pb2 +from pydgraph.utils import graphresponse_pb2 +from pydgraph.utils.meta import VERSION __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' -__version__ = '0.3.2' +__version__ = VERSION __status__ = 'development' diff --git a/pydgraph/graphresponse_pb2.py b/pydgraph/utils/graphresponse_pb2.py similarity index 98% rename from pydgraph/graphresponse_pb2.py rename to pydgraph/utils/graphresponse_pb2.py index 50c0dac4..b144777f 100644 --- a/pydgraph/graphresponse_pb2.py +++ b/pydgraph/utils/graphresponse_pb2.py @@ -7,7 +7,7 @@ from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 + # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -281,9 +281,6 @@ )) _sym_db.RegisterMessage(Response) - -import abc -import six from grpc.beta import implementations as beta_implementations from grpc.beta import interfaces as beta_interfaces from grpc.framework.common import cardinality @@ -299,7 +296,6 @@ def Query(self, request, timeout): Query.future = None def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): - import graphresponse_pb2 import graphresponse_pb2 request_deserializers = { ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.FromString, @@ -314,7 +310,6 @@ def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeo return beta_implementations.server(method_implementations, options=server_options) def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): - import graphresponse_pb2 import graphresponse_pb2 request_serializers = { ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.SerializeToString, diff --git a/setup.py b/setup.py index 465c821a..51b616c3 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,10 @@ import ssl ssl._create_default_https_context = ssl._create_unverified_context +from pydgraph.utils.meta import VERSION + setup(name="pydgraph", - version="0.3.2", + version=VERSION, description="Dgraph driver for Python", license="Apache License, Version 2.0", author="Mohit Ranka", From 4877b2edb29496cabdbe891a1f76e05935ebcce8 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:11:10 +0530 Subject: [PATCH 009/435] Release 0.3.3 --- pydgraph/client.py | 5 +- pydgraph/utils/__init__.py | 0 pydgraph/utils/graphresponse_pb2.py | 325 ++++++++++++++++++++++++++++ pydgraph/utils/meta.py | 1 + setup.py | 4 +- 5 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 pydgraph/utils/__init__.py create mode 100644 pydgraph/utils/graphresponse_pb2.py create mode 100644 pydgraph/utils/meta.py diff --git a/pydgraph/client.py b/pydgraph/client.py index b730bd5d..44b77142 100644 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -18,11 +18,12 @@ Dgraph server over gRPC. """ from grpc.beta import implementations -import graphresponse_pb2 +from pydgraph.utils import graphresponse_pb2 +from pydgraph.utils.meta import VERSION __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' -__version__ = '0.3.2' +__version__ = VERSION __status__ = 'development' diff --git a/pydgraph/utils/__init__.py b/pydgraph/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydgraph/utils/graphresponse_pb2.py b/pydgraph/utils/graphresponse_pb2.py new file mode 100644 index 00000000..b144777f --- /dev/null +++ b/pydgraph/utils/graphresponse_pb2.py @@ -0,0 +1,325 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: graphresponse.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='graphresponse.proto', + package='graph', + syntax='proto3', + serialized_pb=_b('\n\x13graphresponse.proto\x12\x05graph\"\x18\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"%\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x0b\n\x03val\x18\x02 \x01(\x0c\"w\n\x04Node\x12\x0b\n\x03uid\x18\x01 \x01(\x04\x12\x0b\n\x03xid\x18\x02 \x01(\t\x12\x11\n\tattribute\x18\x03 \x01(\t\x12#\n\nproperties\x18\x04 \x03(\x0b\x32\x0f.graph.Property\x12\x1d\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0b.graph.Node\"=\n\x08Response\x12\x16\n\x01n\x18\x01 \x01(\x0b\x32\x0b.graph.Node\x12\x19\n\x01l\x18\x02 \x01(\x0b\x32\x0e.graph.Latency24\n\x06\x44graph\x12*\n\x05Query\x12\x0e.graph.Request\x1a\x0f.graph.Response\"\x00\x62\x06proto3') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_REQUEST = _descriptor.Descriptor( + name='Request', + full_name='graph.Request', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='query', full_name='graph.Request.query', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=30, + serialized_end=54, +) + + +_LATENCY = _descriptor.Descriptor( + name='Latency', + full_name='graph.Latency', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='parsing', full_name='graph.Latency.parsing', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='processing', full_name='graph.Latency.processing', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='pb', full_name='graph.Latency.pb', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=56, + serialized_end=114, +) + + +_PROPERTY = _descriptor.Descriptor( + name='Property', + full_name='graph.Property', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prop', full_name='graph.Property.prop', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='val', full_name='graph.Property.val', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=116, + serialized_end=153, +) + + +_NODE = _descriptor.Descriptor( + name='Node', + full_name='graph.Node', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='uid', full_name='graph.Node.uid', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='xid', full_name='graph.Node.xid', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='attribute', full_name='graph.Node.attribute', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='properties', full_name='graph.Node.properties', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='children', full_name='graph.Node.children', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=155, + serialized_end=274, +) + + +_RESPONSE = _descriptor.Descriptor( + name='Response', + full_name='graph.Response', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='n', full_name='graph.Response.n', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='l', full_name='graph.Response.l', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=276, + serialized_end=337, +) + +_NODE.fields_by_name['properties'].message_type = _PROPERTY +_NODE.fields_by_name['children'].message_type = _NODE +_RESPONSE.fields_by_name['n'].message_type = _NODE +_RESPONSE.fields_by_name['l'].message_type = _LATENCY +DESCRIPTOR.message_types_by_name['Request'] = _REQUEST +DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY +DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY +DESCRIPTOR.message_types_by_name['Node'] = _NODE +DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE + +Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( + DESCRIPTOR = _REQUEST, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Request) + )) +_sym_db.RegisterMessage(Request) + +Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( + DESCRIPTOR = _LATENCY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Latency) + )) +_sym_db.RegisterMessage(Latency) + +Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( + DESCRIPTOR = _PROPERTY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Property) + )) +_sym_db.RegisterMessage(Property) + +Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( + DESCRIPTOR = _NODE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Node) + )) +_sym_db.RegisterMessage(Node) + +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( + DESCRIPTOR = _RESPONSE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:graph.Response) + )) +_sym_db.RegisterMessage(Response) + +from grpc.beta import implementations as beta_implementations +from grpc.beta import interfaces as beta_interfaces +from grpc.framework.common import cardinality +from grpc.framework.interfaces.face import utilities as face_utilities + +class BetaDgraphServicer(object): + def Query(self, request, context): + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + +class BetaDgraphStub(object): + def Query(self, request, timeout): + raise NotImplementedError() + Query.future = None + +def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): + import graphresponse_pb2 + request_deserializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.FromString, + } + response_serializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.SerializeToString, + } + method_implementations = { + ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), + } + server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) + return beta_implementations.server(method_implementations, options=server_options) + +def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): + import graphresponse_pb2 + request_serializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.SerializeToString, + } + response_deserializers = { + ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.FromString, + } + cardinalities = { + 'Query': cardinality.Cardinality.UNARY_UNARY, + } + stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) + return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) +# @@protoc_insertion_point(module_scope) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py new file mode 100644 index 00000000..daed2011 --- /dev/null +++ b/pydgraph/utils/meta.py @@ -0,0 +1 @@ +VERSION="0.3.3" \ No newline at end of file diff --git a/setup.py b/setup.py index 465c821a..51b616c3 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,10 @@ import ssl ssl._create_default_https_context = ssl._create_unverified_context +from pydgraph.utils.meta import VERSION + setup(name="pydgraph", - version="0.3.2", + version=VERSION, description="Dgraph driver for Python", license="Apache License, Version 2.0", author="Mohit Ranka", From 7006701c10a4b75d70567e5a2de8fe444c087298 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:13:47 +0530 Subject: [PATCH 010/435] Updated version --- pydgraph/utils/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py index daed2011..67ced880 100644 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.3" \ No newline at end of file +VERSION="0.3.4" \ No newline at end of file From 49df8ba3c72d81ccfa585896ed3fbe91f8316a5c Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:14:41 +0530 Subject: [PATCH 011/435] Release 0.3.4 --- pydgraph/utils/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py index daed2011..67ced880 100644 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.3" \ No newline at end of file +VERSION="0.3.4" \ No newline at end of file From b1c00af35603d31b2a8cc0d73539f216165d9195 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:22:19 +0530 Subject: [PATCH 012/435] Add pygraph.utils package as well --- pydgraph/utils/meta.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py index 67ced880..babbb8aa 100644 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.4" \ No newline at end of file +VERSION="0.3.5" \ No newline at end of file diff --git a/setup.py b/setup.py index 51b616c3..7b635ecd 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", ], - packages=["pydgraph"], + packages=["pydgraph", "pydgraph.utils"], install_requires=open('requirements.txt').readlines(), test_suite='tests', ) From f7465e00a6d487ebbfa3037b1dc6dd86ac6323c6 Mon Sep 17 00:00:00 2001 From: Mohit Ranka Date: Wed, 6 Jul 2016 16:23:34 +0530 Subject: [PATCH 013/435] Release 0.3.5 --- pydgraph/utils/meta.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py index 67ced880..b6f91f54 100644 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.4" \ No newline at end of file +VERSION="0.3.5" diff --git a/setup.py b/setup.py index 51b616c3..7b635ecd 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", ], - packages=["pydgraph"], + packages=["pydgraph", "pydgraph.utils"], install_requires=open('requirements.txt').readlines(), test_suite='tests', ) From 24860369867a5f16302f0a87a7847e3057759a46 Mon Sep 17 00:00:00 2001 From: David Ziegler Date: Sat, 25 Mar 2017 21:16:11 +0100 Subject: [PATCH 014/435] Update pydgraph client to last stats and apply python 3.x support with newest grpcio --- .gitignore | 0 LICENSE | 0 MANIFEST.in | 0 README.md | 0 pydgraph/__init__.py | 0 pydgraph/client.py | 6 +- pydgraph/graphresponse_pb2.py | 330 ---------------------------- pydgraph/utils/__init__.py | 0 pydgraph/utils/graphresponse_pb2.py | 97 ++++---- pydgraph/utils/meta.py | 2 +- requirements.txt | 2 +- setup.py | 0 tests/__init__.py | 0 tests/test_queries.py | 0 14 files changed, 58 insertions(+), 379 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 LICENSE mode change 100644 => 100755 MANIFEST.in mode change 100644 => 100755 README.md mode change 100644 => 100755 pydgraph/__init__.py mode change 100644 => 100755 pydgraph/client.py delete mode 100644 pydgraph/graphresponse_pb2.py mode change 100644 => 100755 pydgraph/utils/__init__.py mode change 100644 => 100755 pydgraph/utils/graphresponse_pb2.py mode change 100644 => 100755 pydgraph/utils/meta.py mode change 100644 => 100755 requirements.txt mode change 100644 => 100755 setup.py mode change 100644 => 100755 tests/__init__.py mode change 100644 => 100755 tests/test_queries.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/MANIFEST.in b/MANIFEST.in old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py old mode 100644 new mode 100755 diff --git a/pydgraph/client.py b/pydgraph/client.py old mode 100644 new mode 100755 index 44b77142..92e76407 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -17,6 +17,7 @@ This module contains the main user-facing methods for interacting with the Dgraph server over gRPC. """ +import grpc from grpc.beta import implementations from pydgraph.utils import graphresponse_pb2 from pydgraph.utils.meta import VERSION @@ -30,10 +31,11 @@ class DgraphClient(object): def __init__(self, host, port): - self.channel = implementations.insecure_channel(host, port) - self.stub = graphresponse_pb2.beta_create_Dgraph_stub(self.channel) + self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) + self.stub = graphresponse_pb2.BetaDgraphStub(self.channel) def query(self, q, timeout=None): request = graphresponse_pb2.Request(query=q) response = self.stub.Query(request, timeout) return response + diff --git a/pydgraph/graphresponse_pb2.py b/pydgraph/graphresponse_pb2.py deleted file mode 100644 index 50c0dac4..00000000 --- a/pydgraph/graphresponse_pb2.py +++ /dev/null @@ -1,330 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: graphresponse.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='graphresponse.proto', - package='graph', - syntax='proto3', - serialized_pb=_b('\n\x13graphresponse.proto\x12\x05graph\"\x18\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"%\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x0b\n\x03val\x18\x02 \x01(\x0c\"w\n\x04Node\x12\x0b\n\x03uid\x18\x01 \x01(\x04\x12\x0b\n\x03xid\x18\x02 \x01(\t\x12\x11\n\tattribute\x18\x03 \x01(\t\x12#\n\nproperties\x18\x04 \x03(\x0b\x32\x0f.graph.Property\x12\x1d\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0b.graph.Node\"=\n\x08Response\x12\x16\n\x01n\x18\x01 \x01(\x0b\x32\x0b.graph.Node\x12\x19\n\x01l\x18\x02 \x01(\x0b\x32\x0e.graph.Latency24\n\x06\x44graph\x12*\n\x05Query\x12\x0e.graph.Request\x1a\x0f.graph.Response\"\x00\x62\x06proto3') -) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - - -_REQUEST = _descriptor.Descriptor( - name='Request', - full_name='graph.Request', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='query', full_name='graph.Request.query', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=30, - serialized_end=54, -) - - -_LATENCY = _descriptor.Descriptor( - name='Latency', - full_name='graph.Latency', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='parsing', full_name='graph.Latency.parsing', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='processing', full_name='graph.Latency.processing', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='pb', full_name='graph.Latency.pb', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=56, - serialized_end=114, -) - - -_PROPERTY = _descriptor.Descriptor( - name='Property', - full_name='graph.Property', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='prop', full_name='graph.Property.prop', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='val', full_name='graph.Property.val', index=1, - number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=116, - serialized_end=153, -) - - -_NODE = _descriptor.Descriptor( - name='Node', - full_name='graph.Node', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='uid', full_name='graph.Node.uid', index=0, - number=1, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='xid', full_name='graph.Node.xid', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='attribute', full_name='graph.Node.attribute', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='properties', full_name='graph.Node.properties', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='children', full_name='graph.Node.children', index=4, - number=5, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=155, - serialized_end=274, -) - - -_RESPONSE = _descriptor.Descriptor( - name='Response', - full_name='graph.Response', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='n', full_name='graph.Response.n', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='l', full_name='graph.Response.l', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=276, - serialized_end=337, -) - -_NODE.fields_by_name['properties'].message_type = _PROPERTY -_NODE.fields_by_name['children'].message_type = _NODE -_RESPONSE.fields_by_name['n'].message_type = _NODE -_RESPONSE.fields_by_name['l'].message_type = _LATENCY -DESCRIPTOR.message_types_by_name['Request'] = _REQUEST -DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY -DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY -DESCRIPTOR.message_types_by_name['Node'] = _NODE -DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE - -Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( - DESCRIPTOR = _REQUEST, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Request) - )) -_sym_db.RegisterMessage(Request) - -Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( - DESCRIPTOR = _LATENCY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Latency) - )) -_sym_db.RegisterMessage(Latency) - -Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( - DESCRIPTOR = _PROPERTY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Property) - )) -_sym_db.RegisterMessage(Property) - -Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( - DESCRIPTOR = _NODE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Node) - )) -_sym_db.RegisterMessage(Node) - -Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( - DESCRIPTOR = _RESPONSE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Response) - )) -_sym_db.RegisterMessage(Response) - - -import abc -import six -from grpc.beta import implementations as beta_implementations -from grpc.beta import interfaces as beta_interfaces -from grpc.framework.common import cardinality -from grpc.framework.interfaces.face import utilities as face_utilities - -class BetaDgraphServicer(object): - def Query(self, request, context): - context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) - -class BetaDgraphStub(object): - def Query(self, request, timeout): - raise NotImplementedError() - Query.future = None - -def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): - import graphresponse_pb2 - import graphresponse_pb2 - request_deserializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.FromString, - } - response_serializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.SerializeToString, - } - method_implementations = { - ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), - } - server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) - return beta_implementations.server(method_implementations, options=server_options) - -def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): - import graphresponse_pb2 - import graphresponse_pb2 - request_serializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.SerializeToString, - } - response_deserializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.FromString, - } - cardinalities = { - 'Query': cardinality.Cardinality.UNARY_UNARY, - } - stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) - return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) -# @@protoc_insertion_point(module_scope) diff --git a/pydgraph/utils/__init__.py b/pydgraph/utils/__init__.py old mode 100644 new mode 100755 diff --git a/pydgraph/utils/graphresponse_pb2.py b/pydgraph/utils/graphresponse_pb2.py old mode 100644 new mode 100755 index b144777f..2d8a08de --- a/pydgraph/utils/graphresponse_pb2.py +++ b/pydgraph/utils/graphresponse_pb2.py @@ -12,9 +12,6 @@ _sym_db = _symbol_database.Default() - - - DESCRIPTOR = _descriptor.FileDescriptor( name='graphresponse.proto', package='graph', @@ -23,9 +20,6 @@ ) _sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - _REQUEST = _descriptor.Descriptor( name='Request', full_name='graph.Request', @@ -56,7 +50,6 @@ serialized_end=54, ) - _LATENCY = _descriptor.Descriptor( name='Latency', full_name='graph.Latency', @@ -101,7 +94,6 @@ serialized_end=114, ) - _PROPERTY = _descriptor.Descriptor( name='Property', full_name='graph.Property', @@ -139,7 +131,6 @@ serialized_end=153, ) - _NODE = _descriptor.Descriptor( name='Node', full_name='graph.Node', @@ -198,7 +189,6 @@ serialized_end=274, ) - _RESPONSE = _descriptor.Descriptor( name='Response', full_name='graph.Response', @@ -281,45 +271,62 @@ )) _sym_db.RegisterMessage(Response) -from grpc.beta import implementations as beta_implementations -from grpc.beta import interfaces as beta_interfaces -from grpc.framework.common import cardinality -from grpc.framework.interfaces.face import utilities as face_utilities - +import grpc class BetaDgraphServicer(object): def Query(self, request, context): - context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') class BetaDgraphStub(object): - def Query(self, request, timeout): - raise NotImplementedError() - Query.future = None + def __init__(self, channel): + self.Query = channel.unary_unary( + '/graphp.Dgraph/Run', + request_serializer=Request.SerializeToString, + response_deserializer=Response.FromString, + ) -def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): - import graphresponse_pb2 - request_deserializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.FromString, - } - response_serializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.SerializeToString, +def add_DgraphServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Query': grpc.unary_unary_rpc_method_handler( + servicer.Query, + request_deserializer=Request.FromString, + response_serializer=Response.SerializeToString, + ), } - method_implementations = { - ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), - } - server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) - return beta_implementations.server(method_implementations, options=server_options) + generic_handler = grpc.method_handlers_generic_handler( + 'graphp.Dgraph', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + +#Depreciated +# from grpc.framework.common import cardinality +# from grpc.framework.interfaces.face import utilities as face_utilities +# from grpc.beta import implementations as beta_implementations +# from grpc.beta import interfaces as beta_interfaces +# def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): +# request_deserializers = { +# ('graph.Dgraph', 'Query'): Request.FromString, +# } +# response_serializers = { +# ('graph.Dgraph', 'Query'): Response.SerializeToString, +# } +# method_implementations = { +# ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), +# } +# server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) +# return beta_implementations.server(method_implementations, options=server_options) + +# def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): +# request_serializers = { +# ('graph.Dgraph', 'Query'): Request.SerializeToString, +# } +# response_deserializers = { +# ('graph.Dgraph', 'Query'): Response.FromString, +# } +# cardinalities = { +# 'Query': cardinality.Cardinality.UNARY_UNARY, +# } +# stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) +# return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) +# # @@protoc_insertion_point(module_scope) -def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): - import graphresponse_pb2 - request_serializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Request.SerializeToString, - } - response_deserializers = { - ('graph.Dgraph', 'Query'): graphresponse_pb2.Response.FromString, - } - cardinalities = { - 'Query': cardinality.Cardinality.UNARY_UNARY, - } - stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) - return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) -# @@protoc_insertion_point(module_scope) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py old mode 100644 new mode 100755 index b6f91f54..ff78d692 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.5" +VERSION="0.3.6" diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 index d459dbf2..0a1bbf4a --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -grpcio==0.15.0 +grpcio diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 diff --git a/tests/__init__.py b/tests/__init__.py old mode 100644 new mode 100755 diff --git a/tests/test_queries.py b/tests/test_queries.py old mode 100644 new mode 100755 From 4b9e6a186e49cd2b3f4156c04aa2ee1cb1287499 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 10 Jun 2017 12:02:06 +0200 Subject: [PATCH 015/435] Updated py3.x version with newest protos --- pydgraph/client.py | 10 +- pydgraph/utils/graphresponse_pb2.py | 332 ---------- pydgraph/utils/loader.py | 38 ++ pydgraph/utils/meta.py | 2 +- pydgraph/utils/proto/__init__.py | 0 pydgraph/utils/proto/facets_pb2.py | 361 ++++++++++ pydgraph/utils/proto/graphresponse_pb2.py | 773 ++++++++++++++++++++++ pydgraph/utils/proto/schema_pb2.py | 279 ++++++++ 8 files changed, 1458 insertions(+), 337 deletions(-) delete mode 100755 pydgraph/utils/graphresponse_pb2.py create mode 100644 pydgraph/utils/loader.py create mode 100644 pydgraph/utils/proto/__init__.py create mode 100644 pydgraph/utils/proto/facets_pb2.py create mode 100644 pydgraph/utils/proto/graphresponse_pb2.py create mode 100644 pydgraph/utils/proto/schema_pb2.py diff --git a/pydgraph/client.py b/pydgraph/client.py index 92e76407..c4fd3f16 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -19,7 +19,7 @@ """ import grpc from grpc.beta import implementations -from pydgraph.utils import graphresponse_pb2 +from pydgraph.utils import loader from pydgraph.utils.meta import VERSION __author__ = 'Mohit Ranka ' @@ -28,14 +28,16 @@ __status__ = 'development' - class DgraphClient(object): def __init__(self, host, port): self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) - self.stub = graphresponse_pb2.BetaDgraphStub(self.channel) + self.stub = loader.BetaDgraphStub(self.channel) def query(self, q, timeout=None): - request = graphresponse_pb2.Request(query=q) + request = loader.Request(query=q) response = self.stub.Query(request, timeout) return response + async def aQuery(self, q, timeout=None): + request = loader.Request(query=q) + return await self.stub.aQuery(request, timeout) diff --git a/pydgraph/utils/graphresponse_pb2.py b/pydgraph/utils/graphresponse_pb2.py deleted file mode 100755 index 2d8a08de..00000000 --- a/pydgraph/utils/graphresponse_pb2.py +++ /dev/null @@ -1,332 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: graphresponse.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - -DESCRIPTOR = _descriptor.FileDescriptor( - name='graphresponse.proto', - package='graph', - syntax='proto3', - serialized_pb=_b('\n\x13graphresponse.proto\x12\x05graph\"\x18\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"%\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x0b\n\x03val\x18\x02 \x01(\x0c\"w\n\x04Node\x12\x0b\n\x03uid\x18\x01 \x01(\x04\x12\x0b\n\x03xid\x18\x02 \x01(\t\x12\x11\n\tattribute\x18\x03 \x01(\t\x12#\n\nproperties\x18\x04 \x03(\x0b\x32\x0f.graph.Property\x12\x1d\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0b.graph.Node\"=\n\x08Response\x12\x16\n\x01n\x18\x01 \x01(\x0b\x32\x0b.graph.Node\x12\x19\n\x01l\x18\x02 \x01(\x0b\x32\x0e.graph.Latency24\n\x06\x44graph\x12*\n\x05Query\x12\x0e.graph.Request\x1a\x0f.graph.Response\"\x00\x62\x06proto3') -) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -_REQUEST = _descriptor.Descriptor( - name='Request', - full_name='graph.Request', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='query', full_name='graph.Request.query', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=30, - serialized_end=54, -) - -_LATENCY = _descriptor.Descriptor( - name='Latency', - full_name='graph.Latency', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='parsing', full_name='graph.Latency.parsing', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='processing', full_name='graph.Latency.processing', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='pb', full_name='graph.Latency.pb', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=56, - serialized_end=114, -) - -_PROPERTY = _descriptor.Descriptor( - name='Property', - full_name='graph.Property', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='prop', full_name='graph.Property.prop', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='val', full_name='graph.Property.val', index=1, - number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=116, - serialized_end=153, -) - -_NODE = _descriptor.Descriptor( - name='Node', - full_name='graph.Node', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='uid', full_name='graph.Node.uid', index=0, - number=1, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='xid', full_name='graph.Node.xid', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='attribute', full_name='graph.Node.attribute', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='properties', full_name='graph.Node.properties', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='children', full_name='graph.Node.children', index=4, - number=5, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=155, - serialized_end=274, -) - -_RESPONSE = _descriptor.Descriptor( - name='Response', - full_name='graph.Response', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='n', full_name='graph.Response.n', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='l', full_name='graph.Response.l', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=276, - serialized_end=337, -) - -_NODE.fields_by_name['properties'].message_type = _PROPERTY -_NODE.fields_by_name['children'].message_type = _NODE -_RESPONSE.fields_by_name['n'].message_type = _NODE -_RESPONSE.fields_by_name['l'].message_type = _LATENCY -DESCRIPTOR.message_types_by_name['Request'] = _REQUEST -DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY -DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY -DESCRIPTOR.message_types_by_name['Node'] = _NODE -DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE - -Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( - DESCRIPTOR = _REQUEST, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Request) - )) -_sym_db.RegisterMessage(Request) - -Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( - DESCRIPTOR = _LATENCY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Latency) - )) -_sym_db.RegisterMessage(Latency) - -Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( - DESCRIPTOR = _PROPERTY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Property) - )) -_sym_db.RegisterMessage(Property) - -Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( - DESCRIPTOR = _NODE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Node) - )) -_sym_db.RegisterMessage(Node) - -Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( - DESCRIPTOR = _RESPONSE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:graph.Response) - )) -_sym_db.RegisterMessage(Response) - -import grpc -class BetaDgraphServicer(object): - def Query(self, request, context): - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - -class BetaDgraphStub(object): - def __init__(self, channel): - self.Query = channel.unary_unary( - '/graphp.Dgraph/Run', - request_serializer=Request.SerializeToString, - response_deserializer=Response.FromString, - ) - -def add_DgraphServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Query': grpc.unary_unary_rpc_method_handler( - servicer.Query, - request_deserializer=Request.FromString, - response_serializer=Response.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'graphp.Dgraph', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - -#Depreciated -# from grpc.framework.common import cardinality -# from grpc.framework.interfaces.face import utilities as face_utilities -# from grpc.beta import implementations as beta_implementations -# from grpc.beta import interfaces as beta_interfaces -# def beta_create_Dgraph_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): -# request_deserializers = { -# ('graph.Dgraph', 'Query'): Request.FromString, -# } -# response_serializers = { -# ('graph.Dgraph', 'Query'): Response.SerializeToString, -# } -# method_implementations = { -# ('graph.Dgraph', 'Query'): face_utilities.unary_unary_inline(servicer.Query), -# } -# server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) -# return beta_implementations.server(method_implementations, options=server_options) - -# def beta_create_Dgraph_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): -# request_serializers = { -# ('graph.Dgraph', 'Query'): Request.SerializeToString, -# } -# response_deserializers = { -# ('graph.Dgraph', 'Query'): Response.FromString, -# } -# cardinalities = { -# 'Query': cardinality.Cardinality.UNARY_UNARY, -# } -# stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) -# return beta_implementations.dynamic_stub(channel, 'graph.Dgraph', cardinalities, options=stub_options) -# # @@protoc_insertion_point(module_scope) - diff --git a/pydgraph/utils/loader.py b/pydgraph/utils/loader.py new file mode 100644 index 00000000..25ff312c --- /dev/null +++ b/pydgraph/utils/loader.py @@ -0,0 +1,38 @@ +import grpc +from .proto.graphresponse_pb2 import * +from grpc.beta import implementations as beta_implementations +from grpc.beta import interfaces as beta_interfaces + + +class BetaDgraphServicer(object): + def Query(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + +class BetaDgraphStub(object): + def __init__(self, channel): + self.channel = channel + self.unary = self.channel.unary_unary( + '/protos.Dgraph/Run', + request_serializer=Request.SerializeToString, + response_deserializer=Response.FromString, + ) + + def Query(self, *args, **kwargs): + return self.unary(*args, **kwargs) + + async def aQuery(self, *args, **kwargs): + return self.unary(*args, **kwargs) + +def add_DgraphServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Query': grpc.unary_unary_rpc_method_handler( + servicer.Query, + request_deserializer=Request.FromString, + response_serializer=Response.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'protos.Dgraph', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py index ff78d692..b6f91f54 100755 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.6" +VERSION="0.3.5" diff --git a/pydgraph/utils/proto/__init__.py b/pydgraph/utils/proto/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydgraph/utils/proto/facets_pb2.py b/pydgraph/utils/proto/facets_pb2.py new file mode 100644 index 00000000..96dd5266 --- /dev/null +++ b/pydgraph/utils/proto/facets_pb2.py @@ -0,0 +1,361 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: facets.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='facets.proto', + package='protos', + syntax='proto3', + serialized_pb=_b('\n\x0c\x66\x61\x63\x65ts.proto\x12\x06protos\"\x9f\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\'\n\x08val_type\x18\x03 \x01(\x0e\x32\x15.protos.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\'\n\x05Param\x12\x10\n\x08\x61ll_keys\x18\x01 \x01(\x08\x12\x0c\n\x04keys\x18\x02 \x03(\t\"\'\n\x06\x46\x61\x63\x65ts\x12\x1d\n\x06\x66\x61\x63\x65ts\x18\x01 \x03(\x0b\x32\r.protos.Facet\"1\n\nFacetsList\x12#\n\x0b\x66\x61\x63\x65ts_list\x18\x01 \x03(\x0b\x32\x0e.protos.Facets\"3\n\x08\x46unction\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x03 \x03(\t\"^\n\nFilterTree\x12\n\n\x02op\x18\x01 \x01(\t\x12$\n\x08\x63hildren\x18\x02 \x03(\x0b\x32\x12.protos.FilterTree\x12\x1e\n\x04\x66unc\x18\x03 \x01(\x0b\x32\x10.protos.Functionb\x06proto3') +) + + + +_FACET_VALTYPE = _descriptor.EnumDescriptor( + name='ValType', + full_name='protos.Facet.ValType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='STRING', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='INT', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FLOAT', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BOOL', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DATETIME', index=4, number=4, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=119, + serialized_end=184, +) +_sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) + + +_FACET = _descriptor.Descriptor( + name='Facet', + full_name='protos.Facet', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='protos.Facet.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='protos.Facet.value', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='val_type', full_name='protos.Facet.val_type', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tokens', full_name='protos.Facet.tokens', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _FACET_VALTYPE, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=25, + serialized_end=184, +) + + +_PARAM = _descriptor.Descriptor( + name='Param', + full_name='protos.Param', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='all_keys', full_name='protos.Param.all_keys', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='keys', full_name='protos.Param.keys', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=186, + serialized_end=225, +) + + +_FACETS = _descriptor.Descriptor( + name='Facets', + full_name='protos.Facets', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='facets', full_name='protos.Facets.facets', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=227, + serialized_end=266, +) + + +_FACETSLIST = _descriptor.Descriptor( + name='FacetsList', + full_name='protos.FacetsList', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='facets_list', full_name='protos.FacetsList.facets_list', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=268, + serialized_end=317, +) + + +_FUNCTION = _descriptor.Descriptor( + name='Function', + full_name='protos.Function', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='protos.Function.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='key', full_name='protos.Function.key', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='args', full_name='protos.Function.args', index=2, + number=3, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=319, + serialized_end=370, +) + + +_FILTERTREE = _descriptor.Descriptor( + name='FilterTree', + full_name='protos.FilterTree', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='op', full_name='protos.FilterTree.op', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='children', full_name='protos.FilterTree.children', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='func', full_name='protos.FilterTree.func', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=372, + serialized_end=466, +) + +_FACET.fields_by_name['val_type'].enum_type = _FACET_VALTYPE +_FACET_VALTYPE.containing_type = _FACET +_FACETS.fields_by_name['facets'].message_type = _FACET +_FACETSLIST.fields_by_name['facets_list'].message_type = _FACETS +_FILTERTREE.fields_by_name['children'].message_type = _FILTERTREE +_FILTERTREE.fields_by_name['func'].message_type = _FUNCTION +DESCRIPTOR.message_types_by_name['Facet'] = _FACET +DESCRIPTOR.message_types_by_name['Param'] = _PARAM +DESCRIPTOR.message_types_by_name['Facets'] = _FACETS +DESCRIPTOR.message_types_by_name['FacetsList'] = _FACETSLIST +DESCRIPTOR.message_types_by_name['Function'] = _FUNCTION +DESCRIPTOR.message_types_by_name['FilterTree'] = _FILTERTREE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Facet = _reflection.GeneratedProtocolMessageType('Facet', (_message.Message,), dict( + DESCRIPTOR = _FACET, + __module__ = 'facets_pb2' + # @@protoc_insertion_point(class_scope:protos.Facet) + )) +_sym_db.RegisterMessage(Facet) + +Param = _reflection.GeneratedProtocolMessageType('Param', (_message.Message,), dict( + DESCRIPTOR = _PARAM, + __module__ = 'facets_pb2' + # @@protoc_insertion_point(class_scope:protos.Param) + )) +_sym_db.RegisterMessage(Param) + +Facets = _reflection.GeneratedProtocolMessageType('Facets', (_message.Message,), dict( + DESCRIPTOR = _FACETS, + __module__ = 'facets_pb2' + # @@protoc_insertion_point(class_scope:protos.Facets) + )) +_sym_db.RegisterMessage(Facets) + +FacetsList = _reflection.GeneratedProtocolMessageType('FacetsList', (_message.Message,), dict( + DESCRIPTOR = _FACETSLIST, + __module__ = 'facets_pb2' + # @@protoc_insertion_point(class_scope:protos.FacetsList) + )) +_sym_db.RegisterMessage(FacetsList) + +Function = _reflection.GeneratedProtocolMessageType('Function', (_message.Message,), dict( + DESCRIPTOR = _FUNCTION, + __module__ = 'facets_pb2' + # @@protoc_insertion_point(class_scope:protos.Function) + )) +_sym_db.RegisterMessage(Function) + +FilterTree = _reflection.GeneratedProtocolMessageType('FilterTree', (_message.Message,), dict( + DESCRIPTOR = _FILTERTREE, + __module__ = 'facets_pb2' + # @@protoc_insertion_point(class_scope:protos.FilterTree) + )) +_sym_db.RegisterMessage(FilterTree) + + +# @@protoc_insertion_point(module_scope) \ No newline at end of file diff --git a/pydgraph/utils/proto/graphresponse_pb2.py b/pydgraph/utils/proto/graphresponse_pb2.py new file mode 100644 index 00000000..e34d12a3 --- /dev/null +++ b/pydgraph/utils/proto/graphresponse_pb2.py @@ -0,0 +1,773 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: graphresponse.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import facets_pb2 as facets__pb2 +from . import schema_pb2 as schema__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='graphresponse.proto', + package='protos', + syntax='proto3', + serialized_pb=_b('\n\x13graphresponse.proto\x12\x06protos\x1a\x0c\x66\x61\x63\x65ts.proto\x1a\x0cschema.proto\"\xb1\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x10\n\x08objectId\x18\x03 \x01(\t\x12\"\n\x0bobjectValue\x18\x04 \x01(\x0b\x32\r.protos.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x07 \x01(\t\x12\x12\n\nobjectType\x18\x06 \x01(\x11\x12\x1d\n\x06\x66\x61\x63\x65ts\x18\x08 \x03(\x0b\x32\r.protos.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"h\n\x08Mutation\x12\x1a\n\x03set\x18\x01 \x03(\x0b\x32\r.protos.NQuad\x12\x1a\n\x03\x64\x65l\x18\x02 \x03(\x0b\x32\r.protos.NQuad\x12$\n\x06schema\x18\x03 \x03(\x0b\x32\x14.protos.SchemaUpdate\"\xb9\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12\"\n\x08mutation\x18\x02 \x01(\x0b\x32\x10.protos.Mutation\x12%\n\x06schema\x18\x03 \x01(\x0b\x32\x15.protos.SchemaRequest\x12\'\n\x04vars\x18\x04 \x03(\x0b\x32\x19.protos.Request.VarsEntry\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"6\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x1c\n\x05value\x18\x02 \x01(\x0b\x32\r.protos.Value\"_\n\x04Node\x12\x11\n\tattribute\x18\x01 \x01(\t\x12$\n\nproperties\x18\x02 \x03(\x0b\x32\x10.protos.Property\x12\x1e\n\x08\x63hildren\x18\x03 \x03(\x0b\x32\x0c.protos.Node\"\xd2\x01\n\x08Response\x12\x17\n\x01n\x18\x01 \x03(\x0b\x32\x0c.protos.Node\x12\x1a\n\x01l\x18\x02 \x01(\x0b\x32\x0f.protos.Latency\x12\x38\n\x0c\x41ssignedUids\x18\x03 \x03(\x0b\x32\".protos.Response.AssignedUidsEntry\x12\"\n\x06schema\x18\x04 \x03(\x0b\x32\x12.protos.SchemaNode\x1a\x33\n\x11\x41ssignedUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t2f\n\x06\x44graph\x12*\n\x03Run\x12\x0f.protos.Request\x1a\x10.protos.Response\"\x00\x12\x30\n\x0c\x43heckVersion\x12\r.protos.Check\x1a\x0f.protos.Version\"\x00\x62\x06proto3') + , + dependencies=[facets__pb2.DESCRIPTOR,schema__pb2.DESCRIPTOR,]) + + + + +_NQUAD = _descriptor.Descriptor( + name='NQuad', + full_name='protos.NQuad', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='subject', full_name='protos.NQuad.subject', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='predicate', full_name='protos.NQuad.predicate', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='objectId', full_name='protos.NQuad.objectId', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='objectValue', full_name='protos.NQuad.objectValue', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='label', full_name='protos.NQuad.label', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='lang', full_name='protos.NQuad.lang', index=5, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='objectType', full_name='protos.NQuad.objectType', index=6, + number=6, type=17, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='facets', full_name='protos.NQuad.facets', index=7, + number=8, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=60, + serialized_end=237, +) + + +_VALUE = _descriptor.Descriptor( + name='Value', + full_name='protos.Value', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='default_val', full_name='protos.Value.default_val', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='bytes_val', full_name='protos.Value.bytes_val', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='int_val', full_name='protos.Value.int_val', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='bool_val', full_name='protos.Value.bool_val', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='str_val', full_name='protos.Value.str_val', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='double_val', full_name='protos.Value.double_val', index=5, + number=6, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='geo_val', full_name='protos.Value.geo_val', index=6, + number=7, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='date_val', full_name='protos.Value.date_val', index=7, + number=8, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='datetime_val', full_name='protos.Value.datetime_val', index=8, + number=9, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='password_val', full_name='protos.Value.password_val', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='uid_val', full_name='protos.Value.uid_val', index=10, + number=11, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='val', full_name='protos.Value.val', + index=0, containing_type=None, fields=[]), + ], + serialized_start=240, + serialized_end=484, +) + + +_MUTATION = _descriptor.Descriptor( + name='Mutation', + full_name='protos.Mutation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='set', full_name='protos.Mutation.set', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='del', full_name='protos.Mutation.del', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='schema', full_name='protos.Mutation.schema', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=486, + serialized_end=590, +) + + +_REQUEST_VARSENTRY = _descriptor.Descriptor( + name='VarsEntry', + full_name='protos.Request.VarsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='protos.Request.VarsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='protos.Request.VarsEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=735, + serialized_end=778, +) + +_REQUEST = _descriptor.Descriptor( + name='Request', + full_name='protos.Request', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='query', full_name='protos.Request.query', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mutation', full_name='protos.Request.mutation', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='schema', full_name='protos.Request.schema', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='vars', full_name='protos.Request.vars', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_REQUEST_VARSENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=593, + serialized_end=778, +) + + +_LATENCY = _descriptor.Descriptor( + name='Latency', + full_name='protos.Latency', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='parsing', full_name='protos.Latency.parsing', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='processing', full_name='protos.Latency.processing', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='pb', full_name='protos.Latency.pb', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=780, + serialized_end=838, +) + + +_PROPERTY = _descriptor.Descriptor( + name='Property', + full_name='protos.Property', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='prop', full_name='protos.Property.prop', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='protos.Property.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=840, + serialized_end=894, +) + + +_NODE = _descriptor.Descriptor( + name='Node', + full_name='protos.Node', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='attribute', full_name='protos.Node.attribute', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='properties', full_name='protos.Node.properties', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='children', full_name='protos.Node.children', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=896, + serialized_end=991, +) + + +_RESPONSE_ASSIGNEDUIDSENTRY = _descriptor.Descriptor( + name='AssignedUidsEntry', + full_name='protos.Response.AssignedUidsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='protos.Response.AssignedUidsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='protos.Response.AssignedUidsEntry.value', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1153, + serialized_end=1204, +) + +_RESPONSE = _descriptor.Descriptor( + name='Response', + full_name='protos.Response', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='n', full_name='protos.Response.n', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='l', full_name='protos.Response.l', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='AssignedUids', full_name='protos.Response.AssignedUids', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='schema', full_name='protos.Response.schema', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_RESPONSE_ASSIGNEDUIDSENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=994, + serialized_end=1204, +) + + +_CHECK = _descriptor.Descriptor( + name='Check', + full_name='protos.Check', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1206, + serialized_end=1213, +) + + +_VERSION = _descriptor.Descriptor( + name='Version', + full_name='protos.Version', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='tag', full_name='protos.Version.tag', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1215, + serialized_end=1237, +) + +_NQUAD.fields_by_name['objectValue'].message_type = _VALUE +_NQUAD.fields_by_name['facets'].message_type = facets__pb2._FACET +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['default_val']) +_VALUE.fields_by_name['default_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['bytes_val']) +_VALUE.fields_by_name['bytes_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['int_val']) +_VALUE.fields_by_name['int_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['bool_val']) +_VALUE.fields_by_name['bool_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['str_val']) +_VALUE.fields_by_name['str_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['double_val']) +_VALUE.fields_by_name['double_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['geo_val']) +_VALUE.fields_by_name['geo_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['date_val']) +_VALUE.fields_by_name['date_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['datetime_val']) +_VALUE.fields_by_name['datetime_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['password_val']) +_VALUE.fields_by_name['password_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['uid_val']) +_VALUE.fields_by_name['uid_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_MUTATION.fields_by_name['set'].message_type = _NQUAD +_MUTATION.fields_by_name['del'].message_type = _NQUAD +_MUTATION.fields_by_name['schema'].message_type = schema__pb2._SCHEMAUPDATE +_REQUEST_VARSENTRY.containing_type = _REQUEST +_REQUEST.fields_by_name['mutation'].message_type = _MUTATION +_REQUEST.fields_by_name['schema'].message_type = schema__pb2._SCHEMAREQUEST +_REQUEST.fields_by_name['vars'].message_type = _REQUEST_VARSENTRY +_PROPERTY.fields_by_name['value'].message_type = _VALUE +_NODE.fields_by_name['properties'].message_type = _PROPERTY +_NODE.fields_by_name['children'].message_type = _NODE +_RESPONSE_ASSIGNEDUIDSENTRY.containing_type = _RESPONSE +_RESPONSE.fields_by_name['n'].message_type = _NODE +_RESPONSE.fields_by_name['l'].message_type = _LATENCY +_RESPONSE.fields_by_name['AssignedUids'].message_type = _RESPONSE_ASSIGNEDUIDSENTRY +_RESPONSE.fields_by_name['schema'].message_type = schema__pb2._SCHEMANODE +DESCRIPTOR.message_types_by_name['NQuad'] = _NQUAD +DESCRIPTOR.message_types_by_name['Value'] = _VALUE +DESCRIPTOR.message_types_by_name['Mutation'] = _MUTATION +DESCRIPTOR.message_types_by_name['Request'] = _REQUEST +DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY +DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY +DESCRIPTOR.message_types_by_name['Node'] = _NODE +DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE +DESCRIPTOR.message_types_by_name['Check'] = _CHECK +DESCRIPTOR.message_types_by_name['Version'] = _VERSION +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +NQuad = _reflection.GeneratedProtocolMessageType('NQuad', (_message.Message,), dict( + DESCRIPTOR = _NQUAD, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.NQuad) + )) +_sym_db.RegisterMessage(NQuad) + +Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( + DESCRIPTOR = _VALUE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Value) + )) +_sym_db.RegisterMessage(Value) + +Mutation = _reflection.GeneratedProtocolMessageType('Mutation', (_message.Message,), dict( + DESCRIPTOR = _MUTATION, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Mutation) + )) +_sym_db.RegisterMessage(Mutation) + +Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( + + VarsEntry = _reflection.GeneratedProtocolMessageType('VarsEntry', (_message.Message,), dict( + DESCRIPTOR = _REQUEST_VARSENTRY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Request.VarsEntry) + )) + , + DESCRIPTOR = _REQUEST, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Request) + )) +_sym_db.RegisterMessage(Request) +_sym_db.RegisterMessage(Request.VarsEntry) + +Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( + DESCRIPTOR = _LATENCY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Latency) + )) +_sym_db.RegisterMessage(Latency) + +Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( + DESCRIPTOR = _PROPERTY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Property) + )) +_sym_db.RegisterMessage(Property) + +Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( + DESCRIPTOR = _NODE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Node) + )) +_sym_db.RegisterMessage(Node) + +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( + + AssignedUidsEntry = _reflection.GeneratedProtocolMessageType('AssignedUidsEntry', (_message.Message,), dict( + DESCRIPTOR = _RESPONSE_ASSIGNEDUIDSENTRY, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Response.AssignedUidsEntry) + )) + , + DESCRIPTOR = _RESPONSE, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Response) + )) +_sym_db.RegisterMessage(Response) +_sym_db.RegisterMessage(Response.AssignedUidsEntry) + +Check = _reflection.GeneratedProtocolMessageType('Check', (_message.Message,), dict( + DESCRIPTOR = _CHECK, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Check) + )) +_sym_db.RegisterMessage(Check) + +Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), dict( + DESCRIPTOR = _VERSION, + __module__ = 'graphresponse_pb2' + # @@protoc_insertion_point(class_scope:protos.Version) + )) +_sym_db.RegisterMessage(Version) + + +_REQUEST_VARSENTRY.has_options = True +_REQUEST_VARSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_RESPONSE_ASSIGNEDUIDSENTRY.has_options = True +_RESPONSE_ASSIGNEDUIDSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +# @@protoc_insertion_point(module_scope) \ No newline at end of file diff --git a/pydgraph/utils/proto/schema_pb2.py b/pydgraph/utils/proto/schema_pb2.py new file mode 100644 index 00000000..1d148bbb --- /dev/null +++ b/pydgraph/utils/proto/schema_pb2.py @@ -0,0 +1,279 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: schema.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='schema.proto', + package='protos', + syntax='proto3', + serialized_pb=_b('\n\x0cschema.proto\x12\x06protos\"E\n\rSchemaRequest\x12\x10\n\x08group_id\x18\x01 \x01(\r\x12\x12\n\npredicates\x18\x02 \x03(\t\x12\x0e\n\x06\x66ields\x18\x03 \x03(\t\"2\n\x0cSchemaResult\x12\"\n\x06schema\x18\x01 \x03(\x0b\x32\x12.protos.SchemaNode\"`\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\"\xaa\x01\n\x0cSchemaUpdate\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x12\n\nvalue_type\x18\x02 \x01(\r\x12\x31\n\tdirective\x18\x03 \x01(\x0e\x32\x1e.protos.SchemaUpdate.Directive\x12\x11\n\ttokenizer\x18\x04 \x03(\t\"-\n\tDirective\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05INDEX\x10\x01\x12\x0b\n\x07REVERSE\x10\x02\x62\x06proto3') +) + + + +_SCHEMAUPDATE_DIRECTIVE = _descriptor.EnumDescriptor( + name='Directive', + full_name='protos.SchemaUpdate.Directive', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='INDEX', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='REVERSE', index=2, number=2, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=371, + serialized_end=416, +) +_sym_db.RegisterEnumDescriptor(_SCHEMAUPDATE_DIRECTIVE) + + +_SCHEMAREQUEST = _descriptor.Descriptor( + name='SchemaRequest', + full_name='protos.SchemaRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='group_id', full_name='protos.SchemaRequest.group_id', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='predicates', full_name='protos.SchemaRequest.predicates', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='fields', full_name='protos.SchemaRequest.fields', index=2, + number=3, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=24, + serialized_end=93, +) + + +_SCHEMARESULT = _descriptor.Descriptor( + name='SchemaResult', + full_name='protos.SchemaResult', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='schema', full_name='protos.SchemaResult.schema', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=95, + serialized_end=145, +) + + +_SCHEMANODE = _descriptor.Descriptor( + name='SchemaNode', + full_name='protos.SchemaNode', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='predicate', full_name='protos.SchemaNode.predicate', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='type', full_name='protos.SchemaNode.type', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='index', full_name='protos.SchemaNode.index', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tokenizer', full_name='protos.SchemaNode.tokenizer', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='reverse', full_name='protos.SchemaNode.reverse', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=147, + serialized_end=243, +) + + +_SCHEMAUPDATE = _descriptor.Descriptor( + name='SchemaUpdate', + full_name='protos.SchemaUpdate', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='predicate', full_name='protos.SchemaUpdate.predicate', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value_type', full_name='protos.SchemaUpdate.value_type', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='directive', full_name='protos.SchemaUpdate.directive', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='tokenizer', full_name='protos.SchemaUpdate.tokenizer', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _SCHEMAUPDATE_DIRECTIVE, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=246, + serialized_end=416, +) + +_SCHEMARESULT.fields_by_name['schema'].message_type = _SCHEMANODE +_SCHEMAUPDATE.fields_by_name['directive'].enum_type = _SCHEMAUPDATE_DIRECTIVE +_SCHEMAUPDATE_DIRECTIVE.containing_type = _SCHEMAUPDATE +DESCRIPTOR.message_types_by_name['SchemaRequest'] = _SCHEMAREQUEST +DESCRIPTOR.message_types_by_name['SchemaResult'] = _SCHEMARESULT +DESCRIPTOR.message_types_by_name['SchemaNode'] = _SCHEMANODE +DESCRIPTOR.message_types_by_name['SchemaUpdate'] = _SCHEMAUPDATE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +SchemaRequest = _reflection.GeneratedProtocolMessageType('SchemaRequest', (_message.Message,), dict( + DESCRIPTOR = _SCHEMAREQUEST, + __module__ = 'schema_pb2' + # @@protoc_insertion_point(class_scope:protos.SchemaRequest) + )) +_sym_db.RegisterMessage(SchemaRequest) + +SchemaResult = _reflection.GeneratedProtocolMessageType('SchemaResult', (_message.Message,), dict( + DESCRIPTOR = _SCHEMARESULT, + __module__ = 'schema_pb2' + # @@protoc_insertion_point(class_scope:protos.SchemaResult) + )) +_sym_db.RegisterMessage(SchemaResult) + +SchemaNode = _reflection.GeneratedProtocolMessageType('SchemaNode', (_message.Message,), dict( + DESCRIPTOR = _SCHEMANODE, + __module__ = 'schema_pb2' + # @@protoc_insertion_point(class_scope:protos.SchemaNode) + )) +_sym_db.RegisterMessage(SchemaNode) + +SchemaUpdate = _reflection.GeneratedProtocolMessageType('SchemaUpdate', (_message.Message,), dict( + DESCRIPTOR = _SCHEMAUPDATE, + __module__ = 'schema_pb2' + # @@protoc_insertion_point(class_scope:protos.SchemaUpdate) + )) +_sym_db.RegisterMessage(SchemaUpdate) + + +# @@protoc_insertion_point(module_scope) \ No newline at end of file From e1b0886c061ece3cfd2bce89da53fd53adb73212 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 10 Jun 2017 12:31:31 +0200 Subject: [PATCH 016/435] Update Version Info --- pydgraph/utils/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/utils/meta.py b/pydgraph/utils/meta.py index b6f91f54..4e71b5fa 100755 --- a/pydgraph/utils/meta.py +++ b/pydgraph/utils/meta.py @@ -1 +1 @@ -VERSION="0.3.5" +VERSION="0.4.0" From f709c3a88f3153e554cf64e61b0dfc9c7d0c5575 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 10 Jun 2017 16:34:46 +0200 Subject: [PATCH 017/435] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0a1bbf4a..b3e57fc9 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -grpcio +grpcio==1.2.0 From 8d317c7b1c49a6c81e62e9c31ef80f8d9c3da8c7 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 11 Jun 2017 02:42:42 +0200 Subject: [PATCH 018/435] Update Setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7b635ecd..5c5e9e5a 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", ], - packages=["pydgraph", "pydgraph.utils"], + packages=["pydgraph", "pydgraph.utils", "pydgraph.utils.proto"], install_requires=open('requirements.txt').readlines(), test_suite='tests', ) From ee50e7be68a839f2dc2e621301b8321ddd6191fa Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 17:40:13 +0530 Subject: [PATCH 019/435] Removed old utils. --- pydgraph/{utils => }/meta.py | 0 pydgraph/utils/__init__.py | 0 pydgraph/utils/loader.py | 38 -- pydgraph/utils/proto/__init__.py | 0 pydgraph/utils/proto/facets_pb2.py | 361 ---------- pydgraph/utils/proto/graphresponse_pb2.py | 773 ---------------------- pydgraph/utils/proto/schema_pb2.py | 279 -------- 7 files changed, 1451 deletions(-) rename pydgraph/{utils => }/meta.py (100%) delete mode 100755 pydgraph/utils/__init__.py delete mode 100644 pydgraph/utils/loader.py delete mode 100644 pydgraph/utils/proto/__init__.py delete mode 100644 pydgraph/utils/proto/facets_pb2.py delete mode 100644 pydgraph/utils/proto/graphresponse_pb2.py delete mode 100644 pydgraph/utils/proto/schema_pb2.py diff --git a/pydgraph/utils/meta.py b/pydgraph/meta.py similarity index 100% rename from pydgraph/utils/meta.py rename to pydgraph/meta.py diff --git a/pydgraph/utils/__init__.py b/pydgraph/utils/__init__.py deleted file mode 100755 index e69de29b..00000000 diff --git a/pydgraph/utils/loader.py b/pydgraph/utils/loader.py deleted file mode 100644 index 25ff312c..00000000 --- a/pydgraph/utils/loader.py +++ /dev/null @@ -1,38 +0,0 @@ -import grpc -from .proto.graphresponse_pb2 import * -from grpc.beta import implementations as beta_implementations -from grpc.beta import interfaces as beta_interfaces - - -class BetaDgraphServicer(object): - def Query(self, request, context): - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - -class BetaDgraphStub(object): - def __init__(self, channel): - self.channel = channel - self.unary = self.channel.unary_unary( - '/protos.Dgraph/Run', - request_serializer=Request.SerializeToString, - response_deserializer=Response.FromString, - ) - - def Query(self, *args, **kwargs): - return self.unary(*args, **kwargs) - - async def aQuery(self, *args, **kwargs): - return self.unary(*args, **kwargs) - -def add_DgraphServicer_to_server(servicer, server): - rpc_method_handlers = { - 'Query': grpc.unary_unary_rpc_method_handler( - servicer.Query, - request_deserializer=Request.FromString, - response_serializer=Response.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'protos.Dgraph', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) diff --git a/pydgraph/utils/proto/__init__.py b/pydgraph/utils/proto/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pydgraph/utils/proto/facets_pb2.py b/pydgraph/utils/proto/facets_pb2.py deleted file mode 100644 index 96dd5266..00000000 --- a/pydgraph/utils/proto/facets_pb2.py +++ /dev/null @@ -1,361 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: facets.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='facets.proto', - package='protos', - syntax='proto3', - serialized_pb=_b('\n\x0c\x66\x61\x63\x65ts.proto\x12\x06protos\"\x9f\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\'\n\x08val_type\x18\x03 \x01(\x0e\x32\x15.protos.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\'\n\x05Param\x12\x10\n\x08\x61ll_keys\x18\x01 \x01(\x08\x12\x0c\n\x04keys\x18\x02 \x03(\t\"\'\n\x06\x46\x61\x63\x65ts\x12\x1d\n\x06\x66\x61\x63\x65ts\x18\x01 \x03(\x0b\x32\r.protos.Facet\"1\n\nFacetsList\x12#\n\x0b\x66\x61\x63\x65ts_list\x18\x01 \x03(\x0b\x32\x0e.protos.Facets\"3\n\x08\x46unction\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x03 \x03(\t\"^\n\nFilterTree\x12\n\n\x02op\x18\x01 \x01(\t\x12$\n\x08\x63hildren\x18\x02 \x03(\x0b\x32\x12.protos.FilterTree\x12\x1e\n\x04\x66unc\x18\x03 \x01(\x0b\x32\x10.protos.Functionb\x06proto3') -) - - - -_FACET_VALTYPE = _descriptor.EnumDescriptor( - name='ValType', - full_name='protos.Facet.ValType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='STRING', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INT', index=1, number=1, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='FLOAT', index=2, number=2, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BOOL', index=3, number=3, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='DATETIME', index=4, number=4, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=119, - serialized_end=184, -) -_sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) - - -_FACET = _descriptor.Descriptor( - name='Facet', - full_name='protos.Facet', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='protos.Facet.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='value', full_name='protos.Facet.value', index=1, - number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='val_type', full_name='protos.Facet.val_type', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='tokens', full_name='protos.Facet.tokens', index=3, - number=4, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _FACET_VALTYPE, - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=25, - serialized_end=184, -) - - -_PARAM = _descriptor.Descriptor( - name='Param', - full_name='protos.Param', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='all_keys', full_name='protos.Param.all_keys', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='keys', full_name='protos.Param.keys', index=1, - number=2, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=186, - serialized_end=225, -) - - -_FACETS = _descriptor.Descriptor( - name='Facets', - full_name='protos.Facets', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='facets', full_name='protos.Facets.facets', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=227, - serialized_end=266, -) - - -_FACETSLIST = _descriptor.Descriptor( - name='FacetsList', - full_name='protos.FacetsList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='facets_list', full_name='protos.FacetsList.facets_list', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=268, - serialized_end=317, -) - - -_FUNCTION = _descriptor.Descriptor( - name='Function', - full_name='protos.Function', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='protos.Function.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='key', full_name='protos.Function.key', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='args', full_name='protos.Function.args', index=2, - number=3, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=319, - serialized_end=370, -) - - -_FILTERTREE = _descriptor.Descriptor( - name='FilterTree', - full_name='protos.FilterTree', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='op', full_name='protos.FilterTree.op', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='children', full_name='protos.FilterTree.children', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='func', full_name='protos.FilterTree.func', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=372, - serialized_end=466, -) - -_FACET.fields_by_name['val_type'].enum_type = _FACET_VALTYPE -_FACET_VALTYPE.containing_type = _FACET -_FACETS.fields_by_name['facets'].message_type = _FACET -_FACETSLIST.fields_by_name['facets_list'].message_type = _FACETS -_FILTERTREE.fields_by_name['children'].message_type = _FILTERTREE -_FILTERTREE.fields_by_name['func'].message_type = _FUNCTION -DESCRIPTOR.message_types_by_name['Facet'] = _FACET -DESCRIPTOR.message_types_by_name['Param'] = _PARAM -DESCRIPTOR.message_types_by_name['Facets'] = _FACETS -DESCRIPTOR.message_types_by_name['FacetsList'] = _FACETSLIST -DESCRIPTOR.message_types_by_name['Function'] = _FUNCTION -DESCRIPTOR.message_types_by_name['FilterTree'] = _FILTERTREE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Facet = _reflection.GeneratedProtocolMessageType('Facet', (_message.Message,), dict( - DESCRIPTOR = _FACET, - __module__ = 'facets_pb2' - # @@protoc_insertion_point(class_scope:protos.Facet) - )) -_sym_db.RegisterMessage(Facet) - -Param = _reflection.GeneratedProtocolMessageType('Param', (_message.Message,), dict( - DESCRIPTOR = _PARAM, - __module__ = 'facets_pb2' - # @@protoc_insertion_point(class_scope:protos.Param) - )) -_sym_db.RegisterMessage(Param) - -Facets = _reflection.GeneratedProtocolMessageType('Facets', (_message.Message,), dict( - DESCRIPTOR = _FACETS, - __module__ = 'facets_pb2' - # @@protoc_insertion_point(class_scope:protos.Facets) - )) -_sym_db.RegisterMessage(Facets) - -FacetsList = _reflection.GeneratedProtocolMessageType('FacetsList', (_message.Message,), dict( - DESCRIPTOR = _FACETSLIST, - __module__ = 'facets_pb2' - # @@protoc_insertion_point(class_scope:protos.FacetsList) - )) -_sym_db.RegisterMessage(FacetsList) - -Function = _reflection.GeneratedProtocolMessageType('Function', (_message.Message,), dict( - DESCRIPTOR = _FUNCTION, - __module__ = 'facets_pb2' - # @@protoc_insertion_point(class_scope:protos.Function) - )) -_sym_db.RegisterMessage(Function) - -FilterTree = _reflection.GeneratedProtocolMessageType('FilterTree', (_message.Message,), dict( - DESCRIPTOR = _FILTERTREE, - __module__ = 'facets_pb2' - # @@protoc_insertion_point(class_scope:protos.FilterTree) - )) -_sym_db.RegisterMessage(FilterTree) - - -# @@protoc_insertion_point(module_scope) \ No newline at end of file diff --git a/pydgraph/utils/proto/graphresponse_pb2.py b/pydgraph/utils/proto/graphresponse_pb2.py deleted file mode 100644 index e34d12a3..00000000 --- a/pydgraph/utils/proto/graphresponse_pb2.py +++ /dev/null @@ -1,773 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: graphresponse.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from . import facets_pb2 as facets__pb2 -from . import schema_pb2 as schema__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='graphresponse.proto', - package='protos', - syntax='proto3', - serialized_pb=_b('\n\x13graphresponse.proto\x12\x06protos\x1a\x0c\x66\x61\x63\x65ts.proto\x1a\x0cschema.proto\"\xb1\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x10\n\x08objectId\x18\x03 \x01(\t\x12\"\n\x0bobjectValue\x18\x04 \x01(\x0b\x32\r.protos.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x07 \x01(\t\x12\x12\n\nobjectType\x18\x06 \x01(\x11\x12\x1d\n\x06\x66\x61\x63\x65ts\x18\x08 \x03(\x0b\x32\r.protos.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"h\n\x08Mutation\x12\x1a\n\x03set\x18\x01 \x03(\x0b\x32\r.protos.NQuad\x12\x1a\n\x03\x64\x65l\x18\x02 \x03(\x0b\x32\r.protos.NQuad\x12$\n\x06schema\x18\x03 \x03(\x0b\x32\x14.protos.SchemaUpdate\"\xb9\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12\"\n\x08mutation\x18\x02 \x01(\x0b\x32\x10.protos.Mutation\x12%\n\x06schema\x18\x03 \x01(\x0b\x32\x15.protos.SchemaRequest\x12\'\n\x04vars\x18\x04 \x03(\x0b\x32\x19.protos.Request.VarsEntry\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\":\n\x07Latency\x12\x0f\n\x07parsing\x18\x01 \x01(\t\x12\x12\n\nprocessing\x18\x02 \x01(\t\x12\n\n\x02pb\x18\x03 \x01(\t\"6\n\x08Property\x12\x0c\n\x04prop\x18\x01 \x01(\t\x12\x1c\n\x05value\x18\x02 \x01(\x0b\x32\r.protos.Value\"_\n\x04Node\x12\x11\n\tattribute\x18\x01 \x01(\t\x12$\n\nproperties\x18\x02 \x03(\x0b\x32\x10.protos.Property\x12\x1e\n\x08\x63hildren\x18\x03 \x03(\x0b\x32\x0c.protos.Node\"\xd2\x01\n\x08Response\x12\x17\n\x01n\x18\x01 \x03(\x0b\x32\x0c.protos.Node\x12\x1a\n\x01l\x18\x02 \x01(\x0b\x32\x0f.protos.Latency\x12\x38\n\x0c\x41ssignedUids\x18\x03 \x03(\x0b\x32\".protos.Response.AssignedUidsEntry\x12\"\n\x06schema\x18\x04 \x03(\x0b\x32\x12.protos.SchemaNode\x1a\x33\n\x11\x41ssignedUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t2f\n\x06\x44graph\x12*\n\x03Run\x12\x0f.protos.Request\x1a\x10.protos.Response\"\x00\x12\x30\n\x0c\x43heckVersion\x12\r.protos.Check\x1a\x0f.protos.Version\"\x00\x62\x06proto3') - , - dependencies=[facets__pb2.DESCRIPTOR,schema__pb2.DESCRIPTOR,]) - - - - -_NQUAD = _descriptor.Descriptor( - name='NQuad', - full_name='protos.NQuad', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='subject', full_name='protos.NQuad.subject', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='predicate', full_name='protos.NQuad.predicate', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='objectId', full_name='protos.NQuad.objectId', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='objectValue', full_name='protos.NQuad.objectValue', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='label', full_name='protos.NQuad.label', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='lang', full_name='protos.NQuad.lang', index=5, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='objectType', full_name='protos.NQuad.objectType', index=6, - number=6, type=17, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='facets', full_name='protos.NQuad.facets', index=7, - number=8, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=60, - serialized_end=237, -) - - -_VALUE = _descriptor.Descriptor( - name='Value', - full_name='protos.Value', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='default_val', full_name='protos.Value.default_val', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='bytes_val', full_name='protos.Value.bytes_val', index=1, - number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='int_val', full_name='protos.Value.int_val', index=2, - number=3, type=3, cpp_type=2, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='bool_val', full_name='protos.Value.bool_val', index=3, - number=4, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='str_val', full_name='protos.Value.str_val', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='double_val', full_name='protos.Value.double_val', index=5, - number=6, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='geo_val', full_name='protos.Value.geo_val', index=6, - number=7, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='date_val', full_name='protos.Value.date_val', index=7, - number=8, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='datetime_val', full_name='protos.Value.datetime_val', index=8, - number=9, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='password_val', full_name='protos.Value.password_val', index=9, - number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='uid_val', full_name='protos.Value.uid_val', index=10, - number=11, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='val', full_name='protos.Value.val', - index=0, containing_type=None, fields=[]), - ], - serialized_start=240, - serialized_end=484, -) - - -_MUTATION = _descriptor.Descriptor( - name='Mutation', - full_name='protos.Mutation', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='set', full_name='protos.Mutation.set', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='del', full_name='protos.Mutation.del', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='schema', full_name='protos.Mutation.schema', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=486, - serialized_end=590, -) - - -_REQUEST_VARSENTRY = _descriptor.Descriptor( - name='VarsEntry', - full_name='protos.Request.VarsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='protos.Request.VarsEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='value', full_name='protos.Request.VarsEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=735, - serialized_end=778, -) - -_REQUEST = _descriptor.Descriptor( - name='Request', - full_name='protos.Request', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='query', full_name='protos.Request.query', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='mutation', full_name='protos.Request.mutation', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='schema', full_name='protos.Request.schema', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='vars', full_name='protos.Request.vars', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[_REQUEST_VARSENTRY, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=593, - serialized_end=778, -) - - -_LATENCY = _descriptor.Descriptor( - name='Latency', - full_name='protos.Latency', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='parsing', full_name='protos.Latency.parsing', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='processing', full_name='protos.Latency.processing', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='pb', full_name='protos.Latency.pb', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=780, - serialized_end=838, -) - - -_PROPERTY = _descriptor.Descriptor( - name='Property', - full_name='protos.Property', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='prop', full_name='protos.Property.prop', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='value', full_name='protos.Property.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=840, - serialized_end=894, -) - - -_NODE = _descriptor.Descriptor( - name='Node', - full_name='protos.Node', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='attribute', full_name='protos.Node.attribute', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='properties', full_name='protos.Node.properties', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='children', full_name='protos.Node.children', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=896, - serialized_end=991, -) - - -_RESPONSE_ASSIGNEDUIDSENTRY = _descriptor.Descriptor( - name='AssignedUidsEntry', - full_name='protos.Response.AssignedUidsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='protos.Response.AssignedUidsEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='value', full_name='protos.Response.AssignedUidsEntry.value', index=1, - number=2, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1153, - serialized_end=1204, -) - -_RESPONSE = _descriptor.Descriptor( - name='Response', - full_name='protos.Response', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='n', full_name='protos.Response.n', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='l', full_name='protos.Response.l', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='AssignedUids', full_name='protos.Response.AssignedUids', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='schema', full_name='protos.Response.schema', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[_RESPONSE_ASSIGNEDUIDSENTRY, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=994, - serialized_end=1204, -) - - -_CHECK = _descriptor.Descriptor( - name='Check', - full_name='protos.Check', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1206, - serialized_end=1213, -) - - -_VERSION = _descriptor.Descriptor( - name='Version', - full_name='protos.Version', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='tag', full_name='protos.Version.tag', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1215, - serialized_end=1237, -) - -_NQUAD.fields_by_name['objectValue'].message_type = _VALUE -_NQUAD.fields_by_name['facets'].message_type = facets__pb2._FACET -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['default_val']) -_VALUE.fields_by_name['default_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['bytes_val']) -_VALUE.fields_by_name['bytes_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['int_val']) -_VALUE.fields_by_name['int_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['bool_val']) -_VALUE.fields_by_name['bool_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['str_val']) -_VALUE.fields_by_name['str_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['double_val']) -_VALUE.fields_by_name['double_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['geo_val']) -_VALUE.fields_by_name['geo_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['date_val']) -_VALUE.fields_by_name['date_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['datetime_val']) -_VALUE.fields_by_name['datetime_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['password_val']) -_VALUE.fields_by_name['password_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_VALUE.oneofs_by_name['val'].fields.append( - _VALUE.fields_by_name['uid_val']) -_VALUE.fields_by_name['uid_val'].containing_oneof = _VALUE.oneofs_by_name['val'] -_MUTATION.fields_by_name['set'].message_type = _NQUAD -_MUTATION.fields_by_name['del'].message_type = _NQUAD -_MUTATION.fields_by_name['schema'].message_type = schema__pb2._SCHEMAUPDATE -_REQUEST_VARSENTRY.containing_type = _REQUEST -_REQUEST.fields_by_name['mutation'].message_type = _MUTATION -_REQUEST.fields_by_name['schema'].message_type = schema__pb2._SCHEMAREQUEST -_REQUEST.fields_by_name['vars'].message_type = _REQUEST_VARSENTRY -_PROPERTY.fields_by_name['value'].message_type = _VALUE -_NODE.fields_by_name['properties'].message_type = _PROPERTY -_NODE.fields_by_name['children'].message_type = _NODE -_RESPONSE_ASSIGNEDUIDSENTRY.containing_type = _RESPONSE -_RESPONSE.fields_by_name['n'].message_type = _NODE -_RESPONSE.fields_by_name['l'].message_type = _LATENCY -_RESPONSE.fields_by_name['AssignedUids'].message_type = _RESPONSE_ASSIGNEDUIDSENTRY -_RESPONSE.fields_by_name['schema'].message_type = schema__pb2._SCHEMANODE -DESCRIPTOR.message_types_by_name['NQuad'] = _NQUAD -DESCRIPTOR.message_types_by_name['Value'] = _VALUE -DESCRIPTOR.message_types_by_name['Mutation'] = _MUTATION -DESCRIPTOR.message_types_by_name['Request'] = _REQUEST -DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY -DESCRIPTOR.message_types_by_name['Property'] = _PROPERTY -DESCRIPTOR.message_types_by_name['Node'] = _NODE -DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE -DESCRIPTOR.message_types_by_name['Check'] = _CHECK -DESCRIPTOR.message_types_by_name['Version'] = _VERSION -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -NQuad = _reflection.GeneratedProtocolMessageType('NQuad', (_message.Message,), dict( - DESCRIPTOR = _NQUAD, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.NQuad) - )) -_sym_db.RegisterMessage(NQuad) - -Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( - DESCRIPTOR = _VALUE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Value) - )) -_sym_db.RegisterMessage(Value) - -Mutation = _reflection.GeneratedProtocolMessageType('Mutation', (_message.Message,), dict( - DESCRIPTOR = _MUTATION, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Mutation) - )) -_sym_db.RegisterMessage(Mutation) - -Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( - - VarsEntry = _reflection.GeneratedProtocolMessageType('VarsEntry', (_message.Message,), dict( - DESCRIPTOR = _REQUEST_VARSENTRY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Request.VarsEntry) - )) - , - DESCRIPTOR = _REQUEST, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Request) - )) -_sym_db.RegisterMessage(Request) -_sym_db.RegisterMessage(Request.VarsEntry) - -Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( - DESCRIPTOR = _LATENCY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Latency) - )) -_sym_db.RegisterMessage(Latency) - -Property = _reflection.GeneratedProtocolMessageType('Property', (_message.Message,), dict( - DESCRIPTOR = _PROPERTY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Property) - )) -_sym_db.RegisterMessage(Property) - -Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( - DESCRIPTOR = _NODE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Node) - )) -_sym_db.RegisterMessage(Node) - -Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( - - AssignedUidsEntry = _reflection.GeneratedProtocolMessageType('AssignedUidsEntry', (_message.Message,), dict( - DESCRIPTOR = _RESPONSE_ASSIGNEDUIDSENTRY, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Response.AssignedUidsEntry) - )) - , - DESCRIPTOR = _RESPONSE, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Response) - )) -_sym_db.RegisterMessage(Response) -_sym_db.RegisterMessage(Response.AssignedUidsEntry) - -Check = _reflection.GeneratedProtocolMessageType('Check', (_message.Message,), dict( - DESCRIPTOR = _CHECK, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Check) - )) -_sym_db.RegisterMessage(Check) - -Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), dict( - DESCRIPTOR = _VERSION, - __module__ = 'graphresponse_pb2' - # @@protoc_insertion_point(class_scope:protos.Version) - )) -_sym_db.RegisterMessage(Version) - - -_REQUEST_VARSENTRY.has_options = True -_REQUEST_VARSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) -_RESPONSE_ASSIGNEDUIDSENTRY.has_options = True -_RESPONSE_ASSIGNEDUIDSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) -# @@protoc_insertion_point(module_scope) \ No newline at end of file diff --git a/pydgraph/utils/proto/schema_pb2.py b/pydgraph/utils/proto/schema_pb2.py deleted file mode 100644 index 1d148bbb..00000000 --- a/pydgraph/utils/proto/schema_pb2.py +++ /dev/null @@ -1,279 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: schema.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='schema.proto', - package='protos', - syntax='proto3', - serialized_pb=_b('\n\x0cschema.proto\x12\x06protos\"E\n\rSchemaRequest\x12\x10\n\x08group_id\x18\x01 \x01(\r\x12\x12\n\npredicates\x18\x02 \x03(\t\x12\x0e\n\x06\x66ields\x18\x03 \x03(\t\"2\n\x0cSchemaResult\x12\"\n\x06schema\x18\x01 \x03(\x0b\x32\x12.protos.SchemaNode\"`\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\"\xaa\x01\n\x0cSchemaUpdate\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x12\n\nvalue_type\x18\x02 \x01(\r\x12\x31\n\tdirective\x18\x03 \x01(\x0e\x32\x1e.protos.SchemaUpdate.Directive\x12\x11\n\ttokenizer\x18\x04 \x03(\t\"-\n\tDirective\x12\x08\n\x04NONE\x10\x00\x12\t\n\x05INDEX\x10\x01\x12\x0b\n\x07REVERSE\x10\x02\x62\x06proto3') -) - - - -_SCHEMAUPDATE_DIRECTIVE = _descriptor.EnumDescriptor( - name='Directive', - full_name='protos.SchemaUpdate.Directive', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='NONE', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='INDEX', index=1, number=1, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='REVERSE', index=2, number=2, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=371, - serialized_end=416, -) -_sym_db.RegisterEnumDescriptor(_SCHEMAUPDATE_DIRECTIVE) - - -_SCHEMAREQUEST = _descriptor.Descriptor( - name='SchemaRequest', - full_name='protos.SchemaRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='group_id', full_name='protos.SchemaRequest.group_id', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='predicates', full_name='protos.SchemaRequest.predicates', index=1, - number=2, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='fields', full_name='protos.SchemaRequest.fields', index=2, - number=3, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=24, - serialized_end=93, -) - - -_SCHEMARESULT = _descriptor.Descriptor( - name='SchemaResult', - full_name='protos.SchemaResult', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='schema', full_name='protos.SchemaResult.schema', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=95, - serialized_end=145, -) - - -_SCHEMANODE = _descriptor.Descriptor( - name='SchemaNode', - full_name='protos.SchemaNode', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='predicate', full_name='protos.SchemaNode.predicate', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='type', full_name='protos.SchemaNode.type', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='index', full_name='protos.SchemaNode.index', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='tokenizer', full_name='protos.SchemaNode.tokenizer', index=3, - number=4, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='reverse', full_name='protos.SchemaNode.reverse', index=4, - number=5, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=147, - serialized_end=243, -) - - -_SCHEMAUPDATE = _descriptor.Descriptor( - name='SchemaUpdate', - full_name='protos.SchemaUpdate', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='predicate', full_name='protos.SchemaUpdate.predicate', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='value_type', full_name='protos.SchemaUpdate.value_type', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='directive', full_name='protos.SchemaUpdate.directive', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='tokenizer', full_name='protos.SchemaUpdate.tokenizer', index=3, - number=4, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _SCHEMAUPDATE_DIRECTIVE, - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=246, - serialized_end=416, -) - -_SCHEMARESULT.fields_by_name['schema'].message_type = _SCHEMANODE -_SCHEMAUPDATE.fields_by_name['directive'].enum_type = _SCHEMAUPDATE_DIRECTIVE -_SCHEMAUPDATE_DIRECTIVE.containing_type = _SCHEMAUPDATE -DESCRIPTOR.message_types_by_name['SchemaRequest'] = _SCHEMAREQUEST -DESCRIPTOR.message_types_by_name['SchemaResult'] = _SCHEMARESULT -DESCRIPTOR.message_types_by_name['SchemaNode'] = _SCHEMANODE -DESCRIPTOR.message_types_by_name['SchemaUpdate'] = _SCHEMAUPDATE -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -SchemaRequest = _reflection.GeneratedProtocolMessageType('SchemaRequest', (_message.Message,), dict( - DESCRIPTOR = _SCHEMAREQUEST, - __module__ = 'schema_pb2' - # @@protoc_insertion_point(class_scope:protos.SchemaRequest) - )) -_sym_db.RegisterMessage(SchemaRequest) - -SchemaResult = _reflection.GeneratedProtocolMessageType('SchemaResult', (_message.Message,), dict( - DESCRIPTOR = _SCHEMARESULT, - __module__ = 'schema_pb2' - # @@protoc_insertion_point(class_scope:protos.SchemaResult) - )) -_sym_db.RegisterMessage(SchemaResult) - -SchemaNode = _reflection.GeneratedProtocolMessageType('SchemaNode', (_message.Message,), dict( - DESCRIPTOR = _SCHEMANODE, - __module__ = 'schema_pb2' - # @@protoc_insertion_point(class_scope:protos.SchemaNode) - )) -_sym_db.RegisterMessage(SchemaNode) - -SchemaUpdate = _reflection.GeneratedProtocolMessageType('SchemaUpdate', (_message.Message,), dict( - DESCRIPTOR = _SCHEMAUPDATE, - __module__ = 'schema_pb2' - # @@protoc_insertion_point(class_scope:protos.SchemaUpdate) - )) -_sym_db.RegisterMessage(SchemaUpdate) - - -# @@protoc_insertion_point(module_scope) \ No newline at end of file From ad63b7dd01a0f0d7c31c5694d8514f11c886a526 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 18:43:42 +0530 Subject: [PATCH 020/435] Added new proto files. --- pydgraph/proto/__init__.py | 0 pydgraph/proto/api_pb2.py | 1289 ++++++++++++++++++++++++++++++++ pydgraph/proto/api_pb2_grpc.py | 114 +++ 3 files changed, 1403 insertions(+) create mode 100644 pydgraph/proto/__init__.py create mode 100644 pydgraph/proto/api_pb2.py create mode 100644 pydgraph/proto/api_pb2_grpc.py diff --git a/pydgraph/proto/__init__.py b/pydgraph/proto/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py new file mode 100644 index 00000000..58031d9e --- /dev/null +++ b/pydgraph/proto/api_pb2.py @@ -0,0 +1,1289 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: api.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='api.proto', + package='api', + syntax='proto3', + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x80\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"Y\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"}\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') +) + + + +_FACET_VALTYPE = _descriptor.EnumDescriptor( + name='ValType', + full_name='api.Facet.ValType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='STRING', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='INT', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='FLOAT', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BOOL', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DATETIME', index=4, number=4, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=1601, + serialized_end=1666, +) +_sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) + + +_REQUEST_VARSENTRY = _descriptor.Descriptor( + name='VarsEntry', + full_name='api.Request.VarsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='api.Request.VarsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='api.Request.VarsEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=133, + serialized_end=176, +) + +_REQUEST = _descriptor.Descriptor( + name='Request', + full_name='api.Request', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='query', full_name='api.Request.query', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='vars', full_name='api.Request.vars', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='start_ts', full_name='api.Request.start_ts', index=2, + number=13, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='lin_read', full_name='api.Request.lin_read', index=3, + number=14, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_REQUEST_VARSENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=19, + serialized_end=176, +) + + +_RESPONSE = _descriptor.Descriptor( + name='Response', + full_name='api.Response', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='json', full_name='api.Response.json', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='schema', full_name='api.Response.schema', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='txn', full_name='api.Response.txn', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='latency', full_name='api.Response.latency', index=3, + number=12, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=178, + serialized_end=296, +) + + +_ASSIGNED_UIDSENTRY = _descriptor.Descriptor( + name='UidsEntry', + full_name='api.Assigned.UidsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='api.Assigned.UidsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='api.Assigned.UidsEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=384, + serialized_end=427, +) + +_ASSIGNED = _descriptor.Descriptor( + name='Assigned', + full_name='api.Assigned', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='uids', full_name='api.Assigned.uids', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='context', full_name='api.Assigned.context', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_ASSIGNED_UIDSENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=299, + serialized_end=427, +) + + +_MUTATION = _descriptor.Descriptor( + name='Mutation', + full_name='api.Mutation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='set_json', full_name='api.Mutation.set_json', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='delete_json', full_name='api.Mutation.delete_json', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='set_nquads', full_name='api.Mutation.set_nquads', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='del_nquads', full_name='api.Mutation.del_nquads', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='set', full_name='api.Mutation.set', index=4, + number=10, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='del', full_name='api.Mutation.del', index=5, + number=11, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='start_ts', full_name='api.Mutation.start_ts', index=6, + number=13, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='commit_now', full_name='api.Mutation.commit_now', index=7, + number=14, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ignore_index_conflict', full_name='api.Mutation.ignore_index_conflict', index=8, + number=15, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=430, + serialized_end=638, +) + + +_ASSIGNEDIDS = _descriptor.Descriptor( + name='AssignedIds', + full_name='api.AssignedIds', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='startId', full_name='api.AssignedIds.startId', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='endId', full_name='api.AssignedIds.endId', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=640, + serialized_end=685, +) + + +_OPERATION = _descriptor.Descriptor( + name='Operation', + full_name='api.Operation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='schema', full_name='api.Operation.schema', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='drop_attr', full_name='api.Operation.drop_attr', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='drop_all', full_name='api.Operation.drop_all', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=687, + serialized_end=751, +) + + +_PAYLOAD = _descriptor.Descriptor( + name='Payload', + full_name='api.Payload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='Data', full_name='api.Payload.Data', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=753, + serialized_end=776, +) + + +_TXNCONTEXT = _descriptor.Descriptor( + name='TxnContext', + full_name='api.TxnContext', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='start_ts', full_name='api.TxnContext.start_ts', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='commit_ts', full_name='api.TxnContext.commit_ts', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='aborted', full_name='api.TxnContext.aborted', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='keys', full_name='api.TxnContext.keys', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='lin_read', full_name='api.TxnContext.lin_read', index=4, + number=13, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=778, + serialized_end=890, +) + + +_CHECK = _descriptor.Descriptor( + name='Check', + full_name='api.Check', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=892, + serialized_end=899, +) + + +_VERSION = _descriptor.Descriptor( + name='Version', + full_name='api.Version', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='tag', full_name='api.Version.tag', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=901, + serialized_end=923, +) + + +_LINREAD_IDSENTRY = _descriptor.Descriptor( + name='IdsEntry', + full_name='api.LinRead.IdsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='api.LinRead.IdsEntry.key', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='api.LinRead.IdsEntry.value', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=972, + serialized_end=1014, +) + +_LINREAD = _descriptor.Descriptor( + name='LinRead', + full_name='api.LinRead', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='ids', full_name='api.LinRead.ids', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_LINREAD_IDSENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=925, + serialized_end=1014, +) + + +_LATENCY = _descriptor.Descriptor( + name='Latency', + full_name='api.Latency', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='parsing_ns', full_name='api.Latency.parsing_ns', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='processing_ns', full_name='api.Latency.processing_ns', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='encoding_ns', full_name='api.Latency.encoding_ns', index=2, + number=3, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1016, + serialized_end=1089, +) + + +_NQUAD = _descriptor.Descriptor( + name='NQuad', + full_name='api.NQuad', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='subject', full_name='api.NQuad.subject', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='predicate', full_name='api.NQuad.predicate', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='object_id', full_name='api.NQuad.object_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='object_value', full_name='api.NQuad.object_value', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='label', full_name='api.NQuad.label', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='lang', full_name='api.NQuad.lang', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='facets', full_name='api.NQuad.facets', index=6, + number=7, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1092, + serialized_end=1245, +) + + +_VALUE = _descriptor.Descriptor( + name='Value', + full_name='api.Value', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='default_val', full_name='api.Value.default_val', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bytes_val', full_name='api.Value.bytes_val', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='int_val', full_name='api.Value.int_val', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bool_val', full_name='api.Value.bool_val', index=3, + number=4, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='str_val', full_name='api.Value.str_val', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='double_val', full_name='api.Value.double_val', index=5, + number=6, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='geo_val', full_name='api.Value.geo_val', index=6, + number=7, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='date_val', full_name='api.Value.date_val', index=7, + number=8, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='datetime_val', full_name='api.Value.datetime_val', index=8, + number=9, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='password_val', full_name='api.Value.password_val', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='uid_val', full_name='api.Value.uid_val', index=10, + number=11, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='val', full_name='api.Value.val', + index=0, containing_type=None, fields=[]), + ], + serialized_start=1248, + serialized_end=1492, +) + + +_FACET = _descriptor.Descriptor( + name='Facet', + full_name='api.Facet', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='api.Facet.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='api.Facet.value', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='val_type', full_name='api.Facet.val_type', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='tokens', full_name='api.Facet.tokens', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='alias', full_name='api.Facet.alias', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _FACET_VALTYPE, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1495, + serialized_end=1666, +) + + +_SCHEMANODE = _descriptor.Descriptor( + name='SchemaNode', + full_name='api.SchemaNode', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='predicate', full_name='api.SchemaNode.predicate', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='api.SchemaNode.type', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='index', full_name='api.SchemaNode.index', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='tokenizer', full_name='api.SchemaNode.tokenizer', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='reverse', full_name='api.SchemaNode.reverse', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='count', full_name='api.SchemaNode.count', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='list', full_name='api.SchemaNode.list', index=6, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1668, + serialized_end=1793, +) + +_REQUEST_VARSENTRY.containing_type = _REQUEST +_REQUEST.fields_by_name['vars'].message_type = _REQUEST_VARSENTRY +_REQUEST.fields_by_name['lin_read'].message_type = _LINREAD +_RESPONSE.fields_by_name['schema'].message_type = _SCHEMANODE +_RESPONSE.fields_by_name['txn'].message_type = _TXNCONTEXT +_RESPONSE.fields_by_name['latency'].message_type = _LATENCY +_ASSIGNED_UIDSENTRY.containing_type = _ASSIGNED +_ASSIGNED.fields_by_name['uids'].message_type = _ASSIGNED_UIDSENTRY +_ASSIGNED.fields_by_name['context'].message_type = _TXNCONTEXT +_MUTATION.fields_by_name['set'].message_type = _NQUAD +_MUTATION.fields_by_name['del'].message_type = _NQUAD +_TXNCONTEXT.fields_by_name['lin_read'].message_type = _LINREAD +_LINREAD_IDSENTRY.containing_type = _LINREAD +_LINREAD.fields_by_name['ids'].message_type = _LINREAD_IDSENTRY +_NQUAD.fields_by_name['object_value'].message_type = _VALUE +_NQUAD.fields_by_name['facets'].message_type = _FACET +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['default_val']) +_VALUE.fields_by_name['default_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['bytes_val']) +_VALUE.fields_by_name['bytes_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['int_val']) +_VALUE.fields_by_name['int_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['bool_val']) +_VALUE.fields_by_name['bool_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['str_val']) +_VALUE.fields_by_name['str_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['double_val']) +_VALUE.fields_by_name['double_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['geo_val']) +_VALUE.fields_by_name['geo_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['date_val']) +_VALUE.fields_by_name['date_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['datetime_val']) +_VALUE.fields_by_name['datetime_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['password_val']) +_VALUE.fields_by_name['password_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_VALUE.oneofs_by_name['val'].fields.append( + _VALUE.fields_by_name['uid_val']) +_VALUE.fields_by_name['uid_val'].containing_oneof = _VALUE.oneofs_by_name['val'] +_FACET.fields_by_name['val_type'].enum_type = _FACET_VALTYPE +_FACET_VALTYPE.containing_type = _FACET +DESCRIPTOR.message_types_by_name['Request'] = _REQUEST +DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE +DESCRIPTOR.message_types_by_name['Assigned'] = _ASSIGNED +DESCRIPTOR.message_types_by_name['Mutation'] = _MUTATION +DESCRIPTOR.message_types_by_name['AssignedIds'] = _ASSIGNEDIDS +DESCRIPTOR.message_types_by_name['Operation'] = _OPERATION +DESCRIPTOR.message_types_by_name['Payload'] = _PAYLOAD +DESCRIPTOR.message_types_by_name['TxnContext'] = _TXNCONTEXT +DESCRIPTOR.message_types_by_name['Check'] = _CHECK +DESCRIPTOR.message_types_by_name['Version'] = _VERSION +DESCRIPTOR.message_types_by_name['LinRead'] = _LINREAD +DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY +DESCRIPTOR.message_types_by_name['NQuad'] = _NQUAD +DESCRIPTOR.message_types_by_name['Value'] = _VALUE +DESCRIPTOR.message_types_by_name['Facet'] = _FACET +DESCRIPTOR.message_types_by_name['SchemaNode'] = _SCHEMANODE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( + + VarsEntry = _reflection.GeneratedProtocolMessageType('VarsEntry', (_message.Message,), dict( + DESCRIPTOR = _REQUEST_VARSENTRY, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Request.VarsEntry) + )) + , + DESCRIPTOR = _REQUEST, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Request) + )) +_sym_db.RegisterMessage(Request) +_sym_db.RegisterMessage(Request.VarsEntry) + +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( + DESCRIPTOR = _RESPONSE, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Response) + )) +_sym_db.RegisterMessage(Response) + +Assigned = _reflection.GeneratedProtocolMessageType('Assigned', (_message.Message,), dict( + + UidsEntry = _reflection.GeneratedProtocolMessageType('UidsEntry', (_message.Message,), dict( + DESCRIPTOR = _ASSIGNED_UIDSENTRY, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Assigned.UidsEntry) + )) + , + DESCRIPTOR = _ASSIGNED, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Assigned) + )) +_sym_db.RegisterMessage(Assigned) +_sym_db.RegisterMessage(Assigned.UidsEntry) + +Mutation = _reflection.GeneratedProtocolMessageType('Mutation', (_message.Message,), dict( + DESCRIPTOR = _MUTATION, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Mutation) + )) +_sym_db.RegisterMessage(Mutation) + +AssignedIds = _reflection.GeneratedProtocolMessageType('AssignedIds', (_message.Message,), dict( + DESCRIPTOR = _ASSIGNEDIDS, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.AssignedIds) + )) +_sym_db.RegisterMessage(AssignedIds) + +Operation = _reflection.GeneratedProtocolMessageType('Operation', (_message.Message,), dict( + DESCRIPTOR = _OPERATION, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Operation) + )) +_sym_db.RegisterMessage(Operation) + +Payload = _reflection.GeneratedProtocolMessageType('Payload', (_message.Message,), dict( + DESCRIPTOR = _PAYLOAD, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Payload) + )) +_sym_db.RegisterMessage(Payload) + +TxnContext = _reflection.GeneratedProtocolMessageType('TxnContext', (_message.Message,), dict( + DESCRIPTOR = _TXNCONTEXT, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.TxnContext) + )) +_sym_db.RegisterMessage(TxnContext) + +Check = _reflection.GeneratedProtocolMessageType('Check', (_message.Message,), dict( + DESCRIPTOR = _CHECK, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Check) + )) +_sym_db.RegisterMessage(Check) + +Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), dict( + DESCRIPTOR = _VERSION, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Version) + )) +_sym_db.RegisterMessage(Version) + +LinRead = _reflection.GeneratedProtocolMessageType('LinRead', (_message.Message,), dict( + + IdsEntry = _reflection.GeneratedProtocolMessageType('IdsEntry', (_message.Message,), dict( + DESCRIPTOR = _LINREAD_IDSENTRY, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.LinRead.IdsEntry) + )) + , + DESCRIPTOR = _LINREAD, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.LinRead) + )) +_sym_db.RegisterMessage(LinRead) +_sym_db.RegisterMessage(LinRead.IdsEntry) + +Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( + DESCRIPTOR = _LATENCY, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Latency) + )) +_sym_db.RegisterMessage(Latency) + +NQuad = _reflection.GeneratedProtocolMessageType('NQuad', (_message.Message,), dict( + DESCRIPTOR = _NQUAD, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.NQuad) + )) +_sym_db.RegisterMessage(NQuad) + +Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( + DESCRIPTOR = _VALUE, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Value) + )) +_sym_db.RegisterMessage(Value) + +Facet = _reflection.GeneratedProtocolMessageType('Facet', (_message.Message,), dict( + DESCRIPTOR = _FACET, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Facet) + )) +_sym_db.RegisterMessage(Facet) + +SchemaNode = _reflection.GeneratedProtocolMessageType('SchemaNode', (_message.Message,), dict( + DESCRIPTOR = _SCHEMANODE, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.SchemaNode) + )) +_sym_db.RegisterMessage(SchemaNode) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\tio.dgraphB\013DgraphProto')) +_REQUEST_VARSENTRY.has_options = True +_REQUEST_VARSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_ASSIGNED_UIDSENTRY.has_options = True +_ASSIGNED_UIDSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_LINREAD_IDSENTRY.has_options = True +_LINREAD_IDSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) + +_DGRAPH = _descriptor.ServiceDescriptor( + name='Dgraph', + full_name='api.Dgraph', + file=DESCRIPTOR, + index=0, + options=None, + serialized_start=1796, + serialized_end=2024, + methods=[ + _descriptor.MethodDescriptor( + name='Query', + full_name='api.Dgraph.Query', + index=0, + containing_service=None, + input_type=_REQUEST, + output_type=_RESPONSE, + options=None, + ), + _descriptor.MethodDescriptor( + name='Mutate', + full_name='api.Dgraph.Mutate', + index=1, + containing_service=None, + input_type=_MUTATION, + output_type=_ASSIGNED, + options=None, + ), + _descriptor.MethodDescriptor( + name='Alter', + full_name='api.Dgraph.Alter', + index=2, + containing_service=None, + input_type=_OPERATION, + output_type=_PAYLOAD, + options=None, + ), + _descriptor.MethodDescriptor( + name='CommitOrAbort', + full_name='api.Dgraph.CommitOrAbort', + index=3, + containing_service=None, + input_type=_TXNCONTEXT, + output_type=_TXNCONTEXT, + options=None, + ), + _descriptor.MethodDescriptor( + name='CheckVersion', + full_name='api.Dgraph.CheckVersion', + index=4, + containing_service=None, + input_type=_CHECK, + output_type=_VERSION, + options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_DGRAPH) + +DESCRIPTOR.services_by_name['Dgraph'] = _DGRAPH + +# @@protoc_insertion_point(module_scope) diff --git a/pydgraph/proto/api_pb2_grpc.py b/pydgraph/proto/api_pb2_grpc.py new file mode 100644 index 00000000..d0b7760a --- /dev/null +++ b/pydgraph/proto/api_pb2_grpc.py @@ -0,0 +1,114 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +from . import api_pb2 as api__pb2 + + +class DgraphStub(object): + """Graph response. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Query = channel.unary_unary( + '/api.Dgraph/Query', + request_serializer=api__pb2.Request.SerializeToString, + response_deserializer=api__pb2.Response.FromString, + ) + self.Mutate = channel.unary_unary( + '/api.Dgraph/Mutate', + request_serializer=api__pb2.Mutation.SerializeToString, + response_deserializer=api__pb2.Assigned.FromString, + ) + self.Alter = channel.unary_unary( + '/api.Dgraph/Alter', + request_serializer=api__pb2.Operation.SerializeToString, + response_deserializer=api__pb2.Payload.FromString, + ) + self.CommitOrAbort = channel.unary_unary( + '/api.Dgraph/CommitOrAbort', + request_serializer=api__pb2.TxnContext.SerializeToString, + response_deserializer=api__pb2.TxnContext.FromString, + ) + self.CheckVersion = channel.unary_unary( + '/api.Dgraph/CheckVersion', + request_serializer=api__pb2.Check.SerializeToString, + response_deserializer=api__pb2.Version.FromString, + ) + + +class DgraphServicer(object): + """Graph response. + """ + + def Query(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Mutate(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Alter(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CommitOrAbort(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CheckVersion(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_DgraphServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Query': grpc.unary_unary_rpc_method_handler( + servicer.Query, + request_deserializer=api__pb2.Request.FromString, + response_serializer=api__pb2.Response.SerializeToString, + ), + 'Mutate': grpc.unary_unary_rpc_method_handler( + servicer.Mutate, + request_deserializer=api__pb2.Mutation.FromString, + response_serializer=api__pb2.Assigned.SerializeToString, + ), + 'Alter': grpc.unary_unary_rpc_method_handler( + servicer.Alter, + request_deserializer=api__pb2.Operation.FromString, + response_serializer=api__pb2.Payload.SerializeToString, + ), + 'CommitOrAbort': grpc.unary_unary_rpc_method_handler( + servicer.CommitOrAbort, + request_deserializer=api__pb2.TxnContext.FromString, + response_serializer=api__pb2.TxnContext.SerializeToString, + ), + 'CheckVersion': grpc.unary_unary_rpc_method_handler( + servicer.CheckVersion, + request_deserializer=api__pb2.Check.FromString, + response_serializer=api__pb2.Version.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'api.Dgraph', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) From 55244b01172edc4121b9c6d41704a688b1c5630d Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 18:47:07 +0530 Subject: [PATCH 021/435] Updated Dgraph client and added Txn. --- pydgraph/client.py | 50 +++++++++++++++--- pydgraph/txn.py | 129 +++++++++++++++++++++++++++++++++++++++++++++ pydgraph/util.py | 15 ++++++ 3 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 pydgraph/txn.py create mode 100644 pydgraph/util.py diff --git a/pydgraph/client.py b/pydgraph/client.py index c4fd3f16..9cf84889 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -18,9 +18,11 @@ Dgraph server over gRPC. """ import grpc -from grpc.beta import implementations -from pydgraph.utils import loader -from pydgraph.utils.meta import VERSION +from pydgraph import txn +from pydgraph import util +from pydgraph.meta import VERSION +from pydgraph.proto import api_pb2 as api +from pydgraph.proto import api_pb2_grpc as api_grpc __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' @@ -31,13 +33,45 @@ class DgraphClient(object): def __init__(self, host, port): self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) - self.stub = loader.BetaDgraphStub(self.channel) + self.stub = api_grpc.DgraphStub(self.channel) + self.start_ts = 0 + self.lin_read = api.LinRead() - def query(self, q, timeout=None): - request = loader.Request(query=q) + def merge_context(self, context): + """Merges txn_context into client's state.""" + self.start_ts = context.start_ts + util.merge_lin_reads(self.lin_read, context.lin_read) + + def Query(self, q, timeout=None): + request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = self.stub.Query(request, timeout) + self.merge_context(response.txn) return response async def aQuery(self, q, timeout=None): - request = loader.Request(query=q) - return await self.stub.aQuery(request, timeout) + request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) + response = await self.stub.Query.future(request, timeout) + self.merge_context(response.txn) + return response + + def Alter(self, schema, timeout=None): + """Alter schema at the other end of the connection.""" + operation = api.Operation(schema=schema) + return self.stub.Alter(operation, timeout) + + async def aAlter(self, schema, timeout=None): + operation = api.Operation(schema=schema) + return await self.stub.Alter.future(operation, timeout) + + def DropAttr(self, drop_attr, timeout=None): + """Drop an attribute from the dgraph server.""" + operation = api.Operation(drop_attr=drop_attr) + return self.stub.Alter(operation) + + def DropAll(self, timeout=None): + """Drop all schema from the dgraph server.""" + operation = api.Operation(drop_all=True) + return self.stub.Alter(operation) + + def txn(self): + return txn.DgraphTxn(self) diff --git a/pydgraph/txn.py b/pydgraph/txn.py new file mode 100644 index 00000000..87f3bb42 --- /dev/null +++ b/pydgraph/txn.py @@ -0,0 +1,129 @@ +""" +This module contains the methods for using transactions when using +a dgraph server over gRPC. +""" +import grpc +from pydgraph import util +from pydgraph.proto import api_pb2 as api + + +class DgraphTxn(object): + """Class representing a single transaction. A transaction will maintain + three items of state for each transaction. + + Attributes + ========== + + * lin_read: a linearizable read pointer, maintained independently of the + parent client + * start_ts: a starting timestamp, this uniquely identifies a transaction + and doesn't change over its lifetime. + * keys: the set of keys modified by the transaction to aid in conflict + detection + """ + def __init__(self, client): + self.client = client + self.start_ts = client.start_ts + self.lin_read = api.LinRead() + self.lin_read.MergeFrom(client.lin_read) + self.keys = [] + + self._mutated = False + self._finished = False + + def merge_context(self, txn_context): + """Merges context from a txn context into the current txn state.""" + ## This will be true if the server does not return a txn context after + ## a query or a mutation + if not txn_context: return + + self.client.merge_context(txn_context) + util.merge_lin_reads(self.lin_read, txn_context.lin_read) + if self.start_ts == 0: + self.start_ts = txn_context.start_ts + elif self.start_ts != txn_context.start_ts: + raise Exception('StartTs mismatch in txn vs updated context') + + self.keys.extend(txn_context.keys) + + def Query(self, q, *args, **kwargs): + request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) + response = self.client.stub.Query(request, *args, **kwargs) + self.merge_context(response.txn) + return response + + async def aQuery(self, q, *args, **kwargs): + request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) + response = await self.client.stub.Query.future(request, *args, **kwargs) + self.merge_context(response.txn) + return response + + def Mutate(self, setobj, deleteobj, *args, **kwargs): + """Mutate allows modification of the data stored in the DGraph instance. + + A mutation can be described either using JSON or via RDF quads. This + method presently only support mutations described via JSON. + + Mutations also support a commit_now method which commits the transaction + along with the mutation. This mode is presently unsupported. + + Params + ====== + * setobj: an object with data to set, to be encoded as JSON and + converted to utf8 bytes + * deleteobj: an object with data to be deleted, to be encoded as JSON + and converted to utf8 bytes. + """ + if self._finished: raise Exception('Transaction is complete') + mutation = api.Mutation(set_json=json.dumps(setobj).encode('utf8'), + delete_json=json.dumps(deleteobj).encode('utf8'), + start_ts=self.start_ts, + commit_now=False) + assigned = self.client.stub.Mutate(mutation, *args, **kwargs) + self.merge_context(assigned.context) + self._mutated = True + return assigned + + async def aMutate(self, setobj, deleteobj, *args, **kwargs): + if self._finished: raise Exception('Transaction is complete') + mutation = api.Mutation(set_json=json.dumps(setobj).encode('utf8'), + delete_json=json.dumps(deleteobj).encode('utf8'), + start_ts=self.start_ts, + commit_now=False) + assigned = await self.client.stub.Mutate.future(mutation, *args, **kwargs) + self.merge_context(assinged.context) + self._mutated = True + return assigned + + async def aCommit(self, *args, **kwargs): + """Commits any mutations performed in the transaction. Once the + transaction is committed its lifespan is complete and no further + mutations or commits can be made.""" + if self._finished: raise Exception('Cannot commit a transaction which is complete') + + self._finished = True + if not self._mutated: return + + txn_context = api.TxnContext(start_ts=self.start_ts, keys=self.keys, + lin_read=self.lin_read) + resp_txn_context = await self.client.stub.CommitOrAbort.future(txn_context, + *args, **kwargs) + self.merge_context(resp_txn_context) + return resp_txn_context + + async def aAbort(self, *args, **kwargs): + """Aborts any mutations performed in the transaction. Once the + transaction is aborted its lifespan is complete and no further + mutations or commits can be made.""" + if self._finished: raise Exception('Cannot abort a transaction which is complete') + if not self._mutated: return + + txn_context = api.TxnContext(start_ts=self.start_ts, + keys=self.keys, + lin_read=self.lin_read, + aborted=True) + resp_txn_context = await self.client.stub.CommitOrAbort.future(txn_context) + self.merge_context(resp_txn_context) + self._finished = True + return resp_txn_context + diff --git a/pydgraph/util.py b/pydgraph/util.py new file mode 100644 index 00000000..a8fef0a7 --- /dev/null +++ b/pydgraph/util.py @@ -0,0 +1,15 @@ +""" +Module containing utilities for dgraph RPC messages +""" + +def merge_lin_reads(current, update_lin_read): + """Merges LinRead protobufs by adding all keys in the ids field from the + updated_lin_read to the current one. If the key already exists, the one + with the larger value is preserved.""" + # cache for the loop + curr_lin_read_ids = current.ids + curr_lin_read_ids_get = curr_lin_read_ids.get + + for (key, update_value) in update_lin_read.ids.items(): + if curr_lin_read_ids_get(key, 0) <= update_value: + curr_lin_read_ids[key] = update_value From 5378acd5f342a733c25e83ffdabdf38522dccd79 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 19:44:41 +0530 Subject: [PATCH 022/435] Removed some async methods. --- pydgraph/client.py | 6 ++++- pydgraph/txn.py | 61 +++++++++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 9cf84889..1ffae7be 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -33,10 +33,14 @@ class DgraphClient(object): def __init__(self, host, port): self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) - self.stub = api_grpc.DgraphStub(self.channel) + self._stub = api_grpc.DgraphStub(self.channel) self.start_ts = 0 self.lin_read = api.LinRead() + @property + def stub(self): + return self._stub + def merge_context(self, context): """Merges txn_context into client's state.""" self.start_ts = context.start_ts diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 87f3bb42..858c8878 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -3,6 +3,8 @@ a dgraph server over gRPC. """ import grpc +import json + from pydgraph import util from pydgraph.proto import api_pb2 as api @@ -37,13 +39,15 @@ def merge_context(self, txn_context): ## a query or a mutation if not txn_context: return - self.client.merge_context(txn_context) - util.merge_lin_reads(self.lin_read, txn_context.lin_read) if self.start_ts == 0: self.start_ts = txn_context.start_ts elif self.start_ts != txn_context.start_ts: raise Exception('StartTs mismatch in txn vs updated context') + # TODO(kochhar): should this be made configurable? + self.client.merge_context(txn_context) + util.merge_lin_reads(self.lin_read, txn_context.lin_read) + self.keys.extend(txn_context.keys) def Query(self, q, *args, **kwargs): @@ -58,27 +62,44 @@ async def aQuery(self, q, *args, **kwargs): self.merge_context(response.txn) return response - def Mutate(self, setobj, deleteobj, *args, **kwargs): + def Mutate(self, setobj=None, delobj=None, *args, **kwargs): """Mutate allows modification of the data stored in the DGraph instance. A mutation can be described either using JSON or via RDF quads. This - method presently only support mutations described via JSON. + method presently support mutations described via JSON. Mutations also support a commit_now method which commits the transaction along with the mutation. This mode is presently unsupported. Params ====== - * setobj: an object with data to set, to be encoded as JSON and - converted to utf8 bytes - * deleteobj: an object with data to be deleted, to be encoded as JSON - and converted to utf8 bytes. + * setobj: an object with data to set, to be encoded as JSON and + converted to utf8 bytes + * delobj: an object with data to be deleted, to be encoded as JSON + and converted to utf8 bytes. """ if self._finished: raise Exception('Transaction is complete') - mutation = api.Mutation(set_json=json.dumps(setobj).encode('utf8'), - delete_json=json.dumps(deleteobj).encode('utf8'), - start_ts=self.start_ts, - commit_now=False) + mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) + if setobj: + mutation.set_json=json.dumps(setobj).encode('utf8') + if delobj: + mutation.delete_json=json.dumps(delobj).encode('utf8') + + assigned = self.client.stub.Mutate(mutation, *args, **kwargs) + self.merge_context(assigned.context) + self._mutated = True + return assigned + + def MutateNQuad(self, setnquads=None, delnquads=None, *args, **kwargs): + """MutateNQuads extends Mutate to allow mutations to be specified as + N-Quad strings.""" + if self._finished: raise Exception('Transaction is complete') + mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) + if setnquads: + mutation.set_nquads=setnquads.encode('utf8') + if delnquads: + mutation.del_nquads=delnquads.encode('utf8') + assigned = self.client.stub.Mutate(mutation, *args, **kwargs) self.merge_context(assigned.context) self._mutated = True @@ -95,7 +116,7 @@ async def aMutate(self, setobj, deleteobj, *args, **kwargs): self._mutated = True return assigned - async def aCommit(self, *args, **kwargs): + def Commit(self, *args, **kwargs): """Commits any mutations performed in the transaction. Once the transaction is committed its lifespan is complete and no further mutations or commits can be made.""" @@ -104,26 +125,26 @@ async def aCommit(self, *args, **kwargs): self._finished = True if not self._mutated: return - txn_context = api.TxnContext(start_ts=self.start_ts, keys=self.keys, + txn_context = api.TxnContext(start_ts=self.start_ts, + keys=self.keys, lin_read=self.lin_read) - resp_txn_context = await self.client.stub.CommitOrAbort.future(txn_context, - *args, **kwargs) + resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) self.merge_context(resp_txn_context) return resp_txn_context - async def aAbort(self, *args, **kwargs): + def Abort(self, *args, **kwargs): """Aborts any mutations performed in the transaction. Once the transaction is aborted its lifespan is complete and no further mutations or commits can be made.""" if self._finished: raise Exception('Cannot abort a transaction which is complete') + + self._finished = True if not self._mutated: return txn_context = api.TxnContext(start_ts=self.start_ts, keys=self.keys, lin_read=self.lin_read, aborted=True) - resp_txn_context = await self.client.stub.CommitOrAbort.future(txn_context) + resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) self.merge_context(resp_txn_context) - self._finished = True return resp_txn_context - From 31c22304124b0fb1b90664aa6a7e20ee95e63017 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 19:47:12 +0530 Subject: [PATCH 023/435] Mutation by N-Quad is default. --- pydgraph/txn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 858c8878..cf96db54 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -62,7 +62,7 @@ async def aQuery(self, q, *args, **kwargs): self.merge_context(response.txn) return response - def Mutate(self, setobj=None, delobj=None, *args, **kwargs): + def MutateObj(self, setobj=None, delobj=None, *args, **kwargs): """Mutate allows modification of the data stored in the DGraph instance. A mutation can be described either using JSON or via RDF quads. This From 0787412a459bfd297dc3ef07db42fd5693c0c9ab Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 19:50:39 +0530 Subject: [PATCH 024/435] Added docs for NQuad --- pydgraph/txn.py | 20 +++++++++++++++++--- pydgraph/util.py | 3 +++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index cf96db54..4a971ea8 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -8,6 +8,8 @@ from pydgraph import util from pydgraph.proto import api_pb2 as api +__author__ = 'Shailesh Kochhar ' + class DgraphTxn(object): """Class representing a single transaction. A transaction will maintain @@ -90,9 +92,21 @@ def MutateObj(self, setobj=None, delobj=None, *args, **kwargs): self._mutated = True return assigned - def MutateNQuad(self, setnquads=None, delnquads=None, *args, **kwargs): - """MutateNQuads extends Mutate to allow mutations to be specified as - N-Quad strings.""" + def Mutate(self, setnquads=None, delnquads=None, *args, **kwargs): + """Mutate extends MutateObj to allow mutations to be specified as + N-Quad strings. + + Mutations also support a commit_now method which commits the transaction + along with the mutation. This mode is presently unsupported. + + Params + ====== + * setnquads: a string containing nquads to set + * delnquads: a string containing nquads to delete + + N-Quad format is + . + """ if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) if setnquads: diff --git a/pydgraph/util.py b/pydgraph/util.py index a8fef0a7..d33ea017 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -2,6 +2,9 @@ Module containing utilities for dgraph RPC messages """ +__author__ = 'Shailesh Kochhar ' + + def merge_lin_reads(current, update_lin_read): """Merges LinRead protobufs by adding all keys in the ids field from the updated_lin_read to the current one. If the key already exists, the one From ee3857d3f36e310611d447461647e473eacd5d6c Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 20:08:00 +0530 Subject: [PATCH 025/435] Updated async mutation name --- pydgraph/txn.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 4a971ea8..90da6b07 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -119,12 +119,14 @@ def Mutate(self, setnquads=None, delnquads=None, *args, **kwargs): self._mutated = True return assigned - async def aMutate(self, setobj, deleteobj, *args, **kwargs): + async def aMutateObj(self, setobj=None, delobj=None, *args, **kwargs): if self._finished: raise Exception('Transaction is complete') - mutation = api.Mutation(set_json=json.dumps(setobj).encode('utf8'), - delete_json=json.dumps(deleteobj).encode('utf8'), - start_ts=self.start_ts, - commit_now=False) + mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) + if setobj: + mutation.set_json=json.dumps(setobj).encode('utf8') + if delobj: + mutation.del_json=json.dumps(delobj).encode('utf8'), + assigned = await self.client.stub.Mutate.future(mutation, *args, **kwargs) self.merge_context(assinged.context) self._mutated = True From 1ccb2294148fc4a1edba8b2803ce048d4c7e7b7c Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 7 Jan 2018 20:10:47 +0530 Subject: [PATCH 026/435] Added check support. --- pydgraph/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pydgraph/client.py b/pydgraph/client.py index 1ffae7be..99a31a72 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -46,6 +46,10 @@ def merge_context(self, context): self.start_ts = context.start_ts util.merge_lin_reads(self.lin_read, context.lin_read) + def Check(self, timeout=None): + check = api.Check() + return self.stub.CheckVersion(check, timeout) + def Query(self, q, timeout=None): request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = self.stub.Query(request, timeout) From f210468ece475a25df7913053e32af504d4282b3 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Tue, 9 Jan 2018 20:22:16 +0530 Subject: [PATCH 027/435] Updated txn method to ignore state when commiting. --- pydgraph/txn.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 90da6b07..66fc255a 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -44,21 +44,24 @@ def merge_context(self, txn_context): if self.start_ts == 0: self.start_ts = txn_context.start_ts elif self.start_ts != txn_context.start_ts: - raise Exception('StartTs mismatch in txn vs updated context') + raise Exception('StartTs mismatch in txn(%s) vs updated context(%s)' % + (self.start_ts, txn_context.start_ts)) - # TODO(kochhar): should this be made configurable? - self.client.merge_context(txn_context) + # TODO(kochhar): disabling this as it makes client be part of the txn + # self.client.merge_context(txn_context) util.merge_lin_reads(self.lin_read, txn_context.lin_read) self.keys.extend(txn_context.keys) def Query(self, q, *args, **kwargs): + if self._finished: raise Exception('Transaction is complete') request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = self.client.stub.Query(request, *args, **kwargs) self.merge_context(response.txn) return response async def aQuery(self, q, *args, **kwargs): + if self._finished: raise Exception('Transaction is complete') request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = await self.client.stub.Query.future(request, *args, **kwargs) self.merge_context(response.txn) @@ -145,7 +148,6 @@ def Commit(self, *args, **kwargs): keys=self.keys, lin_read=self.lin_read) resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) - self.merge_context(resp_txn_context) return resp_txn_context def Abort(self, *args, **kwargs): @@ -162,5 +164,4 @@ def Abort(self, *args, **kwargs): lin_read=self.lin_read, aborted=True) resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) - self.merge_context(resp_txn_context) return resp_txn_context From 0d19adce60c7c77ca1efc2fe1823fd8e73b412be Mon Sep 17 00:00:00 2001 From: Shailesh Date: Tue, 9 Jan 2018 21:49:06 +0530 Subject: [PATCH 028/435] Small updates, made more of client read-only. --- pydgraph/client.py | 23 +++++++++++++++-------- pydgraph/txn.py | 1 - 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 99a31a72..e7d3468d 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -34,32 +34,39 @@ class DgraphClient(object): def __init__(self, host, port): self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) self._stub = api_grpc.DgraphStub(self.channel) - self.start_ts = 0 - self.lin_read = api.LinRead() + self._start_ts = 0 + self._lin_read = api.LinRead() @property def stub(self): return self._stub - def merge_context(self, context): + @property + def start_ts(self): + return self._start_ts + + @property + def lin_read(self): + return self._lin_read + + def _merge_context(self, context): """Merges txn_context into client's state.""" - self.start_ts = context.start_ts + self._start_ts = context.start_ts util.merge_lin_reads(self.lin_read, context.lin_read) def Check(self, timeout=None): - check = api.Check() - return self.stub.CheckVersion(check, timeout) + return self.stub.CheckVersion(api.Check(), timeout) def Query(self, q, timeout=None): request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = self.stub.Query(request, timeout) - self.merge_context(response.txn) + self._merge_context(response.txn) return response async def aQuery(self, q, timeout=None): request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = await self.stub.Query.future(request, timeout) - self.merge_context(response.txn) + self._merge_context(response.txn) return response def Alter(self, schema, timeout=None): diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 66fc255a..752c2deb 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -40,7 +40,6 @@ def merge_context(self, txn_context): ## This will be true if the server does not return a txn context after ## a query or a mutation if not txn_context: return - if self.start_ts == 0: self.start_ts = txn_context.start_ts elif self.start_ts != txn_context.start_ts: From 6895c59da77093b5e64879a3adb7d54053a8b4c0 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Tue, 9 Jan 2018 21:49:47 +0530 Subject: [PATCH 029/435] Added first integrtation test for parallel account upsert. --- tests/test_integrations.py | 174 +++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 tests/test_integrations.py diff --git a/tests/test_integrations.py b/tests/test_integrations.py new file mode 100644 index 00000000..a4b9a383 --- /dev/null +++ b/tests/test_integrations.py @@ -0,0 +1,174 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Shailesh Kochhar ' +__maintainer__ = 'Shailesh Kochhar ' + +import grpc +import functools +import json +import logging +import multiprocessing +import multiprocessing.dummy as mpd +import time +import unittest +from pydgraph import client + +logging.basicConfig(level=logging.DEBUG) + + +class DgraphClientIntegrationTestCase(unittest.TestCase): + """Base class for other integration test cases. Provides a client object + with a connection to the dgraph server and ensures that the server is + 0.9 or greater. + """ + TEST_HOSTNAME = 'localhost' + TEST_PORT = 9080 + + def setUp(self): + """Sets up the client and verifies the version is compatible.""" + self.client = client.DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) + version = self.client.Check() + # version.tag string format is v.. + # version_tup = [MAJOR, MINOR, PATCH] + version_tup = version.tag[1:].split('.') + + version_supported = (int(version_tup[0]) > 0 or + (int(version_tup[0]) == 0 and int(version_tup[1]) >= 9)) + self.assertTrue(version_supported, + 'Server version %s must be > 0.9' % version.tag) + + +class AcctUpsertIntegrationTestCase(DgraphClientIntegrationTestCase): + """Account upsert integration test.""" + def setUp(self): + """Drops existing schema and loads new schema for the test.""" + super(AcctUpsertIntegrationTestCase, self).setUp() + self.concurrency = 5 + + self.firsts = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] + self.lasts = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] + self.ages = [20, 25, 30, 35] + self.accounts = [ + {'first': f, 'last': l, 'age': a} + for f in self.firsts for l in self.lasts for a in self.ages + ] + logging.info(len(self.accounts)) + + _ = self.client.DropAll() + _ = self.client.Alter(schema=""" + first: string @index(term) . + last: string @index(hash) . + age: int @index(int) . + when: int . + """) + + def test_acctUpsertIntegration(self): + """Account upsert integration. Will run upserts concurrently.""" + self.doUpserts(self.accounts, self.concurrency) + self.assertChanges(self.firsts, self.accounts) + + def doUpserts(self, account_list, concurrency): + """Will run the upsert command for the accouts in `account_list`. Execution + happens in concurrent processes.""" + success_ctr = multiprocessing.Value('i', 0, lock=True) + retry_ctr = multiprocessing.Value('i', 0, lock=True) + + pool = mpd.Pool(concurrency) + updater = lambda acct: upsert_account(hostname=self.TEST_HOSTNAME, + port=self.TEST_PORT, + account=acct, + success_ctr=success_ctr, + retry_ctr=retry_ctr) + results = [ pool.apply_async(updater, (acct,)) + for acct in account_list for _ in range(concurrency)] + [res.get() for res in results] + + def assertChanges(self, firsts, accounts): + """Will check to see changes have been made.""" + q = ''' + {{ + all(func: anyofterms(first, "{}")) {{ + first + last + age + }} + }}'''.format(' '.join(firsts)) + logging.debug(q) + result = json.loads(self.client.Query(q=q).json) + account_set = set() + for acct in result['all']: + self.assertTrue(acct['first'] is not None) + self.assertTrue(acct['last'] is not None) + self.assertTrue(acct['age'] is not None) + account_set.add('{first}_{last}_{age}'.format(**acct)) + self.assertEqual(len(account_set), len(accounts)) + for acct in accounts: + self.assertTrue('{first}_{last}_{age}'.format(**acct) in account_set) + + +def upsert_account(hostname, port, account, success_ctr, retry_ctr): + c = client.DgraphClient(hostname, port) + q = ''' + {{ + acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ + uid + }} + }}'''.format(**account) + + last_update_time = time.time() - 10000 + while True: + if time.time() > last_update_time + 10000: + logging.debug('Success: %d Retries: %d', success_ctr.value, retry_ctr.value) + last_update_time = time.time() + + try: + txn = c.txn() + result = json.loads(txn.Query(q=q).json) + assert len(result['acct']) <= 1, ('Lookup of account %s found ' + 'multiple accounts' % account) + + if not result['acct']: + # Account does not exist, so create it + nquads = ''' + _:acct "{first}" . + _:acct "{last}" . + _:acct "{age}"^^ . + '''.format(**account) + created = txn.Mutate(setnquads=nquads) + uid = created.uids.get('acct') + assert uid is not None and uid != '', 'Account with uid None/""' + else: + # Account exists, read the uid + acct = result['acct'][0] + uid = acct['uid'] + assert uid is not None, 'Account with uid None' + + updatequads = ''' + <{0}> "{1:d}"^^ . + '''.format(uid, int(time.time())) + updated = txn.Mutate(setnquads=updatequads) + txn.Commit() + with success_ctr.get_lock(): + success_ctr.value += 1 + # txn successful, break the loop + return + except grpc._channel._Rendezvous as e: + with retry_ctr.get_lock(): + retry_ctr.value += 1 + # txn failed, retry the loop + +def foo(x): + return x+5 + +if __name__ == '__main__': + unittest.main() From b984f80dba57374b5c67f5e16f03e62493470635 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Tue, 9 Jan 2018 22:06:12 +0530 Subject: [PATCH 030/435] Renamed vars to lower. --- pydgraph/client.py | 14 ++++----- pydgraph/txn.py | 60 +++++++++++++++++++------------------- tests/test_integrations.py | 16 +++++----- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index e7d3468d..5bc6e4da 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -54,36 +54,36 @@ def _merge_context(self, context): self._start_ts = context.start_ts util.merge_lin_reads(self.lin_read, context.lin_read) - def Check(self, timeout=None): + def check(self, timeout=None): return self.stub.CheckVersion(api.Check(), timeout) - def Query(self, q, timeout=None): + def query(self, q, timeout=None): request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = self.stub.Query(request, timeout) self._merge_context(response.txn) return response - async def aQuery(self, q, timeout=None): + async def aquery(self, q, timeout=None): request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = await self.stub.Query.future(request, timeout) self._merge_context(response.txn) return response - def Alter(self, schema, timeout=None): + def alter(self, schema, timeout=None): """Alter schema at the other end of the connection.""" operation = api.Operation(schema=schema) return self.stub.Alter(operation, timeout) - async def aAlter(self, schema, timeout=None): + async def aalter(self, schema, timeout=None): operation = api.Operation(schema=schema) return await self.stub.Alter.future(operation, timeout) - def DropAttr(self, drop_attr, timeout=None): + def drop_attr(self, drop_attr, timeout=None): """Drop an attribute from the dgraph server.""" operation = api.Operation(drop_attr=drop_attr) return self.stub.Alter(operation) - def DropAll(self, timeout=None): + def drop_all(self, timeout=None): """Drop all schema from the dgraph server.""" operation = api.Operation(drop_all=True) return self.stub.Alter(operation) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 752c2deb..0cff9b0b 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -52,76 +52,76 @@ def merge_context(self, txn_context): self.keys.extend(txn_context.keys) - def Query(self, q, *args, **kwargs): + def query(self, q, *args, **kwargs): if self._finished: raise Exception('Transaction is complete') request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = self.client.stub.Query(request, *args, **kwargs) self.merge_context(response.txn) return response - async def aQuery(self, q, *args, **kwargs): + async def aquery(self, q, *args, **kwargs): if self._finished: raise Exception('Transaction is complete') request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) response = await self.client.stub.Query.future(request, *args, **kwargs) self.merge_context(response.txn) return response - def MutateObj(self, setobj=None, delobj=None, *args, **kwargs): - """Mutate allows modification of the data stored in the DGraph instance. - - A mutation can be described either using JSON or via RDF quads. This - method presently support mutations described via JSON. + def mutate(self, setnquads=None, delnquads=None, *args, **kwargs): + """Mutate extends MutateObj to allow mutations to be specified as + N-Quad strings. Mutations also support a commit_now method which commits the transaction along with the mutation. This mode is presently unsupported. Params ====== - * setobj: an object with data to set, to be encoded as JSON and - converted to utf8 bytes - * delobj: an object with data to be deleted, to be encoded as JSON - and converted to utf8 bytes. + * setnquads: a string containing nquads to set + * delnquads: a string containing nquads to delete + + N-Quad format is + . """ if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) - if setobj: - mutation.set_json=json.dumps(setobj).encode('utf8') - if delobj: - mutation.delete_json=json.dumps(delobj).encode('utf8') + if setnquads: + mutation.set_nquads=setnquads.encode('utf8') + if delnquads: + mutation.del_nquads=delnquads.encode('utf8') assigned = self.client.stub.Mutate(mutation, *args, **kwargs) self.merge_context(assigned.context) self._mutated = True return assigned - def Mutate(self, setnquads=None, delnquads=None, *args, **kwargs): - """Mutate extends MutateObj to allow mutations to be specified as - N-Quad strings. + def mutate_obj(self, setobj=None, delobj=None, *args, **kwargs): + """Mutate allows modification of the data stored in the DGraph instance. + + A mutation can be described either using JSON or via RDF quads. This + method presently support mutations described via JSON. Mutations also support a commit_now method which commits the transaction along with the mutation. This mode is presently unsupported. Params ====== - * setnquads: a string containing nquads to set - * delnquads: a string containing nquads to delete - - N-Quad format is - . + * setobj: an object with data to set, to be encoded as JSON and + converted to utf8 bytes + * delobj: an object with data to be deleted, to be encoded as JSON + and converted to utf8 bytes. """ if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) - if setnquads: - mutation.set_nquads=setnquads.encode('utf8') - if delnquads: - mutation.del_nquads=delnquads.encode('utf8') + if setobj: + mutation.set_json=json.dumps(setobj).encode('utf8') + if delobj: + mutation.delete_json=json.dumps(delobj).encode('utf8') assigned = self.client.stub.Mutate(mutation, *args, **kwargs) self.merge_context(assigned.context) self._mutated = True return assigned - async def aMutateObj(self, setobj=None, delobj=None, *args, **kwargs): + async def amutate_obj(self, setobj=None, delobj=None, *args, **kwargs): if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) if setobj: @@ -134,7 +134,7 @@ async def aMutateObj(self, setobj=None, delobj=None, *args, **kwargs): self._mutated = True return assigned - def Commit(self, *args, **kwargs): + def commit(self, *args, **kwargs): """Commits any mutations performed in the transaction. Once the transaction is committed its lifespan is complete and no further mutations or commits can be made.""" @@ -149,7 +149,7 @@ def Commit(self, *args, **kwargs): resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) return resp_txn_context - def Abort(self, *args, **kwargs): + def abort(self, *args, **kwargs): """Aborts any mutations performed in the transaction. Once the transaction is aborted its lifespan is complete and no further mutations or commits can be made.""" diff --git a/tests/test_integrations.py b/tests/test_integrations.py index a4b9a383..7322d30b 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -37,7 +37,7 @@ class DgraphClientIntegrationTestCase(unittest.TestCase): def setUp(self): """Sets up the client and verifies the version is compatible.""" self.client = client.DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) - version = self.client.Check() + version = self.client.check() # version.tag string format is v.. # version_tup = [MAJOR, MINOR, PATCH] version_tup = version.tag[1:].split('.') @@ -64,8 +64,8 @@ def setUp(self): ] logging.info(len(self.accounts)) - _ = self.client.DropAll() - _ = self.client.Alter(schema=""" + _ = self.client.drop_all() + _ = self.client.alter(schema=""" first: string @index(term) . last: string @index(hash) . age: int @index(int) . @@ -104,7 +104,7 @@ def assertChanges(self, firsts, accounts): }} }}'''.format(' '.join(firsts)) logging.debug(q) - result = json.loads(self.client.Query(q=q).json) + result = json.loads(self.client.query(q=q).json) account_set = set() for acct in result['all']: self.assertTrue(acct['first'] is not None) @@ -133,7 +133,7 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): try: txn = c.txn() - result = json.loads(txn.Query(q=q).json) + result = json.loads(txn.query(q=q).json) assert len(result['acct']) <= 1, ('Lookup of account %s found ' 'multiple accounts' % account) @@ -144,7 +144,7 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): _:acct "{last}" . _:acct "{age}"^^ . '''.format(**account) - created = txn.Mutate(setnquads=nquads) + created = txn.mutate(setnquads=nquads) uid = created.uids.get('acct') assert uid is not None and uid != '', 'Account with uid None/""' else: @@ -156,8 +156,8 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): updatequads = ''' <{0}> "{1:d}"^^ . '''.format(uid, int(time.time())) - updated = txn.Mutate(setnquads=updatequads) - txn.Commit() + updated = txn.mutate(setnquads=updatequads) + txn.commit() with success_ctr.get_lock(): success_ctr.value += 1 # txn successful, break the loop From 9ec0244c9e22e3311ddf189be95f0cca646daabd Mon Sep 17 00:00:00 2001 From: Shailesh Date: Tue, 9 Jan 2018 22:27:07 +0530 Subject: [PATCH 031/435] Removed superfluous function. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5c5e9e5a..898018e3 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", ], - packages=["pydgraph", "pydgraph.utils", "pydgraph.utils.proto"], + packages=["pydgraph", "pydgraph.proto"], install_requires=open('requirements.txt').readlines(), test_suite='tests', - ) +) From ac0bd1682038e0d913342980a3fe5071170b1a5d Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 14 Jan 2018 17:08:09 +0530 Subject: [PATCH 032/435] Method naming changes. --- tests/test_integrations.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 7322d30b..0dc3aa9c 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -48,8 +48,9 @@ def setUp(self): 'Server version %s must be > 0.9' % version.tag) -class AcctUpsertIntegrationTestCase(DgraphClientIntegrationTestCase): +class AcountUpsertIntegrationTestCase(DgraphClientIntegrationTestCase): """Account upsert integration test.""" + def setUp(self): """Drops existing schema and loads new schema for the test.""" super(AcctUpsertIntegrationTestCase, self).setUp() @@ -72,12 +73,12 @@ def setUp(self): when: int . """) - def test_acctUpsertIntegration(self): + def test_acount_upsert(self): """Account upsert integration. Will run upserts concurrently.""" - self.doUpserts(self.accounts, self.concurrency) - self.assertChanges(self.firsts, self.accounts) + self.do_upserts(self.accounts, self.concurrency) + self.assert_changes(self.firsts, self.accounts) - def doUpserts(self, account_list, concurrency): + def do_upserts(self, account_list, concurrency): """Will run the upsert command for the accouts in `account_list`. Execution happens in concurrent processes.""" success_ctr = multiprocessing.Value('i', 0, lock=True) @@ -93,7 +94,7 @@ def doUpserts(self, account_list, concurrency): for acct in account_list for _ in range(concurrency)] [res.get() for res in results] - def assertChanges(self, firsts, accounts): + def assert_changes(self, firsts, accounts): """Will check to see changes have been made.""" q = ''' {{ @@ -167,8 +168,6 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): retry_ctr.value += 1 # txn failed, retry the loop -def foo(x): - return x+5 if __name__ == '__main__': unittest.main() From b55532a94a9029da57def3e00af936c1389f8761 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 14 Jan 2018 17:09:05 +0530 Subject: [PATCH 033/435] meta. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 898018e3..4c560382 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ import ssl ssl._create_default_https_context = ssl._create_unverified_context -from pydgraph.utils.meta import VERSION +from pydgraph.meta import VERSION setup(name="pydgraph", version=VERSION, From fa2866888e6d6413c0a07793148c19daeeacda43 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 14 Jan 2018 17:09:31 +0530 Subject: [PATCH 034/435] Fixed usage of start_ts ina txn and client. --- pydgraph/client.py | 14 ++++---------- pydgraph/txn.py | 7 +++---- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 5bc6e4da..f9d4533c 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -34,37 +34,31 @@ class DgraphClient(object): def __init__(self, host, port): self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) self._stub = api_grpc.DgraphStub(self.channel) - self._start_ts = 0 self._lin_read = api.LinRead() @property def stub(self): return self._stub - @property - def start_ts(self): - return self._start_ts - @property def lin_read(self): return self._lin_read - def _merge_context(self, context): + def merge_context(self, context): """Merges txn_context into client's state.""" - self._start_ts = context.start_ts util.merge_lin_reads(self.lin_read, context.lin_read) def check(self, timeout=None): return self.stub.CheckVersion(api.Check(), timeout) def query(self, q, timeout=None): - request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) + request = api.Request(query=q, lin_read=self.lin_read) response = self.stub.Query(request, timeout) - self._merge_context(response.txn) + self.merge_context(response.txn) return response async def aquery(self, q, timeout=None): - request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) + request = api.Request(query=q, lin_read=self.lin_read) response = await self.stub.Query.future(request, timeout) self._merge_context(response.txn) return response diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 0cff9b0b..31b186fe 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -27,7 +27,7 @@ class DgraphTxn(object): """ def __init__(self, client): self.client = client - self.start_ts = client.start_ts + self.start_ts = 0 self.lin_read = api.LinRead() self.lin_read.MergeFrom(client.lin_read) self.keys = [] @@ -40,16 +40,15 @@ def merge_context(self, txn_context): ## This will be true if the server does not return a txn context after ## a query or a mutation if not txn_context: return + if self.start_ts == 0: self.start_ts = txn_context.start_ts elif self.start_ts != txn_context.start_ts: raise Exception('StartTs mismatch in txn(%s) vs updated context(%s)' % (self.start_ts, txn_context.start_ts)) - # TODO(kochhar): disabling this as it makes client be part of the txn - # self.client.merge_context(txn_context) + self.client.merge_context(txn_context) util.merge_lin_reads(self.lin_read, txn_context.lin_read) - self.keys.extend(txn_context.keys) def query(self, q, *args, **kwargs): From 887a0a060f8fbe26588115d675b2d4e4110734f3 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 14 Jan 2018 17:11:44 +0530 Subject: [PATCH 035/435] Added tests for bank xfers. --- tests/test_bank.py | 159 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 tests/test_bank.py diff --git a/tests/test_bank.py b/tests/test_bank.py new file mode 100644 index 00000000..ffb3ff9c --- /dev/null +++ b/tests/test_bank.py @@ -0,0 +1,159 @@ +""" +test_bank.py + +implements test case for running transfer transactions between bank +accounts. runs concurrent writers running multiple transactions in parallel. +""" +import grpc +import json +import logging +import multiprocessing as mp +import multiprocessing.dummy as mpd +import os +import random +import time +import unittest + +from pydgraph import client +import test_integrations as integ + +USERS = 100 +CONCURRENCY = 10 +XFER_COUNT = 1000 + + +class TestBankXfer(integ.DgraphClientIntegrationTestCase): + """Bank transfer integration test.""" + def setUp(self): + """Drops existing schema and sets up schema for new test.""" + super(TestBankXfer, self).setUp() + self.concurrency = CONCURRENCY + self.accounts = [ + {'bal': 100} for _ in range(USERS) + ] + self.uids = [] + logging.debug(len(self.accounts)) + + def test_bank_xfer(self): + """Transfers, will run them concurrently.""" + self.create_accounts() + + try: + total_watcher = self.start_total_watcher() + + success_ctr = mp.Value('i', 0, lock=True) + retry_ctr = mp.Value('i', 0, lock=True) + pool = mpd.Pool(self.concurrency) + results = [pool.apply_async(run_xfers, (self.TEST_HOSTNAME, self.TEST_PORT, + XFER_COUNT, self.uids, + success_ctr, retry_ctr)) + for _ in range(self.concurrency)] + [res.get() for res in results] + finally: + total_watcher.terminate() + time.sleep(0.1) + + def create_accounts(self): + """Creates the default set of accounts.""" + _ = self.client.drop_all() + _ = self.client.alter(schema="""bal: int .""") + + txn = self.client.txn() + assigned = txn.mutate_obj(setobj=self.accounts) + txn.commit() + self.uids.extend(assigned.uids.values()) + logging.debug('Created %d accounts', len(assigned.uids)) + + def start_total_watcher(self): + """Watcher, will keep an eye on the total account balances.""" + total_watch = looper(run_total, self.client, self.uids) + process = mp.Process(target=total_watch, name='total_watcher') + process.start() + return process + + +def looper(func, *args, **kwargs): + def _looper(): + while True: + func(*args, **kwargs) + time.sleep(1) + + return _looper + + +def run_total(c, account_uids): + """Calculates the total ammount in the accounts.""" + q = """{{ + var(func: uid("{uids:s}")) {{ + b as bal + }} + total() {{ + bal: sum(val(b)) + }} + }} + """.format(uids='", "'.join(account_uids)) + resp = c.query(q=q) + total = json.loads(resp.json)['total'] + logging.debug("Response: %s", total) + assert total[0]['bal'] == 10000 + + +def run_xfers(hostname, port, xfer_count, account_ids, success_ctr, retry_ctr): + pname = mpd.current_process().name + log = logging.getLogger('test_bank.run_txfers[%s]' % (pname,)) + c = client.DgraphClient(hostname, port) + + while True: + from_acc, to_acc = select_account_pair(account_ids) + query = """{{ + me(func: uid("{uid1:s}", "{uid2:s}")) {{ + uid, + bal + }} + }}""".format(uid1=from_acc, uid2=to_acc) + txn = c.txn() + accounts = load_from_query(txn, query, 'me') + accounts[0]['bal'] += 5 + accounts[1]['bal'] -= 5 + try: + dump_from_obj(txn, accounts) + with success_ctr.get_lock(): + success_ctr.value += 1 + + if not success_ctr.value % 100: + log.debug('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) + if success_ctr.value >= xfer_count: + break + except grpc._channel._Rendezvous as e: + logging.warn(e) + with retry_ctr.get_lock(): + retry_ctr.value += 1 + + +def select_account_pair(accounts): + """Selects a pair of accounts at random from accounts ensuring they are not + the same.""" + while True: + from_acc = random.choice(accounts) + to_acc = random.choice(accounts) + if not from_acc == to_acc: + return (from_acc, to_acc) + + +def load_from_query(txn, query, field): + """Loads a field from the results of a query executed in a txn.""" + resp = txn.query(q=query) + return json.loads(resp.json)[field] + + +def dump_from_obj(txn, obj, commit=False): + assigned = txn.mutate_obj(setobj=obj) + + if not commit: + return assigned + return txn.commit() + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + unittest.main() From 178408b21f17aa0437193e3837da0cfba9b5fd72 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 14 Jan 2018 17:22:39 +0530 Subject: [PATCH 036/435] REnamed. --- tests/{test_integrations.py => test_acct_upsert.py} | 3 +-- tests/test_bank.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) rename tests/{test_integrations.py => test_acct_upsert.py} (99%) diff --git a/tests/test_integrations.py b/tests/test_acct_upsert.py similarity index 99% rename from tests/test_integrations.py rename to tests/test_acct_upsert.py index 0dc3aa9c..d450e39a 100644 --- a/tests/test_integrations.py +++ b/tests/test_acct_upsert.py @@ -23,8 +23,6 @@ import unittest from pydgraph import client -logging.basicConfig(level=logging.DEBUG) - class DgraphClientIntegrationTestCase(unittest.TestCase): """Base class for other integration test cases. Provides a client object @@ -170,4 +168,5 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) unittest.main() diff --git a/tests/test_bank.py b/tests/test_bank.py index ffb3ff9c..cde2c868 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -15,7 +15,7 @@ import unittest from pydgraph import client -import test_integrations as integ +import test_acct_upsert as integ USERS = 100 CONCURRENCY = 10 @@ -94,7 +94,7 @@ def run_total(c, account_uids): """.format(uids='", "'.join(account_uids)) resp = c.query(q=q) total = json.loads(resp.json)['total'] - logging.debug("Response: %s", total) + logging.info("Response: %s", total) assert total[0]['bal'] == 10000 @@ -121,7 +121,7 @@ def run_xfers(hostname, port, xfer_count, account_ids, success_ctr, retry_ctr): success_ctr.value += 1 if not success_ctr.value % 100: - log.debug('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) + log.info('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) if success_ctr.value >= xfer_count: break except grpc._channel._Rendezvous as e: From 830ca20fcaaef005c5aea8c5729fc5c373ee5a8b Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 14 Jan 2018 17:23:52 +0530 Subject: [PATCH 037/435] Missed rename. --- pydgraph/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index f9d4533c..64700423 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -60,7 +60,7 @@ def query(self, q, timeout=None): async def aquery(self, q, timeout=None): request = api.Request(query=q, lin_read=self.lin_read) response = await self.stub.Query.future(request, timeout) - self._merge_context(response.txn) + self.merge_context(response.txn) return response def alter(self, schema, timeout=None): From 2d1a5389530b1e5a1e4788965f5ea24fac897928 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 21 Jan 2018 00:43:44 +0530 Subject: [PATCH 038/435] Added support for ignore_index_conflict. --- pydgraph/txn.py | 6 ++++++ tests/test_bank.py | 1 + 2 files changed, 7 insertions(+) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 31b186fe..ade72ecd 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -82,6 +82,8 @@ def mutate(self, setnquads=None, delnquads=None, *args, **kwargs): """ if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) + if kwargs.pop('ignore_index_conflict', None): + mutation.ignore_index_conflict = True if setnquads: mutation.set_nquads=setnquads.encode('utf8') if delnquads: @@ -110,6 +112,8 @@ def mutate_obj(self, setobj=None, delobj=None, *args, **kwargs): """ if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) + if kwargs.pop('ignore_index_conflict', None): + mutation.ignore_index_conflict = True if setobj: mutation.set_json=json.dumps(setobj).encode('utf8') if delobj: @@ -123,6 +127,8 @@ def mutate_obj(self, setobj=None, delobj=None, *args, **kwargs): async def amutate_obj(self, setobj=None, delobj=None, *args, **kwargs): if self._finished: raise Exception('Transaction is complete') mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) + if kwargs.pop('ignore_index_conflict', None): + mutation.ignore_index_conflict = True if setobj: mutation.set_json=json.dumps(setobj).encode('utf8') if delobj: diff --git a/tests/test_bank.py b/tests/test_bank.py index cde2c868..de2d2a07 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -24,6 +24,7 @@ class TestBankXfer(integ.DgraphClientIntegrationTestCase): """Bank transfer integration test.""" + def setUp(self): """Drops existing schema and sets up schema for new test.""" super(TestBankXfer, self).setUp() From d9af3158b3d19b8dceee32880c7c2b3e89b7053a Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 21 Jan 2018 00:44:03 +0530 Subject: [PATCH 039/435] Added transaction test cases. --- tests/test_txn.py | 358 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 tests/test_txn.py diff --git a/tests/test_txn.py b/tests/test_txn.py new file mode 100644 index 00000000..85c376d6 --- /dev/null +++ b/tests/test_txn.py @@ -0,0 +1,358 @@ +""" +test_txn.py + +implements test case for transactions +""" +import grpc +import json +import logging +import os +import random +import time +import unittest + +from pydgraph import client +import test_acct_upsert as integ + + +class TestClientTxns(integ.DgraphClientIntegrationTestCase): + """Transactions test cases.""" + + def setUp(self): + """Drops all the existing schema and creates schema for tests.""" + super(TestClientTxns, self).setUp() + + _ = self.client.drop_all() + _ = self.client.alter(schema="""name: string @index(fulltext) .""") + + def test_TxnReadAtStartTs(self): + """Tests read after write when readTs == startTs""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + + for _, uid in assigned.uids.items(): + uid = uid + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + resp = txn.query(query) + self.assertEqual([{"name": "Manish"}], json.loads(resp.json).get("me")) + + def test_TxnReadBeforeStartTs(self): + """Tests read before write when readTs < startTs""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + + for _, uid in assigned.uids.items(): + uid = uid + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + + txn2 = self.client.txn() + resp = txn2.query(query) + self.assertEqual([], json.loads(resp.json).get("me")) + + def test_TxnReadAfterStartTs(self): + """Tests read after commiting a write when readTs > startTs""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + + for _, uid in assigned.uids.items(): + uid = uid + _ = txn.commit() + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + txn2 = self.client.txn() + resp = txn2.query(query) + self.assertEqual([{"name": "Manish"}], json.loads(resp.json).get("me")) + + def test_TxnReadBeforeAndAfterStartTs(self): + """Test read before and after commiting a transaction when + readTs1 < startTs and readTs2 > startTs""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + + for _, uid in assigned.uids.items(): + uid = uid + _ = txn.commit() + + txn2 = self.client.txn() + # start a new txn and mutate the object + txn3 = self.client.txn() + assigned = txn3.mutate_obj({"uid": uid, "name": "Manish2"}) + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + + # the object is unmutated in other txns since txn3 is uncommitted + resp2 = txn2.query(query) + self.assertEqual([{"name": "Manish"}], json.loads(resp2.json).get("me")) + + # once txn3 is committed, other txns observe the update + txn3.commit() + txn4 = self.client.txn() + resp4 = txn4.query(query) + self.assertEqual([{"name": "Manish2"}], json.loads(resp4.json).get("me")) + + def test_ReadFromNewClient(self): + """Tests committed reads from a new client with startTs == 0.""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + + for _, uid in assigned.uids.items(): + uid = uid + _ = txn.commit() + + client2 = client.DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + resp2 = client2.query(query) + self.assertEqual([{"name": "Manish"}], json.loads(resp2.json).get("me")) + self.assertTrue(resp2.txn.start_ts > 0) + + txn2 = client2.txn() + assigned = txn2.mutate_obj({"uid": uid, "name": "Manish2"}) + self.assertTrue(assigned.context.start_ts > 0) + txn2.commit() + + resp = self.client.query(query) + self.assertEqual([{"name": "Manish2"}], json.loads(resp.json).get("me")) + + def test_Conflict(self): + """Tests committing two transactions which conflict.""" + _ = self.client.drop_all() + + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + for _, uid in assigned.uids.items(): + uid = uid + + txn2 = self.client.txn() + assigned2 = txn2.mutate_obj({"uid": uid, "name": "Manish"}) + + txn.commit() + self.assertRaises(grpc._channel._Rendezvous, txn2.commit) + + txn3 = self.client.txn() + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + resp3 = txn3.query(query) + self.assertEqual([{"name": "Manish"}], json.loads(resp3.json).get("me")) + + def test_ConflictReverseOrder(self): + """Tests committing a transaction after a newer transaction has been + committed.""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + for _, uid in assigned.uids.items(): + uid = uid + + txn2 = self.client.txn() + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + resp = txn2.query(query) + self.assertEqual([], json.loads(resp.json).get("me")) + + assigned2 = txn2.mutate_obj({"uid": uid, "name": "Jan the man"}) + txn2.commit() + self.assertRaises(grpc._channel._Rendezvous, txn.commit) + + txn3 = self.client.txn() + resp = txn3.query(query) + self.assertEqual([{"name": "Jan the man"}], json.loads(resp.json).get("me")) + + def test_MutationAfterCommit(self): + """Tests a second mutation after failing to commit a first mutation.""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + for _, uid in assigned.uids.items(): + uid = uid + + txn2 = self.client.txn() + assigned2 = txn2.mutate_obj({"uid": uid, "name": "Jan the man"}) + + txn.commit() + self.assertRaises(grpc._channel._Rendezvous, txn2.commit) + + txn3 = self.client.txn() + assigned3 = txn3.mutate_obj({"uid": uid, "name": "Jan the man"}) + txn3.commit() + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + txn4 = self.client.txn() + resp4 = txn4.query(query) + self.assertEqual([{"name": "Jan the man"}], json.loads(resp4.json).get("me")) + + def test_ConflictIgnore(self): + """Tests a mutation with ignore index conflict.""" + txn = self.client.txn() + assigned1 = txn.mutate_obj({"name": "Manish"}, ignore_index_conflict=True) + self.assertEqual(1, len(assigned1.uids), "Nothing was assigned") + for _, uid in assigned1.uids.items(): + uid1 = uid + + txn2 = self.client.txn() + assigned2 = txn2.mutate_obj({"name": "Manish"}, ignore_index_conflict=True) + self.assertEqual(1, len(assigned2.uids), "Nothing was assigned") + for _, uid in assigned2.uids.items(): + uid2 = uid + + txn.commit() + txn2.commit() + + txn3 = self.client.txn() + query = """{ + me(func: eq(name, "Manish")) { + uid + } + }""" + resp = txn3.query(query) + self.assertEqual([{"uid": uid1}, {"uid": uid2}], json.loads(resp.json).get("me")) + + def test_ReadIndexKeySameTxn(self): + """Tests reading an indexed field within a transaction.""" + _ = self.client.drop_all() + _ = self.client.alter(schema="""name: string @index(exact) .""") + + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}, ignore_index_conflict=True) + self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + for _, uid in assigned.uids.items(): + uid = uid + + query = """{ + me(func: le(name, "Manish")) { + uid + } + }""" + resp = txn.query(query) + self.assertEqual([{"uid": uid}], json.loads(resp.json).get("me")) + + +class TestSPStar(integ.DgraphClientIntegrationTestCase): + + def setUp(self): + """Drops everything and sets some schema.""" + super(TestSPStar, self).setUp() + _ = self.client.drop_all() + _ = self.client.alter(schema="""friend: uid .""") + + def test_SPStar(self): + """Tests a Subj Predicate Star query.""" + txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish", "friend": [{"name": "Jan"}]}) + uid1 = assigned.uids["blank-0"] + self.assertEqual(2, len(assigned.uids), "Expecting 2 uids to be created") + txn.commit() + + txn2 = self.client.txn() + assigned2 = txn2.mutate_obj(delobj={"uid": uid1, "friend": None}) + self.assertEqual(0, len(assigned2.uids)) + + assigned3 = txn2.mutate_obj({ + "uid": uid1, + "name": "Manish", + "friend": [{"name": "Jan2"}] + }) + self.assertEqual(1, len(assigned3.uids)) + uid2 = assigned3.uids["blank-0"] + + query = """{{ + me(func: uid("{uid:s}")) {{ + uid + friend {{ + uid + name + }} + }} + }}""".format(uid=uid1) + resp = txn2.query(query) + self.assertEqual([{ + "uid": uid1, + "friend": [{"name": "Jan2", "uid": uid2}] + }], json.loads(resp.json).get("me")) + + def test_SPStar2(self): + """Second test of Subject Predicate Star""" + txn = self.client.txn() + # Add edge to Jan + assigned = txn.mutate_obj({"name": "Manish", "friend": [{"name": "Jan"}]}) + self.assertEqual(2, len(assigned.uids)) + uid1, uid2 = assigned.uids["blank-0"], assigned.uids["blank-1"] + query = """{{ + me(func: uid("{uid:s}")) {{ + uid + friend {{ + uid + name + }} + }} + }}""".format(uid=uid1) + resp = txn.query(query) + self.assertEqual([{ + "uid": uid1, + "friend": [{"name": "Jan", "uid": uid2}] + }], json.loads(resp.json).get("me")) + # Delete S P * + deleted = txn.mutate_obj(delobj={"uid": uid1, "friend": None}) + self.assertEqual(0, len(deleted.uids)) + + resp = txn.query(query) + self.assertEqual([{"uid": uid1}], json.loads(resp.json).get("me")) + + # Add an edge to Jan2 + assigned2 = txn.mutate_obj({ + "uid": uid1, + "name": "Manish", + "friend": [{"name": "Jan2"}] + }) + self.assertEqual(1, len(assigned2.uids)) + uid2 = assigned2.uids["blank-0"] + resp = txn.query(query) + self.assertEqual([{ + "uid": uid1, + "friend": [{"name": "Jan2", "uid": uid2}] + }], json.loads(resp.json).get("me")) + # Delete S P * + deleted2 = txn.mutate_obj(delobj={"uid": uid1, "friend": None}) + self.assertEqual(0, len(deleted2.uids)) + resp = txn.query(query) + self.assertEqual([{"uid": uid1}], json.loads(resp.json).get("me")) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + unittest.main() From 1b765cc80c2830e9a425960f284fc37e340d67b2 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sun, 21 Jan 2018 00:50:45 +0530 Subject: [PATCH 040/435] Fix rename. --- tests/test_acct_upsert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index d450e39a..b08ae3a7 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -51,7 +51,7 @@ class AcountUpsertIntegrationTestCase(DgraphClientIntegrationTestCase): def setUp(self): """Drops existing schema and loads new schema for the test.""" - super(AcctUpsertIntegrationTestCase, self).setUp() + super(AcountUpsertIntegrationTestCase, self).setUp() self.concurrency = 5 self.firsts = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] From ae0de794d592ce221aa3068a635c7fc6b8eafdf9 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sat, 3 Feb 2018 22:59:40 +0530 Subject: [PATCH 041/435] Added write in txn after read in client test. --- tests/test_essentials.py | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_essentials.py diff --git a/tests/test_essentials.py b/tests/test_essentials.py new file mode 100644 index 00000000..4f104f98 --- /dev/null +++ b/tests/test_essentials.py @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Shailesh Kochhar ' +__maintainer__ = 'Shailesh Kochhar ' + +import grpc +import json +import logging +import unittest +from pydgraph import client + +import test_acct_upsert as integ + +class TestEssentials(integ.DgraphClientIntegrationTestCase): + """Tests the essentials of the client.""" + + def testMutationAfterQuery(self): + """Tests what happens when making a mutation on a txn after querying + on the client.""" + _ = self.client.query('''{firsts(func: has(first)) { uid first }}''') + + txn = self.client.txn() + mutation = txn.mutate(setnquads='_:node "Node name first" .') + self.assertTrue(len(mutation.uids) > 0, "Mutation did not create new node") + created = mutation.uids.get('node') + self.assertIsNotNone(created) + txn.commit() + + query = '{{node(func: uid({uid:s})) {{ uid }} }}'.format(uid=created) + reread = self.client.query(query) + self.assertEqual(created, json.loads(reread.json).get('node')[0]['uid']) + + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + unittest.main() From 8714e38ac089d72375e74c4432fbd34a837757b9 Mon Sep 17 00:00:00 2001 From: Shailesh Date: Sat, 3 Feb 2018 23:11:07 +0530 Subject: [PATCH 042/435] Overwrite txn modified keys when merging contexts. --- pydgraph/txn.py | 2 +- tests/test_essentials.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index ade72ecd..a3d73eb8 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -49,7 +49,7 @@ def merge_context(self, txn_context): self.client.merge_context(txn_context) util.merge_lin_reads(self.lin_read, txn_context.lin_read) - self.keys.extend(txn_context.keys) + self.keys = txn_context.keys[:] def query(self, q, *args, **kwargs): if self._finished: raise Exception('Transaction is complete') diff --git a/tests/test_essentials.py b/tests/test_essentials.py index 4f104f98..34e2429e 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -41,6 +41,22 @@ def testMutationAfterQuery(self): self.assertEqual(created, json.loads(reread.json).get('node')[0]['uid']) + def testMultipleMutationsInTxn(self): + """Test what happens when running multiple mutations in a txn.""" + txn = self.client.txn() + + for i in range(100): + m = txn.mutate('''_:node "{seq:d}" .'''.format(seq=i)) + + self.assertEqual(100, len(txn.keys), + "Expected txn to have only 100 modified keys.") + key_counter = {} + for key in txn.keys: + key_counter[key] = key_counter.setdefault(key, 0) + 1 + recurring_keys = [(k, c) for (k, c) in key_counter.items() if c > 1] + self.assertEqual(0, len(recurring_keys), + "Expected txn not to have recurring keys") + if __name__ == '__main__': logging.basicConfig(level=logging.INFO) From ab41e8f91ea8e1081662b46b30abfa4c4ea9b999 Mon Sep 17 00:00:00 2001 From: Pawan Rawal Date: Mon, 12 Feb 2018 13:44:23 +1100 Subject: [PATCH 043/435] Update README to mention kochhar/pydgraph client. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a702ee5..ad6d9849 100755 --- a/README.md +++ b/README.md @@ -2,4 +2,5 @@ Python client for Dgraph ## Documentation -Please refer the [wiki](https://wiki.dgraph.io/Clients#Python) for **Installation** and **How-To**s. + +The code here is outdated. The community has been working on an updated client which works with Dgraph `v1.0.0` onwards. It's available at [https://github.com/kochhar/pydgraph](https://github.com/kochhar/pydgraph). From 913c7f7b795bdba493e7be7b50f27fc53ba8bfbd Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 13 Mar 2018 18:46:25 +0530 Subject: [PATCH 044/435] Add protogen.py, update dependencies and refactor --- .gitignore | 33 ++++++-- README.md | 5 +- pydgraph/client.py | 3 +- pydgraph/meta.py | 2 +- pydgraph/proto/api.proto | 160 ++++++++++++++++++++++++++++++++++++++ pydgraph/proto/api_pb2.py | 80 ++++++++++--------- pydgraph/txn.py | 1 + requirements.txt | 2 +- scripts/protogen.py | 28 +++++++ setup.py | 34 ++++---- tests/test_acct_upsert.py | 2 + tests/test_bank.py | 2 +- tests/test_essentials.py | 2 +- tests/test_queries.py | 1 - tests/test_txn.py | 16 +++- 15 files changed, 302 insertions(+), 69 deletions(-) mode change 100755 => 100644 pydgraph/meta.py create mode 100644 pydgraph/proto/api.proto create mode 100644 scripts/protogen.py diff --git a/.gitignore b/.gitignore index 329829b2..5a313219 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,29 @@ -*.pyc +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# IntelliJ .idea -build/* -dist/* -pydgraph.egg-info/* -.eggs diff --git a/README.md b/README.md index ad6d9849..c7c3976f 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ # pydgraph -Python client for Dgraph -## Documentation - -The code here is outdated. The community has been working on an updated client which works with Dgraph `v1.0.0` onwards. It's available at [https://github.com/kochhar/pydgraph](https://github.com/kochhar/pydgraph). +Official Dgraph client implementation for Python. diff --git a/pydgraph/client.py b/pydgraph/client.py index 64700423..228b9f47 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -1,4 +1,3 @@ -# # Copyright 2016 DGraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,11 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ This module contains the main user-facing methods for interacting with the Dgraph server over gRPC. """ + import grpc from pydgraph import txn from pydgraph import util diff --git a/pydgraph/meta.py b/pydgraph/meta.py old mode 100755 new mode 100644 index 4e71b5fa..5052c1fd --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -1 +1 @@ -VERSION="0.4.0" +VERSION = '0.4.0' diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto new file mode 100644 index 00000000..eb5445ac --- /dev/null +++ b/pydgraph/proto/api.proto @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2017 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package api; + +option java_package = "io.dgraph"; +option java_outer_classname = "DgraphProto"; + +// Graph response. +service Dgraph { + rpc Query (Request) returns (Response) {} + rpc Mutate (Mutation) returns (Assigned) {} + rpc Alter (Operation) returns (Payload) {} + rpc CommitOrAbort (TxnContext) returns (TxnContext) {} + rpc CheckVersion(Check) returns (Version) {} +} + +message Request { + string query = 1; + map vars = 2; // Support for GraphQL like variables. + + uint64 start_ts = 13; + LinRead lin_read = 14; +} + +message Response { + bytes json = 1; + repeated SchemaNode schema = 2; + TxnContext txn = 3; + Latency latency = 12; +} + +message Assigned { + map uids = 1; + TxnContext context = 2; + Latency latency = 12; +} + +message Mutation { + bytes set_json = 1; + bytes delete_json = 2; + bytes set_nquads = 3; + bytes del_nquads = 4; + + repeated NQuad set = 10; + repeated NQuad del = 11; + uint64 start_ts = 13; + bool commit_now = 14; + bool ignore_index_conflict = 15; +} + + +message AssignedIds { + uint64 startId = 1; + uint64 endId = 2; +} + +message Operation { + string schema = 1; + string drop_attr = 2; + bool drop_all = 3; +} + +// Worker services. +message Payload { + bytes Data = 1; +} + +message TxnContext { + uint64 start_ts = 1; + uint64 commit_ts = 2; + bool aborted = 3; + repeated string keys = 4; + LinRead lin_read = 13; +} + +message Check {} + +message Version { + string tag = 1; +} + +message LinRead { + map ids = 1; +} + +message Latency { + uint64 parsing_ns = 1; + uint64 processing_ns = 2; + uint64 encoding_ns = 3; +} + +message NQuad { + string subject = 1; + string predicate = 2; + string object_id = 3; + Value object_value = 4; + string label = 5; + string lang = 6; + repeated Facet facets = 7; +} + +message Value { + oneof val { + string default_val = 1; + bytes bytes_val = 2; + int64 int_val = 3; + bool bool_val = 4; + string str_val = 5; + double double_val = 6; + bytes geo_val = 7; // Geo data in WKB format + bytes date_val = 8; + bytes datetime_val = 9; + string password_val = 10; + uint64 uid_val=11; + } +} + +message Facet { + enum ValType { + STRING = 0; + INT = 1; + FLOAT = 2; + BOOL = 3; + DATETIME = 4; + } + + string key = 1; + bytes value = 2; + ValType val_type = 3; + repeated string tokens = 4; // tokens of value. + string alias = 5; // not stored, only used for query. +} + +message SchemaNode { + string predicate = 1; + string type = 2; + bool index = 3; + repeated string tokenizer = 4; + bool reverse = 5; + bool count = 6; + bool list = 7; +} + +// vim: noexpandtab sw=2 ts=2 diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 58031d9e..413aeaa5 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -19,7 +19,7 @@ name='api.proto', package='api', syntax='proto3', - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x80\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"Y\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"}\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"Y\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"}\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -53,8 +53,8 @@ ], containing_type=None, options=None, - serialized_start=1601, - serialized_end=1666, + serialized_start=1632, + serialized_end=1697, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -233,8 +233,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=384, - serialized_end=427, + serialized_start=415, + serialized_end=458, ) _ASSIGNED = _descriptor.Descriptor( @@ -258,6 +258,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='latency', full_name='api.Assigned.latency', index=2, + number=12, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -271,7 +278,7 @@ oneofs=[ ], serialized_start=299, - serialized_end=427, + serialized_end=458, ) @@ -357,8 +364,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=430, - serialized_end=638, + serialized_start=461, + serialized_end=669, ) @@ -395,8 +402,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=640, - serialized_end=685, + serialized_start=671, + serialized_end=716, ) @@ -440,8 +447,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=687, - serialized_end=751, + serialized_start=718, + serialized_end=782, ) @@ -471,8 +478,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=753, - serialized_end=776, + serialized_start=784, + serialized_end=807, ) @@ -530,8 +537,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=778, - serialized_end=890, + serialized_start=809, + serialized_end=921, ) @@ -554,8 +561,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=892, - serialized_end=899, + serialized_start=923, + serialized_end=930, ) @@ -585,8 +592,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=901, - serialized_end=923, + serialized_start=932, + serialized_end=954, ) @@ -623,8 +630,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=972, - serialized_end=1014, + serialized_start=1003, + serialized_end=1045, ) _LINREAD = _descriptor.Descriptor( @@ -653,8 +660,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=925, - serialized_end=1014, + serialized_start=956, + serialized_end=1045, ) @@ -698,8 +705,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1016, - serialized_end=1089, + serialized_start=1047, + serialized_end=1120, ) @@ -771,8 +778,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1092, - serialized_end=1245, + serialized_start=1123, + serialized_end=1276, ) @@ -875,8 +882,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1248, - serialized_end=1492, + serialized_start=1279, + serialized_end=1523, ) @@ -935,8 +942,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1495, - serialized_end=1666, + serialized_start=1526, + serialized_end=1697, ) @@ -1008,8 +1015,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1668, - serialized_end=1793, + serialized_start=1699, + serialized_end=1824, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1021,6 +1028,7 @@ _ASSIGNED_UIDSENTRY.containing_type = _ASSIGNED _ASSIGNED.fields_by_name['uids'].message_type = _ASSIGNED_UIDSENTRY _ASSIGNED.fields_by_name['context'].message_type = _TXNCONTEXT +_ASSIGNED.fields_by_name['latency'].message_type = _LATENCY _MUTATION.fields_by_name['set'].message_type = _NQUAD _MUTATION.fields_by_name['del'].message_type = _NQUAD _TXNCONTEXT.fields_by_name['lin_read'].message_type = _LINREAD @@ -1233,8 +1241,8 @@ file=DESCRIPTOR, index=0, options=None, - serialized_start=1796, - serialized_end=2024, + serialized_start=1827, + serialized_end=2055, methods=[ _descriptor.MethodDescriptor( name='Query', diff --git a/pydgraph/txn.py b/pydgraph/txn.py index a3d73eb8..e21f9026 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -2,6 +2,7 @@ This module contains the methods for using transactions when using a dgraph server over gRPC. """ + import grpc import json diff --git a/requirements.txt b/requirements.txt index b3e57fc9..b043a9e9 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -grpcio==1.2.0 +grpcio==1.10.0 diff --git a/scripts/protogen.py b/scripts/protogen.py new file mode 100644 index 00000000..580d1c90 --- /dev/null +++ b/scripts/protogen.py @@ -0,0 +1,28 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Runs protoc with the gRPC plugin to generate messages and gRPC stubs.""" + +import os +from grpc_tools import protoc + +dirpath = os.path.dirname(os.path.realpath(__file__)) +protopath = os.path.realpath(os.path.join(dirpath, '../pydgraph/proto')) + +protoc.main(( + '', + '-I' + protopath, + '--python_out=' + protopath, + '--grpc_python_out=' + protopath, + os.path.join(protopath, 'api.proto'), +)) diff --git a/setup.py b/setup.py index 4c560382..9f76a46d 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# # Copyright 2016 DGraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import sys try: from setuptools import setup @@ -24,24 +24,26 @@ from pydgraph.meta import VERSION -setup(name="pydgraph", +setup(name='pydgraph', version=VERSION, - description="Dgraph driver for Python", - license="Apache License, Version 2.0", - author="Mohit Ranka", - author_email="mohitranka@gmail.com", - url="https://github.com/dgraph-io/pydgraph", + description='Official Dgraph client implementation for Python', + license='Apache License, Version 2.0', + author='Mohit Ranka', + author_email='mohitranka@gmail.com', + url='https://github.com/dgraph-io/pydgraph', classifiers=[ - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Topic :: Database", - "Topic :: Software Development", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Topic :: Database', + 'Topic :: Software Development', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], - packages=["pydgraph", "pydgraph.proto"], + packages=['pydgraph', 'pydgraph.proto'], install_requires=open('requirements.txt').readlines(), test_suite='tests', ) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index b08ae3a7..38716371 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -1,3 +1,5 @@ +# Copyright 2016 DGraph Labs, Inc. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_bank.py b/tests/test_bank.py index de2d2a07..3e0e310e 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -15,7 +15,7 @@ import unittest from pydgraph import client -import test_acct_upsert as integ +from . import test_acct_upsert as integ USERS = 100 CONCURRENCY = 10 diff --git a/tests/test_essentials.py b/tests/test_essentials.py index 34e2429e..763c521f 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -19,7 +19,7 @@ import unittest from pydgraph import client -import test_acct_upsert as integ +from . import test_acct_upsert as integ class TestEssentials(integ.DgraphClientIntegrationTestCase): """Tests the essentials of the client.""" diff --git a/tests/test_queries.py b/tests/test_queries.py index 5cd19862..cb77bb01 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -1,4 +1,3 @@ -# # Copyright 2016 DGraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tests/test_txn.py b/tests/test_txn.py index 85c376d6..751bec62 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -1,8 +1,22 @@ +# Copyright 2016 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """ test_txn.py implements test case for transactions """ + import grpc import json import logging @@ -12,7 +26,7 @@ import unittest from pydgraph import client -import test_acct_upsert as integ +from . import test_acct_upsert as integ class TestClientTxns(integ.DgraphClientIntegrationTestCase): From 14c74b83f14f4e2464259a1d1e8b1e97a20d18d7 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 13 Mar 2018 19:01:05 +0530 Subject: [PATCH 045/435] Update version to v1.0a1.0 --- pydgraph/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 5052c1fd..231120f6 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -1 +1 @@ -VERSION = '0.4.0' +VERSION = '1.0a1.0' From 0d552452d899cafb96b39fc24318432b80d32510 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 15 Mar 2018 20:01:18 +0530 Subject: [PATCH 046/435] Add DgraphClientStub class --- pydgraph/client.py | 60 ++++++++++--------------------------- pydgraph/client_stub.py | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 44 deletions(-) create mode 100644 pydgraph/client_stub.py diff --git a/pydgraph/client.py b/pydgraph/client.py index 228b9f47..0baafb2e 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -11,12 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -This module contains the main user-facing methods for interacting with the +"""This module contains the main user-facing methods for interacting with the Dgraph server over gRPC. """ import grpc +import random from pydgraph import txn from pydgraph import util from pydgraph.meta import VERSION @@ -30,56 +30,28 @@ class DgraphClient(object): - def __init__(self, host, port): - self.channel = grpc.insecure_channel("{host}:{port}".format(host=host, port=port)) - self._stub = api_grpc.DgraphStub(self.channel) - self._lin_read = api.LinRead() - - @property - def stub(self): - return self._stub - - @property - def lin_read(self): - return self._lin_read - - def merge_context(self, context): - """Merges txn_context into client's state.""" - util.merge_lin_reads(self.lin_read, context.lin_read) - - def check(self, timeout=None): - return self.stub.CheckVersion(api.Check(), timeout) + def __init__(self, *clients): + if len(clients) == 0: + raise ValueError('no clients provided in DgraphClient constructor') - def query(self, q, timeout=None): - request = api.Request(query=q, lin_read=self.lin_read) - response = self.stub.Query(request, timeout) - self.merge_context(response.txn) - return response - - async def aquery(self, q, timeout=None): - request = api.Request(query=q, lin_read=self.lin_read) - response = await self.stub.Query.future(request, timeout) - self.merge_context(response.txn) - return response + self._clients = [*clients] + self._lin_read = api.LinRead() def alter(self, schema, timeout=None): """Alter schema at the other end of the connection.""" operation = api.Operation(schema=schema) - return self.stub.Alter(operation, timeout) + return self.any_client().alter(operation, timeout=timeout) async def aalter(self, schema, timeout=None): operation = api.Operation(schema=schema) - return await self.stub.Alter.future(operation, timeout) - - def drop_attr(self, drop_attr, timeout=None): - """Drop an attribute from the dgraph server.""" - operation = api.Operation(drop_attr=drop_attr) - return self.stub.Alter(operation) - - def drop_all(self, timeout=None): - """Drop all schema from the dgraph server.""" - operation = api.Operation(drop_all=True) - return self.stub.Alter(operation) + return await self.any_client().alter_future(operation, timeout=timeout) def txn(self): return txn.DgraphTxn(self) + + def merge_context(self, context): + """Merges txn_context into client's state.""" + util.merge_lin_reads(self._lin_read, context.lin_read) + + def any_client(self): + return random.choice(self._clients) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py new file mode 100644 index 00000000..0d459cd6 --- /dev/null +++ b/pydgraph/client_stub.py @@ -0,0 +1,65 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc +from pydgraph import txn +from pydgraph import util +from pydgraph.meta import VERSION +from pydgraph.proto import api_pb2 as api +from pydgraph.proto import api_pb2_grpc as api_grpc + +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' +__version__ = VERSION +__status__ = 'development' + + +class DgraphClientStub(object): + def __init__(self, addr="localhost:9080", credentials=None, options=None): + if credentials is None: + self.channel = grpc.insecure_channel(addr) + else: + self.channel = grpc.secure_channel(addr, credentials, options) + + self.stub = api_grpc.DgraphStub(self.channel) + + def alter(self, op, timeout=None, metadata=None, credentials=None): + return self.stub.Alter(op, timeout=timeout, metadata=metadata, credentials=credentials) + + def alter_future(self, op, timeout=None, metadata=None, credentials=None): + return self.stub.Alter.future(op, timeout=timeout, metadata=metadata, credentials=credentials) + + def query(self, req, timeout=None, metadata=None, credentials=None): + return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) + + def query_future(self, req, timeout=None, metadata=None, credentials=None): + return self.stub.Query.future(req, timeout=timeout, metadata=metadata, credentials=credentials) + + def mutate(self, mu, timeout=None, metadata=None, credentials=None): + return self.stub.Mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) + + def mutate_future(self, mu, timeout=None, metadata=None, credentials=None): + return self.stub.Mutate.future(mu, timeout=timeout, metadata=metadata, credentials=credentials) + + def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): + return self.stub.CommitOrAbort(ctx, timeout=timeout, metadata=metadata, credentials=credentials) + + def commit_or_abort_future(self, ctx, timeout=None, metadata=None, credentials=None): + return self.stub.CommitOrAbort.future(ctx, timeout=timeout, metadata=metadata, credentials=credentials) + + def checkVersion(self, check, timeout=None, metadata=None, credentials=None): + return self.stub.CheckVersion(check, timeout=timeout, metadata=metadata, credentials=credentials) + + def check_version_future(self, check, timeout=None, metadata=None, credentials=None): + return self.stub.CheckVersion.future(check, timeout=timeout, metadata=metadata, credentials=credentials) From 12f8411a58dd0e415975ac73f9fcfb8657f834de Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 15 Mar 2018 20:08:56 +0530 Subject: [PATCH 047/435] Fix client_stub.py and client.py --- pydgraph/client.py | 28 +++++++++++++--------------- pydgraph/client_stub.py | 12 ++++++------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 0baafb2e..0ed2127a 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -11,17 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""This module contains the main user-facing methods for interacting with the -Dgraph server over gRPC. -""" import grpc import random -from pydgraph import txn -from pydgraph import util + +from pydgraph import txn, util from pydgraph.meta import VERSION -from pydgraph.proto import api_pb2 as api -from pydgraph.proto import api_pb2_grpc as api_grpc +from pydgraph.proto import api_pb2 as api, api_pb2_grpc as api_grpc __author__ = 'Mohit Ranka ' __maintainer__ = 'Mohit Ranka ' @@ -30,6 +26,12 @@ class DgraphClient(object): + """Creates a new Client for interacting with the Dgraph store. + + The client can be backed by multiple connections (to the same server, or + multiple servers in a cluster). + """ + def __init__(self, *clients): if len(clients) == 0: raise ValueError('no clients provided in DgraphClient constructor') @@ -37,20 +39,16 @@ def __init__(self, *clients): self._clients = [*clients] self._lin_read = api.LinRead() - def alter(self, schema, timeout=None): - """Alter schema at the other end of the connection.""" - operation = api.Operation(schema=schema) - return self.any_client().alter(operation, timeout=timeout) + def alter(self, op, timeout=None, metadata=None, credentials=None): + return self.any_client().alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - async def aalter(self, schema, timeout=None): - operation = api.Operation(schema=schema) - return await self.any_client().alter_future(operation, timeout=timeout) + async def async_alter_future(self, op, timeout=None, metadata=None, credentials=None): + return self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) def txn(self): return txn.DgraphTxn(self) def merge_context(self, context): - """Merges txn_context into client's state.""" util.merge_lin_reads(self._lin_read, context.lin_read) def any_client(self): diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 0d459cd6..3c249597 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -37,29 +37,29 @@ def __init__(self, addr="localhost:9080", credentials=None, options=None): def alter(self, op, timeout=None, metadata=None, credentials=None): return self.stub.Alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - def alter_future(self, op, timeout=None, metadata=None, credentials=None): + def async_alter(self, op, timeout=None, metadata=None, credentials=None): return self.stub.Alter.future(op, timeout=timeout, metadata=metadata, credentials=credentials) def query(self, req, timeout=None, metadata=None, credentials=None): return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) - def query_future(self, req, timeout=None, metadata=None, credentials=None): + def async_query(self, req, timeout=None, metadata=None, credentials=None): return self.stub.Query.future(req, timeout=timeout, metadata=metadata, credentials=credentials) def mutate(self, mu, timeout=None, metadata=None, credentials=None): return self.stub.Mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) - def mutate_future(self, mu, timeout=None, metadata=None, credentials=None): + def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): return self.stub.Mutate.future(mu, timeout=timeout, metadata=metadata, credentials=credentials) def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): return self.stub.CommitOrAbort(ctx, timeout=timeout, metadata=metadata, credentials=credentials) - def commit_or_abort_future(self, ctx, timeout=None, metadata=None, credentials=None): + def async_commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): return self.stub.CommitOrAbort.future(ctx, timeout=timeout, metadata=metadata, credentials=credentials) - def checkVersion(self, check, timeout=None, metadata=None, credentials=None): + def check_version(self, check, timeout=None, metadata=None, credentials=None): return self.stub.CheckVersion(check, timeout=timeout, metadata=metadata, credentials=credentials) - def check_version_future(self, check, timeout=None, metadata=None, credentials=None): + def async_check_version(self, check, timeout=None, metadata=None, credentials=None): return self.stub.CheckVersion.future(check, timeout=timeout, metadata=metadata, credentials=credentials) From 95d90b97392ff2a271b3646fcba778b00586f049 Mon Sep 17 00:00:00 2001 From: Pawan Rawal Date: Fri, 16 Mar 2018 14:57:35 +1100 Subject: [PATCH 048/435] Remove outdated note. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index ad6d9849..b2444bc5 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,2 @@ # pydgraph Python client for Dgraph - -## Documentation - -The code here is outdated. The community has been working on an updated client which works with Dgraph `v1.0.0` onwards. It's available at [https://github.com/kochhar/pydgraph](https://github.com/kochhar/pydgraph). From 74ea4f6416f8a13625f88d4f5bff3b03120e520f Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 10:32:00 +0530 Subject: [PATCH 049/435] Some refactoring --- pydgraph/client.py | 8 ++++++++ pydgraph/client_stub.py | 4 ++-- pydgraph/meta.py | 14 ++++++++++++++ pydgraph/txn.py | 22 ++++++++++++++++++---- pydgraph/util.py | 39 +++++++++++++++++++++++++++------------ 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 0ed2127a..588b4bf3 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -48,6 +48,14 @@ async def async_alter_future(self, op, timeout=None, metadata=None, credentials= def txn(self): return txn.DgraphTxn(self) + def get_lin_read(self): + lr = api.LinRead() + ids = lr.ids + for (key, value) in ids.items(): + ids[key] = value + + return lr + def merge_context(self, context): util.merge_lin_reads(self._lin_read, context.lin_read) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 3c249597..8bce2196 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -13,11 +13,11 @@ # limitations under the License. import grpc + from pydgraph import txn from pydgraph import util from pydgraph.meta import VERSION -from pydgraph.proto import api_pb2 as api -from pydgraph.proto import api_pb2_grpc as api_grpc +from pydgraph.proto import api_pb2 as api, api_pb2_grpc as api_grpc __author__ = 'Garvit Pahal ' __maintainer__ = 'Garvit Pahal ' diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 231120f6..fc92841e 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -1 +1,15 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + VERSION = '1.0a1.0' diff --git a/pydgraph/txn.py b/pydgraph/txn.py index e21f9026..585d711d 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -1,15 +1,28 @@ -""" -This module contains the methods for using transactions when using -a dgraph server over gRPC. -""" +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import grpc import json from pydgraph import util +from pydgraph.meta import VERSION from pydgraph.proto import api_pb2 as api __author__ = 'Shailesh Kochhar ' +__maintainer__ = 'Garvit Pahal ' +__version__ = VERSION +__status__ = 'development' class DgraphTxn(object): @@ -26,6 +39,7 @@ class DgraphTxn(object): * keys: the set of keys modified by the transaction to aid in conflict detection """ + def __init__(self, client): self.client = client self.start_ts = 0 diff --git a/pydgraph/util.py b/pydgraph/util.py index d33ea017..63825192 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -1,18 +1,33 @@ -""" -Module containing utilities for dgraph RPC messages -""" +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pydgraph.meta import VERSION __author__ = 'Shailesh Kochhar ' +__maintainer__ = 'Garvit Pahal ' +__version__ = VERSION +__status__ = 'development' + +def merge_lin_reads(target, src): + if src is None: + return target -def merge_lin_reads(current, update_lin_read): - """Merges LinRead protobufs by adding all keys in the ids field from the - updated_lin_read to the current one. If the key already exists, the one - with the larger value is preserved.""" # cache for the loop - curr_lin_read_ids = current.ids - curr_lin_read_ids_get = curr_lin_read_ids.get + target_ids = target.ids + target_ids_get = target_ids.get - for (key, update_value) in update_lin_read.ids.items(): - if curr_lin_read_ids_get(key, 0) <= update_value: - curr_lin_read_ids[key] = update_value + for (key, src_value) in src.ids.items(): + if target_ids_get(key, 0) <= src_value: + target_ids[key] = src_value From 0ad92d4e8ac9104dcc5517aa0414820623365140 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 10:36:59 +0530 Subject: [PATCH 050/435] Fix spelling error --- pydgraph/txn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 585d711d..9f7d8ad4 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -150,7 +150,7 @@ async def amutate_obj(self, setobj=None, delobj=None, *args, **kwargs): mutation.del_json=json.dumps(delobj).encode('utf8'), assigned = await self.client.stub.Mutate.future(mutation, *args, **kwargs) - self.merge_context(assinged.context) + self.merge_context(assigned.context) self._mutated = True return assigned From d7fee63ee4f6921af4745db7575cee047a27231b Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 12:08:32 +0530 Subject: [PATCH 051/435] Complete initial implementation --- pydgraph/client.py | 10 +- pydgraph/txn.py | 308 +++++++++++++++++++++++---------------------- pydgraph/util.py | 10 +- 3 files changed, 172 insertions(+), 156 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 588b4bf3..67665767 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -42,22 +42,22 @@ def __init__(self, *clients): def alter(self, op, timeout=None, metadata=None, credentials=None): return self.any_client().alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_alter_future(self, op, timeout=None, metadata=None, credentials=None): + async def async_alter(self, op, timeout=None, metadata=None, credentials=None): return self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) def txn(self): - return txn.DgraphTxn(self) + return txn.Txn(self) def get_lin_read(self): lr = api.LinRead() ids = lr.ids - for (key, value) in ids.items(): + for key, value in ids.items(): ids[key] = value return lr - def merge_context(self, context): - util.merge_lin_reads(self._lin_read, context.lin_read) + def merge_lin_reads(self, src): + util.merge_lin_reads(self._lin_read, src) def any_client(self): return random.choice(self._clients) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 9f7d8ad4..9b678697 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -25,162 +25,170 @@ __status__ = 'development' -class DgraphTxn(object): - """Class representing a single transaction. A transaction will maintain - three items of state for each transaction. - - Attributes - ========== - - * lin_read: a linearizable read pointer, maintained independently of the - parent client - * start_ts: a starting timestamp, this uniquely identifies a transaction - and doesn't change over its lifetime. - * keys: the set of keys modified by the transaction to aid in conflict - detection +class Txn(object): + """Txn is a single atomic transaction. + + A transaction lifecycle is as follows: + + 1. Created using Client.newTxn. + + 2. Various query and qutate calls made. + + 3. commit or discard used. If any mutations have been made, It's important + that at least one of these methods is called to clean up resources. discard + is a no-op if dommit has already been called, so it's safe to call discard + after calling commit. """ def __init__(self, client): - self.client = client - self.start_ts = 0 - self.lin_read = api.LinRead() - self.lin_read.MergeFrom(client.lin_read) - self.keys = [] + self._dc = client + self._ctx = api.TxnContext() + self._ctx.lin_read = self._dc.get_lin_read() - self._mutated = False self._finished = False + self._mutated = False - def merge_context(self, txn_context): - """Merges context from a txn context into the current txn state.""" - ## This will be true if the server does not return a txn context after - ## a query or a mutation - if not txn_context: return - - if self.start_ts == 0: - self.start_ts = txn_context.start_ts - elif self.start_ts != txn_context.start_ts: - raise Exception('StartTs mismatch in txn(%s) vs updated context(%s)' % - (self.start_ts, txn_context.start_ts)) - - self.client.merge_context(txn_context) - util.merge_lin_reads(self.lin_read, txn_context.lin_read) - self.keys = txn_context.keys[:] - - def query(self, q, *args, **kwargs): - if self._finished: raise Exception('Transaction is complete') - request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) - response = self.client.stub.Query(request, *args, **kwargs) - self.merge_context(response.txn) - return response - - async def aquery(self, q, *args, **kwargs): - if self._finished: raise Exception('Transaction is complete') - request = api.Request(query=q, start_ts=self.start_ts, lin_read=self.lin_read) - response = await self.client.stub.Query.future(request, *args, **kwargs) - self.merge_context(response.txn) - return response - - def mutate(self, setnquads=None, delnquads=None, *args, **kwargs): - """Mutate extends MutateObj to allow mutations to be specified as - N-Quad strings. - - Mutations also support a commit_now method which commits the transaction - along with the mutation. This mode is presently unsupported. - - Params - ====== - * setnquads: a string containing nquads to set - * delnquads: a string containing nquads to delete - - N-Quad format is - . - """ - if self._finished: raise Exception('Transaction is complete') - mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) - if kwargs.pop('ignore_index_conflict', None): - mutation.ignore_index_conflict = True - if setnquads: - mutation.set_nquads=setnquads.encode('utf8') - if delnquads: - mutation.del_nquads=delnquads.encode('utf8') - - assigned = self.client.stub.Mutate(mutation, *args, **kwargs) - self.merge_context(assigned.context) - self._mutated = True - return assigned - - def mutate_obj(self, setobj=None, delobj=None, *args, **kwargs): - """Mutate allows modification of the data stored in the DGraph instance. - - A mutation can be described either using JSON or via RDF quads. This - method presently support mutations described via JSON. - - Mutations also support a commit_now method which commits the transaction - along with the mutation. This mode is presently unsupported. - - Params - ====== - * setobj: an object with data to set, to be encoded as JSON and - converted to utf8 bytes - * delobj: an object with data to be deleted, to be encoded as JSON - and converted to utf8 bytes. - """ - if self._finished: raise Exception('Transaction is complete') - mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) - if kwargs.pop('ignore_index_conflict', None): - mutation.ignore_index_conflict = True - if setobj: - mutation.set_json=json.dumps(setobj).encode('utf8') - if delobj: - mutation.delete_json=json.dumps(delobj).encode('utf8') - - assigned = self.client.stub.Mutate(mutation, *args, **kwargs) - self.merge_context(assigned.context) + def query(self, q, vars=None, timeout=None, metadata=None, credentials=None): + req = self._common_query(q, vars=vars) + res = self._dc.any_client().query(req, timeout=timeout, metadata=metadata, credentials=credentials) + self.merge_context(res.txn) + return res + + async def async_query(self, q, vars=None, timeout=None, metadata=None, credentials=None): + req = self._common_query(q, vars=vars) + res = await self._dc.any_client().async_query(req, timeout=timeout, metadata=metadata, credentials=credentials) + self.merge_context(res.txn) + return res + + def _common_query(self, q, vars=None): + if self._finished: + raise Exception('Transaction has already been committed or discarded') + + req = api.Request(query=q, start_ts=self._ctx.start_ts, lin_read=self._ctx.lin_read) + if vars is not None: + for key, value in vars.items(): + if util.is_string(key) and util.is_string(value): + req.vars[key] = value + + return req + + def mutate(self, mu, timeout=None, metadata=None, credentials=None): + mu = self._common_mutate(mu) + try: + ag = self._dc.any_client().mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) + except Exception as e: + try: + self.discard(timeout=timeout, metadata=metadata, credentials=credentials) + except: + pass + + self._common_except_mutate(e, timeout=timeout, metadata=metadata, credentials=credentials) + + self.merge_context(ag.context) + return ag + + async def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): + mu = self._common_mutate(mu) + try: + ag = await self._dc.any_client().async_mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) + except Exception as e: + try: + await self.async_discard(timeout=timeout, metadata=metadata, credentials=credentials) + except: + pass + + self._common_except_mutate(e, timeout=timeout, metadata=metadata, credentials=credentials) + + self.merge_context(ag.context) + return ag + + def _common_mutate(self, mu): + if self._finished: + raise Exception('Transaction has already been committed or discarded') + self._mutated = True - return assigned - - async def amutate_obj(self, setobj=None, delobj=None, *args, **kwargs): - if self._finished: raise Exception('Transaction is complete') - mutation = api.Mutation(start_ts=self.start_ts, commit_now=False) - if kwargs.pop('ignore_index_conflict', None): - mutation.ignore_index_conflict = True - if setobj: - mutation.set_json=json.dumps(setobj).encode('utf8') - if delobj: - mutation.del_json=json.dumps(delobj).encode('utf8'), - - assigned = await self.client.stub.Mutate.future(mutation, *args, **kwargs) - self.merge_context(assigned.context) - self._mutated = True - return assigned - - def commit(self, *args, **kwargs): - """Commits any mutations performed in the transaction. Once the - transaction is committed its lifespan is complete and no further - mutations or commits can be made.""" - if self._finished: raise Exception('Cannot commit a transaction which is complete') - + mu.start_ts = self._ctx.start_ts + return mu + + def _common_except_mutate(self, e, timeout=None, metadata=None, credentials=None): + if isinstance(e, grpc.RpcError): + e.details() + status_code = e.code() + if status_code == grpc.StatusCode.ABORTED or status_code == grpc.StatusCode.FAILED_PRECONDITION: + raise Exception('Transaction has been aborted. Please retry') + + raise e + + def commit(self, timeout=None, metadata=None, credentials=None): + if not self._common_commit(): + return + + try: + self._dc.any_client().commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + except Exception as e: + self._common_except_commit(e) + + async def async_commit(self, timeout=None, metadata=None, credentials=None): + if not self._common_commit(): + return + + try: + await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + except Exception as e: + self._common_except_commit(e) + + def _common_commit(self): + if self._finished: + raise Exception('Transaction has already been committed or discarded') + self._finished = True - if not self._mutated: return - - txn_context = api.TxnContext(start_ts=self.start_ts, - keys=self.keys, - lin_read=self.lin_read) - resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) - return resp_txn_context - - def abort(self, *args, **kwargs): - """Aborts any mutations performed in the transaction. Once the - transaction is aborted its lifespan is complete and no further - mutations or commits can be made.""" - if self._finished: raise Exception('Cannot abort a transaction which is complete') - + return self._mutated + + def _common_except_commit(self, e): + if isinstance(e, grpc.RpcError): + e.details() + status_code = e.code() + if status_code == grpc.StatusCode.ABORTED: + raise Exception('Transaction has been aborted. Please retry') + + raise e + + def discard(self, timeout=None, metadata=None, credentials=None): + if not self._common_commit(): + return + + self._dc.any_client().commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + + async def async_discard(self, timeout=None, metadata=None, credentials=None): + if not self._common_discard(): + return + + await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + + def _common_discard(self): + if self._finished: + return False + self._finished = True - if not self._mutated: return - - txn_context = api.TxnContext(start_ts=self.start_ts, - keys=self.keys, - lin_read=self.lin_read, - aborted=True) - resp_txn_context = self.client.stub.CommitOrAbort(txn_context, *args, **kwargs) - return resp_txn_context + if not self._mutated: + return False + + self._ctx.aborted = True + return True + + def merge_context(self, src=None): + if src is None: + # This condition will be true only if the server doesn't return a + # txn context after a query or mutation. + return + + util.merge_lin_reads(self._ctx.lin_read, src.lin_read) + self._client.merge_lin_reads(src.lin_read) + + if self._ctx.start_ts == 0: + self._ctx.start_ts = src.start_ts + elif self._ctx.start_ts != src.start_ts: + # This condition should never be true. + raise Exception('StartTs mismatch') + + self.keys = src.keys[:] diff --git a/pydgraph/util.py b/pydgraph/util.py index 63825192..e694065b 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from pydgraph.meta import VERSION __author__ = 'Shailesh Kochhar ' @@ -28,6 +30,12 @@ def merge_lin_reads(target, src): target_ids = target.ids target_ids_get = target_ids.get - for (key, src_value) in src.ids.items(): + for key, src_value in src.ids.items(): if target_ids_get(key, 0) <= src_value: target_ids[key] = src_value + +def is_string(s): + if sys.version_info[0] < 3: + return isinstance(s, basestring) + else: + return isinstance(s, str) From 52c3b2c3225730f1e734592f9dc20e45cf35559b Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 12:09:32 +0530 Subject: [PATCH 052/435] Update maintainer --- pydgraph/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 67665767..a6f5a90d 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -20,7 +20,7 @@ from pydgraph.proto import api_pb2 as api, api_pb2_grpc as api_grpc __author__ = 'Mohit Ranka ' -__maintainer__ = 'Mohit Ranka ' +__maintainer__ = 'Garvit Pahal ' __version__ = VERSION __status__ = 'development' From c401459d9084271179b2604de0e37dc10e78e185 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 12:18:25 +0530 Subject: [PATCH 053/435] Pass options to insecure client --- pydgraph/client_stub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 8bce2196..15550666 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -28,7 +28,7 @@ class DgraphClientStub(object): def __init__(self, addr="localhost:9080", credentials=None, options=None): if credentials is None: - self.channel = grpc.insecure_channel(addr) + self.channel = grpc.insecure_channel(addr, options) else: self.channel = grpc.secure_channel(addr, credentials, options) From 58dff244b836855abebff8cc6351a484a0acc059 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 12:19:23 +0530 Subject: [PATCH 054/435] Update error message --- pydgraph/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index a6f5a90d..c9716522 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -34,7 +34,7 @@ class DgraphClient(object): def __init__(self, *clients): if len(clients) == 0: - raise ValueError('no clients provided in DgraphClient constructor') + raise ValueError('No clients provided in DgraphClient constructor') self._clients = [*clients] self._lin_read = api.LinRead() From 8a6f2a7a0e0d74a992fadb6fe444da9c58d26440 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 12:22:11 +0530 Subject: [PATCH 055/435] Fix Txn.merge_context --- pydgraph/txn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 9b678697..95a0becd 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -183,7 +183,7 @@ def merge_context(self, src=None): return util.merge_lin_reads(self._ctx.lin_read, src.lin_read) - self._client.merge_lin_reads(src.lin_read) + self._dc.merge_lin_reads(src.lin_read) if self._ctx.start_ts == 0: self._ctx.start_ts = src.start_ts @@ -191,4 +191,4 @@ def merge_context(self, src=None): # This condition should never be true. raise Exception('StartTs mismatch') - self.keys = src.keys[:] + self._ctx.keys = src.keys[:] From 442e4645ee7415cdc4b40ceacc91c8189df076cc Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 12:35:04 +0530 Subject: [PATCH 056/435] Add comments to mutate methods regarding discard call --- pydgraph/txn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 95a0becd..74ec3e90 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -80,6 +80,7 @@ def mutate(self, mu, timeout=None, metadata=None, credentials=None): try: self.discard(timeout=timeout, metadata=metadata, credentials=credentials) except: + # Ignore error - user should see the original error. pass self._common_except_mutate(e, timeout=timeout, metadata=metadata, credentials=credentials) @@ -95,6 +96,7 @@ async def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): try: await self.async_discard(timeout=timeout, metadata=metadata, credentials=credentials) except: + # Ignore error - user should see the original error. pass self._common_except_mutate(e, timeout=timeout, metadata=metadata, credentials=credentials) @@ -150,7 +152,7 @@ def _common_except_commit(self, e): status_code = e.code() if status_code == grpc.StatusCode.ABORTED: raise Exception('Transaction has been aborted. Please retry') - + raise e def discard(self, timeout=None, metadata=None, credentials=None): From 603c151a4492ecb6d114e703254d1561a0abec8d Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 14:19:00 +0530 Subject: [PATCH 057/435] Add test_util.py --- pydgraph/util.py | 2 ++ tests/helper.py | 39 ++++++++++++++++++++++ tests/test_util.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 tests/helper.py create mode 100644 tests/test_util.py diff --git a/pydgraph/util.py b/pydgraph/util.py index e694065b..9a96f783 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -34,6 +34,8 @@ def merge_lin_reads(target, src): if target_ids_get(key, 0) <= src_value: target_ids[key] = src_value + return target + def is_string(s): if sys.version_info[0] < 3: return isinstance(s, basestring) diff --git a/tests/helper.py b/tests/helper.py new file mode 100644 index 00000000..85699ded --- /dev/null +++ b/tests/helper.py @@ -0,0 +1,39 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' + +from pydgraph.proto import api_pb2 as api + +def create_lin_read(ids): + lr = api.LinRead() + ids = lr.ids + for key, value in ids: + ids[key] = value + + return lr + +def are_lin_reads_equal(a, b): + aIds = a.ids + bIds = b.ids + + if len(aIds) != len(bIds): + return False + + for key in aIds.items(): + if key not in bIds: + return False + + return True diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 00000000..7c566086 --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,81 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' + +import unittest +import grpc + +from pydgraph import util +from pydgraph.proto import api_pb2 as api + +from . import helper + + +class TestMergeLinReads(unittest.TestCase): + def common_test(self, lr1, lr2, expected): + self.assertTrue(helper.are_lin_reads_equal(util.merge_lin_reads(lr1, lr2), expected)) + self.assertTrue(helper.are_lin_reads_equal(lr1, expected)) + + def test_simple(self): + lr1 = helper.create_lin_read({1: 1}) + lr2 = helper.create_lin_read({2: 2, 3: 3}) + res = helper.create_lin_read({1: 1, 2: 2, 3: 3}) + self.common_test(lr1, lr2, res) + + def test_lower_value(self): + lr1 = helper.create_lin_read({1: 2}) + lr2 = helper.create_lin_read({1: 1}) + res = helper.create_lin_read({1: 2}) + self.common_test(lr1, lr2, res) + + def test_higher_value(self): + lr1 = helper.create_lin_read({1: 1}) + lr2 = helper.create_lin_read({1: 2}) + res = helper.create_lin_read({1: 2}) + self.common_test(lr1, lr2, res) + + def test_equal_value(self): + lr1 = helper.create_lin_read({1: 1}) + lr2 = helper.create_lin_read({1: 1}) + res = helper.create_lin_read({1: 1}) + self.common_test(lr1, lr2, res) + + def test_none(self): + lr1 = helper.create_lin_read({1: 1}) + lr2 = None + res = helper.create_lin_read({1: 1}) + self.common_test(lr1, lr2, res) + + def test_no_src_ids(self): + lr1 = helper.create_lin_read({1: 1}) + lr2 = api.LinRead() + res = helper.create_lin_read({1: 1}) + self.common_test(lr1, lr2, res) + + def test_no_target_ids(self): + lr1 = api.LinRead() + lr2 = helper.create_lin_read({1: 1}) + res = helper.create_lin_read({1: 1}) + self.common_test(lr1, lr2, res) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(TestMergeLinReads()) + return suite + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) From ce334d0681fca96b4af980b51a8bad89af478572 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 14:31:24 +0530 Subject: [PATCH 058/435] Add more tests to test_util.py --- tests/test_util.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 7c566086..f7bb850d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -16,7 +16,7 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import grpc +import sys from pydgraph import util from pydgraph.proto import api_pb2 as api @@ -29,7 +29,7 @@ def common_test(self, lr1, lr2, expected): self.assertTrue(helper.are_lin_reads_equal(util.merge_lin_reads(lr1, lr2), expected)) self.assertTrue(helper.are_lin_reads_equal(lr1, expected)) - def test_simple(self): + def test_disjoint(self): lr1 = helper.create_lin_read({1: 1}) lr2 = helper.create_lin_read({2: 2, 3: 3}) res = helper.create_lin_read({1: 1, 2: 2, 3: 3}) @@ -71,6 +71,16 @@ def test_no_target_ids(self): res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) +class TestIsString(unittest.TestCase): + def test_is_string(self): + self.assertTrue(util.is_string('')) + self.assertTrue(util.is_string('a')) + self.assertFalse(util.is_string(object())) + self.assertFalse(util.is_string({})) + + if sys.version_info[0] < 3: + self.assertTrue(util.is_string(unicode('a'))) + def suite(): suite = unittest.TestSuite() suite.addTest(TestMergeLinReads()) From 1de597f2489b307c3375e0b64b95a27fea7e53d1 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 14:41:06 +0530 Subject: [PATCH 059/435] Remove python v2.7, v3.3 and v3.4 as they don't support async/await --- pydgraph/client.py | 2 +- pydgraph/util.py | 5 +---- setup.py | 3 --- tests/test_util.py | 3 --- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index c9716522..7d67987d 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -36,7 +36,7 @@ def __init__(self, *clients): if len(clients) == 0: raise ValueError('No clients provided in DgraphClient constructor') - self._clients = [*clients] + self._clients = clients[:] self._lin_read = api.LinRead() def alter(self, op, timeout=None, metadata=None, credentials=None): diff --git a/pydgraph/util.py b/pydgraph/util.py index 9a96f783..97434e72 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -37,7 +37,4 @@ def merge_lin_reads(target, src): return target def is_string(s): - if sys.version_info[0] < 3: - return isinstance(s, basestring) - else: - return isinstance(s, str) + return isinstance(s, str) diff --git a/setup.py b/setup.py index 9f76a46d..0d4e1688 100755 --- a/setup.py +++ b/setup.py @@ -37,9 +37,6 @@ 'Operating System :: OS Independent', 'Topic :: Database', 'Topic :: Software Development', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], diff --git a/tests/test_util.py b/tests/test_util.py index f7bb850d..9b9475a8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -78,9 +78,6 @@ def test_is_string(self): self.assertFalse(util.is_string(object())) self.assertFalse(util.is_string({})) - if sys.version_info[0] < 3: - self.assertTrue(util.is_string(unicode('a'))) - def suite(): suite = unittest.TestSuite() suite.addTest(TestMergeLinReads()) From eb66cff43f793451df6495d7b5bc2e9a5308b7f7 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 15:12:37 +0530 Subject: [PATCH 060/435] Fix client.py and make async functions use async def --- pydgraph/client.py | 2 +- pydgraph/client_stub.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 7d67987d..922a1b9d 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -43,7 +43,7 @@ def alter(self, op, timeout=None, metadata=None, credentials=None): return self.any_client().alter(op, timeout=timeout, metadata=metadata, credentials=credentials) async def async_alter(self, op, timeout=None, metadata=None, credentials=None): - return self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) + return await self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) def txn(self): return txn.Txn(self) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 15550666..a873b92d 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -37,29 +37,29 @@ def __init__(self, addr="localhost:9080", credentials=None, options=None): def alter(self, op, timeout=None, metadata=None, credentials=None): return self.stub.Alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - def async_alter(self, op, timeout=None, metadata=None, credentials=None): - return self.stub.Alter.future(op, timeout=timeout, metadata=metadata, credentials=credentials) + async def async_alter(self, op, timeout=None, metadata=None, caredentials=None): + return await self.stub.Alter.future(op, timeout=timeout, metadata=metadata, credentials=credentials) def query(self, req, timeout=None, metadata=None, credentials=None): return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) - def async_query(self, req, timeout=None, metadata=None, credentials=None): - return self.stub.Query.future(req, timeout=timeout, metadata=metadata, credentials=credentials) + async def async_query(self, req, timeout=None, metadata=None, credentials=None): + return await self.stub.Query.future(req, timeout=timeout, metadata=metadata, credentials=credentials) def mutate(self, mu, timeout=None, metadata=None, credentials=None): return self.stub.Mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) - def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): - return self.stub.Mutate.future(mu, timeout=timeout, metadata=metadata, credentials=credentials) + async def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): + return await self.stub.Mutate.future(mu, timeout=timeout, metadata=metadata, credentials=credentials) def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): return self.stub.CommitOrAbort(ctx, timeout=timeout, metadata=metadata, credentials=credentials) - def async_commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): - return self.stub.CommitOrAbort.future(ctx, timeout=timeout, metadata=metadata, credentials=credentials) + async def async_commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): + return await self.stub.CommitOrAbort.future(ctx, timeout=timeout, metadata=metadata, credentials=credentials) def check_version(self, check, timeout=None, metadata=None, credentials=None): return self.stub.CheckVersion(check, timeout=timeout, metadata=metadata, credentials=credentials) - def async_check_version(self, check, timeout=None, metadata=None, credentials=None): - return self.stub.CheckVersion.future(check, timeout=timeout, metadata=metadata, credentials=credentials) + async def async_check_version(self, check, timeout=None, metadata=None, credentials=None): + return await self.stub.CheckVersion.future(check, timeout=timeout, metadata=metadata, credentials=credentials) From 3e97fd90eac22f566b55e1d305d8a3f65527d90f Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 16 Mar 2018 15:13:27 +0530 Subject: [PATCH 061/435] Fix spelling mistake --- pydgraph/client_stub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index a873b92d..e7b42ceb 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -37,7 +37,7 @@ def __init__(self, addr="localhost:9080", credentials=None, options=None): def alter(self, op, timeout=None, metadata=None, credentials=None): return self.stub.Alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_alter(self, op, timeout=None, metadata=None, caredentials=None): + async def async_alter(self, op, timeout=None, metadata=None, credentials=None): return await self.stub.Alter.future(op, timeout=timeout, metadata=metadata, credentials=credentials) def query(self, req, timeout=None, metadata=None, credentials=None): From 69848f0955b82fb69dcd37f9e400830f9623f529 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 22 Mar 2018 13:58:16 +0530 Subject: [PATCH 062/435] Add tests for client and client util --- tests/helper.py | 20 +++++++++++++++++ tests/test_client.py | 35 +++++++++++++++++++++++++++++ tests/test_client_stub.py | 46 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 tests/test_client.py create mode 100644 tests/test_client_stub.py diff --git a/tests/helper.py b/tests/helper.py index 85699ded..93ad6793 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -15,6 +15,10 @@ __author__ = 'Garvit Pahal ' __maintainer__ = 'Garvit Pahal ' +import grpc + +from pydgraph.client_stub import DgraphClientStub +from pydgraph.client import DgraphClient from pydgraph.proto import api_pb2 as api def create_lin_read(ids): @@ -37,3 +41,19 @@ def are_lin_reads_equal(a, b): return False return True + +SERVER_ADDR = "localhost:9080" + +def createClient(): + return DgraphClient(DgraphClientStub(SERVER_ADDR)) + +def setSchema(client, schema): + return client.alter(api.Operation(schema=schema)) + +def dropAll(client): + return client.alter(api.Operation(drop_all=True)) + +def setup(): + client = createClient() + dropAll(client) + return client diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 00000000..a14ab925 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,35 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' + +import unittest + +from pydgraph.client import DgraphClient + + +class TestDgraphClient(unittest.TestCase): + def test_constructor(self): + with self.assertRaises(ValueError): + DgraphClient() + +def suite(): + suite = unittest.TestSuite() + suite.addTest(TestDgraphClient()) + return suite + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py new file mode 100644 index 00000000..3fcaf089 --- /dev/null +++ b/tests/test_client_stub.py @@ -0,0 +1,46 @@ +# Copyright 2018 DGraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' + +import unittest + +from pydgraph.client_stub import DgraphClientStub +from pydgraph.proto import api_pb2 as api + + +class TestDgraphClientStub(unittest.TestCase): + def validate_version_object(self, version): + tag = version.tag + self.assertIsInstance(tag, str) + + def check_version(self, stub): + self.validate_version_object(stub.check_version(api.Check())) + + def test_constructor(self): + self.check_version(DgraphClientStub()) + + def test_timeout(self): + with self.assertRaises(Exception): + DgraphClientStub().check_version(api.Check(), timeout=-1) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(TestDgraphClientStub()) + return suite + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()) From a2ff811166080a84c8885f68a22acff392fbc2ce Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 22 Mar 2018 14:19:01 +0530 Subject: [PATCH 063/435] Move integration test base class to helper.py --- pydgraph/client.py | 1 + tests/helper.py | 31 ++++++++++++++++++++++++++++--- tests/test_acct_upsert.py | 22 ---------------------- tests/test_txn.py | 25 ++++++++++++------------- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 922a1b9d..1e5c20a4 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -14,6 +14,7 @@ import grpc import random +import json from pydgraph import txn, util from pydgraph.meta import VERSION diff --git a/tests/helper.py b/tests/helper.py index 93ad6793..753dceb3 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -15,6 +15,7 @@ __author__ = 'Garvit Pahal ' __maintainer__ = 'Garvit Pahal ' +import unittest import grpc from pydgraph.client_stub import DgraphClientStub @@ -42,10 +43,10 @@ def are_lin_reads_equal(a, b): return True -SERVER_ADDR = "localhost:9080" +SERVER_ADDR = 'localhost:9080' -def createClient(): - return DgraphClient(DgraphClientStub(SERVER_ADDR)) +def createClient(addr = SERVER_ADDR): + return DgraphClient(DgraphClientStub(addr)) def setSchema(client, schema): return client.alter(api.Operation(schema=schema)) @@ -57,3 +58,27 @@ def setup(): client = createClient() dropAll(client) return client + + +class ClientIntegrationTestCase(unittest.TestCase): + """Base class for other integration test cases. Provides a client object + with a connection to the dgraph server and ensures that the server is + v1.0 or greater. + """ + + TEST_SERVER_ADDR = SERVER_ADDR + + def setUp(self): + """Sets up the client and verifies the version is compatible.""" + + self.client = createClient(self.TEST_SERVER_ADDR) + version = self.client.any_client().check_version(api.Check()); + + # version.tag string format is v.. + # version_tup = [MAJOR, MINOR, PATCH] + version_tup = version.tag[1:].split('.') + + version_supported = int(version_tup[0]) > 0 + self.assertTrue( + version_supported, + 'Dgraph server version must be >= v1.0.0, got %s' % version.tag) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 38716371..2ec74c4d 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -26,28 +26,6 @@ from pydgraph import client -class DgraphClientIntegrationTestCase(unittest.TestCase): - """Base class for other integration test cases. Provides a client object - with a connection to the dgraph server and ensures that the server is - 0.9 or greater. - """ - TEST_HOSTNAME = 'localhost' - TEST_PORT = 9080 - - def setUp(self): - """Sets up the client and verifies the version is compatible.""" - self.client = client.DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) - version = self.client.check() - # version.tag string format is v.. - # version_tup = [MAJOR, MINOR, PATCH] - version_tup = version.tag[1:].split('.') - - version_supported = (int(version_tup[0]) > 0 or - (int(version_tup[0]) == 0 and int(version_tup[1]) >= 9)) - self.assertTrue(version_supported, - 'Server version %s must be > 0.9' % version.tag) - - class AcountUpsertIntegrationTestCase(DgraphClientIntegrationTestCase): """Account upsert integration test.""" diff --git a/tests/test_txn.py b/tests/test_txn.py index 751bec62..5acfa5a7 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -11,37 +11,35 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -test_txn.py - -implements test case for transactions -""" +import unittest import grpc import json import logging import os import random import time -import unittest -from pydgraph import client -from . import test_acct_upsert as integ +from pydgraph.client import DgraphClient +from pydgraph.proto import api_pb2 as api +from . import helper -class TestClientTxns(integ.DgraphClientIntegrationTestCase): - """Transactions test cases.""" + +class TestTxn(helper.ClientIntegrationTestCase): + """Transaction test cases.""" def setUp(self): """Drops all the existing schema and creates schema for tests.""" - super(TestClientTxns, self).setUp() + super(TestTxn, self).setUp() - _ = self.client.drop_all() - _ = self.client.alter(schema="""name: string @index(fulltext) .""") + helper.dropAll(self.client) + helper.setSchema(self.client, 'name: string @index(fulltext) .') def test_TxnReadAtStartTs(self): """Tests read after write when readTs == startTs""" txn = self.client.txn() + assigned = txn.mutate_obj({"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") @@ -59,6 +57,7 @@ def test_TxnReadAtStartTs(self): def test_TxnReadBeforeStartTs(self): """Tests read before write when readTs < startTs""" txn = self.client.txn() + api.Mutation() assigned = txn.mutate_obj({"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") From 031868a2671586a0c6ca7c63b7d1e27aed4dadf9 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 22 Mar 2018 15:28:38 +0530 Subject: [PATCH 064/435] Fix bugs and tests --- pydgraph/client.py | 17 ++++--- pydgraph/txn.py | 44 ++++++++++++++---- tests/helper.py | 12 ++--- tests/test_queries.py | 101 +++++++++++------------------------------- tests/test_txn.py | 79 +++++++++++++++++---------------- 5 files changed, 116 insertions(+), 137 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 1e5c20a4..acaf4b72 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -14,7 +14,6 @@ import grpc import random -import json from pydgraph import txn, util from pydgraph.meta import VERSION @@ -45,17 +44,21 @@ def alter(self, op, timeout=None, metadata=None, credentials=None): async def async_alter(self, op, timeout=None, metadata=None, credentials=None): return await self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) + + def query(self, q, vars=None, timeout=None, metadata=None, credentials=None): + return self.txn().query(q, vars=vars, timeout=timeout, metadata=metadata, credentials=credentials) + + async def async_query(self, q, vars=None, timeout=None, metadata=None, credentials=None): + return self.txn().async_query(q, vars=vars, timeout=timeout, metadata=metadata, credentials=credentials) def txn(self): return txn.Txn(self) - def get_lin_read(self): - lr = api.LinRead() - ids = lr.ids + def set_lin_read(self, ctx): + ctx_lr_ids = ctx.lin_read.ids + ids = self._lin_read.ids for key, value in ids.items(): - ids[key] = value - - return lr + ctx_lr_ids[key] = value def merge_lin_reads(self, src): util.merge_lin_reads(self._lin_read, src) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 74ec3e90..8fd528b5 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -43,7 +43,7 @@ class Txn(object): def __init__(self, client): self._dc = client self._ctx = api.TxnContext() - self._ctx.lin_read = self._dc.get_lin_read() + self._dc.set_lin_read(self._ctx) self._finished = False self._mutated = False @@ -72,8 +72,12 @@ def _common_query(self, q, vars=None): return req - def mutate(self, mu, timeout=None, metadata=None, credentials=None): - mu = self._common_mutate(mu) + def mutate( + self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, + timeout=None, metadata=None, credentials=None): + mu = self._common_mutate( + mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, + ignore_index_conflict=ignore_index_conflict) try: ag = self._dc.any_client().mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) except Exception as e: @@ -88,8 +92,12 @@ def mutate(self, mu, timeout=None, metadata=None, credentials=None): self.merge_context(ag.context) return ag - async def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): - mu = self._common_mutate(mu) + async def async_mutate( + self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, + timeout=None, metadata=None, credentials=None): + mu = self._common_mutate( + mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, + ignore_index_conflict=ignore_index_conflict) try: ag = await self._dc.any_client().async_mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) except Exception as e: @@ -104,7 +112,20 @@ async def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): self.merge_context(ag.context) return ag - def _common_mutate(self, mu): + def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None): + if not mu: + mu = api.Mutation() + if set_obj: + mu.set_json = json.dumps(set_obj).encode('utf8') + if del_obj: + mu.delete_json = json.dumps(del_obj).encode('utf8') + if set_nquads: + mu.set_nquads = set_nquads.encode('utf8') + if del_nquads: + mu.del_nquads = del_nquads.encode('utf8') + if ignore_index_conflict: + mu.ignore_index_conflict = True + if self._finished: raise Exception('Transaction has already been committed or discarded') @@ -117,7 +138,7 @@ def _common_except_mutate(self, e, timeout=None, metadata=None, credentials=None e.details() status_code = e.code() if status_code == grpc.StatusCode.ABORTED or status_code == grpc.StatusCode.FAILED_PRECONDITION: - raise Exception('Transaction has been aborted. Please retry') + raise AbortedError() raise e @@ -151,7 +172,7 @@ def _common_except_commit(self, e): e.details() status_code = e.code() if status_code == grpc.StatusCode.ABORTED: - raise Exception('Transaction has been aborted. Please retry') + raise AbortedError() raise e @@ -193,4 +214,9 @@ def merge_context(self, src=None): # This condition should never be true. raise Exception('StartTs mismatch') - self._ctx.keys = src.keys[:] + self._ctx.keys[:] = src.keys[:] + + +class AbortedError(Exception): + def __init__(self): + super(AbortedError, self).__init__('Transaction has been aborted. Please retry') \ No newline at end of file diff --git a/tests/helper.py b/tests/helper.py index 753dceb3..b04077e4 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -45,18 +45,18 @@ def are_lin_reads_equal(a, b): SERVER_ADDR = 'localhost:9080' -def createClient(addr = SERVER_ADDR): +def create_client(addr = SERVER_ADDR): return DgraphClient(DgraphClientStub(addr)) -def setSchema(client, schema): +def set_schema(client, schema): return client.alter(api.Operation(schema=schema)) -def dropAll(client): +def drop_all(client): return client.alter(api.Operation(drop_all=True)) def setup(): - client = createClient() - dropAll(client) + client = create_client() + drop_all(client) return client @@ -71,7 +71,7 @@ class ClientIntegrationTestCase(unittest.TestCase): def setUp(self): """Sets up the client and verifies the version is compatible.""" - self.client = createClient(self.TEST_SERVER_ADDR) + self.client = create_client(self.TEST_SERVER_ADDR) version = self.client.any_client().check_version(api.Check()); # version.tag string format is v.. diff --git a/tests/test_queries.py b/tests/test_queries.py index cb77bb01..46d31c60 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -13,95 +13,44 @@ # limitations under the License. __author__ = 'Mohit Ranka ' -__maintainer__ = 'Mohit Ranka ' +__maintainer__ = 'Garvit Pahal ' import unittest -from pydgraph.client import DgraphClient +import json +from pydgraph.client import DgraphClient +from pydgraph.proto import api_pb2 as api -class DgraphClientTestCases(unittest.TestCase): - TEST_HOSTNAME = 'localhost' - TEST_PORT = 8081 +from . import helper +class TestQueries(helper.ClientIntegrationTestCase): def setUp(self): - self.client = DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) + super(TestQueries, self).setUp() - def test_mutation_and_query(self): - query_string = """ - mutation - { - set - { - \"Alice\" . - \"Greg\" . - . - } - } + helper.drop_all(self.client) + helper.set_schema(self.client, 'name: string @index(term) .') - query - { - me(_xid_: alice) + def test_mutation_and_query(self): + txn = self.client.txn() + assigned = txn.mutate(api.Mutation(commit_now=True), set_nquads=""" + <_:alice> \"Alice\" . + <_:greg> \"Greg\" . + <_:alice> <_:greg> . + """) + + query_string = """{ + me(func: anyofterms(name, "Alice")) { + name follows { - name _xid_ - } - } - } - """ - response = self.client.query(query_string) - self.assertEqual(response.n.children[0].xid, "greg") - self.assertEqual(response.n.children[0].attribute, "follows") - self.assertEqual(response.n.children[0].properties[0].prop, "name") - self.assertEqual(response.n.children[0].properties[0].val, "Greg") - self.assertTrue(isinstance(response.l.parsing, basestring), 'Parsing latency is not available') - self.assertTrue(isinstance(response.l.pb, basestring), 'Protocol buffers latency is not available') - self.assertTrue(isinstance(response.l.processing, basestring), 'Processing latency is not available') - - def test_mutation_and_query_two_levels(self): - query_string = """ - mutation - { - set - { - \"Bob\" . - \"Josh\" . - \"Rose\" . - . - . - } - } - - query - { - me(_xid_: bob) - { - name _xid_ follows - { - name _xid_ follows - { - name _xid_ - } + name } } } """ response = self.client.query(query_string) - - self.assertEqual(len(response.n.children), 1) - self.assertEqual(response.n.children[0].xid, "josh") - self.assertEqual(response.n.children[0].attribute, "follows") - self.assertEqual(response.n.children[0].properties[0].prop, "name") - self.assertEqual(response.n.children[0].properties[0].val, "Josh") - - self.assertEqual(len(response.n.children[0].children), 1) - self.assertEqual(response.n.children[0].children[0].xid, "rose") - self.assertEqual(response.n.children[0].children[0].attribute, "follows") - self.assertEqual(response.n.children[0].children[0].properties[0].prop, "name") - self.assertEqual(response.n.children[0].children[0].properties[0].val, "Rose") - - - self.assertTrue(isinstance(response.l.parsing, basestring), 'Parsing latency is not available') - self.assertTrue(isinstance(response.l.pb, basestring), 'Protocol buffers latency is not available') - self.assertTrue(isinstance(response.l.processing, basestring), 'Processing latency is not available') - + self.assertEqual([{"name": "Alice", "follows": [{"name": "Greg"}]}], json.loads(response.json).get("me")) + self.assertTrue(isinstance(response.latency.parsing_ns, int), 'Parsing latency is not available') + self.assertTrue(isinstance(response.latency.processing_ns, int), 'Processing latency is not available') + self.assertTrue(isinstance(response.latency.encoding_ns, int), 'Encoding latency is not available') diff --git a/tests/test_txn.py b/tests/test_txn.py index 5acfa5a7..42b90120 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -21,6 +21,7 @@ import time from pydgraph.client import DgraphClient +from pydgraph.txn import AbortedError from pydgraph.proto import api_pb2 as api from . import helper @@ -33,14 +34,13 @@ def setUp(self): """Drops all the existing schema and creates schema for tests.""" super(TestTxn, self).setUp() - helper.dropAll(self.client) - helper.setSchema(self.client, 'name: string @index(fulltext) .') + helper.drop_all(self.client) + helper.set_schema(self.client, 'name: string @index(fulltext) .') def test_TxnReadAtStartTs(self): """Tests read after write when readTs == startTs""" txn = self.client.txn() - - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): @@ -57,8 +57,7 @@ def test_TxnReadAtStartTs(self): def test_TxnReadBeforeStartTs(self): """Tests read before write when readTs < startTs""" txn = self.client.txn() - api.Mutation() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): @@ -77,7 +76,7 @@ def test_TxnReadBeforeStartTs(self): def test_TxnReadAfterStartTs(self): """Tests read after commiting a write when readTs > startTs""" txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): @@ -97,7 +96,7 @@ def test_TxnReadBeforeAndAfterStartTs(self): """Test read before and after commiting a transaction when readTs1 < startTs and readTs2 > startTs""" txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): @@ -107,7 +106,7 @@ def test_TxnReadBeforeAndAfterStartTs(self): txn2 = self.client.txn() # start a new txn and mutate the object txn3 = self.client.txn() - assigned = txn3.mutate_obj({"uid": uid, "name": "Manish2"}) + assigned = txn3.mutate(set_obj={"uid": uid, "name": "Manish2"}) query = """{{ me(func: uid("{uid:s}")) {{ @@ -128,14 +127,14 @@ def test_TxnReadBeforeAndAfterStartTs(self): def test_ReadFromNewClient(self): """Tests committed reads from a new client with startTs == 0.""" txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): uid = uid _ = txn.commit() - client2 = client.DgraphClient(self.TEST_HOSTNAME, self.TEST_PORT) + client2 = helper.create_client(self.TEST_SERVER_ADDR) query = """{{ me(func: uid("{uid:s}")) {{ name @@ -146,7 +145,7 @@ def test_ReadFromNewClient(self): self.assertTrue(resp2.txn.start_ts > 0) txn2 = client2.txn() - assigned = txn2.mutate_obj({"uid": uid, "name": "Manish2"}) + assigned = txn2.mutate(set_obj={"uid": uid, "name": "Manish2"}) self.assertTrue(assigned.context.start_ts > 0) txn2.commit() @@ -155,18 +154,18 @@ def test_ReadFromNewClient(self): def test_Conflict(self): """Tests committing two transactions which conflict.""" - _ = self.client.drop_all() + helper.drop_all(self.client) txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) for _, uid in assigned.uids.items(): uid = uid txn2 = self.client.txn() - assigned2 = txn2.mutate_obj({"uid": uid, "name": "Manish"}) + assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Manish"}) txn.commit() - self.assertRaises(grpc._channel._Rendezvous, txn2.commit) + self.assertRaises(AbortedError, txn2.commit) txn3 = self.client.txn() query = """{{ @@ -181,7 +180,7 @@ def test_ConflictReverseOrder(self): """Tests committing a transaction after a newer transaction has been committed.""" txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) for _, uid in assigned.uids.items(): uid = uid @@ -194,9 +193,9 @@ def test_ConflictReverseOrder(self): resp = txn2.query(query) self.assertEqual([], json.loads(resp.json).get("me")) - assigned2 = txn2.mutate_obj({"uid": uid, "name": "Jan the man"}) + assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Jan the man"}) txn2.commit() - self.assertRaises(grpc._channel._Rendezvous, txn.commit) + self.assertRaises(AbortedError, txn.commit) txn3 = self.client.txn() resp = txn3.query(query) @@ -205,19 +204,19 @@ def test_ConflictReverseOrder(self): def test_MutationAfterCommit(self): """Tests a second mutation after failing to commit a first mutation.""" txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}) + assigned = txn.mutate(set_obj={"name": "Manish"}) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): uid = uid txn2 = self.client.txn() - assigned2 = txn2.mutate_obj({"uid": uid, "name": "Jan the man"}) + assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Jan the man"}) txn.commit() - self.assertRaises(grpc._channel._Rendezvous, txn2.commit) + self.assertRaises(AbortedError, txn2.commit) txn3 = self.client.txn() - assigned3 = txn3.mutate_obj({"uid": uid, "name": "Jan the man"}) + assigned3 = txn3.mutate(set_obj={"uid": uid, "name": "Jan the man"}) txn3.commit() query = """{{ @@ -232,13 +231,13 @@ def test_MutationAfterCommit(self): def test_ConflictIgnore(self): """Tests a mutation with ignore index conflict.""" txn = self.client.txn() - assigned1 = txn.mutate_obj({"name": "Manish"}, ignore_index_conflict=True) + assigned1 = txn.mutate(set_obj={"name": "Manish"}, ignore_index_conflict=True) self.assertEqual(1, len(assigned1.uids), "Nothing was assigned") for _, uid in assigned1.uids.items(): uid1 = uid txn2 = self.client.txn() - assigned2 = txn2.mutate_obj({"name": "Manish"}, ignore_index_conflict=True) + assigned2 = txn2.mutate(set_obj={"name": "Manish"}, ignore_index_conflict=True) self.assertEqual(1, len(assigned2.uids), "Nothing was assigned") for _, uid in assigned2.uids.items(): uid2 = uid @@ -257,11 +256,11 @@ def test_ConflictIgnore(self): def test_ReadIndexKeySameTxn(self): """Tests reading an indexed field within a transaction.""" - _ = self.client.drop_all() - _ = self.client.alter(schema="""name: string @index(exact) .""") + helper.drop_all(self.client) + helper.set_schema(self.client, 'name: string @index(exact) .') txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish"}, ignore_index_conflict=True) + assigned = txn.mutate(set_obj={"name": "Manish"}, ignore_index_conflict=True) self.assertEqual(1, len(assigned.uids), "Nothing was assigned") for _, uid in assigned.uids.items(): uid = uid @@ -275,27 +274,27 @@ def test_ReadIndexKeySameTxn(self): self.assertEqual([{"uid": uid}], json.loads(resp.json).get("me")) -class TestSPStar(integ.DgraphClientIntegrationTestCase): - +class TestSPStar(helper.ClientIntegrationTestCase): def setUp(self): """Drops everything and sets some schema.""" super(TestSPStar, self).setUp() - _ = self.client.drop_all() - _ = self.client.alter(schema="""friend: uid .""") + + helper.drop_all(self.client) + helper.set_schema(self.client, 'friend: uid .') def test_SPStar(self): """Tests a Subj Predicate Star query.""" txn = self.client.txn() - assigned = txn.mutate_obj({"name": "Manish", "friend": [{"name": "Jan"}]}) + assigned = txn.mutate(set_obj={"name": "Manish", "friend": [{"name": "Jan"}]}) uid1 = assigned.uids["blank-0"] self.assertEqual(2, len(assigned.uids), "Expecting 2 uids to be created") txn.commit() txn2 = self.client.txn() - assigned2 = txn2.mutate_obj(delobj={"uid": uid1, "friend": None}) + assigned2 = txn2.mutate(del_obj={"uid": uid1, "friend": None}) self.assertEqual(0, len(assigned2.uids)) - assigned3 = txn2.mutate_obj({ + assigned3 = txn2.mutate(set_obj={ "uid": uid1, "name": "Manish", "friend": [{"name": "Jan2"}] @@ -322,7 +321,7 @@ def test_SPStar2(self): """Second test of Subject Predicate Star""" txn = self.client.txn() # Add edge to Jan - assigned = txn.mutate_obj({"name": "Manish", "friend": [{"name": "Jan"}]}) + assigned = txn.mutate(set_obj={"name": "Manish", "friend": [{"name": "Jan"}]}) self.assertEqual(2, len(assigned.uids)) uid1, uid2 = assigned.uids["blank-0"], assigned.uids["blank-1"] query = """{{ @@ -339,15 +338,16 @@ def test_SPStar2(self): "uid": uid1, "friend": [{"name": "Jan", "uid": uid2}] }], json.loads(resp.json).get("me")) + # Delete S P * - deleted = txn.mutate_obj(delobj={"uid": uid1, "friend": None}) + deleted = txn.mutate(del_obj={"uid": uid1, "friend": None}) self.assertEqual(0, len(deleted.uids)) resp = txn.query(query) self.assertEqual([{"uid": uid1}], json.loads(resp.json).get("me")) # Add an edge to Jan2 - assigned2 = txn.mutate_obj({ + assigned2 = txn.mutate(set_obj={ "uid": uid1, "name": "Manish", "friend": [{"name": "Jan2"}] @@ -359,8 +359,9 @@ def test_SPStar2(self): "uid": uid1, "friend": [{"name": "Jan2", "uid": uid2}] }], json.loads(resp.json).get("me")) + # Delete S P * - deleted2 = txn.mutate_obj(delobj={"uid": uid1, "friend": None}) + deleted2 = txn.mutate(del_obj={"uid": uid1, "friend": None}) self.assertEqual(0, len(deleted2.uids)) resp = txn.query(query) self.assertEqual([{"uid": uid1}], json.loads(resp.json).get("me")) From e51ef77f114343ed0e2a3903ded09a5eb62ec7f2 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 22 Mar 2018 15:31:52 +0530 Subject: [PATCH 065/435] Fix test_essentials --- tests/test_essentials.py | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/tests/test_essentials.py b/tests/test_essentials.py index 763c521f..a70ef8e8 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -11,17 +11,18 @@ # limitations under the License. __author__ = 'Shailesh Kochhar ' -__maintainer__ = 'Shailesh Kochhar ' +__maintainer__ = 'Garvit Pahal ' +import unittest import grpc import json import logging -import unittest + from pydgraph import client -from . import test_acct_upsert as integ +from . import helper -class TestEssentials(integ.DgraphClientIntegrationTestCase): +class TestEssentials(helper.ClientIntegrationTestCase): """Tests the essentials of the client.""" def testMutationAfterQuery(self): @@ -30,7 +31,7 @@ def testMutationAfterQuery(self): _ = self.client.query('''{firsts(func: has(first)) { uid first }}''') txn = self.client.txn() - mutation = txn.mutate(setnquads='_:node "Node name first" .') + mutation = txn.mutate(set_nquads='_:node "Node name first" .') self.assertTrue(len(mutation.uids) > 0, "Mutation did not create new node") created = mutation.uids.get('node') self.assertIsNotNone(created) @@ -41,23 +42,6 @@ def testMutationAfterQuery(self): self.assertEqual(created, json.loads(reread.json).get('node')[0]['uid']) - def testMultipleMutationsInTxn(self): - """Test what happens when running multiple mutations in a txn.""" - txn = self.client.txn() - - for i in range(100): - m = txn.mutate('''_:node "{seq:d}" .'''.format(seq=i)) - - self.assertEqual(100, len(txn.keys), - "Expected txn to have only 100 modified keys.") - key_counter = {} - for key in txn.keys: - key_counter[key] = key_counter.setdefault(key, 0) + 1 - recurring_keys = [(k, c) for (k, c) in key_counter.items() if c > 1] - self.assertEqual(0, len(recurring_keys), - "Expected txn not to have recurring keys") - - if __name__ == '__main__': logging.basicConfig(level=logging.INFO) unittest.main() From 06a2e608834822675569f34ae12413736c194cd1 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 26 Mar 2018 16:04:39 +0530 Subject: [PATCH 066/435] Fix most common tests --- LICENSE | 27 +--------------- pydgraph/client.py | 2 +- pydgraph/txn.py | 4 +-- pydgraph/util.py | 2 +- scripts/protogen.py | 2 +- setup.py | 2 +- tests/test_acct_upsert.py | 50 ++++++++++++++++++++-------- tests/test_bank.py | 68 +++++++++++++++++++++------------------ tests/test_essentials.py | 2 ++ tests/test_queries.py | 2 +- tests/test_txn.py | 2 +- tests/test_util.py | 2 +- 12 files changed, 85 insertions(+), 80 deletions(-) diff --git a/LICENSE b/LICENSE index 8dada3ed..2bb9ad24 100755 --- a/LICENSE +++ b/LICENSE @@ -173,29 +173,4 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/pydgraph/client.py b/pydgraph/client.py index acaf4b72..8513c167 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -1,4 +1,4 @@ -# Copyright 2016 DGraph Labs, Inc. +# Copyright 2016 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 8fd528b5..14b568bd 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -177,7 +177,7 @@ def _common_except_commit(self, e): raise e def discard(self, timeout=None, metadata=None, credentials=None): - if not self._common_commit(): + if not self._common_discard(): return self._dc.any_client().commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) diff --git a/pydgraph/util.py b/pydgraph/util.py index 97434e72..984bf20c 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/scripts/protogen.py b/scripts/protogen.py index 580d1c90..40476500 100644 --- a/scripts/protogen.py +++ b/scripts/protogen.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/setup.py b/setup.py index 0d4e1688..2a0f6c45 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2016 DGraph Labs, Inc. +# Copyright 2016 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 2ec74c4d..feaa9c22 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -23,15 +23,18 @@ import multiprocessing.dummy as mpd import time import unittest -from pydgraph import client +from pydgraph.txn import AbortedError -class AcountUpsertIntegrationTestCase(DgraphClientIntegrationTestCase): +from . import helper + + +class TestAcountUpsert(helper.ClientIntegrationTestCase): """Account upsert integration test.""" def setUp(self): """Drops existing schema and loads new schema for the test.""" - super(AcountUpsertIntegrationTestCase, self).setUp() + super(TestAcountUpsert, self).setUp() self.concurrency = 5 self.firsts = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] @@ -43,13 +46,13 @@ def setUp(self): ] logging.info(len(self.accounts)) - _ = self.client.drop_all() - _ = self.client.alter(schema=""" + helper.drop_all(self.client) + helper.set_schema(self.client, """ first: string @index(term) . last: string @index(hash) . age: int @index(int) . when: int . - """) + """) def test_acount_upsert(self): """Account upsert integration. Will run upserts concurrently.""" @@ -63,8 +66,7 @@ def do_upserts(self, account_list, concurrency): retry_ctr = multiprocessing.Value('i', 0, lock=True) pool = mpd.Pool(concurrency) - updater = lambda acct: upsert_account(hostname=self.TEST_HOSTNAME, - port=self.TEST_PORT, + updater = lambda acct: upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, success_ctr=success_ctr, retry_ctr=retry_ctr) @@ -95,12 +97,16 @@ def assert_changes(self, firsts, accounts): self.assertTrue('{first}_{last}_{age}'.format(**acct) in account_set) -def upsert_account(hostname, port, account, success_ctr, retry_ctr): - c = client.DgraphClient(hostname, port) +def upsert_account(addr, account, success_ctr, retry_ctr): + c = helper.create_client(addr) q = ''' {{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid + first + last + age + when }} }}'''.format(**account) @@ -110,20 +116,22 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): logging.debug('Success: %d Retries: %d', success_ctr.value, retry_ctr.value) last_update_time = time.time() + txn = c.txn() try: - txn = c.txn() result = json.loads(txn.query(q=q).json) assert len(result['acct']) <= 1, ('Lookup of account %s found ' 'multiple accounts' % account) if not result['acct']: + if account['first'] == 'Paul': + print('creating') # Account does not exist, so create it nquads = ''' _:acct "{first}" . _:acct "{last}" . _:acct "{age}"^^ . '''.format(**account) - created = txn.mutate(setnquads=nquads) + created = txn.mutate(set_nquads=nquads) uid = created.uids.get('acct') assert uid is not None and uid != '', 'Account with uid None/""' else: @@ -135,16 +143,30 @@ def upsert_account(hostname, port, account, success_ctr, retry_ctr): updatequads = ''' <{0}> "{1:d}"^^ . '''.format(uid, int(time.time())) - updated = txn.mutate(setnquads=updatequads) + + prevresult = json.loads(txn.query(q=q).json)['acct'] + + updated = txn.mutate(set_nquads=updatequads) + txn.commit() + + # result = json.loads(c.query(q=q).json)['acct'] + # if len(result) > 1 and result[0]['first'] == 'Paul': + # print('1:', prevresult) + # print('2:', updatequads) + # print('3:', result) + # print() + with success_ctr.get_lock(): success_ctr.value += 1 # txn successful, break the loop return - except grpc._channel._Rendezvous as e: + except (AbortedError, grpc._channel._Rendezvous): with retry_ctr.get_lock(): retry_ctr.value += 1 # txn failed, retry the loop + finally: + txn.discard() if __name__ == '__main__': diff --git a/tests/test_bank.py b/tests/test_bank.py index 3e0e310e..e8b75874 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -1,9 +1,18 @@ -""" -test_bank.py +# Copyright 2018 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -implements test case for running transfer transactions between bank -accounts. runs concurrent writers running multiple transactions in parallel. -""" +import unittest import grpc import json import logging @@ -12,22 +21,22 @@ import os import random import time -import unittest -from pydgraph import client -from . import test_acct_upsert as integ +from pydgraph.client import DgraphClient +from pydgraph.txn import AbortedError + +from . import helper USERS = 100 CONCURRENCY = 10 XFER_COUNT = 1000 - -class TestBankXfer(integ.DgraphClientIntegrationTestCase): +class TestBank(helper.ClientIntegrationTestCase): """Bank transfer integration test.""" def setUp(self): """Drops existing schema and sets up schema for new test.""" - super(TestBankXfer, self).setUp() + super(TestBank, self).setUp() self.concurrency = CONCURRENCY self.accounts = [ {'bal': 100} for _ in range(USERS) @@ -45,7 +54,7 @@ def test_bank_xfer(self): success_ctr = mp.Value('i', 0, lock=True) retry_ctr = mp.Value('i', 0, lock=True) pool = mpd.Pool(self.concurrency) - results = [pool.apply_async(run_xfers, (self.TEST_HOSTNAME, self.TEST_PORT, + results = [pool.apply_async(run_xfers, (self.TEST_SERVER_ADDR, XFER_COUNT, self.uids, success_ctr, retry_ctr)) for _ in range(self.concurrency)] @@ -56,11 +65,11 @@ def test_bank_xfer(self): def create_accounts(self): """Creates the default set of accounts.""" - _ = self.client.drop_all() - _ = self.client.alter(schema="""bal: int .""") + helper.drop_all(self.client) + helper.set_schema(self.client, 'bal: int .') txn = self.client.txn() - assigned = txn.mutate_obj(setobj=self.accounts) + assigned = txn.mutate(set_obj=self.accounts) txn.commit() self.uids.extend(assigned.uids.values()) logging.debug('Created %d accounts', len(assigned.uids)) @@ -81,7 +90,6 @@ def _looper(): return _looper - def run_total(c, account_uids): """Calculates the total ammount in the accounts.""" q = """{{ @@ -93,16 +101,15 @@ def run_total(c, account_uids): }} }} """.format(uids='", "'.join(account_uids)) - resp = c.query(q=q) + resp = c.query(q) total = json.loads(resp.json)['total'] - logging.info("Response: %s", total) + logging.info('Response: %s', total) assert total[0]['bal'] == 10000 - -def run_xfers(hostname, port, xfer_count, account_ids, success_ctr, retry_ctr): +def run_xfers(addr, xfer_count, account_ids, success_ctr, retry_ctr): pname = mpd.current_process().name log = logging.getLogger('test_bank.run_txfers[%s]' % (pname,)) - c = client.DgraphClient(hostname, port) + c = helper.create_client(addr) while True: from_acc, to_acc = select_account_pair(account_ids) @@ -113,10 +120,10 @@ def run_xfers(hostname, port, xfer_count, account_ids, success_ctr, retry_ctr): }} }}""".format(uid1=from_acc, uid2=to_acc) txn = c.txn() - accounts = load_from_query(txn, query, 'me') - accounts[0]['bal'] += 5 - accounts[1]['bal'] -= 5 try: + accounts = load_from_query(txn, query, 'me') + accounts[0]['bal'] += 5 + accounts[1]['bal'] -= 5 dump_from_obj(txn, accounts) with success_ctr.get_lock(): success_ctr.value += 1 @@ -125,11 +132,12 @@ def run_xfers(hostname, port, xfer_count, account_ids, success_ctr, retry_ctr): log.info('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) if success_ctr.value >= xfer_count: break - except grpc._channel._Rendezvous as e: - logging.warn(e) + except (AbortedError, grpc._channel._Rendezvous): with retry_ctr.get_lock(): retry_ctr.value += 1 - + + with success_ctr.get_lock(), retry_ctr.get_lock(): + log.info("success: %d, retries: %d", success_ctr.value, retry_ctr.value) def select_account_pair(accounts): """Selects a pair of accounts at random from accounts ensuring they are not @@ -140,15 +148,13 @@ def select_account_pair(accounts): if not from_acc == to_acc: return (from_acc, to_acc) - def load_from_query(txn, query, field): """Loads a field from the results of a query executed in a txn.""" - resp = txn.query(q=query) + resp = txn.query(query) return json.loads(resp.json)[field] - def dump_from_obj(txn, obj, commit=False): - assigned = txn.mutate_obj(setobj=obj) + assigned = txn.mutate(set_obj=obj) if not commit: return assigned diff --git a/tests/test_essentials.py b/tests/test_essentials.py index a70ef8e8..464552de 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -1,3 +1,5 @@ +# Copyright 2018 Dgraph Labs, Inc. +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_queries.py b/tests/test_queries.py index 46d31c60..c9c0a43e 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -1,4 +1,4 @@ -# Copyright 2016 DGraph Labs, Inc. +# Copyright 2016 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_txn.py b/tests/test_txn.py index 42b90120..77cfd284 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -1,4 +1,4 @@ -# Copyright 2016 DGraph Labs, Inc. +# Copyright 2016 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_util.py b/tests/test_util.py index 9b9475a8..89be5446 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 5a40870f3d40d68d7412f107f823e9c1f3d53a65 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 26 Mar 2018 16:05:59 +0530 Subject: [PATCH 067/435] Update copyright notice --- pydgraph/client_stub.py | 2 +- pydgraph/meta.py | 2 +- tests/helper.py | 2 +- tests/test_acct_upsert.py | 2 +- tests/test_client.py | 2 +- tests/test_client_stub.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index e7b42ceb..85525938 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pydgraph/meta.py b/pydgraph/meta.py index fc92841e..be7993e3 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/helper.py b/tests/helper.py index b04077e4..2f469009 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index feaa9c22..4dfcd28c 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -1,4 +1,4 @@ -# Copyright 2016 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_client.py b/tests/test_client.py index a14ab925..227752eb 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index 3fcaf089..1c86a28d 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -1,4 +1,4 @@ -# Copyright 2018 DGraph Labs, Inc. +# Copyright 2018 Dgraph Labs, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 0749fafe47d48048e3469e31cd2be5aa6face14b Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 26 Mar 2018 16:07:59 +0530 Subject: [PATCH 068/435] Update maintainer field --- tests/test_acct_upsert.py | 2 +- tests/test_bank.py | 3 +++ tests/test_txn.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 4dfcd28c..a483c267 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -13,7 +13,7 @@ # limitations under the License. __author__ = 'Shailesh Kochhar ' -__maintainer__ = 'Shailesh Kochhar ' +__maintainer__ = 'Garvit Pahal ' import grpc import functools diff --git a/tests/test_bank.py b/tests/test_bank.py index e8b75874..083eb14a 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' + import unittest import grpc import json diff --git a/tests/test_txn.py b/tests/test_txn.py index 77cfd284..8d16e048 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' + import unittest import grpc import json From 2aff729ef4200ea913755bbde843d4f79d70bd71 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 27 Mar 2018 16:51:01 +0530 Subject: [PATCH 069/435] Fix tests and bugs --- .gitignore | 1 + pydgraph/client.py | 17 +++---- pydgraph/client_stub.py | 4 +- pydgraph/errors.py | 25 ++++++++++ pydgraph/txn.py | 96 +++++++++++++++++++-------------------- pydgraph/util.py | 3 +- tests/helper.py | 43 ++++++++++-------- tests/test_acct_upsert.py | 38 +++++----------- tests/test_bank.py | 13 +++--- tests/test_client.py | 12 +++-- tests/test_client_stub.py | 14 +++--- tests/test_essentials.py | 4 +- tests/test_queries.py | 3 +- tests/test_txn.py | 14 ++---- tests/test_util.py | 10 ++-- 15 files changed, 151 insertions(+), 146 deletions(-) create mode 100644 pydgraph/errors.py diff --git a/.gitignore b/.gitignore index 5a313219..56dc6213 100755 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ MANIFEST # IntelliJ .idea +pydgraph.iml diff --git a/pydgraph/client.py b/pydgraph/client.py index 8513c167..97b9a94c 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc import random from pydgraph import txn, util from pydgraph.meta import VERSION -from pydgraph.proto import api_pb2 as api, api_pb2_grpc as api_grpc +from pydgraph.proto import api_pb2 as api __author__ = 'Mohit Ranka ' __maintainer__ = 'Garvit Pahal ' @@ -45,20 +44,18 @@ def alter(self, op, timeout=None, metadata=None, credentials=None): async def async_alter(self, op, timeout=None, metadata=None, credentials=None): return await self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - def query(self, q, vars=None, timeout=None, metadata=None, credentials=None): - return self.txn().query(q, vars=vars, timeout=timeout, metadata=metadata, credentials=credentials) + def query(self, q, variables=None, timeout=None, metadata=None, credentials=None): + return self.txn().query(q, variables=variables, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_query(self, q, vars=None, timeout=None, metadata=None, credentials=None): - return self.txn().async_query(q, vars=vars, timeout=timeout, metadata=metadata, credentials=credentials) + async def async_query(self, q, variables=None, timeout=None, metadata=None, credentials=None): + return self.txn().async_query(q, variables=variables, timeout=timeout, metadata=metadata, + credentials=credentials) def txn(self): return txn.Txn(self) def set_lin_read(self, ctx): - ctx_lr_ids = ctx.lin_read.ids - ids = self._lin_read.ids - for key, value in ids.items(): - ctx_lr_ids[key] = value + ctx.lin_read.MergeFrom(self._lin_read) def merge_lin_reads(self, src): util.merge_lin_reads(self._lin_read, src) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 85525938..90ed6506 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -14,10 +14,8 @@ import grpc -from pydgraph import txn -from pydgraph import util from pydgraph.meta import VERSION -from pydgraph.proto import api_pb2 as api, api_pb2_grpc as api_grpc +from pydgraph.proto import api_pb2_grpc as api_grpc __author__ = 'Garvit Pahal ' __maintainer__ = 'Garvit Pahal ' diff --git a/pydgraph/errors.py b/pydgraph/errors.py new file mode 100644 index 00000000..4f54f371 --- /dev/null +++ b/pydgraph/errors.py @@ -0,0 +1,25 @@ +# Copyright 2018 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pydgraph.meta import VERSION + +__author__ = 'Garvit Pahal ' +__maintainer__ = 'Garvit Pahal ' +__version__ = VERSION +__status__ = 'development' + + +class AbortedError(Exception): + def __init__(self): + super(AbortedError, self).__init__('Transaction has been aborted. Please retry') diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 14b568bd..b81febff 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -15,7 +15,7 @@ import grpc import json -from pydgraph import util +from pydgraph import errors, util from pydgraph.meta import VERSION from pydgraph.proto import api_pb2 as api @@ -32,11 +32,11 @@ class Txn(object): 1. Created using Client.newTxn. - 2. Various query and qutate calls made. + 2. Various query and mutate calls made. 3. commit or discard used. If any mutations have been made, It's important that at least one of these methods is called to clean up resources. discard - is a no-op if dommit has already been called, so it's safe to call discard + is a no-op if commit has already been called, so it's safe to call discard after calling commit. """ @@ -48,38 +48,39 @@ def __init__(self, client): self._finished = False self._mutated = False - def query(self, q, vars=None, timeout=None, metadata=None, credentials=None): - req = self._common_query(q, vars=vars) + def query(self, q, variables=None, timeout=None, metadata=None, credentials=None): + req = self._common_query(q, variables=variables) res = self._dc.any_client().query(req, timeout=timeout, metadata=metadata, credentials=credentials) self.merge_context(res.txn) return res - async def async_query(self, q, vars=None, timeout=None, metadata=None, credentials=None): - req = self._common_query(q, vars=vars) + async def async_query(self, q, variables=None, timeout=None, metadata=None, credentials=None): + req = self._common_query(q, variables=variables) res = await self._dc.any_client().async_query(req, timeout=timeout, metadata=metadata, credentials=credentials) self.merge_context(res.txn) return res - def _common_query(self, q, vars=None): + def _common_query(self, q, variables=None): if self._finished: raise Exception('Transaction has already been committed or discarded') req = api.Request(query=q, start_ts=self._ctx.start_ts, lin_read=self._ctx.lin_read) - if vars is not None: - for key, value in vars.items(): + if variables is not None: + for key, value in variables.items(): if util.is_string(key) and util.is_string(value): req.vars[key] = value return req - def mutate( - self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, - timeout=None, metadata=None, credentials=None): - mu = self._common_mutate( - mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, - ignore_index_conflict=ignore_index_conflict) + def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, + timeout=None, metadata=None, credentials=None): + mu = self._common_mutate(mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, + ignore_index_conflict=ignore_index_conflict) + try: ag = self._dc.any_client().mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) + self.merge_context(ag.context) + return ag except Exception as e: try: self.discard(timeout=timeout, metadata=metadata, credentials=credentials) @@ -87,19 +88,18 @@ def mutate( # Ignore error - user should see the original error. pass - self._common_except_mutate(e, timeout=timeout, metadata=metadata, credentials=credentials) - - self.merge_context(ag.context) - return ag + self._common_except_mutate(e) - async def async_mutate( - self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, - timeout=None, metadata=None, credentials=None): - mu = self._common_mutate( - mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, - ignore_index_conflict=ignore_index_conflict) + async def async_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, + ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): + mu = self._common_mutate(mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, + ignore_index_conflict=ignore_index_conflict) + try: - ag = await self._dc.any_client().async_mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) + ag = await self._dc.any_client().async_mutate(mu, timeout=timeout, metadata=metadata, + credentials=credentials) + self.merge_context(ag.context) + return ag except Exception as e: try: await self.async_discard(timeout=timeout, metadata=metadata, credentials=credentials) @@ -107,12 +107,10 @@ async def async_mutate( # Ignore error - user should see the original error. pass - self._common_except_mutate(e, timeout=timeout, metadata=metadata, credentials=credentials) - - self.merge_context(ag.context) - return ag + self._common_except_mutate(e) - def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None): + def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, + ignore_index_conflict=None): if not mu: mu = api.Mutation() if set_obj: @@ -132,13 +130,14 @@ def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, d self._mutated = True mu.start_ts = self._ctx.start_ts return mu - - def _common_except_mutate(self, e, timeout=None, metadata=None, credentials=None): - if isinstance(e, grpc.RpcError): + + @staticmethod + def _common_except_mutate(e): + if isinstance(e, grpc._channel._Rendezvous): e.details() status_code = e.code() if status_code == grpc.StatusCode.ABORTED or status_code == grpc.StatusCode.FAILED_PRECONDITION: - raise AbortedError() + raise errors.AbortedError() raise e @@ -147,7 +146,8 @@ def commit(self, timeout=None, metadata=None, credentials=None): return try: - self._dc.any_client().commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, + credentials=credentials) except Exception as e: self._common_except_commit(e) @@ -156,7 +156,8 @@ async def async_commit(self, timeout=None, metadata=None, credentials=None): return try: - await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, + credentials=credentials) except Exception as e: self._common_except_commit(e) @@ -166,13 +167,14 @@ def _common_commit(self): self._finished = True return self._mutated - - def _common_except_commit(self, e): - if isinstance(e, grpc.RpcError): + + @staticmethod + def _common_except_commit(e): + if isinstance(e, grpc._channel._Rendezvous): e.details() status_code = e.code() if status_code == grpc.StatusCode.ABORTED: - raise AbortedError() + raise errors.AbortedError() raise e @@ -180,13 +182,14 @@ def discard(self, timeout=None, metadata=None, credentials=None): if not self._common_discard(): return - self._dc.any_client().commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, credentials=credentials) async def async_discard(self, timeout=None, metadata=None, credentials=None): if not self._common_discard(): return - await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=None, metadata=None, credentials=None) + await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, + credentials=credentials) def _common_discard(self): if self._finished: @@ -215,8 +218,3 @@ def merge_context(self, src=None): raise Exception('StartTs mismatch') self._ctx.keys[:] = src.keys[:] - - -class AbortedError(Exception): - def __init__(self): - super(AbortedError, self).__init__('Transaction has been aborted. Please retry') \ No newline at end of file diff --git a/pydgraph/util.py b/pydgraph/util.py index 984bf20c..530c9814 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys - from pydgraph.meta import VERSION __author__ = 'Shailesh Kochhar ' @@ -36,5 +34,6 @@ def merge_lin_reads(target, src): return target + def is_string(s): return isinstance(s, str) diff --git a/tests/helper.py b/tests/helper.py index 2f469009..9d3e691e 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -16,48 +16,53 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import grpc -from pydgraph.client_stub import DgraphClientStub -from pydgraph.client import DgraphClient +from pydgraph import client_stub, client from pydgraph.proto import api_pb2 as api -def create_lin_read(ids): + +def create_lin_read(src_ids): lr = api.LinRead() ids = lr.ids - for key, value in ids: + for key, value in src_ids.items(): ids[key] = value return lr + def are_lin_reads_equal(a, b): - aIds = a.ids - bIds = b.ids + a_ids = a.ids + b_ids = b.ids - if len(aIds) != len(bIds): + if len(a_ids) != len(b_ids): return False - for key in aIds.items(): - if key not in bIds: + for (key, value) in a_ids.items(): + if key not in b_ids or b.ids[key] != value: return False return True + SERVER_ADDR = 'localhost:9080' -def create_client(addr = SERVER_ADDR): - return DgraphClient(DgraphClientStub(addr)) -def set_schema(client, schema): - return client.alter(api.Operation(schema=schema)) +def create_client(addr=SERVER_ADDR): + return client.DgraphClient(client_stub.DgraphClientStub(addr)) + + +def set_schema(c, schema): + return c.alter(api.Operation(schema=schema)) + + +def drop_all(c): + return c.alter(api.Operation(drop_all=True)) -def drop_all(client): - return client.alter(api.Operation(drop_all=True)) def setup(): - client = create_client() - drop_all(client) - return client + c = create_client() + drop_all(c) + return c class ClientIntegrationTestCase(unittest.TestCase): diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index a483c267..3664b68a 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -15,31 +15,30 @@ __author__ = 'Shailesh Kochhar ' __maintainer__ = 'Garvit Pahal ' -import grpc -import functools +import unittest import json import logging import multiprocessing import multiprocessing.dummy as mpd import time -import unittest -from pydgraph.txn import AbortedError +from pydgraph import errors from . import helper -class TestAcountUpsert(helper.ClientIntegrationTestCase): +class TestAccountUpsert(helper.ClientIntegrationTestCase): """Account upsert integration test.""" def setUp(self): """Drops existing schema and loads new schema for the test.""" - super(TestAcountUpsert, self).setUp() - self.concurrency = 5 + super(TestAccountUpsert, self).setUp() + self.concurrency = 2 self.firsts = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] self.lasts = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] self.ages = [20, 25, 30, 35] + self.accounts = [ {'first': f, 'last': l, 'age': a} for f in self.firsts for l in self.lasts for a in self.ages @@ -70,8 +69,8 @@ def do_upserts(self, account_list, concurrency): account=acct, success_ctr=success_ctr, retry_ctr=retry_ctr) - results = [ pool.apply_async(updater, (acct,)) - for acct in account_list for _ in range(concurrency)] + results = [pool.apply_async(updater, (acct,)) + for acct in account_list for _ in range(concurrency)] [res.get() for res in results] def assert_changes(self, firsts, accounts): @@ -103,10 +102,6 @@ def upsert_account(addr, account, success_ctr, retry_ctr): {{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid - first - last - age - when }} }}'''.format(**account) @@ -123,8 +118,6 @@ def upsert_account(addr, account, success_ctr, retry_ctr): 'multiple accounts' % account) if not result['acct']: - if account['first'] == 'Paul': - print('creating') # Account does not exist, so create it nquads = ''' _:acct "{first}" . @@ -144,24 +137,15 @@ def upsert_account(addr, account, success_ctr, retry_ctr): <{0}> "{1:d}"^^ . '''.format(uid, int(time.time())) - prevresult = json.loads(txn.query(q=q).json)['acct'] - - updated = txn.mutate(set_nquads=updatequads) - + txn.mutate(set_nquads=updatequads) txn.commit() - # result = json.loads(c.query(q=q).json)['acct'] - # if len(result) > 1 and result[0]['first'] == 'Paul': - # print('1:', prevresult) - # print('2:', updatequads) - # print('3:', result) - # print() - with success_ctr.get_lock(): success_ctr.value += 1 + # txn successful, break the loop return - except (AbortedError, grpc._channel._Rendezvous): + except errors.AbortedError: with retry_ctr.get_lock(): retry_ctr.value += 1 # txn failed, retry the loop diff --git a/tests/test_bank.py b/tests/test_bank.py index 083eb14a..e80e2a51 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -16,24 +16,20 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import grpc import json import logging import multiprocessing as mp import multiprocessing.dummy as mpd -import os import random import time -from pydgraph.client import DgraphClient -from pydgraph.txn import AbortedError - from . import helper USERS = 100 CONCURRENCY = 10 XFER_COUNT = 1000 + class TestBank(helper.ClientIntegrationTestCase): """Bank transfer integration test.""" @@ -93,6 +89,7 @@ def _looper(): return _looper + def run_total(c, account_uids): """Calculates the total ammount in the accounts.""" q = """{{ @@ -109,6 +106,7 @@ def run_total(c, account_uids): logging.info('Response: %s', total) assert total[0]['bal'] == 10000 + def run_xfers(addr, xfer_count, account_ids, success_ctr, retry_ctr): pname = mpd.current_process().name log = logging.getLogger('test_bank.run_txfers[%s]' % (pname,)) @@ -135,13 +133,14 @@ def run_xfers(addr, xfer_count, account_ids, success_ctr, retry_ctr): log.info('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) if success_ctr.value >= xfer_count: break - except (AbortedError, grpc._channel._Rendezvous): + except: with retry_ctr.get_lock(): retry_ctr.value += 1 with success_ctr.get_lock(), retry_ctr.get_lock(): log.info("success: %d, retries: %d", success_ctr.value, retry_ctr.value) + def select_account_pair(accounts): """Selects a pair of accounts at random from accounts ensuring they are not the same.""" @@ -151,11 +150,13 @@ def select_account_pair(accounts): if not from_acc == to_acc: return (from_acc, to_acc) + def load_from_query(txn, query, field): """Loads a field from the results of a query executed in a txn.""" resp = txn.query(query) return json.loads(resp.json)[field] + def dump_from_obj(txn, obj, commit=False): assigned = txn.mutate(set_obj=obj) diff --git a/tests/test_client.py b/tests/test_client.py index 227752eb..623539fa 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -17,18 +17,20 @@ import unittest -from pydgraph.client import DgraphClient +from pydgraph import client class TestDgraphClient(unittest.TestCase): def test_constructor(self): with self.assertRaises(ValueError): - DgraphClient() + client.DgraphClient() + def suite(): - suite = unittest.TestSuite() - suite.addTest(TestDgraphClient()) - return suite + s = unittest.TestSuite() + s.addTest(TestDgraphClient()) + return s + if __name__ == '__main__': runner = unittest.TextTestRunner() diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index 1c86a28d..d6ff07b5 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -17,7 +17,7 @@ import unittest -from pydgraph.client_stub import DgraphClientStub +from pydgraph import client_stub from pydgraph.proto import api_pb2 as api @@ -30,16 +30,18 @@ def check_version(self, stub): self.validate_version_object(stub.check_version(api.Check())) def test_constructor(self): - self.check_version(DgraphClientStub()) + self.check_version(client_stub.DgraphClientStub()) def test_timeout(self): with self.assertRaises(Exception): - DgraphClientStub().check_version(api.Check(), timeout=-1) + client_stub.DgraphClientStub().check_version(api.Check(), timeout=-1) + def suite(): - suite = unittest.TestSuite() - suite.addTest(TestDgraphClientStub()) - return suite + s = unittest.TestSuite() + s.addTest(TestDgraphClientStub()) + return s + if __name__ == '__main__': runner = unittest.TextTestRunner() diff --git a/tests/test_essentials.py b/tests/test_essentials.py index 464552de..e37f0c3d 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -16,14 +16,12 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import grpc import json import logging -from pydgraph import client - from . import helper + class TestEssentials(helper.ClientIntegrationTestCase): """Tests the essentials of the client.""" diff --git a/tests/test_queries.py b/tests/test_queries.py index c9c0a43e..339a2019 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -15,14 +15,13 @@ __author__ = 'Mohit Ranka ' __maintainer__ = 'Garvit Pahal ' -import unittest import json -from pydgraph.client import DgraphClient from pydgraph.proto import api_pb2 as api from . import helper + class TestQueries(helper.ClientIntegrationTestCase): def setUp(self): super(TestQueries, self).setUp() diff --git a/tests/test_txn.py b/tests/test_txn.py index 8d16e048..5e13ee09 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -16,16 +16,10 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import grpc import json import logging -import os -import random -import time -from pydgraph.client import DgraphClient -from pydgraph.txn import AbortedError -from pydgraph.proto import api_pb2 as api +from pydgraph import errors from . import helper @@ -168,7 +162,7 @@ def test_Conflict(self): assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Manish"}) txn.commit() - self.assertRaises(AbortedError, txn2.commit) + self.assertRaises(errors.AbortedError, txn2.commit) txn3 = self.client.txn() query = """{{ @@ -198,7 +192,7 @@ def test_ConflictReverseOrder(self): assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Jan the man"}) txn2.commit() - self.assertRaises(AbortedError, txn.commit) + self.assertRaises(errors.AbortedError, txn.commit) txn3 = self.client.txn() resp = txn3.query(query) @@ -216,7 +210,7 @@ def test_MutationAfterCommit(self): assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Jan the man"}) txn.commit() - self.assertRaises(AbortedError, txn2.commit) + self.assertRaises(errors.AbortedError, txn2.commit) txn3 = self.client.txn() assigned3 = txn3.mutate(set_obj={"uid": uid, "name": "Jan the man"}) diff --git a/tests/test_util.py b/tests/test_util.py index 89be5446..92b1de8a 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -16,7 +16,6 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import sys from pydgraph import util from pydgraph.proto import api_pb2 as api @@ -71,6 +70,7 @@ def test_no_target_ids(self): res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) + class TestIsString(unittest.TestCase): def test_is_string(self): self.assertTrue(util.is_string('')) @@ -78,10 +78,12 @@ def test_is_string(self): self.assertFalse(util.is_string(object())) self.assertFalse(util.is_string({})) + def suite(): - suite = unittest.TestSuite() - suite.addTest(TestMergeLinReads()) - return suite + s = unittest.TestSuite() + s.addTest(TestMergeLinReads()) + return s + if __name__ == '__main__': runner = unittest.TextTestRunner() From 13f9bef316a2868e0c75e63a4d24a403e743cb32 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 29 Mar 2018 11:54:37 +0530 Subject: [PATCH 070/435] Code refactoring --- pydgraph/client_stub.py | 2 +- tests/test_acct_upsert.py | 71 +++++------ tests/test_bank.py | 64 +++++----- tests/test_essentials.py | 20 +++- tests/test_queries.py | 23 +++- tests/test_txn.py | 241 +++++++++++++++++++++----------------- tests/test_util.py | 1 + 7 files changed, 246 insertions(+), 176 deletions(-) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 90ed6506..02ba6ba2 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -24,7 +24,7 @@ class DgraphClientStub(object): - def __init__(self, addr="localhost:9080", credentials=None, options=None): + def __init__(self, addr='localhost:9080', credentials=None, options=None): if credentials is None: self.channel = grpc.insecure_channel(addr, options) else: diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 3664b68a..591e59d7 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -16,32 +16,29 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import json import logging +import json +import time import multiprocessing import multiprocessing.dummy as mpd -import time from pydgraph import errors from . import helper +CONCURRENCY = 2 +FIRSTS = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] +LASTS = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] +AGES = [20, 25, 30, 35] -class TestAccountUpsert(helper.ClientIntegrationTestCase): - """Account upsert integration test.""" +class TestAccountUpsert(helper.ClientIntegrationTestCase): def setUp(self): - """Drops existing schema and loads new schema for the test.""" super(TestAccountUpsert, self).setUp() - self.concurrency = 2 - - self.firsts = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] - self.lasts = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] - self.ages = [20, 25, 30, 35] self.accounts = [ {'first': f, 'last': l, 'age': a} - for f in self.firsts for l in self.lasts for a in self.ages + for f in FIRSTS for l in LASTS for a in AGES ] logging.info(len(self.accounts)) @@ -53,14 +50,15 @@ def setUp(self): when: int . """) - def test_acount_upsert(self): - """Account upsert integration. Will run upserts concurrently.""" - self.do_upserts(self.accounts, self.concurrency) - self.assert_changes(self.firsts, self.accounts) + def test_account_upsert(self): + """Run upserts concurrently.""" + self.do_upserts(self.accounts, CONCURRENCY) + self.assert_changes(FIRSTS, self.accounts) def do_upserts(self, account_list, concurrency): - """Will run the upsert command for the accouts in `account_list`. Execution + """Runs the upsert command for the accounts in `account_list`. Execution happens in concurrent processes.""" + success_ctr = multiprocessing.Value('i', 0, lock=True) retry_ctr = multiprocessing.Value('i', 0, lock=True) @@ -69,28 +67,32 @@ def do_upserts(self, account_list, concurrency): account=acct, success_ctr=success_ctr, retry_ctr=retry_ctr) - results = [pool.apply_async(updater, (acct,)) - for acct in account_list for _ in range(concurrency)] + results = [ + pool.apply_async(updater, (acct,)) + for acct in account_list for _ in range(concurrency) + ] [res.get() for res in results] def assert_changes(self, firsts, accounts): """Will check to see changes have been made.""" - q = ''' - {{ + + q = """{{ all(func: anyofterms(first, "{}")) {{ first last age }} - }}'''.format(' '.join(firsts)) + }}""".format(' '.join(firsts)) logging.debug(q) result = json.loads(self.client.query(q=q).json) + account_set = set() for acct in result['all']: self.assertTrue(acct['first'] is not None) self.assertTrue(acct['last'] is not None) self.assertTrue(acct['age'] is not None) account_set.add('{first}_{last}_{age}'.format(**acct)) + self.assertEqual(len(account_set), len(accounts)) for acct in accounts: self.assertTrue('{first}_{last}_{age}'.format(**acct) in account_set) @@ -98,12 +100,11 @@ def assert_changes(self, firsts, accounts): def upsert_account(addr, account, success_ctr, retry_ctr): c = helper.create_client(addr) - q = ''' - {{ + q = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid }} - }}'''.format(**account) + }}""".format(**account) last_update_time = time.time() - 10000 while True: @@ -118,7 +119,7 @@ def upsert_account(addr, account, success_ctr, retry_ctr): 'multiple accounts' % account) if not result['acct']: - # Account does not exist, so create it + # account does not exist, so create it nquads = ''' _:acct "{first}" . _:acct "{last}" . @@ -126,17 +127,14 @@ def upsert_account(addr, account, success_ctr, retry_ctr): '''.format(**account) created = txn.mutate(set_nquads=nquads) uid = created.uids.get('acct') - assert uid is not None and uid != '', 'Account with uid None/""' + assert uid is not None and uid != '', 'Account with uid None' else: - # Account exists, read the uid + # account exists, read the uid acct = result['acct'][0] uid = acct['uid'] assert uid is not None, 'Account with uid None' - updatequads = ''' - <{0}> "{1:d}"^^ . - '''.format(uid, int(time.time())) - + updatequads = '<{0}> "{1:d}"^^ .'.format(uid, int(time.time())) txn.mutate(set_nquads=updatequads) txn.commit() @@ -153,6 +151,13 @@ def upsert_account(addr, account, success_ctr, retry_ctr): txn.discard() +def suite(): + s = unittest.TestSuite() + s.addTest(TestAccountUpsert()) + return s + + if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) - unittest.main() + logging.basicConfig(level=logging.INFO) + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_bank.py b/tests/test_bank.py index e80e2a51..261e45b3 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -16,35 +16,33 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import json import logging -import multiprocessing as mp -import multiprocessing.dummy as mpd +import json import random import time +import multiprocessing as mp +import multiprocessing.dummy as mpd from . import helper USERS = 100 CONCURRENCY = 10 -XFER_COUNT = 1000 +TRANSFER_COUNT = 1000 class TestBank(helper.ClientIntegrationTestCase): - """Bank transfer integration test.""" - def setUp(self): - """Drops existing schema and sets up schema for new test.""" super(TestBank, self).setUp() - self.concurrency = CONCURRENCY + self.accounts = [ {'bal': 100} for _ in range(USERS) ] self.uids = [] + logging.debug(len(self.accounts)) - def test_bank_xfer(self): - """Transfers, will run them concurrently.""" + def test_bank_transfer(self): + """Run transfers concurrently.""" self.create_accounts() try: @@ -52,11 +50,12 @@ def test_bank_xfer(self): success_ctr = mp.Value('i', 0, lock=True) retry_ctr = mp.Value('i', 0, lock=True) - pool = mpd.Pool(self.concurrency) - results = [pool.apply_async(run_xfers, (self.TEST_SERVER_ADDR, - XFER_COUNT, self.uids, - success_ctr, retry_ctr)) - for _ in range(self.concurrency)] + pool = mpd.Pool(CONCURRENCY) + results = [pool.apply_async( + run_transfers, + (self.TEST_SERVER_ADDR, TRANSFER_COUNT, self.uids, success_ctr, retry_ctr) + ) for _ in range(CONCURRENCY)] + [res.get() for res in results] finally: total_watcher.terminate() @@ -64,17 +63,19 @@ def test_bank_xfer(self): def create_accounts(self): """Creates the default set of accounts.""" + helper.drop_all(self.client) helper.set_schema(self.client, 'bal: int .') txn = self.client.txn() assigned = txn.mutate(set_obj=self.accounts) txn.commit() + self.uids.extend(assigned.uids.values()) logging.debug('Created %d accounts', len(assigned.uids)) def start_total_watcher(self): - """Watcher, will keep an eye on the total account balances.""" + """Watcher keeps an eye on the total account balances.""" total_watch = looper(run_total, self.client, self.uids) process = mp.Process(target=total_watch, name='total_watcher') process.start() @@ -90,8 +91,9 @@ def _looper(): return _looper -def run_total(c, account_uids): - """Calculates the total ammount in the accounts.""" +def run_total(c, uids): + """Calculates the total amount in the accounts.""" + q = """{{ var(func: uid("{uids:s}")) {{ b as bal @@ -99,17 +101,17 @@ def run_total(c, account_uids): total() {{ bal: sum(val(b)) }} - }} - """.format(uids='", "'.join(account_uids)) + }}""".format(uids='", "'.join(uids)) + resp = c.query(q) total = json.loads(resp.json)['total'] logging.info('Response: %s', total) assert total[0]['bal'] == 10000 -def run_xfers(addr, xfer_count, account_ids, success_ctr, retry_ctr): +def run_transfers(addr, transfer_count, account_ids, success_ctr, retry_ctr): pname = mpd.current_process().name - log = logging.getLogger('test_bank.run_txfers[%s]' % (pname,)) + log = logging.getLogger('test_bank.run_transfers[%s]' % (pname,)) c = helper.create_client(addr) while True: @@ -120,6 +122,7 @@ def run_xfers(addr, xfer_count, account_ids, success_ctr, retry_ctr): bal }} }}""".format(uid1=from_acc, uid2=to_acc) + txn = c.txn() try: accounts = load_from_query(txn, query, 'me') @@ -131,14 +134,14 @@ def run_xfers(addr, xfer_count, account_ids, success_ctr, retry_ctr): if not success_ctr.value % 100: log.info('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) - if success_ctr.value >= xfer_count: + if success_ctr.value >= transfer_count: break except: with retry_ctr.get_lock(): retry_ctr.value += 1 with success_ctr.get_lock(), retry_ctr.get_lock(): - log.info("success: %d, retries: %d", success_ctr.value, retry_ctr.value) + log.info('success: %d, retries: %d', success_ctr.value, retry_ctr.value) def select_account_pair(accounts): @@ -147,8 +150,8 @@ def select_account_pair(accounts): while True: from_acc = random.choice(accounts) to_acc = random.choice(accounts) - if not from_acc == to_acc: - return (from_acc, to_acc) + if from_acc != to_acc: + return from_acc, to_acc def load_from_query(txn, query, field): @@ -165,6 +168,13 @@ def dump_from_obj(txn, obj, commit=False): return txn.commit() +def suite(): + s = unittest.TestSuite() + s.addTest(TestBank()) + return s + + if __name__ == '__main__': logging.basicConfig(level=logging.INFO) - unittest.main() + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_essentials.py b/tests/test_essentials.py index e37f0c3d..022e3b49 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -16,25 +16,26 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import json import logging +import json from . import helper class TestEssentials(helper.ClientIntegrationTestCase): - """Tests the essentials of the client.""" - def testMutationAfterQuery(self): """Tests what happens when making a mutation on a txn after querying on the client.""" - _ = self.client.query('''{firsts(func: has(first)) { uid first }}''') + + _ = self.client.query('{firsts(func: has(first)) { uid first }}') txn = self.client.txn() mutation = txn.mutate(set_nquads='_:node "Node name first" .') - self.assertTrue(len(mutation.uids) > 0, "Mutation did not create new node") + self.assertTrue(len(mutation.uids) > 0, 'Mutation did not create new node') + created = mutation.uids.get('node') self.assertIsNotNone(created) + txn.commit() query = '{{node(func: uid({uid:s})) {{ uid }} }}'.format(uid=created) @@ -42,6 +43,13 @@ def testMutationAfterQuery(self): self.assertEqual(created, json.loads(reread.json).get('node')[0]['uid']) +def suite(): + s = unittest.TestSuite() + s.addTest(TestEssentials()) + return s + + if __name__ == '__main__': logging.basicConfig(level=logging.INFO) - unittest.main() + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_queries.py b/tests/test_queries.py index 339a2019..a2f1afd4 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -15,6 +15,8 @@ __author__ = 'Mohit Ranka ' __maintainer__ = 'Garvit Pahal ' +import unittest +import logging import json from pydgraph.proto import api_pb2 as api @@ -31,13 +33,13 @@ def setUp(self): def test_mutation_and_query(self): txn = self.client.txn() - assigned = txn.mutate(api.Mutation(commit_now=True), set_nquads=""" + _ = txn.mutate(api.Mutation(commit_now=True), set_nquads=""" <_:alice> \"Alice\" . <_:greg> \"Greg\" . <_:alice> <_:greg> . """) - query_string = """{ + query = """{ me(func: anyofterms(name, "Alice")) { name @@ -48,8 +50,21 @@ def test_mutation_and_query(self): } } """ - response = self.client.query(query_string) - self.assertEqual([{"name": "Alice", "follows": [{"name": "Greg"}]}], json.loads(response.json).get("me")) + + response = self.client.query(query) + self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], json.loads(response.json).get('me')) self.assertTrue(isinstance(response.latency.parsing_ns, int), 'Parsing latency is not available') self.assertTrue(isinstance(response.latency.processing_ns, int), 'Processing latency is not available') self.assertTrue(isinstance(response.latency.encoding_ns, int), 'Encoding latency is not available') + + +def suite(): + s = unittest.TestSuite() + s.addTest(TestQueries()) + return s + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_txn.py b/tests/test_txn.py index 5e13ee09..5601a9b3 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -16,8 +16,8 @@ __maintainer__ = 'Garvit Pahal ' import unittest -import json import logging +import json from pydgraph import errors @@ -25,20 +25,18 @@ class TestTxn(helper.ClientIntegrationTestCase): - """Transaction test cases.""" - def setUp(self): - """Drops all the existing schema and creates schema for tests.""" super(TestTxn, self).setUp() helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(fulltext) .') - def test_TxnReadAtStartTs(self): + def test_read_at_start_ts(self): """Tests read after write when readTs == startTs""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') for _, uid in assigned.uids.items(): uid = uid @@ -49,13 +47,14 @@ def test_TxnReadAtStartTs(self): }} }}""".format(uid=uid) resp = txn.query(query) - self.assertEqual([{"name": "Manish"}], json.loads(resp.json).get("me")) + self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) - def test_TxnReadBeforeStartTs(self): + def test_read_before_start_ts(self): """Tests read before write when readTs < startTs""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') for _, uid in assigned.uids.items(): uid = uid @@ -66,44 +65,46 @@ def test_TxnReadBeforeStartTs(self): }} }}""".format(uid=uid) - txn2 = self.client.txn() - resp = txn2.query(query) - self.assertEqual([], json.loads(resp.json).get("me")) + resp = self.client.query(query) + self.assertEqual([], json.loads(resp.json).get('me')) + + def test_read_after_start_ts(self): + """Tests read after committing a write when readTs > startTs""" - def test_TxnReadAfterStartTs(self): - """Tests read after commiting a write when readTs > startTs""" txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') for _, uid in assigned.uids.items(): uid = uid - _ = txn.commit() + txn.commit() query = """{{ me(func: uid("{uid:s}")) {{ name }} }}""".format(uid=uid) - txn2 = self.client.txn() - resp = txn2.query(query) - self.assertEqual([{"name": "Manish"}], json.loads(resp.json).get("me")) - def test_TxnReadBeforeAndAfterStartTs(self): - """Test read before and after commiting a transaction when + resp = self.client.query(query) + self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) + + def test_read_before_and_after_start_ts(self): + """Test read before and after committing a transaction when readTs1 < startTs and readTs2 > startTs""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') for _, uid in assigned.uids.items(): uid = uid - _ = txn.commit() + txn.commit() txn2 = self.client.txn() + # start a new txn and mutate the object txn3 = self.client.txn() - assigned = txn3.mutate(set_obj={"uid": uid, "name": "Manish2"}) + _ = txn3.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) query = """{{ me(func: uid("{uid:s}")) {{ @@ -111,25 +112,26 @@ def test_TxnReadBeforeAndAfterStartTs(self): }} }}""".format(uid=uid) - # the object is unmutated in other txns since txn3 is uncommitted + # object is unchanged since txn3 is uncommitted resp2 = txn2.query(query) - self.assertEqual([{"name": "Manish"}], json.loads(resp2.json).get("me")) + self.assertEqual([{'name': 'Manish'}], json.loads(resp2.json).get('me')) # once txn3 is committed, other txns observe the update txn3.commit() - txn4 = self.client.txn() - resp4 = txn4.query(query) - self.assertEqual([{"name": "Manish2"}], json.loads(resp4.json).get("me")) - def test_ReadFromNewClient(self): + resp4 = self.client.query(query) + self.assertEqual([{'name': 'Manish2'}], json.loads(resp4.json).get('me')) + + def test_read_from_new_client(self): """Tests committed reads from a new client with startTs == 0.""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') for _, uid in assigned.uids.items(): uid = uid - _ = txn.commit() + txn.commit() client2 = helper.create_client(self.TEST_SERVER_ADDR) query = """{{ @@ -137,29 +139,33 @@ def test_ReadFromNewClient(self): name }} }}""".format(uid=uid) + resp2 = client2.query(query) - self.assertEqual([{"name": "Manish"}], json.loads(resp2.json).get("me")) + self.assertEqual([{'name': 'Manish'}], json.loads(resp2.json).get('me')) self.assertTrue(resp2.txn.start_ts > 0) txn2 = client2.txn() - assigned = txn2.mutate(set_obj={"uid": uid, "name": "Manish2"}) + assigned = txn2.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) self.assertTrue(assigned.context.start_ts > 0) txn2.commit() resp = self.client.query(query) - self.assertEqual([{"name": "Manish2"}], json.loads(resp.json).get("me")) + self.assertEqual([{'name': 'Manish2'}], json.loads(resp.json).get('me')) - def test_Conflict(self): + def test_conflict(self): """Tests committing two transactions which conflict.""" + helper.drop_all(self.client) txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + for _, uid in assigned.uids.items(): uid = uid txn2 = self.client.txn() - assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Manish"}) + _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Manish'}) txn.commit() self.assertRaises(errors.AbortedError, txn2.commit) @@ -170,14 +176,18 @@ def test_Conflict(self): name }} }}""".format(uid=uid) + resp3 = txn3.query(query) - self.assertEqual([{"name": "Manish"}], json.loads(resp3.json).get("me")) + self.assertEqual([{'name': 'Manish'}], json.loads(resp3.json).get('me')) - def test_ConflictReverseOrder(self): + def test_conflict_reverse_order(self): """Tests committing a transaction after a newer transaction has been committed.""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + for _, uid in assigned.uids.items(): uid = uid @@ -187,33 +197,37 @@ def test_ConflictReverseOrder(self): name }} }}""".format(uid=uid) + resp = txn2.query(query) - self.assertEqual([], json.loads(resp.json).get("me")) + self.assertEqual([], json.loads(resp.json).get('me')) - assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Jan the man"}) + _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) txn2.commit() + self.assertRaises(errors.AbortedError, txn.commit) txn3 = self.client.txn() resp = txn3.query(query) - self.assertEqual([{"name": "Jan the man"}], json.loads(resp.json).get("me")) + self.assertEqual([{'name': 'Jan the man'}], json.loads(resp.json).get('me')) - def test_MutationAfterCommit(self): + def test_mutation_after_commit(self): """Tests a second mutation after failing to commit a first mutation.""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + for _, uid in assigned.uids.items(): uid = uid txn2 = self.client.txn() - assigned2 = txn2.mutate(set_obj={"uid": uid, "name": "Jan the man"}) + _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) txn.commit() self.assertRaises(errors.AbortedError, txn2.commit) txn3 = self.client.txn() - assigned3 = txn3.mutate(set_obj={"uid": uid, "name": "Jan the man"}) + _ = txn3.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) txn3.commit() query = """{{ @@ -221,44 +235,49 @@ def test_MutationAfterCommit(self): name }} }}""".format(uid=uid) - txn4 = self.client.txn() - resp4 = txn4.query(query) - self.assertEqual([{"name": "Jan the man"}], json.loads(resp4.json).get("me")) - def test_ConflictIgnore(self): + resp4 = self.client.query(query) + self.assertEqual([{'name': 'Jan the man'}], json.loads(resp4.json).get('me')) + + def test_conflict_ignore(self): """Tests a mutation with ignore index conflict.""" + txn = self.client.txn() - assigned1 = txn.mutate(set_obj={"name": "Manish"}, ignore_index_conflict=True) - self.assertEqual(1, len(assigned1.uids), "Nothing was assigned") + assigned1 = txn.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) + self.assertEqual(1, len(assigned1.uids), 'Nothing was assigned') + for _, uid in assigned1.uids.items(): uid1 = uid txn2 = self.client.txn() - assigned2 = txn2.mutate(set_obj={"name": "Manish"}, ignore_index_conflict=True) - self.assertEqual(1, len(assigned2.uids), "Nothing was assigned") + assigned2 = txn2.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) + self.assertEqual(1, len(assigned2.uids), 'Nothing was assigned') + for _, uid in assigned2.uids.items(): uid2 = uid txn.commit() txn2.commit() - txn3 = self.client.txn() query = """{ me(func: eq(name, "Manish")) { uid } }""" - resp = txn3.query(query) - self.assertEqual([{"uid": uid1}, {"uid": uid2}], json.loads(resp.json).get("me")) - def test_ReadIndexKeySameTxn(self): + resp = self.client.query(query) + self.assertEqual([{'uid': uid1}, {'uid': uid2}], json.loads(resp.json).get('me')) + + def test_read_index_key_same_txn(self): """Tests reading an indexed field within a transaction.""" + helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish"}, ignore_index_conflict=True) - self.assertEqual(1, len(assigned.uids), "Nothing was assigned") + assigned = txn.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + for _, uid in assigned.uids.items(): uid = uid @@ -267,37 +286,39 @@ def test_ReadIndexKeySameTxn(self): uid } }""" + resp = txn.query(query) - self.assertEqual([{"uid": uid}], json.loads(resp.json).get("me")) + self.assertEqual([{'uid': uid}], json.loads(resp.json).get('me')) class TestSPStar(helper.ClientIntegrationTestCase): def setUp(self): - """Drops everything and sets some schema.""" super(TestSPStar, self).setUp() helper.drop_all(self.client) helper.set_schema(self.client, 'friend: uid .') - def test_SPStar(self): - """Tests a Subj Predicate Star query.""" + def test_sp_star(self): + """Tests a Subject Predicate Star query.""" + txn = self.client.txn() - assigned = txn.mutate(set_obj={"name": "Manish", "friend": [{"name": "Jan"}]}) - uid1 = assigned.uids["blank-0"] - self.assertEqual(2, len(assigned.uids), "Expecting 2 uids to be created") + assigned = txn.mutate(set_obj={'name': 'Manish', 'friend': [{'name': 'Jan'}]}) + uid1 = assigned.uids['blank-0'] + self.assertEqual(2, len(assigned.uids), 'Expected 2 nodes to be created') + txn.commit() txn2 = self.client.txn() - assigned2 = txn2.mutate(del_obj={"uid": uid1, "friend": None}) + assigned2 = txn2.mutate(del_obj={'uid': uid1, 'friend': None}) self.assertEqual(0, len(assigned2.uids)) assigned3 = txn2.mutate(set_obj={ - "uid": uid1, - "name": "Manish", - "friend": [{"name": "Jan2"}] + 'uid': uid1, + 'name': 'Manish', + 'friend': [{'name': 'Jan2'}] }) self.assertEqual(1, len(assigned3.uids)) - uid2 = assigned3.uids["blank-0"] + uid2 = assigned3.uids['blank-0'] query = """{{ me(func: uid("{uid:s}")) {{ @@ -308,19 +329,21 @@ def test_SPStar(self): }} }} }}""".format(uid=uid1) + resp = txn2.query(query) self.assertEqual([{ - "uid": uid1, - "friend": [{"name": "Jan2", "uid": uid2}] - }], json.loads(resp.json).get("me")) + 'uid': uid1, + 'friend': [{'name': 'Jan2', 'uid': uid2}] + }], json.loads(resp.json).get('me')) - def test_SPStar2(self): + def test_sp_star2(self): """Second test of Subject Predicate Star""" + txn = self.client.txn() - # Add edge to Jan - assigned = txn.mutate(set_obj={"name": "Manish", "friend": [{"name": "Jan"}]}) + assigned = txn.mutate(set_obj={'name': 'Manish', 'friend': [{'name': 'Jan'}]}) self.assertEqual(2, len(assigned.uids)) - uid1, uid2 = assigned.uids["blank-0"], assigned.uids["blank-1"] + uid1, uid2 = assigned.uids['blank-0'], assigned.uids['blank-1'] + query = """{{ me(func: uid("{uid:s}")) {{ uid @@ -330,40 +353,48 @@ def test_SPStar2(self): }} }} }}""".format(uid=uid1) + resp = txn.query(query) self.assertEqual([{ - "uid": uid1, - "friend": [{"name": "Jan", "uid": uid2}] - }], json.loads(resp.json).get("me")) + 'uid': uid1, + 'friend': [{'name': 'Jan', 'uid': uid2}] + }], json.loads(resp.json).get('me')) - # Delete S P * - deleted = txn.mutate(del_obj={"uid": uid1, "friend": None}) + deleted = txn.mutate(del_obj={'uid': uid1, 'friend': None}) self.assertEqual(0, len(deleted.uids)) resp = txn.query(query) - self.assertEqual([{"uid": uid1}], json.loads(resp.json).get("me")) + self.assertEqual([{'uid': uid1}], json.loads(resp.json).get('me')) - # Add an edge to Jan2 + # add an edge to Jan2 assigned2 = txn.mutate(set_obj={ - "uid": uid1, - "name": "Manish", - "friend": [{"name": "Jan2"}] + 'uid': uid1, + 'name': 'Manish', + 'friend': [{'name': 'Jan2'}] }) self.assertEqual(1, len(assigned2.uids)) - uid2 = assigned2.uids["blank-0"] + uid2 = assigned2.uids['blank-0'] + resp = txn.query(query) self.assertEqual([{ - "uid": uid1, - "friend": [{"name": "Jan2", "uid": uid2}] - }], json.loads(resp.json).get("me")) + 'uid': uid1, + 'friend': [{'name': 'Jan2', 'uid': uid2}] + }], json.loads(resp.json).get('me')) - # Delete S P * - deleted2 = txn.mutate(del_obj={"uid": uid1, "friend": None}) + deleted2 = txn.mutate(del_obj={'uid': uid1, 'friend': None}) self.assertEqual(0, len(deleted2.uids)) resp = txn.query(query) - self.assertEqual([{"uid": uid1}], json.loads(resp.json).get("me")) + self.assertEqual([{'uid': uid1}], json.loads(resp.json).get('me')) + + +def suite(): + s = unittest.TestSuite() + s.addTest(TestTxn()) + s.addTest(TestSPStar()) + return s if __name__ == '__main__': logging.basicConfig(level=logging.INFO) - unittest.main() + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_util.py b/tests/test_util.py index 92b1de8a..9728a2bb 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -82,6 +82,7 @@ def test_is_string(self): def suite(): s = unittest.TestSuite() s.addTest(TestMergeLinReads()) + s.addTest(TestIsString()) return s From 87b5a952e2297ba6454ea72b427ba66490d357ff Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Thu, 29 Mar 2018 12:19:33 +0530 Subject: [PATCH 071/435] Increase concurrency, test failing --- tests/test_acct_upsert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 591e59d7..650ebc01 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -26,7 +26,7 @@ from . import helper -CONCURRENCY = 2 +CONCURRENCY = 5 FIRSTS = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] LASTS = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] AGES = [20, 25, 30, 35] @@ -120,11 +120,11 @@ def upsert_account(addr, account, success_ctr, retry_ctr): if not result['acct']: # account does not exist, so create it - nquads = ''' + nquads = """ _:acct "{first}" . _:acct "{last}" . _:acct "{age}"^^ . - '''.format(**account) + """.format(**account) created = txn.mutate(set_nquads=nquads) uid = created.uids.get('acct') assert uid is not None and uid != '', 'Account with uid None' From cfffac0dcd03b0537c5d31aa2f1e6c6541542ae6 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 11:35:28 +0530 Subject: [PATCH 072/435] Fix txn mutate and merge_context --- pydgraph/txn.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index b81febff..34874569 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -79,8 +79,6 @@ def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquad try: ag = self._dc.any_client().mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) - self.merge_context(ag.context) - return ag except Exception as e: try: self.discard(timeout=timeout, metadata=metadata, credentials=credentials) @@ -89,6 +87,12 @@ def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquad pass self._common_except_mutate(e) + + if mu.commit_now: + self._finished = True + + self.merge_context(ag.context) + return ag async def async_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): @@ -217,4 +221,4 @@ def merge_context(self, src=None): # This condition should never be true. raise Exception('StartTs mismatch') - self._ctx.keys[:] = src.keys[:] + self._ctx.keys.extend(src.keys[:]) From eedc4d7dffbcf3fe935be4edc1c021a5fe4ca7c2 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 11:39:58 +0530 Subject: [PATCH 073/435] Update upsert schema --- tests/test_acct_upsert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 650ebc01..2397380f 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -44,9 +44,9 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, """ - first: string @index(term) . - last: string @index(hash) . - age: int @index(int) . + first: string @index(term) @upsert . + last: string @index(hash) @upsert . + age: int @index(int) @upsert . when: int . """) From 03684aa25a0bf85277062ae66b6e7a41ce634e06 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 14:23:05 +0530 Subject: [PATCH 074/435] Update exports in __init__.py --- README.md | 0 pydgraph/__init__.py | 21 ++++++++++++++++++++- tests/helper.py | 13 ++++++------- tests/test_acct_upsert.py | 4 ++-- tests/test_client.py | 4 ++-- tests/test_client_stub.py | 9 ++++----- tests/test_queries.py | 4 ++-- tests/test_txn.py | 8 ++++---- tests/test_util.py | 6 +++--- 9 files changed, 43 insertions(+), 26 deletions(-) mode change 100755 => 100644 README.md diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py index 98f6d1ef..eaafc0f2 100755 --- a/pydgraph/__init__.py +++ b/pydgraph/__init__.py @@ -1 +1,20 @@ -__all__ = ['client'] +# Copyright 2018 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pydgraph.proto.api_pb2 import Operation, Payload, Request, Response, Mutation, Assigned, TxnContext, Check,\ + Version, NQuad, Value, Facet, SchemaNode, LinRead, Latency +from pydgraph.client_stub import * +from pydgraph.client import * +from pydgraph.txn import * +from pydgraph.errors import * diff --git a/tests/helper.py b/tests/helper.py index 9d3e691e..b73449a7 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -17,12 +17,11 @@ import unittest -from pydgraph import client_stub, client -from pydgraph.proto import api_pb2 as api +import pydgraph def create_lin_read(src_ids): - lr = api.LinRead() + lr = pydgraph.LinRead() ids = lr.ids for key, value in src_ids.items(): ids[key] = value @@ -48,15 +47,15 @@ def are_lin_reads_equal(a, b): def create_client(addr=SERVER_ADDR): - return client.DgraphClient(client_stub.DgraphClientStub(addr)) + return pydgraph.DgraphClient(pydgraph.DgraphClientStub(addr)) def set_schema(c, schema): - return c.alter(api.Operation(schema=schema)) + return c.alter(pydgraph.Operation(schema=schema)) def drop_all(c): - return c.alter(api.Operation(drop_all=True)) + return c.alter(pydgraph.Operation(drop_all=True)) def setup(): @@ -77,7 +76,7 @@ def setUp(self): """Sets up the client and verifies the version is compatible.""" self.client = create_client(self.TEST_SERVER_ADDR) - version = self.client.any_client().check_version(api.Check()); + version = self.client.any_client().check_version(pydgraph.Check()) # version.tag string format is v.. # version_tup = [MAJOR, MINOR, PATCH] diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 2397380f..1d19cbd8 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -22,7 +22,7 @@ import multiprocessing import multiprocessing.dummy as mpd -from pydgraph import errors +import pydgraph from . import helper @@ -143,7 +143,7 @@ def upsert_account(addr, account, success_ctr, retry_ctr): # txn successful, break the loop return - except errors.AbortedError: + except pydgraph.AbortedError: with retry_ctr.get_lock(): retry_ctr.value += 1 # txn failed, retry the loop diff --git a/tests/test_client.py b/tests/test_client.py index 623539fa..cbf1c5d1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -17,13 +17,13 @@ import unittest -from pydgraph import client +import pydgraph class TestDgraphClient(unittest.TestCase): def test_constructor(self): with self.assertRaises(ValueError): - client.DgraphClient() + pydgraph.DgraphClient() def suite(): diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index d6ff07b5..dd40e398 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -17,8 +17,7 @@ import unittest -from pydgraph import client_stub -from pydgraph.proto import api_pb2 as api +import pydgraph class TestDgraphClientStub(unittest.TestCase): @@ -27,14 +26,14 @@ def validate_version_object(self, version): self.assertIsInstance(tag, str) def check_version(self, stub): - self.validate_version_object(stub.check_version(api.Check())) + self.validate_version_object(stub.check_version(pydgraph.Check())) def test_constructor(self): - self.check_version(client_stub.DgraphClientStub()) + self.check_version(pydgraph.DgraphClientStub()) def test_timeout(self): with self.assertRaises(Exception): - client_stub.DgraphClientStub().check_version(api.Check(), timeout=-1) + pydgraph.DgraphClientStub().check_version(pydgraph.Check(), timeout=-1) def suite(): diff --git a/tests/test_queries.py b/tests/test_queries.py index a2f1afd4..60607fdf 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -19,7 +19,7 @@ import logging import json -from pydgraph.proto import api_pb2 as api +import pydgraph from . import helper @@ -33,7 +33,7 @@ def setUp(self): def test_mutation_and_query(self): txn = self.client.txn() - _ = txn.mutate(api.Mutation(commit_now=True), set_nquads=""" + _ = txn.mutate(pydgraph.Mutation(commit_now=True), set_nquads=""" <_:alice> \"Alice\" . <_:greg> \"Greg\" . <_:alice> <_:greg> . diff --git a/tests/test_txn.py b/tests/test_txn.py index 5601a9b3..da6dae08 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -19,7 +19,7 @@ import logging import json -from pydgraph import errors +import pydgraph from . import helper @@ -168,7 +168,7 @@ def test_conflict(self): _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Manish'}) txn.commit() - self.assertRaises(errors.AbortedError, txn2.commit) + self.assertRaises(pydgraph.AbortedError, txn2.commit) txn3 = self.client.txn() query = """{{ @@ -204,7 +204,7 @@ def test_conflict_reverse_order(self): _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) txn2.commit() - self.assertRaises(errors.AbortedError, txn.commit) + self.assertRaises(pydgraph.AbortedError, txn.commit) txn3 = self.client.txn() resp = txn3.query(query) @@ -224,7 +224,7 @@ def test_mutation_after_commit(self): _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) txn.commit() - self.assertRaises(errors.AbortedError, txn2.commit) + self.assertRaises(pydgraph.AbortedError, txn2.commit) txn3 = self.client.txn() _ = txn3.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) diff --git a/tests/test_util.py b/tests/test_util.py index 9728a2bb..e6d900cb 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -18,7 +18,7 @@ import unittest from pydgraph import util -from pydgraph.proto import api_pb2 as api +import pydgraph from . import helper @@ -60,12 +60,12 @@ def test_none(self): def test_no_src_ids(self): lr1 = helper.create_lin_read({1: 1}) - lr2 = api.LinRead() + lr2 = pydgraph.LinRead() res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) def test_no_target_ids(self): - lr1 = api.LinRead() + lr1 = pydgraph.LinRead() lr2 = helper.create_lin_read({1: 1}) res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) From dcadf55b49b43c05aa107f76712eedc59dca08da Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 14:58:41 +0530 Subject: [PATCH 075/435] Add simple example project --- examples/simple/__init__.py | 0 examples/simple/requirements.txt | 1 + examples/simple/simple.py | 133 +++++++++++++++++++++++++++++++ pydgraph/txn.py | 4 +- 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 examples/simple/__init__.py create mode 100755 examples/simple/requirements.txt create mode 100644 examples/simple/simple.py diff --git a/examples/simple/__init__.py b/examples/simple/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/simple/requirements.txt b/examples/simple/requirements.txt new file mode 100755 index 00000000..63a374a8 --- /dev/null +++ b/examples/simple/requirements.txt @@ -0,0 +1 @@ +pydgraph>=1.0a1.0 diff --git a/examples/simple/simple.py b/examples/simple/simple.py new file mode 100644 index 00000000..0599f643 --- /dev/null +++ b/examples/simple/simple.py @@ -0,0 +1,133 @@ +import datetime +import json + +import pydgraph + + +# Create a client stub. +def create_client_stub(): + return pydgraph.DgraphClientStub("localhost:9080") + + +# Create a client. +def create_client(client_stub): + return pydgraph.DgraphClient(client_stub) + + +# Drop All - discard all data and start from a clean slate. +def drop_all(client): + return client.alter(pydgraph.Operation(drop_all=True)) + + +# Set schema. +def set_schema(client): + schema = """ + name: string @index(exact) . + age: int . + married: bool . + loc: geo . + dob: datetime . + """ + return client.alter(pydgraph.Operation(schema=schema)) + + +# Create data using JSON. +def create_data(client): + # Create a new transaction. + txn = client.txn() + try: + # Create data. + p = { + 'name': 'Alice', + 'age': 26, + 'married': True, + 'loc': { + 'type': "Point", + 'coordinates': [1.1, 2], + }, + 'dob': datetime.datetime(1980, 1, 1, 23, 0, 0, 0).isoformat(), + 'friend': [ + { + 'name': 'Bob', + 'age': 24, + }, + { + 'name': 'Charlie', + 'age': 29, + } + ], + 'school': [ + { + 'name': 'Crown Public School', + } + ] + } + + # Run mutation. + assigned = txn.mutate(set_obj=p) + + # Commit transaction. + txn.commit() + + # Get uid of the outermost object (person named "Alice"). + # assigned.uids returns a map from blank node names to uids. + # For a json mutation, blank node names "blank-0", "blank-1", ... are used + # for all the created nodes. + print('Created person named "Alice" with uid = {}\n'.format(assigned.uids['blank-0'])) + + print('All created nodes (map from blank node names to uids):') + for uid in assigned.uids: + print('{} => {}'.format(uid, assigned.uids[uid])) + print() + finally: + # Clean up. Calling this after txn.commit() is a no-op + # and hence safe. + txn.discard() + + +# Query for data. +def query_data(client): + # Run query. + query = """query all($a: string) { + all(func: eq(name, $a)) { + uid + name + age + married + loc + dob + friend { + name + age + } + school { + name + } + } + }""" + + variables = {'$a': 'Alice'} + res = client.query(query, variables=variables) + ppl = json.loads(res.json) + + # Print results. + print('Number of people named "Alice": {}'.format(len(ppl['all']))) + for person in ppl['all']: + print(person) + + +def main(): + client_stub = create_client_stub() + client = create_client(client_stub) + drop_all(client) + set_schema(client) + create_data(client) + query_data(client) + + +if __name__ == '__main__': + try: + main() + print('\nDONE!') + except Exception as e: + print('Error: {}'.format(e)) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 34874569..12999b06 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -118,9 +118,9 @@ def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, d if not mu: mu = api.Mutation() if set_obj: - mu.set_json = json.dumps(set_obj).encode('utf8') + mu.set_json = json.dumps(set_obj, default=str).encode('utf8') if del_obj: - mu.delete_json = json.dumps(del_obj).encode('utf8') + mu.delete_json = json.dumps(del_obj, default=str).encode('utf8') if set_nquads: mu.set_nquads = set_nquads.encode('utf8') if del_nquads: From 20ad3fa942cf81b3412b1797d98e9b6a144f7452 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 15:05:51 +0530 Subject: [PATCH 076/435] Add README for the simple example --- examples/simple/README.md | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 examples/simple/README.md diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 00000000..17edeedb --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,80 @@ +# Simple example project + +Simple project demonstrating the use of [pydgraph], the official python client +for Dgraph. + +[pydgraph]:https://github.com/dgraph-io/pydgraph + +## Running + +### Start Dgraph server + +You will need to install [Dgraph v1.0.0 or above][releases] and run it. + +[releases]: https://github.com/dgraph-io/dgraph/releases + +You can run the commands below to start a clean Dgraph server every time, for testing +and exploration. + +First, create two separate directories for `dgraph zero` and `dgraph server`. + +```sh +mkdir -p dgraphdata/zero dgraphdata/data +``` + +Then start `dgraph zero`: + +```sh +cd dgraphdata/zero +rm -r zw; dgraph zero +# If running Dgraph version <= 1.0.2, use the following command instead: +# rm -r zw; dgraph zero --port_offset -2000 +``` + +Finally, start the `dgraph server`: + +```sh +cd dgraphdata/data +rm -r p w; dgraph server --memory_mb=1024 --zero localhost:5080 +``` + +For more configuration options, and other details, refer to +[docs.dgraph.io](https://docs.dgraph.io) + +## Install dependencies + +```sh +npm install +``` + +## Run the sample code + +```sh +python simple.py +``` + +Your output should look something like this (uid values may be different): + +```console +Created person named "Alice" with uid = 0x7569 + +All created nodes (map from blank node names to uids): +blank-0: 0x7569 +blank-1: 0x756a +blank-2: 0x756b +blank-3: 0x756c + +Number of people named "Alice": 1 +{ uid: '0x7569', + name: 'Alice', + age: 26, + married: true, + loc: { type: 'Point', coordinates: [ 1.1, 2 ] }, + dob: '1980-02-01T17:30:00Z', + friend: [ { name: 'Bob', age: 24 }, { name: 'Charlie', age: 29 } ], + school: [ { name: 'Crown Public School' } ] } + +DONE! +``` + +You can explore the source code in the `simple.py` file. From bf277f4bc32c3d44079795a6e1f947e2cf95e0e0 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 15:06:59 +0530 Subject: [PATCH 077/435] Fix simple example installation instructions --- examples/simple/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple/README.md b/examples/simple/README.md index 17edeedb..cafa5add 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -44,7 +44,7 @@ For more configuration options, and other details, refer to ## Install dependencies ```sh -npm install +pip install -r requirements.txt ``` ## Run the sample code From 9137fc3c02477dfb535d813b5535af437452a346 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 16:14:02 +0530 Subject: [PATCH 078/435] Update README and refactor code a bit --- README.md | 282 +++++++++++++++++++++++++++++++++++++- examples/simple/simple.py | 7 +- pydgraph/client_stub.py | 4 + pydgraph/txn.py | 18 +-- 4 files changed, 300 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c7c3976f..6d739590 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,283 @@ # pydgraph -Official Dgraph client implementation for Python. +Official Dgraph client implementation for python (python v3.5 and above), +using [grpc]. + +[grpc]: https://grpc.io/ + +This client follows the [Dgraph Go client][goclient] closely. + +[goclient]: https://github.com/dgraph-io/dgraph/tree/master/client + +Before using this client, we highly recommend that you go through [docs.dgraph.io], +and understand how to run and work with Dgraph. + +[docs.dgraph.io]:https://docs.dgraph.io + +## Table of contents + +- [Install](#install) +- [Quickstart](#quickstart) +- [Using a client](#using-a-client) + - [Create a client](#create-a-client) + - [Alter the database](#alter-the-database) + - [Create a transaction](#create-a-transaction) + - [Run a mutation](#run-a-mutation) + - [Run a query](#run-a-query) + - [Commit a transaction](#commit-a-transaction) + - [Cleanup Resources](#cleanup-resources) + - [Debug mode](#debug-mode) +- [Development](#development) + - [Building the source](#building-the-source) + - [Running tests](#running-tests) + +## Install + +Install using pip: + +```sh +pip install pydgraph +``` + +## Quickstart + +Build and run the [simple] project in the `examples` folder, which +contains an end-to-end example of using the Dgraph python client. Follow the +instructions in the README of that project. + +[simple]: https://github.com/dgraph-io/pydgraph/tree/master/examples/simple + +## Using a client + +### Create a client + +A `DgraphClient` object can be initialised by passing it a list of +`DgraphClientStub` clients as variadic arguments. Connecting to multiple Dgraph +servers in the same cluster allows for better distribution of workload. + +The following code snippet shows just one connection. + +```python +import pydgraph + +client_stub = pydgraph.DgraphClientStub('localhost:9080') +client = pydgraph.DgraphClient(client_stub) +``` + +### Alter the database + +To set the schema, create an `Operation` object, set the schema and pass it to +`DgraphClient#alter(Operation)` method. + +```python +schema = 'name: string @index(exact) .' +op = pydgraph.Operation(schema=schema) +client.alter(op) +``` + +`Operation` contains other fields as well, including drop predicate and drop all. +Drop all is useful if you wish to discard all the data, and start from a clean +slate, without bringing the instance down. + +```python +# Drop all data including schema from the Dgraph instance. This is useful +# for small examples such as this, since it puts Dgraph into a clean +# state. +op = pydgraph.Operation(drop_all=True) +client.alter(op) +``` + +### Create a transaction + +To create a transaction, call `DgraphClient#txn()` method, which returns a +new `Txn` object. This operation incurs no network overhead. + +It is good practise to call `Txn#discard()` in a `finally` block after running +the transaction. Calling `Txn#discard()` after `Txn#commit()` is a no-op +and you can call `Txn#discard()` multiple times with no additional side-effects. + +```python +txn = client.txn() +try: + # Do something here + # ... +finally: + txn.discard() + # ... +``` + +### Run a mutation + +`Txn#mutate(mu=Mutation)` runs a mutation. It takes in a `Mutation` object, +which provides two main ways to set data: JSON and RDF N-Quad. You can choose +whichever way is convenient. Most users won't need to create a `Mutation` +object themselves. + +`Txn#mutate()` provides convenience keyword arguments `set_obj` and `del_obj` +for setting JSON values and `set_nquads` and `del_nquads` for setting N-Quad +values. See examples below for usage. + +We define a person object to represent a person and use it in a transaction. + +```python +# Create data. +p = { + 'name': 'Alice', +} + +# Run mutation. +txn.mutate(set_obj=p) + +# If you want to use a mutation object, use this instead: +# mu = pydgraph.Mutation(set_json=json.dumps(p).encode('utf8')) +# txn.mutate(mu) + +# If you want to use N-Quads, use this instead: +# txn.mutate(set_nquads='_:alice "Alice"') +``` + +For a more complete example with multiple fields and relationships, look at the +[simple] project in the `examples` folder. + +Sometimes, you only want to commit a mutation, without querying anything further. +In such cases, you can set the keyword argument `commit_now=True` to indicate +that the mutation must be immediately committed. + +Keyword argument `ignore_index_conflict=True` can be used to not run conflict +detection over the index, which would decrease the number of transaction +conflicts and aborts. However, this would come at the cost of potentially +inconsistent upsert operations. + +### Run a query + +You can run a query by calling `Txn#query(string)`. You will need to pass in a +GraphQL+- query string. If you want to pass an additional dictionary of any +variables that you might want to set in the query, call +`Txn#query(string, variables=d)` with the variables dictionary `d`. + +The response would contain the field `json`, which returns the response +JSON. + +Let’s run the following query with a variable $a: + +```console +query all($a: string) { + all(func: eq(name, $a)) + { + name + } +} +``` + +Run the query, deserialize the result from JSON and print it out: + +```python +# Run query. +query = """query all($a: string) { + all(func: eq(name, $a)) + { + name + } +}""" +variables = {'$a': 'Alice'} + +res = client.txn().query(query, variables=variables) +# If not doing a mutation in the same transaction, simply use: +# res = client.query(query, variables=variables) + +ppl = json.loads(res.json); + +# Print results. +print('Number of people named "Alice": {}'.format(len(ppl['all']))) +for person in ppl['all']: + print(person) +``` + +This should print: + +```console +Number of people named "Alice": 1 +Alice +``` + +### Commit a transaction + +A transaction can be committed using the `Txn#commit()` method. If your transaction +consisted solely of calls to `Txn#query` or `Txn#queryWithVars`, and no calls to +`Txn#mutate`, then calling `Txn#commit()` is not necessary. + +An error will be raised if other transactions running concurrently modify the same +data that was modified in this transaction. It is up to the user to retry +transactions when they fail. + +```python +txn = client.txn(); +try: + # ... + # Perform any number of queries and mutations + # ... + # and finally... + txn.commit() +except Exception as e: + if isinstance(e, pydgraph.AbortedError): + # Retry or handle exception. + else: + raise e +finally: + # Clean up. Calling this after txn.commit() is a no-op + # and hence safe. + txn.discard() +``` + +### Cleanup Resources + +To cleanup resources, you have to call `DgraphClientStub#close()` individually for +all the instances of `DgraphClientStub`. + +```python +SERVER_ADDR = "localhost:9080" + +# Create instances of DgraphClientStub. +stub1 = pydgraph.DgraphClientStub(SERVER_ADDR) +stub2 = dgraph.DgraphClientStub(SERVER_ADDR) + +# Create an instance of DgraphClient. +client = pydgraph.DgraphClient(stub1, stub2) + +# ... +# Use client +# ... + +# Cleanup resources by closing all client stubs. +stub1.close() +stub2.close() +``` + +## Development + +### Building the source + +```sh +python setup.py install +# To install for the current user, use this instead: +# python setup.py install --user +``` + +If you have made changes to the `pydgraph/proto/api.proto` file, you need need +to regenerate the source files generated by Protocol Buffer tools. To do that, +install the [grpcio-tools][grpcio-tools] library and then run the following +command: + +[grpcio_tools]: https://pypi.python.org/pypi/grpcio-tools + +```sh +python scripts/protogen.py +``` + +### Running tests + +Make sure you have a Dgraph server running on localhost before you run this task. + +```sh +python setup.py test +``` diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 0599f643..20663c78 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -6,7 +6,7 @@ # Create a client stub. def create_client_stub(): - return pydgraph.DgraphClientStub("localhost:9080") + return pydgraph.DgraphClientStub('localhost:9080') # Create a client. @@ -42,7 +42,7 @@ def create_data(client): 'age': 26, 'married': True, 'loc': { - 'type': "Point", + 'type': 'Point', 'coordinates': [1.1, 2], }, 'dob': datetime.datetime(1980, 1, 1, 23, 0, 0, 0).isoformat(), @@ -124,6 +124,9 @@ def main(): create_data(client) query_data(client) + # Close the client stub. + client_stub.close() + if __name__ == '__main__': try: diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 02ba6ba2..43df49a1 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -61,3 +61,7 @@ def check_version(self, check, timeout=None, metadata=None, credentials=None): async def async_check_version(self, check, timeout=None, metadata=None, credentials=None): return await self.stub.CheckVersion.future(check, timeout=timeout, metadata=metadata, credentials=credentials) + + def close(self): + del self.channel + del self.stub diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 12999b06..feafe1c2 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -72,10 +72,10 @@ def _common_query(self, q, variables=None): return req - def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, ignore_index_conflict=None, - timeout=None, metadata=None, credentials=None): + def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, commit_now=None, + ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): mu = self._common_mutate(mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, - ignore_index_conflict=ignore_index_conflict) + commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) try: ag = self._dc.any_client().mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) @@ -95,9 +95,9 @@ def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquad return ag async def async_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, - ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): + commit_now=None, ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): mu = self._common_mutate(mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, - ignore_index_conflict=ignore_index_conflict) + commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) try: ag = await self._dc.any_client().async_mutate(mu, timeout=timeout, metadata=metadata, @@ -114,17 +114,19 @@ async def async_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=Non self._common_except_mutate(e) def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, - ignore_index_conflict=None): + commit_now=None, ignore_index_conflict=None): if not mu: mu = api.Mutation() if set_obj: - mu.set_json = json.dumps(set_obj, default=str).encode('utf8') + mu.set_json = json.dumps(set_obj).encode('utf8') if del_obj: - mu.delete_json = json.dumps(del_obj, default=str).encode('utf8') + mu.delete_json = json.dumps(del_obj).encode('utf8') if set_nquads: mu.set_nquads = set_nquads.encode('utf8') if del_nquads: mu.del_nquads = del_nquads.encode('utf8') + if commit_now: + mu.commit_now = True if ignore_index_conflict: mu.ignore_index_conflict = True From c97a27a53c5db19ed1d66fc00be5d14e4e1e5664 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 16:17:27 +0530 Subject: [PATCH 079/435] Fix example in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d739590..37c249a0 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ SERVER_ADDR = "localhost:9080" # Create instances of DgraphClientStub. stub1 = pydgraph.DgraphClientStub(SERVER_ADDR) -stub2 = dgraph.DgraphClientStub(SERVER_ADDR) +stub2 = pydgraph.DgraphClientStub(SERVER_ADDR) # Create an instance of DgraphClient. client = pydgraph.DgraphClient(stub1, stub2) From d96298521de5c4997eb0d71db8f2235b2da767ab Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Mon, 2 Apr 2018 16:21:03 +0530 Subject: [PATCH 080/435] Cleanup README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 37c249a0..84be795c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ and understand how to run and work with Dgraph. - [Run a query](#run-a-query) - [Commit a transaction](#commit-a-transaction) - [Cleanup Resources](#cleanup-resources) - - [Debug mode](#debug-mode) - [Development](#development) - [Building the source](#building-the-source) - [Running tests](#running-tests) @@ -268,7 +267,7 @@ to regenerate the source files generated by Protocol Buffer tools. To do that, install the [grpcio-tools][grpcio-tools] library and then run the following command: -[grpcio_tools]: https://pypi.python.org/pypi/grpcio-tools +[grpcio-tools]: https://pypi.python.org/pypi/grpcio-tools ```sh python scripts/protogen.py From 23cf1815bb7d6efdd15c2ceac2751f47a1adcd47 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 14:20:20 +0530 Subject: [PATCH 081/435] Add travis and coveralls --- .gitignore | 3 +++ .travis.yml | 13 +++++++++++ scripts/build.sh | 14 ++++++++++++ scripts/functions.sh | 46 +++++++++++++++++++++++++++++++++++++++ scripts/install_dgraph.sh | 3 +++ 5 files changed, 79 insertions(+) create mode 100644 .travis.yml create mode 100755 scripts/build.sh create mode 100755 scripts/functions.sh create mode 100755 scripts/install_dgraph.sh diff --git a/.gitignore b/.gitignore index 56dc6213..d4bc70b7 100755 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ wheels/ *.egg MANIFEST +# Coverage +.coverage + # IntelliJ .idea pydgraph.iml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..a20c34a5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: + - "3.5" + - "3.6" +sudo: required +dist: trusty +before_install: + - scripts/install_dgraph.sh +install: + - pip install -r requirements.txt +script: + - scripts/build.sh +after_success: coveralls \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..7f1370f2 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e +set -x + +source scripts/functions.sh + +init +startZero +start + +coverage run --source=pydgraph setup.py test + +quit 0 diff --git a/scripts/functions.sh b/scripts/functions.sh new file mode 100755 index 00000000..7bb7a7c9 --- /dev/null +++ b/scripts/functions.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +sleepTime=5 +if [[ "$TRAVIS" == true ]]; then + sleepTime=30 +fi + +function quit { + echo "Shutting down Dgraph server and zero." + curl -s localhost:8080/admin/shutdown + # Kill Dgraph zero. + kill -9 $(pgrep -f "dgraph zero") > /dev/null + + if pgrep -x dgraph > /dev/null + then + while pgrep dgraph; + do + echo "Sleeping for 5 secs so that Dgraph can shutdown." + sleep 5 + done + fi + + echo "Clean shutdown done." + return $1 +} + +function start { + echo -e "Starting first server." + dgraph server -p data/p -w data/w --memory_mb 4096 --zero localhost:5080 > data/server.log 2>&1 & + # Wait for membership sync to happen. + sleep $sleepTime + return 0 +} + +function startZero { + echo -e "Starting Dgraph zero.\n" + dgraph zero -w data/wz > data/zero.log 2>&1 & + # To ensure Dgraph doesn't start before Dgraph zero. + # It takes time for zero to start on travis mac. + sleep $sleepTime +} + +function init { + echo -e "Initializing.\n" + mkdir data +} diff --git a/scripts/install_dgraph.sh b/scripts/install_dgraph.sh new file mode 100755 index 00000000..b2ec3d76 --- /dev/null +++ b/scripts/install_dgraph.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +curl https://get.dgraph.io -sSf | bash -s nightly From 090150165eaff83a3a22e159bef8a0bc2dcdd897 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 14:38:03 +0530 Subject: [PATCH 082/435] Update version --- pydgraph/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/meta.py b/pydgraph/meta.py index be7993e3..dfe2d4ca 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = '1.0a1.0' +VERSION = '1.0.0a1' From d1673465a67200181a48c280e0d5f6343523afbc Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 14:45:37 +0530 Subject: [PATCH 083/435] Update setup.py and README --- README.md | 2 +- setup.py | 44 ++++++++++++++++++++------------------------ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 84be795c..e75244e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pydgraph -Official Dgraph client implementation for python (python v3.5 and above), +Official Dgraph client implementation for Python (Python v3.5 and above), using [grpc]. [grpc]: https://grpc.io/ diff --git a/setup.py b/setup.py index 2a0f6c45..6b728202 100755 --- a/setup.py +++ b/setup.py @@ -12,35 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys try: from setuptools import setup except ImportError: from distutils.core import setup -if sys.version_info >= (2, 7, 9): - import ssl - ssl._create_default_https_context = ssl._create_unverified_context - from pydgraph.meta import VERSION -setup(name='pydgraph', - version=VERSION, - description='Official Dgraph client implementation for Python', - license='Apache License, Version 2.0', - author='Mohit Ranka', - author_email='mohitranka@gmail.com', - url='https://github.com/dgraph-io/pydgraph', - classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Topic :: Database', - 'Topic :: Software Development', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - ], - packages=['pydgraph', 'pydgraph.proto'], - install_requires=open('requirements.txt').readlines(), - test_suite='tests', +setup( + name='pydgraph', + version=VERSION, + description='Official Dgraph client implementation for Python', + license='Apache License, Version 2.0', + author='Dgraph Labs', + author_email='contact@dgraph.io', + url='https://github.com/dgraph-io/pydgraph', + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Topic :: Database', + 'Topic :: Software Development', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + packages=['pydgraph', 'pydgraph.proto'], + install_requires=open('requirements.txt').readlines(), + test_suite='tests', ) From b1eb9fb2983269472b652ee960c6e1b00ee60546 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 14:55:12 +0530 Subject: [PATCH 084/435] Add setup.cfg --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..b88034e4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md From a77156df3ced4487ea5c6f16a98d8d5e4d9a7f2b Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 15:06:05 +0530 Subject: [PATCH 085/435] Fix travis package installation --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a20c34a5..20ff8b2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ before_install: - scripts/install_dgraph.sh install: - pip install -r requirements.txt + - pip install coveralls script: - scripts/build.sh after_success: coveralls \ No newline at end of file From 36a70141ec49c46fa96edd7567a862c947335394 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 15:06:55 +0530 Subject: [PATCH 086/435] Fix README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f10e3e59..e75244e9 100644 --- a/README.md +++ b/README.md @@ -280,4 +280,3 @@ Make sure you have a Dgraph server running on localhost before you run this task ```sh python setup.py test ``` ->>>>>>> gp/v1 From 3dd8d520541a11aa7c938f77b3425be23a3bed29 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 15:23:46 +0530 Subject: [PATCH 087/435] Update go client link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e75244e9..c6789966 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ using [grpc]. This client follows the [Dgraph Go client][goclient] closely. -[goclient]: https://github.com/dgraph-io/dgraph/tree/master/client +[goclient]: https://github.com/dgraph-io/dgo Before using this client, we highly recommend that you go through [docs.dgraph.io], and understand how to run and work with Dgraph. From 8930019fb408a19ff938f6daa9680deeee989f1d Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 15:40:27 +0530 Subject: [PATCH 088/435] Replace --memory_mb with --lru_mb --- scripts/functions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/functions.sh b/scripts/functions.sh index 7bb7a7c9..1a7e4c29 100755 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -26,7 +26,7 @@ function quit { function start { echo -e "Starting first server." - dgraph server -p data/p -w data/w --memory_mb 4096 --zero localhost:5080 > data/server.log 2>&1 & + dgraph server -p data/p -w data/w --lru_mb 4096 --zero localhost:5080 > data/server.log 2>&1 & # Wait for membership sync to happen. sleep $sleepTime return 0 From dcb97885d0430e17fff85b2a39d9bfb1f650b7e3 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 15:50:08 +0530 Subject: [PATCH 089/435] Remove python 3.5 temporarily --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 20ff8b2b..79ddde81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python python: - - "3.5" - "3.6" sudo: required dist: trusty From 35b073e42fed71e6aba2a81bbcb34acaf69508a0 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 17:10:38 +0530 Subject: [PATCH 090/435] Remove support for async API grpcio currently doesn't support the asyncio future interface or async/await syntax. This makes it difficult to use pydgraph async. This feature might be reintroduced once that is resolved or we can find a proper workaround. --- pydgraph/client.py | 7 ------- pydgraph/client_stub.py | 15 -------------- pydgraph/txn.py | 42 --------------------------------------- tests/test_client_stub.py | 7 +++++++ 4 files changed, 7 insertions(+), 64 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 97b9a94c..639c07a8 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -40,16 +40,9 @@ def __init__(self, *clients): def alter(self, op, timeout=None, metadata=None, credentials=None): return self.any_client().alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - - async def async_alter(self, op, timeout=None, metadata=None, credentials=None): - return await self.any_client().async_alter(op, timeout=timeout, metadata=metadata, credentials=credentials) def query(self, q, variables=None, timeout=None, metadata=None, credentials=None): return self.txn().query(q, variables=variables, timeout=timeout, metadata=metadata, credentials=credentials) - - async def async_query(self, q, variables=None, timeout=None, metadata=None, credentials=None): - return self.txn().async_query(q, variables=variables, timeout=timeout, metadata=metadata, - credentials=credentials) def txn(self): return txn.Txn(self) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 43df49a1..ecb3aa8b 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -35,32 +35,17 @@ def __init__(self, addr='localhost:9080', credentials=None, options=None): def alter(self, op, timeout=None, metadata=None, credentials=None): return self.stub.Alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_alter(self, op, timeout=None, metadata=None, credentials=None): - return await self.stub.Alter.future(op, timeout=timeout, metadata=metadata, credentials=credentials) - def query(self, req, timeout=None, metadata=None, credentials=None): return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_query(self, req, timeout=None, metadata=None, credentials=None): - return await self.stub.Query.future(req, timeout=timeout, metadata=metadata, credentials=credentials) - def mutate(self, mu, timeout=None, metadata=None, credentials=None): return self.stub.Mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_mutate(self, mu, timeout=None, metadata=None, credentials=None): - return await self.stub.Mutate.future(mu, timeout=timeout, metadata=metadata, credentials=credentials) - def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): return self.stub.CommitOrAbort(ctx, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): - return await self.stub.CommitOrAbort.future(ctx, timeout=timeout, metadata=metadata, credentials=credentials) - def check_version(self, check, timeout=None, metadata=None, credentials=None): return self.stub.CheckVersion(check, timeout=timeout, metadata=metadata, credentials=credentials) - - async def async_check_version(self, check, timeout=None, metadata=None, credentials=None): - return await self.stub.CheckVersion.future(check, timeout=timeout, metadata=metadata, credentials=credentials) def close(self): del self.channel diff --git a/pydgraph/txn.py b/pydgraph/txn.py index feafe1c2..83df8ac4 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -54,12 +54,6 @@ def query(self, q, variables=None, timeout=None, metadata=None, credentials=None self.merge_context(res.txn) return res - async def async_query(self, q, variables=None, timeout=None, metadata=None, credentials=None): - req = self._common_query(q, variables=variables) - res = await self._dc.any_client().async_query(req, timeout=timeout, metadata=metadata, credentials=credentials) - self.merge_context(res.txn) - return res - def _common_query(self, q, variables=None): if self._finished: raise Exception('Transaction has already been committed or discarded') @@ -94,25 +88,6 @@ def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquad self.merge_context(ag.context) return ag - async def async_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, - commit_now=None, ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): - mu = self._common_mutate(mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, - commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) - - try: - ag = await self._dc.any_client().async_mutate(mu, timeout=timeout, metadata=metadata, - credentials=credentials) - self.merge_context(ag.context) - return ag - except Exception as e: - try: - await self.async_discard(timeout=timeout, metadata=metadata, credentials=credentials) - except: - # Ignore error - user should see the original error. - pass - - self._common_except_mutate(e) - def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, commit_now=None, ignore_index_conflict=None): if not mu: @@ -157,16 +132,6 @@ def commit(self, timeout=None, metadata=None, credentials=None): except Exception as e: self._common_except_commit(e) - async def async_commit(self, timeout=None, metadata=None, credentials=None): - if not self._common_commit(): - return - - try: - await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, - credentials=credentials) - except Exception as e: - self._common_except_commit(e) - def _common_commit(self): if self._finished: raise Exception('Transaction has already been committed or discarded') @@ -190,13 +155,6 @@ def discard(self, timeout=None, metadata=None, credentials=None): self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, credentials=credentials) - async def async_discard(self, timeout=None, metadata=None, credentials=None): - if not self._common_discard(): - return - - await self._dc.any_client().async_commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, - credentials=credentials) - def _common_discard(self): if self._finished: return False diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index dd40e398..35a24019 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -35,6 +35,13 @@ def test_timeout(self): with self.assertRaises(Exception): pydgraph.DgraphClientStub().check_version(pydgraph.Check(), timeout=-1) + def test_close(self): + client_stub = pydgraph.DgraphClientStub() + self.check_version(client_stub) + client_stub.close() + with self.assertRaises(Exception): + client_stub.check_version(pydgraph.Check()) + def suite(): s = unittest.TestSuite() From bff848ea9d479603f550ee26399773b90d6c26b1 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 17:51:27 +0530 Subject: [PATCH 091/435] Add support for Python 2.7 --- .travis.yml | 1 + README.md | 2 +- pydgraph/util.py | 5 +++++ setup.py | 15 ++++++++------- tests/test_acct_upsert.py | 11 ++++++----- tests/test_bank.py | 2 ++ tests/test_client_stub.py | 5 +++++ tests/test_queries.py | 14 +++++++++++--- 8 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79ddde81..cd101fc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - "3.6" + - "2.7" sudo: required dist: trusty before_install: diff --git a/README.md b/README.md index c6789966..1bbd1788 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pydgraph -Official Dgraph client implementation for Python (Python v3.5 and above), +Official Dgraph client implementation for Python (Python >= v2.7 and >= v3.5), using [grpc]. [grpc]: https://grpc.io/ diff --git a/pydgraph/util.py b/pydgraph/util.py index 530c9814..273e7d96 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + from pydgraph.meta import VERSION __author__ = 'Shailesh Kochhar ' @@ -36,4 +38,7 @@ def merge_lin_reads(target, src): def is_string(s): + if sys.version_info[0] < 3: + return isinstance(s, basestring) + return isinstance(s, str) diff --git a/setup.py b/setup.py index 6b728202..a9ed4923 100755 --- a/setup.py +++ b/setup.py @@ -28,13 +28,14 @@ author_email='contact@dgraph.io', url='https://github.com/dgraph-io/pydgraph', classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Topic :: Database', - 'Topic :: Software Development', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Topic :: Database', + 'Topic :: Software Development', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], packages=['pydgraph', 'pydgraph.proto'], install_requires=open('requirements.txt').readlines(), diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 1d19cbd8..c299c5c4 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -62,16 +62,17 @@ def do_upserts(self, account_list, concurrency): success_ctr = multiprocessing.Value('i', 0, lock=True) retry_ctr = multiprocessing.Value('i', 0, lock=True) + def _updater(acct): + upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, success_ctr=success_ctr, retry_ctr=retry_ctr) + pool = mpd.Pool(concurrency) - updater = lambda acct: upsert_account(addr=self.TEST_SERVER_ADDR, - account=acct, - success_ctr=success_ctr, - retry_ctr=retry_ctr) results = [ - pool.apply_async(updater, (acct,)) + pool.apply_async(_updater, (acct,)) for acct in account_list for _ in range(concurrency) ] + [res.get() for res in results] + pool.close() def assert_changes(self, firsts, accounts): """Will check to see changes have been made.""" diff --git a/tests/test_bank.py b/tests/test_bank.py index 261e45b3..228237c6 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -50,6 +50,7 @@ def test_bank_transfer(self): success_ctr = mp.Value('i', 0, lock=True) retry_ctr = mp.Value('i', 0, lock=True) + pool = mpd.Pool(CONCURRENCY) results = [pool.apply_async( run_transfers, @@ -57,6 +58,7 @@ def test_bank_transfer(self): ) for _ in range(CONCURRENCY)] [res.get() for res in results] + pool.close() finally: total_watcher.terminate() time.sleep(0.1) diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index 35a24019..70e7b7a4 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -16,6 +16,7 @@ __maintainer__ = 'Garvit Pahal ' import unittest +import sys import pydgraph @@ -23,6 +24,10 @@ class TestDgraphClientStub(unittest.TestCase): def validate_version_object(self, version): tag = version.tag + if sys.version_info[0] < 3: + self.assertIsInstance(tag, basestring) + return + self.assertIsInstance(tag, str) def check_version(self, stub): diff --git a/tests/test_queries.py b/tests/test_queries.py index 60607fdf..0ffea06d 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -16,6 +16,7 @@ __maintainer__ = 'Garvit Pahal ' import unittest +import sys import logging import json @@ -53,9 +54,16 @@ def test_mutation_and_query(self): response = self.client.query(query) self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], json.loads(response.json).get('me')) - self.assertTrue(isinstance(response.latency.parsing_ns, int), 'Parsing latency is not available') - self.assertTrue(isinstance(response.latency.processing_ns, int), 'Processing latency is not available') - self.assertTrue(isinstance(response.latency.encoding_ns, int), 'Encoding latency is not available') + self.assertTrue(is_number(response.latency.parsing_ns), 'Parsing latency is not available') + self.assertTrue(is_number(response.latency.processing_ns), 'Processing latency is not available') + self.assertTrue(is_number(response.latency.encoding_ns), 'Encoding latency is not available') + + +def is_number(n): + if sys.version_info[0] < 3: + return isinstance(n, (int, long)) + + return isinstance(n, int) def suite(): From fa0e222dc219bf97469ff54a49816fa883b58585 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 18:02:31 +0530 Subject: [PATCH 092/435] Add more tests --- tests/test_queries.py | 7 +++---- tests/test_txn.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/tests/test_queries.py b/tests/test_queries.py index 0ffea06d..68b7e5b1 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -40,7 +40,7 @@ def test_mutation_and_query(self): <_:alice> <_:greg> . """) - query = """{ + query = """query me($a: string) { me(func: anyofterms(name, "Alice")) { name @@ -49,10 +49,9 @@ def test_mutation_and_query(self): name } } - } - """ + }""" - response = self.client.query(query) + response = self.client.query(query, variables={'$a': 'Alice'}) self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], json.loads(response.json).get('me')) self.assertTrue(is_number(response.latency.parsing_ns), 'Parsing latency is not available') self.assertTrue(is_number(response.latency.processing_ns), 'Processing latency is not available') diff --git a/tests/test_txn.py b/tests/test_txn.py index da6dae08..3730abcd 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -31,6 +31,35 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(fulltext) .') + def test_query_after_commit(self): + txn = self.client.txn() + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + + for _, uid in assigned.uids.items(): + uid = uid + + txn.commit() + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + + with self.assertRaises(Exception): + txn.query(query) + + def test_mutate_after_commit(self): + txn = self.client.txn() + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + + txn.commit() + + with self.assertRaises(Exception): + txn.mutate(set_obj={'name': 'Manish2'}) + def test_read_at_start_ts(self): """Tests read after write when readTs == startTs""" From 44d7cb2744202a78e8e29ff134c850a48aa64635 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 18:22:46 +0530 Subject: [PATCH 093/435] Update coverage command to exclude proto files --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 7f1370f2..c446f49c 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -9,6 +9,6 @@ init startZero start -coverage run --source=pydgraph setup.py test +coverage run --source=pydgraph --omit=pydgraph/proto/* setup.py test quit 0 From fbc09945d6b45c33c3c11b05eecfe25404020511 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 18:34:50 +0530 Subject: [PATCH 094/435] Add more txn tests --- tests/test_txn.py | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/test_txn.py b/tests/test_txn.py index 3730abcd..9b652c04 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -29,7 +29,7 @@ def setUp(self): super(TestTxn, self).setUp() helper.drop_all(self.client) - helper.set_schema(self.client, 'name: string @index(fulltext) .') + helper.set_schema(self.client, 'name: string @index(fulltext) @upsert .') def test_query_after_commit(self): txn = self.client.txn() @@ -60,6 +60,24 @@ def test_mutate_after_commit(self): with self.assertRaises(Exception): txn.mutate(set_obj={'name': 'Manish2'}) + def test_commit_now(self): + txn = self.client.txn() + assigned = txn.mutate(set_obj={'name': 'Manish'}, commit_now=True) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + + for _, uid in assigned.uids.items(): + uid = uid + + self.assertRaises(Exception, txn.commit) + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + resp = self.client.query(query) + self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) + def test_read_at_start_ts(self): """Tests read after write when readTs == startTs""" @@ -239,9 +257,7 @@ def test_conflict_reverse_order(self): resp = txn3.query(query) self.assertEqual([{'name': 'Jan the man'}], json.loads(resp.json).get('me')) - def test_mutation_after_commit(self): - """Tests a second mutation after failing to commit a first mutation.""" - + def test_commit_conflict(self): txn = self.client.txn() assigned = txn.mutate(set_obj={'name': 'Manish'}) self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') @@ -268,9 +284,26 @@ def test_mutation_after_commit(self): resp4 = self.client.query(query) self.assertEqual([{'name': 'Jan the man'}], json.loads(resp4.json).get('me')) + def test_mutate_conflict(self): + txn = self.client.txn() + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + + for _, uid in assigned.uids.items(): + uid = uid + + txn2 = self.client.txn() + _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) + + txn2.commit() + with self.assertRaises(pydgraph.AbortedError): + txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + def test_conflict_ignore(self): """Tests a mutation with ignore index conflict.""" + helper.set_schema(self.client, 'name: string @index(fulltext) .') + txn = self.client.txn() assigned1 = txn.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) self.assertEqual(1, len(assigned1.uids), 'Nothing was assigned') From 302591085f5404e3755b16919b5d8ed243b5bda1 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 18:43:32 +0530 Subject: [PATCH 095/435] Fix test failure --- tests/test_txn.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_txn.py b/tests/test_txn.py index 9b652c04..0b8eb617 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -296,8 +296,9 @@ def test_mutate_conflict(self): _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Jan the man'}) txn2.commit() - with self.assertRaises(pydgraph.AbortedError): - txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + + _ = txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + self.assertRaises(pydgraph.AbortedError, txn.commit) def test_conflict_ignore(self): """Tests a mutation with ignore index conflict.""" From 41d501f632a0d60787825e0d03bc14fde06cbcaa Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Tue, 3 Apr 2018 18:55:26 +0530 Subject: [PATCH 096/435] Add tests for mutate and discard --- tests/test_txn.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/test_txn.py b/tests/test_txn.py index 0b8eb617..446af970 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -78,6 +78,37 @@ def test_commit_now(self): resp = self.client.query(query) self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) + def test_discard(self): + txn = self.client.txn() + assigned = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + txn.commit() + + for _, uid in assigned.uids.items(): + uid = uid + + txn2 = self.client.txn() + _ = txn2.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + + txn.discard() + self.assertRaises(Exception, txn.commit) + + query = """{{ + me(func: uid("{uid:s}")) {{ + name + }} + }}""".format(uid=uid) + resp = self.client.query(query) + self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) + + def test_mutate_error(self): + txn = self.client.txn() + with self.assertRaises(Exception): + # Following N-Quad is invalid + _ = txn.mutate(set_nquads='_:node Manish') + + self.assertRaises(Exception, txn.commit) + def test_read_at_start_ts(self): """Tests read after write when readTs == startTs""" From a3a89b2df2abbf17d433e1458fe04db7aa30ca1b Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 4 Apr 2018 11:59:46 +0530 Subject: [PATCH 097/435] Add travis and coveralls badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bbd1788..a7a1f305 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# pydgraph +# pydgraph [![Build Status](https://img.shields.io/travis/dgraph-io/pydgraph/master.svg?style=flat)](https://travis-ci.org/dgraph-io/pydgraph) [![Coverage Status](https://img.shields.io/coveralls/github/dgraph-io/pydgraph/master.svg?style=flat)](https://coveralls.io/github/dgraph-io/pydgraph?branch=master) Official Dgraph client implementation for Python (Python >= v2.7 and >= v3.5), using [grpc]. From 106a654d917a0d9b62b4bffe6a01bd273d812243 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 4 Apr 2018 12:27:04 +0530 Subject: [PATCH 098/435] Update setup.cfg --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index b88034e4..1cab3df7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,6 @@ [metadata] description-file = README.md + +[bdist_wheel] +# This flag says to generate wheels that support both Python 2 and Python 3. +universal = 1 From 9c70de864097f02e73f5977e8d9ee8140d1c7bf8 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 4 Apr 2018 12:41:31 +0530 Subject: [PATCH 099/435] Add README as long description --- pydgraph/meta.py | 2 +- setup.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pydgraph/meta.py b/pydgraph/meta.py index dfe2d4ca..2c33bfa9 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = '1.0.0a1' +VERSION = '1.0.0a2' diff --git a/setup.py b/setup.py index a9ed4923..8c459ad8 100755 --- a/setup.py +++ b/setup.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os +from pypandoc import convert + try: from setuptools import setup except ImportError: @@ -19,10 +22,13 @@ from pydgraph.meta import VERSION +README = os.path.join(os.path.dirname(__file__), 'README.md') + setup( name='pydgraph', version=VERSION, description='Official Dgraph client implementation for Python', + long_description=convert(README, 'rst'), license='Apache License, Version 2.0', author='Dgraph Labs', author_email='contact@dgraph.io', From 9151fb79cfccccc9d025543ff7caf58170ed4029 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 4 Apr 2018 13:15:06 +0530 Subject: [PATCH 100/435] Fix travis builds --- pydgraph/meta.py | 2 +- setup.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 2c33bfa9..5f141bd7 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = '1.0.0a2' +VERSION = '1.0.0a3' diff --git a/setup.py b/setup.py index 8c459ad8..119abaf3 100755 --- a/setup.py +++ b/setup.py @@ -13,13 +13,23 @@ # limitations under the License. import os -from pypandoc import convert +import sys try: from setuptools import setup except ImportError: from distutils.core import setup +try: + from pypandoc import convert +except ImportError as e: + # NOTE: error is thrown only for package build steps + if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv: + raise e + + def convert(f, _): + return open(f, 'r').read() + from pydgraph.meta import VERSION README = os.path.join(os.path.dirname(__file__), 'README.md') From 51c9e0050d7a031536f168e3209192ea30a2680e Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 4 Apr 2018 13:38:42 +0530 Subject: [PATCH 101/435] Add PUBLISHING.md --- PUBLISHING.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 PUBLISHING.md diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 00000000..a4460fef --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,59 @@ +# Publishing to PyPI + +This document contains instructions to publish pydgraph to [PyPI]. + +[PyPI]: https://www.pypi.org/ + +## Before deploying + +- Get access to credentials for Dgraph's account on PyPI with username dgraph-io +- Setup you `~/.pypirc` file like this: (remember to replace `${password}` with + the actual password) + +``` +[distutils] +index-servers= + pypi + testpypi + +[pypi] +username: dgraph-io +password: ${password} + +[testpypi] +repository: https://test.pypi.org/legacy/ +username: dgraph-io +password: ${password} +``` + +## Deploying + +- Build and test the code that needs to be published +- Bump version by modifying the `VERSION` variable in `pydgraph/meta.py` file +- If necessary, update the `CHANGELOG.md` file to reflect new changes +- Commit the changes +- Make sure you have [setuptools], [wheel], [twine], and [pypandoc] + installed +- Run the following commands: + +```sh +# Remove build and dist directories +rm -rf build +rm -rf dist + +# Package you project: source distribution and wheel +python setup.py sdist +python setup.py bdist_wheel + +# Upload it to PyPI +twine upload dist/* +# For testing, try uploading to testpypi: +# twine upload --repository testpypi dist/* +``` + +- If necessary, create a new release tag on the Github repository + +[setuptools]: https://pypi.org/project/setuptools/ +[wheel]: https://pypi.org/project/wheel/ +[twine]: https://pypi.org/project/twine/ +[pypandoc]: https://pypi.org/project/pypandoc/ From e82cfa42890d7737e815c373d13de03c2778ddee Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 4 Apr 2018 14:11:14 +0530 Subject: [PATCH 102/435] Add CHANGELOG --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..37cb9080 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Full compatibility with Dgraph v1.0.0 + +[Unreleased]: https://github.com/dgraph-io/dgraph-js/tree/master From da0bc0c685a54aa95269adcf0c320723d3c4da48 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 6 Apr 2018 10:44:55 +0530 Subject: [PATCH 103/435] Add upsert and lang fields to api.SchemaNode --- pydgraph/proto/api.proto | 2 ++ pydgraph/proto/api_pb2.py | 24 +++++++++++++++++++----- pydgraph/proto/api_pb2_grpc.py | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index eb5445ac..0e41450d 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -155,6 +155,8 @@ message SchemaNode { bool reverse = 5; bool count = 6; bool list = 7; + bool upsert = 8; + bool lang = 9; } // vim: noexpandtab sw=2 ts=2 diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 413aeaa5..84d56830 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -19,7 +19,7 @@ name='api.proto', package='api', syntax='proto3', - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"Y\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"}\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"Y\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -1003,6 +1003,20 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='upsert', full_name='api.SchemaNode.upsert', index=7, + number=8, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='lang', full_name='api.SchemaNode.lang', index=8, + number=9, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -1015,8 +1029,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1699, - serialized_end=1824, + serialized_start=1700, + serialized_end=1855, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1241,8 +1255,8 @@ file=DESCRIPTOR, index=0, options=None, - serialized_start=1827, - serialized_end=2055, + serialized_start=1858, + serialized_end=2086, methods=[ _descriptor.MethodDescriptor( name='Query', diff --git a/pydgraph/proto/api_pb2_grpc.py b/pydgraph/proto/api_pb2_grpc.py index d0b7760a..4c4bbb36 100644 --- a/pydgraph/proto/api_pb2_grpc.py +++ b/pydgraph/proto/api_pb2_grpc.py @@ -1,7 +1,7 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! import grpc -from . import api_pb2 as api__pb2 +import api_pb2 as api__pb2 class DgraphStub(object): From 92c82a09c649585a1905d65ec448666007ee4bfa Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 6 Apr 2018 11:03:16 +0530 Subject: [PATCH 104/435] Add option for server-side linearizability --- pydgraph/proto/api.proto | 6 +++ pydgraph/proto/api_pb2.py | 70 +++++++++++++++++++++++++--------- pydgraph/proto/api_pb2_grpc.py | 2 +- pydgraph/txn.py | 10 ++++- tests/test_txn.py | 4 +- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index 0e41450d..6a457e72 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -96,7 +96,13 @@ message Version { } message LinRead { + enum Sequencing { + CLIENT_SIDE = 0; + SERVER_SIDE = 1; + } + map ids = 1; + Sequencing sequencing = 2; } message Latency { diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 84d56830..daffc579 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -19,11 +19,33 @@ name='api.proto', package='api', syntax='proto3', - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"Y\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) +_LINREAD_SEQUENCING = _descriptor.EnumDescriptor( + name='Sequencing', + full_name='api.LinRead.Sequencing', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='CLIENT_SIDE', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SERVER_SIDE', index=1, number=1, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=1093, + serialized_end=1139, +) +_sym_db.RegisterEnumDescriptor(_LINREAD_SEQUENCING) + _FACET_VALTYPE = _descriptor.EnumDescriptor( name='ValType', full_name='api.Facet.ValType', @@ -53,8 +75,8 @@ ], containing_type=None, options=None, - serialized_start=1632, - serialized_end=1697, + serialized_start=1726, + serialized_end=1791, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -630,8 +652,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1003, - serialized_end=1045, + serialized_start=1049, + serialized_end=1091, ) _LINREAD = _descriptor.Descriptor( @@ -648,11 +670,19 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sequencing', full_name='api.LinRead.sequencing', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[_LINREAD_IDSENTRY, ], enum_types=[ + _LINREAD_SEQUENCING, ], options=None, is_extendable=False, @@ -660,8 +690,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=956, - serialized_end=1045, + serialized_start=957, + serialized_end=1139, ) @@ -705,8 +735,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1047, - serialized_end=1120, + serialized_start=1141, + serialized_end=1214, ) @@ -778,8 +808,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1123, - serialized_end=1276, + serialized_start=1217, + serialized_end=1370, ) @@ -882,8 +912,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1279, - serialized_end=1523, + serialized_start=1373, + serialized_end=1617, ) @@ -942,8 +972,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1526, - serialized_end=1697, + serialized_start=1620, + serialized_end=1791, ) @@ -1029,8 +1059,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1700, - serialized_end=1855, + serialized_start=1794, + serialized_end=1949, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1048,6 +1078,8 @@ _TXNCONTEXT.fields_by_name['lin_read'].message_type = _LINREAD _LINREAD_IDSENTRY.containing_type = _LINREAD _LINREAD.fields_by_name['ids'].message_type = _LINREAD_IDSENTRY +_LINREAD.fields_by_name['sequencing'].enum_type = _LINREAD_SEQUENCING +_LINREAD_SEQUENCING.containing_type = _LINREAD _NQUAD.fields_by_name['object_value'].message_type = _VALUE _NQUAD.fields_by_name['facets'].message_type = _FACET _VALUE.oneofs_by_name['val'].fields.append( @@ -1255,8 +1287,8 @@ file=DESCRIPTOR, index=0, options=None, - serialized_start=1858, - serialized_end=2086, + serialized_start=1952, + serialized_end=2180, methods=[ _descriptor.MethodDescriptor( name='Query', diff --git a/pydgraph/proto/api_pb2_grpc.py b/pydgraph/proto/api_pb2_grpc.py index 4c4bbb36..d0b7760a 100644 --- a/pydgraph/proto/api_pb2_grpc.py +++ b/pydgraph/proto/api_pb2_grpc.py @@ -1,7 +1,7 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! import grpc -import api_pb2 as api__pb2 +from . import api_pb2 as api__pb2 class DgraphStub(object): diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 83df8ac4..cfe997a9 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -47,6 +47,10 @@ def __init__(self, client): self._finished = False self._mutated = False + self._sequencing = api.LinRead.CLIENT_SIDE + + def sequencing(self, sequencing): + self._sequencing = sequencing def query(self, q, variables=None, timeout=None, metadata=None, credentials=None): req = self._common_query(q, variables=variables) @@ -57,8 +61,10 @@ def query(self, q, variables=None, timeout=None, metadata=None, credentials=None def _common_query(self, q, variables=None): if self._finished: raise Exception('Transaction has already been committed or discarded') - - req = api.Request(query=q, start_ts=self._ctx.start_ts, lin_read=self._ctx.lin_read) + + lin_read = self._ctx.lin_read + lin_read.sequencing = self._sequencing + req = api.Request(query=q, start_ts=self._ctx.start_ts, lin_read=lin_read) if variables is not None: for key, value in variables.items(): if util.is_string(key) and util.is_string(value): diff --git a/tests/test_txn.py b/tests/test_txn.py index 446af970..b70438ba 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -328,8 +328,8 @@ def test_mutate_conflict(self): txn2.commit() - _ = txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) - self.assertRaises(pydgraph.AbortedError, txn.commit) + with self.assertRaises(pydgraph.AbortedError): + _ = txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) def test_conflict_ignore(self): """Tests a mutation with ignore index conflict.""" From 0e5e43c680a65f2dd0b966cd67b20dcde18e4b1c Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Fri, 6 Apr 2018 11:24:30 +0530 Subject: [PATCH 105/435] Fix tests --- tests/test_txn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_txn.py b/tests/test_txn.py index b70438ba..446af970 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -328,8 +328,8 @@ def test_mutate_conflict(self): txn2.commit() - with self.assertRaises(pydgraph.AbortedError): - _ = txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + _ = txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + self.assertRaises(pydgraph.AbortedError, txn.commit) def test_conflict_ignore(self): """Tests a mutation with ignore index conflict.""" From 12e83bcdc63a457ce8085b2cfda8f21085527e70 Mon Sep 17 00:00:00 2001 From: Garvit Pahal Date: Wed, 16 May 2018 11:45:35 +0530 Subject: [PATCH 106/435] Bump version to v1.0.0 --- CHANGELOG.md | 5 ++++- pydgraph/meta.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37cb9080..9c19fb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.0.0] - 2018-05-16 + ### Added - Full compatibility with Dgraph v1.0.0 -[Unreleased]: https://github.com/dgraph-io/dgraph-js/tree/master +[Unreleased]: https://github.com/dgraph-io/dgraph-js/compare/v1.0.0...HEAD +[v1.0.0]: https://github.com/dgraph-io/dgraph-js/tree/v1.0.0 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 5f141bd7..c2b65535 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = '1.0.0a3' +VERSION = '1.0.0' From d3422af36eb8295310abb4add703d17e7cfbaaa3 Mon Sep 17 00:00:00 2001 From: Karandeep Singh <35473399+kdsgambhir@users.noreply.github.com> Date: Mon, 25 Jun 2018 13:40:56 -0400 Subject: [PATCH 107/435] Update simple.py (#23) * Update simple.py Updated methods in example to find uid of a node and Deleting it. * Update README.md Doc Update for Delete Node --- README.md | 21 ++++++++++ examples/simple/simple.py | 83 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a7a1f305..fa93cdba 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,27 @@ txn.mutate(set_obj=p) # txn.mutate(set_nquads='_:alice "Alice"') ``` +```python +# Delete data. + +query1 = """query all($a: string) + { + all(func: eq(name, $a)) + { + uid + } + }""" + +variables1 = {'$a': 'Bob'} + +res1 = client.query(query1, variables=variables1) + +ppl1 = json.loads(res1.json) + +#For mutation to delete node, use this: +txn.mutate(del_obj= person) +``` + For a more complete example with multiple fields and relationships, look at the [simple] project in the `examples` folder. diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 20663c78..375653d1 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -23,6 +23,7 @@ def drop_all(client): def set_schema(client): schema = """ name: string @index(exact) . + friend: uid @reverse . age: int . married: bool . loc: geo . @@ -78,11 +79,45 @@ def create_data(client): print('All created nodes (map from blank node names to uids):') for uid in assigned.uids: print('{} => {}'.format(uid, assigned.uids[uid])) - print() finally: # Clean up. Calling this after txn.commit() is a no-op # and hence safe. txn.discard() + print('\n') + + +#Deleting a data +def delete_data(client): + # Create a new transaction. + txn = client.txn() + try: + query1 = """query all($a: string) + { + all(func: eq(name, $a)) + { + uid + } + }""" + variables1 = {'$a': 'Bob'} + res1 = client.query(query1, variables=variables1) + ppl1 = json.loads(res1.json) + for person in ppl1['all']: + print('Query to find Uid for Bob :') + print(query1) + print('\n') + print("Bob's UID : ") + print(person) + print('\n') + print('Bob deleted') + print('\n') + + + assigned = txn.mutate(del_obj= person) + + txn.commit() + + finally: + txn.discard() # Query for data. @@ -112,8 +147,48 @@ def query_data(client): # Print results. print('Number of people named "Alice": {}'.format(len(ppl['all']))) + print('\n') for person in ppl['all']: + print('Query for Alice : \n' +query) + print('\n') + print('Result :') + print(person) + print('\n') + +#Query to check for deleted node +def query_data01(client): + query01 = """query all($b: string) + { all(func: eq(name, $b)) + { uid, + name, + age + friend + { + uid, + name, + age + } + ~friend + { + uid, + name, + age + } + } + }""" + + variables01 = {'$b': 'Bob'} + res01 = client.query(query01, variables=variables01) + ppl01 = json.loads(res01.json) + + print('Number of people named "Bob": {}'.format(len(ppl01['all']))) + print('\n') + for person in ppl01['all']: + print('Query for Bob :\n' + query01) + print('\n') + print('Result :') print(person) + print('\n') def main(): @@ -122,7 +197,11 @@ def main(): drop_all(client) set_schema(client) create_data(client) - query_data(client) + query_data(client) # query for Alice + query_data01(client) # query for Bob + delete_data(client) # delete Bob + query_data(client) # query for Alice + query_data01(client) # query for Bob # Close the client stub. client_stub.close() From b30338b5fceefae753b5ee16502dc4f9dc833439 Mon Sep 17 00:00:00 2001 From: Paul Korzhyk Date: Fri, 19 Oct 2018 23:46:15 +0300 Subject: [PATCH 108/435] Upgrade grpc version because 1.10.0 had build issues on Mac. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b043a9e9..b4e92f75 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -grpcio==1.10.0 +grpcio==1.14.2 From 213ded4f2ebed602e8c238b6f7478380713e54e3 Mon Sep 17 00:00:00 2001 From: Michel Conrado Date: Tue, 18 Dec 2018 23:09:36 -0300 Subject: [PATCH 109/435] Update scripts (#29) Update scripts * Fix script with nightly issue * Add --accept-licence to accepts licence --- requirements.txt | 3 ++- scripts/functions.sh | 6 +++--- scripts/install_dgraph.sh | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index b4e92f75..67013147 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -grpcio==1.14.2 +grpcio==1.17.1 +protobuf==3.6.1 diff --git a/scripts/functions.sh b/scripts/functions.sh index 1a7e4c29..519db284 100755 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -6,7 +6,7 @@ if [[ "$TRAVIS" == true ]]; then fi function quit { - echo "Shutting down Dgraph server and zero." + echo "Shutting down Dgraph Alpha and zero." curl -s localhost:8080/admin/shutdown # Kill Dgraph zero. kill -9 $(pgrep -f "dgraph zero") > /dev/null @@ -25,8 +25,8 @@ function quit { } function start { - echo -e "Starting first server." - dgraph server -p data/p -w data/w --lru_mb 4096 --zero localhost:5080 > data/server.log 2>&1 & + echo -e "Starting first Alpha." + dgraph alpha -p data/p -w data/w --lru_mb 4096 --zero localhost:5080 > data/alpha.log 2>&1 & # Wait for membership sync to happen. sleep $sleepTime return 0 diff --git a/scripts/install_dgraph.sh b/scripts/install_dgraph.sh index b2ec3d76..184c52e8 100755 --- a/scripts/install_dgraph.sh +++ b/scripts/install_dgraph.sh @@ -1,3 +1,3 @@ #!/bin/bash -curl https://get.dgraph.io -sSf | bash -s nightly +curl https://get.dgraph.io -sSf | bash -s -- --accept-license From 7543b0cef601663794be76b1e1cf5eaefa78f40b Mon Sep 17 00:00:00 2001 From: Daniel Mai Date: Wed, 19 Dec 2018 11:25:23 -0800 Subject: [PATCH 110/435] Fix read-index-after-write test case. (#28) This test case to read-index-after-write within the same txn is no longer valid. See https://github.com/dgraph-io/dgraph/pull/2678 --- tests/test_txn.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_txn.py b/tests/test_txn.py index 446af970..c6d1affc 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -363,7 +363,9 @@ def test_conflict_ignore(self): self.assertEqual([{'uid': uid1}, {'uid': uid2}], json.loads(resp.json).get('me')) def test_read_index_key_same_txn(self): - """Tests reading an indexed field within a transaction.""" + """Tests reading an indexed field within a transaction. The read + should return the results from before any writes of the same + txn.""" helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') @@ -382,7 +384,8 @@ def test_read_index_key_same_txn(self): }""" resp = txn.query(query) - self.assertEqual([{'uid': uid}], json.loads(resp.json).get('me')) + self.assertEqual([], json.loads(resp.json).get('me'), + "Expected 0 nodes read from index") class TestSPStar(helper.ClientIntegrationTestCase): From 300fb7fec6403b4eab32361731e0f1059f8ce1ab Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 20 Dec 2018 11:42:58 -0800 Subject: [PATCH 111/435] Update protobufs to match those in dgo and fix tests. (#36) * Update protobuf definitions to match those in the Go client. * Fix tests to default to port 9180 and update README file. * Fix Travis script and import statement in generated file. --- README.md | 16 +- pydgraph/proto/api.proto | 35 ++- pydgraph/proto/api_pb2.py | 490 +++++++++++++++++++-------------- pydgraph/proto/api_pb2_grpc.py | 17 ++ scripts/functions.sh | 4 +- scripts/local-test.sh | 19 ++ tests/helper.py | 23 +- tests/test_bank.py | 2 +- tests/test_client_stub.py | 12 +- 9 files changed, 372 insertions(+), 246 deletions(-) create mode 100755 scripts/local-test.sh diff --git a/README.md b/README.md index fa93cdba..e5e99e4e 100644 --- a/README.md +++ b/README.md @@ -296,8 +296,14 @@ python scripts/protogen.py ### Running tests -Make sure you have a Dgraph server running on localhost before you run this task. - -```sh -python setup.py test -``` +To run the tests in your local machine you can run the script +`scripts/local-tests.sh`. This script assumes Dgraph and dgo (Go client) are +already built on the local machine. The script will take care of bringing up a +Dgraph cluster and bringing it down after the tests are executed. The script +uses the port 9180 by default to prevent interference with clusters running on +the default port. Docker and docker-compose need to be installed before running +the script. Refer to the official Docker documentation for instructions on how +to install those packages. + +The `test.sh` script downloads and installs Dgraph. It is meant for use by our +CI systems and using it for local development is not recommended. diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index 6a457e72..c6189cef 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -14,15 +14,28 @@ * limitations under the License. */ +// Style guide for Protocol Buffer 3. +// Use CamelCase (with an initial capital) for message names – for example, +// SongServerRequest. Use underscore_separated_names for field names – for +// example, song_name. + syntax = "proto3"; package api; +/* import "gogoproto/gogo.proto"; */ + +/* option (gogoproto.marshaler_all) = true; */ +/* option (gogoproto.sizer_all) = true; */ +/* option (gogoproto.unmarshaler_all) = true; */ +/* option (gogoproto.goproto_getters_all) = true; */ + option java_package = "io.dgraph"; option java_outer_classname = "DgraphProto"; // Graph response. service Dgraph { + rpc Login (LoginRequest) returns (Response) {} rpc Query (Request) returns (Response) {} rpc Mutate (Mutation) returns (Assigned) {} rpc Alter (Operation) returns (Payload) {} @@ -36,6 +49,7 @@ message Request { uint64 start_ts = 13; LinRead lin_read = 14; + bool read_only = 15; } message Response { @@ -61,15 +75,10 @@ message Mutation { repeated NQuad del = 11; uint64 start_ts = 13; bool commit_now = 14; - bool ignore_index_conflict = 15; + bool ignore_index_conflict = 15; // this field is not parsed and used by the server anymore. } -message AssignedIds { - uint64 startId = 1; - uint64 endId = 2; -} - message Operation { string schema = 1; string drop_attr = 2; @@ -85,7 +94,8 @@ message TxnContext { uint64 start_ts = 1; uint64 commit_ts = 2; bool aborted = 3; - repeated string keys = 4; + repeated string keys = 4; // List of keys to be used for conflict detection. + repeated string preds = 5; // List of predicates involved in this transaction. LinRead lin_read = 13; } @@ -165,4 +175,15 @@ message SchemaNode { bool lang = 9; } +message LoginRequest { + string userid = 1; + string password = 2; + string refresh_token = 3; +} + +message Jwt { + string access_jwt = 1; + string refresh_jwt = 2; +} + // vim: noexpandtab sw=2 ts=2 diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index daffc579..84976061 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -7,7 +7,6 @@ from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -19,7 +18,8 @@ name='api.proto', package='api', syntax='proto3', - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\x9d\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"-\n\x0b\x41ssignedIds\x12\x0f\n\x07startId\x18\x01 \x01(\x04\x12\r\n\x05\x65ndId\x18\x02 \x01(\x04\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"p\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\x32\xe4\x01\n\x06\x44graph\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xb0\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -32,17 +32,17 @@ values=[ _descriptor.EnumValueDescriptor( name='CLIENT_SIDE', index=0, number=0, - options=None, + serialized_options=None, type=None), _descriptor.EnumValueDescriptor( name='SERVER_SIDE', index=1, number=1, - options=None, + serialized_options=None, type=None), ], containing_type=None, - options=None, - serialized_start=1093, - serialized_end=1139, + serialized_options=None, + serialized_start=1080, + serialized_end=1126, ) _sym_db.RegisterEnumDescriptor(_LINREAD_SEQUENCING) @@ -54,29 +54,29 @@ values=[ _descriptor.EnumValueDescriptor( name='STRING', index=0, number=0, - options=None, + serialized_options=None, type=None), _descriptor.EnumValueDescriptor( name='INT', index=1, number=1, - options=None, + serialized_options=None, type=None), _descriptor.EnumValueDescriptor( name='FLOAT', index=2, number=2, - options=None, + serialized_options=None, type=None), _descriptor.EnumValueDescriptor( name='BOOL', index=3, number=3, - options=None, + serialized_options=None, type=None), _descriptor.EnumValueDescriptor( name='DATETIME', index=4, number=4, - options=None, + serialized_options=None, type=None), ], containing_type=None, - options=None, - serialized_start=1726, - serialized_end=1791, + serialized_options=None, + serialized_start=1713, + serialized_end=1778, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -94,28 +94,28 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Request.VarsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + serialized_options=_b('8\001'), is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=133, - serialized_end=176, + serialized_start=152, + serialized_end=195, ) _REQUEST = _descriptor.Descriptor( @@ -131,42 +131,49 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='vars', full_name='api.Request.vars', index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='start_ts', full_name='api.Request.start_ts', index=2, number=13, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='lin_read', full_name='api.Request.lin_read', index=3, number=14, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='read_only', full_name='api.Request.read_only', index=4, + number=15, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[_REQUEST_VARSENTRY, ], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], serialized_start=19, - serialized_end=176, + serialized_end=195, ) @@ -183,42 +190,42 @@ has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='schema', full_name='api.Response.schema', index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='txn', full_name='api.Response.txn', index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='latency', full_name='api.Response.latency', index=3, number=12, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=178, - serialized_end=296, + serialized_start=197, + serialized_end=315, ) @@ -235,28 +242,28 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Assigned.UidsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + serialized_options=_b('8\001'), is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=415, - serialized_end=458, + serialized_start=434, + serialized_end=477, ) _ASSIGNED = _descriptor.Descriptor( @@ -272,35 +279,35 @@ has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='context', full_name='api.Assigned.context', index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='latency', full_name='api.Assigned.latency', index=2, number=12, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[_ASSIGNED_UIDSENTRY, ], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=299, - serialized_end=458, + serialized_start=318, + serialized_end=477, ) @@ -317,115 +324,77 @@ has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='delete_json', full_name='api.Mutation.delete_json', index=1, number=2, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='set_nquads', full_name='api.Mutation.set_nquads', index=2, number=3, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='del_nquads', full_name='api.Mutation.del_nquads', index=3, number=4, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='set', full_name='api.Mutation.set', index=4, number=10, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='del', full_name='api.Mutation.del', index=5, number=11, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='start_ts', full_name='api.Mutation.start_ts', index=6, number=13, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='commit_now', full_name='api.Mutation.commit_now', index=7, number=14, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='ignore_index_conflict', full_name='api.Mutation.ignore_index_conflict', index=8, number=15, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=461, - serialized_end=669, -) - - -_ASSIGNEDIDS = _descriptor.Descriptor( - name='AssignedIds', - full_name='api.AssignedIds', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='startId', full_name='api.AssignedIds.startId', index=0, - number=1, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='endId', full_name='api.AssignedIds.endId', index=1, - number=2, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=671, - serialized_end=716, + serialized_start=480, + serialized_end=688, ) @@ -442,35 +411,35 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='drop_attr', full_name='api.Operation.drop_attr', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='drop_all', full_name='api.Operation.drop_all', index=2, number=3, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=718, - serialized_end=782, + serialized_start=690, + serialized_end=754, ) @@ -487,21 +456,21 @@ has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=784, - serialized_end=807, + serialized_start=756, + serialized_end=779, ) @@ -518,49 +487,56 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='commit_ts', full_name='api.TxnContext.commit_ts', index=1, number=2, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='aborted', full_name='api.TxnContext.aborted', index=2, number=3, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='keys', full_name='api.TxnContext.keys', index=3, number=4, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='preds', full_name='api.TxnContext.preds', index=4, + number=5, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='lin_read', full_name='api.TxnContext.lin_read', index=4, + name='lin_read', full_name='api.TxnContext.lin_read', index=5, number=13, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=809, - serialized_end=921, + serialized_start=781, + serialized_end=908, ) @@ -577,14 +553,14 @@ nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=923, - serialized_end=930, + serialized_start=910, + serialized_end=917, ) @@ -601,21 +577,21 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=932, - serialized_end=954, + serialized_start=919, + serialized_end=941, ) @@ -632,28 +608,28 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.LinRead.IdsEntry.value', index=1, number=2, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + serialized_options=_b('8\001'), is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=1049, - serialized_end=1091, + serialized_start=1036, + serialized_end=1078, ) _LINREAD = _descriptor.Descriptor( @@ -669,14 +645,14 @@ has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='sequencing', full_name='api.LinRead.sequencing', index=1, number=2, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -684,14 +660,14 @@ enum_types=[ _LINREAD_SEQUENCING, ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=957, - serialized_end=1139, + serialized_start=944, + serialized_end=1126, ) @@ -708,35 +684,35 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='processing_ns', full_name='api.Latency.processing_ns', index=1, number=2, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='encoding_ns', full_name='api.Latency.encoding_ns', index=2, number=3, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=1141, - serialized_end=1214, + serialized_start=1128, + serialized_end=1201, ) @@ -753,63 +729,63 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='predicate', full_name='api.NQuad.predicate', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='object_id', full_name='api.NQuad.object_id', index=2, number=3, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='object_value', full_name='api.NQuad.object_value', index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='label', full_name='api.NQuad.label', index=4, number=5, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='lang', full_name='api.NQuad.lang', index=5, number=6, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='facets', full_name='api.NQuad.facets', index=6, number=7, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=1217, - serialized_end=1370, + serialized_start=1204, + serialized_end=1357, ) @@ -826,84 +802,84 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='bytes_val', full_name='api.Value.bytes_val', index=1, number=2, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='int_val', full_name='api.Value.int_val', index=2, number=3, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='bool_val', full_name='api.Value.bool_val', index=3, number=4, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='str_val', full_name='api.Value.str_val', index=4, number=5, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='double_val', full_name='api.Value.double_val', index=5, number=6, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='geo_val', full_name='api.Value.geo_val', index=6, number=7, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='date_val', full_name='api.Value.date_val', index=7, number=8, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='datetime_val', full_name='api.Value.datetime_val', index=8, number=9, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='password_val', full_name='api.Value.password_val', index=9, number=10, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='uid_val', full_name='api.Value.uid_val', index=10, number=11, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -912,8 +888,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1373, - serialized_end=1617, + serialized_start=1360, + serialized_end=1604, ) @@ -930,35 +906,35 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Facet.value', index=1, number=2, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='val_type', full_name='api.Facet.val_type', index=2, number=3, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='tokens', full_name='api.Facet.tokens', index=3, number=4, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='alias', full_name='api.Facet.alias', index=4, number=5, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -966,14 +942,14 @@ enum_types=[ _FACET_VALTYPE, ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=1620, - serialized_end=1791, + serialized_start=1607, + serialized_end=1778, ) @@ -990,77 +966,160 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='type', full_name='api.SchemaNode.type', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='index', full_name='api.SchemaNode.index', index=2, number=3, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='tokenizer', full_name='api.SchemaNode.tokenizer', index=3, number=4, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='reverse', full_name='api.SchemaNode.reverse', index=4, number=5, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='count', full_name='api.SchemaNode.count', index=5, number=6, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='list', full_name='api.SchemaNode.list', index=6, number=7, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='upsert', full_name='api.SchemaNode.upsert', index=7, number=8, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='lang', full_name='api.SchemaNode.lang', index=8, number=9, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], - options=None, + serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=1794, - serialized_end=1949, + serialized_start=1781, + serialized_end=1936, +) + + +_LOGINREQUEST = _descriptor.Descriptor( + name='LoginRequest', + full_name='api.LoginRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='userid', full_name='api.LoginRequest.userid', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='password', full_name='api.LoginRequest.password', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='refresh_token', full_name='api.LoginRequest.refresh_token', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1938, + serialized_end=2009, +) + + +_JWT = _descriptor.Descriptor( + name='Jwt', + full_name='api.Jwt', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='access_jwt', full_name='api.Jwt.access_jwt', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='refresh_jwt', full_name='api.Jwt.refresh_jwt', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2011, + serialized_end=2057, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1121,7 +1180,6 @@ DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE DESCRIPTOR.message_types_by_name['Assigned'] = _ASSIGNED DESCRIPTOR.message_types_by_name['Mutation'] = _MUTATION -DESCRIPTOR.message_types_by_name['AssignedIds'] = _ASSIGNEDIDS DESCRIPTOR.message_types_by_name['Operation'] = _OPERATION DESCRIPTOR.message_types_by_name['Payload'] = _PAYLOAD DESCRIPTOR.message_types_by_name['TxnContext'] = _TXNCONTEXT @@ -1133,6 +1191,8 @@ DESCRIPTOR.message_types_by_name['Value'] = _VALUE DESCRIPTOR.message_types_by_name['Facet'] = _FACET DESCRIPTOR.message_types_by_name['SchemaNode'] = _SCHEMANODE +DESCRIPTOR.message_types_by_name['LoginRequest'] = _LOGINREQUEST +DESCRIPTOR.message_types_by_name['Jwt'] = _JWT _sym_db.RegisterFileDescriptor(DESCRIPTOR) Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( @@ -1179,13 +1239,6 @@ )) _sym_db.RegisterMessage(Mutation) -AssignedIds = _reflection.GeneratedProtocolMessageType('AssignedIds', (_message.Message,), dict( - DESCRIPTOR = _ASSIGNEDIDS, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.AssignedIds) - )) -_sym_db.RegisterMessage(AssignedIds) - Operation = _reflection.GeneratedProtocolMessageType('Operation', (_message.Message,), dict( DESCRIPTOR = _OPERATION, __module__ = 'api_pb2' @@ -1271,69 +1324,88 @@ )) _sym_db.RegisterMessage(SchemaNode) +LoginRequest = _reflection.GeneratedProtocolMessageType('LoginRequest', (_message.Message,), dict( + DESCRIPTOR = _LOGINREQUEST, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.LoginRequest) + )) +_sym_db.RegisterMessage(LoginRequest) + +Jwt = _reflection.GeneratedProtocolMessageType('Jwt', (_message.Message,), dict( + DESCRIPTOR = _JWT, + __module__ = 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Jwt) + )) +_sym_db.RegisterMessage(Jwt) + -DESCRIPTOR.has_options = True -DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\tio.dgraphB\013DgraphProto')) -_REQUEST_VARSENTRY.has_options = True -_REQUEST_VARSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) -_ASSIGNED_UIDSENTRY.has_options = True -_ASSIGNED_UIDSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) -_LINREAD_IDSENTRY.has_options = True -_LINREAD_IDSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +DESCRIPTOR._options = None +_REQUEST_VARSENTRY._options = None +_ASSIGNED_UIDSENTRY._options = None +_LINREAD_IDSENTRY._options = None _DGRAPH = _descriptor.ServiceDescriptor( name='Dgraph', full_name='api.Dgraph', file=DESCRIPTOR, index=0, - options=None, - serialized_start=1952, - serialized_end=2180, + serialized_options=None, + serialized_start=2060, + serialized_end=2333, methods=[ + _descriptor.MethodDescriptor( + name='Login', + full_name='api.Dgraph.Login', + index=0, + containing_service=None, + input_type=_LOGINREQUEST, + output_type=_RESPONSE, + serialized_options=None, + ), _descriptor.MethodDescriptor( name='Query', full_name='api.Dgraph.Query', - index=0, + index=1, containing_service=None, input_type=_REQUEST, output_type=_RESPONSE, - options=None, + serialized_options=None, ), _descriptor.MethodDescriptor( name='Mutate', full_name='api.Dgraph.Mutate', - index=1, + index=2, containing_service=None, input_type=_MUTATION, output_type=_ASSIGNED, - options=None, + serialized_options=None, ), _descriptor.MethodDescriptor( name='Alter', full_name='api.Dgraph.Alter', - index=2, + index=3, containing_service=None, input_type=_OPERATION, output_type=_PAYLOAD, - options=None, + serialized_options=None, ), _descriptor.MethodDescriptor( name='CommitOrAbort', full_name='api.Dgraph.CommitOrAbort', - index=3, + index=4, containing_service=None, input_type=_TXNCONTEXT, output_type=_TXNCONTEXT, - options=None, + serialized_options=None, ), _descriptor.MethodDescriptor( name='CheckVersion', full_name='api.Dgraph.CheckVersion', - index=4, + index=5, containing_service=None, input_type=_CHECK, output_type=_VERSION, - options=None, + serialized_options=None, ), ]) _sym_db.RegisterServiceDescriptor(_DGRAPH) diff --git a/pydgraph/proto/api_pb2_grpc.py b/pydgraph/proto/api_pb2_grpc.py index d0b7760a..c8f57620 100644 --- a/pydgraph/proto/api_pb2_grpc.py +++ b/pydgraph/proto/api_pb2_grpc.py @@ -14,6 +14,11 @@ def __init__(self, channel): Args: channel: A grpc.Channel. """ + self.Login = channel.unary_unary( + '/api.Dgraph/Login', + request_serializer=api__pb2.LoginRequest.SerializeToString, + response_deserializer=api__pb2.Response.FromString, + ) self.Query = channel.unary_unary( '/api.Dgraph/Query', request_serializer=api__pb2.Request.SerializeToString, @@ -45,6 +50,13 @@ class DgraphServicer(object): """Graph response. """ + def Login(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def Query(self, request, context): # missing associated documentation comment in .proto file pass @@ -83,6 +95,11 @@ def CheckVersion(self, request, context): def add_DgraphServicer_to_server(servicer, server): rpc_method_handlers = { + 'Login': grpc.unary_unary_rpc_method_handler( + servicer.Login, + request_deserializer=api__pb2.LoginRequest.FromString, + response_serializer=api__pb2.Response.SerializeToString, + ), 'Query': grpc.unary_unary_rpc_method_handler( servicer.Query, request_deserializer=api__pb2.Request.FromString, diff --git a/scripts/functions.sh b/scripts/functions.sh index 519db284..0368162d 100755 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -7,7 +7,7 @@ fi function quit { echo "Shutting down Dgraph Alpha and zero." - curl -s localhost:8080/admin/shutdown + curl -s localhost:8180/admin/shutdown # Kill Dgraph zero. kill -9 $(pgrep -f "dgraph zero") > /dev/null @@ -26,7 +26,7 @@ function quit { function start { echo -e "Starting first Alpha." - dgraph alpha -p data/p -w data/w --lru_mb 4096 --zero localhost:5080 > data/alpha.log 2>&1 & + dgraph alpha -o 100 -p data/p -w data/w --lru_mb 4096 --zero localhost:5080 > data/alpha.log 2>&1 & # Wait for membership sync to happen. sleep $sleepTime return 0 diff --git a/scripts/local-test.sh b/scripts/local-test.sh new file mode 100755 index 00000000..5965e7f8 --- /dev/null +++ b/scripts/local-test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Runs the 6-node test cluster (3 Zeros, 3 Alphas, replication level 3) whose +# first Alpha runs on port 9180. This script does not install Dgraph since it +# is intended for local development. Instead it assumes dgraph is already +# installed. + +readonly SRCDIR=$(readlink -f ${BASH_SOURCE[0]%/*}) + +# Install dependencies +pip install -r requirements.txt +pip install coveralls + +# Run cluster and tests +pushd $(dirname $SRCDIR) +source $GOPATH/src/github.com/dgraph-io/dgraph/contrib/scripts/functions.sh +restartCluster +coverage run --source=pydgraph --omit=pydgraph/proto/* setup.py test +stopCluster +popd diff --git a/tests/helper.py b/tests/helper.py index b73449a7..0e44ae1a 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -25,7 +25,7 @@ def create_lin_read(src_ids): ids = lr.ids for key, value in src_ids.items(): ids[key] = value - + return lr @@ -35,15 +35,15 @@ def are_lin_reads_equal(a, b): if len(a_ids) != len(b_ids): return False - + for (key, value) in a_ids.items(): if key not in b_ids or b.ids[key] != value: return False - + return True -SERVER_ADDR = 'localhost:9080' +SERVER_ADDR = 'localhost:9180' def create_client(addr=SERVER_ADDR): @@ -66,23 +66,12 @@ def setup(): class ClientIntegrationTestCase(unittest.TestCase): """Base class for other integration test cases. Provides a client object - with a connection to the dgraph server and ensures that the server is - v1.0 or greater. + with a connection to the dgraph server. """ TEST_SERVER_ADDR = SERVER_ADDR def setUp(self): - """Sets up the client and verifies the version is compatible.""" + """Sets up the client.""" self.client = create_client(self.TEST_SERVER_ADDR) - version = self.client.any_client().check_version(pydgraph.Check()) - - # version.tag string format is v.. - # version_tup = [MAJOR, MINOR, PATCH] - version_tup = version.tag[1:].split('.') - - version_supported = int(version_tup[0]) > 0 - self.assertTrue( - version_supported, - 'Dgraph server version must be >= v1.0.0, got %s' % version.tag) diff --git a/tests/test_bank.py b/tests/test_bank.py index 228237c6..e9f09192 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -141,7 +141,7 @@ def run_transfers(addr, transfer_count, account_ids, success_ctr, retry_ctr): except: with retry_ctr.get_lock(): retry_ctr.value += 1 - + with success_ctr.get_lock(), retry_ctr.get_lock(): log.info('success: %d, retries: %d', success_ctr.value, retry_ctr.value) diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index 70e7b7a4..254698a3 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -19,9 +19,10 @@ import sys import pydgraph +from . import helper +class TestDgraphClientStub(helper.ClientIntegrationTestCase): -class TestDgraphClientStub(unittest.TestCase): def validate_version_object(self, version): tag = version.tag if sys.version_info[0] < 3: @@ -34,14 +35,15 @@ def check_version(self, stub): self.validate_version_object(stub.check_version(pydgraph.Check())) def test_constructor(self): - self.check_version(pydgraph.DgraphClientStub()) - + self.check_version(pydgraph.DgraphClientStub(addr=self.TEST_SERVER_ADDR)) + def test_timeout(self): with self.assertRaises(Exception): - pydgraph.DgraphClientStub().check_version(pydgraph.Check(), timeout=-1) + pydgraph.DgraphClientStub(self.TEST_SERVER_ADDR).check_version( + pydgraph.Check(), timeout=-1) def test_close(self): - client_stub = pydgraph.DgraphClientStub() + client_stub = pydgraph.DgraphClientStub(addr=self.TEST_SERVER_ADDR) self.check_version(client_stub) client_stub.close() with self.assertRaises(Exception): From edad66d8cd2597642437180cc5e5cf6dd608fcca Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 28 Dec 2018 10:33:53 -0800 Subject: [PATCH 112/435] Fix formatting issues (#37) * Update maintainer fields to my name and email address. * Fix most issues reported by pylint (some of them are false positives so I am ignoring them. This change has no functional changes. --- pydgraph/client.py | 28 +++++-- pydgraph/client_stub.py | 48 ++++++++---- pydgraph/errors.py | 9 ++- pydgraph/meta.py | 2 + pydgraph/txn.py | 158 ++++++++++++++++++++++---------------- pydgraph/util.py | 12 ++- tests/helper.py | 42 ++++++---- tests/test_acct_upsert.py | 38 +++++---- tests/test_bank.py | 36 +++++---- tests/test_client.py | 12 ++- tests/test_client_stub.py | 12 ++- tests/test_essentials.py | 13 +++- tests/test_queries.py | 38 +++++---- tests/test_util.py | 29 ++++--- 14 files changed, 301 insertions(+), 176 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 639c07a8..d12e5daa 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Dgraph python client.""" + import random from pydgraph import txn, util @@ -19,39 +21,49 @@ from pydgraph.proto import api_pb2 as api __author__ = 'Mohit Ranka ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' __version__ = VERSION __status__ = 'development' class DgraphClient(object): """Creates a new Client for interacting with the Dgraph store. - + The client can be backed by multiple connections (to the same server, or multiple servers in a cluster). """ def __init__(self, *clients): - if len(clients) == 0: + if not clients: raise ValueError('No clients provided in DgraphClient constructor') self._clients = clients[:] self._lin_read = api.LinRead() - def alter(self, op, timeout=None, metadata=None, credentials=None): - return self.any_client().alter(op, timeout=timeout, metadata=metadata, credentials=credentials) - - def query(self, q, variables=None, timeout=None, metadata=None, credentials=None): - return self.txn().query(q, variables=variables, timeout=timeout, metadata=metadata, credentials=credentials) + def alter(self, operation, timeout=None, metadata=None, credentials=None): + """Runs a modification via this client.""" + return self.any_client().alter(operation, timeout=timeout, + metadata=metadata, + credentials=credentials) + + def query(self, query, variables=None, timeout=None, metadata=None, + credentials=None): + """Runs a query via this client.""" + return self.txn().query(query, variables=variables, timeout=timeout, + metadata=metadata, credentials=credentials) def txn(self): + """Creates a transaction.""" return txn.Txn(self) def set_lin_read(self, ctx): + """Sets linread map in ctx to the one from this instace.""" ctx.lin_read.MergeFrom(self._lin_read) def merge_lin_reads(self, src): + """Merges linread map in this instance with src.""" util.merge_lin_reads(self._lin_read, src) def any_client(self): + """Returns a random client.""" return random.choice(self._clients) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index ecb3aa8b..1e16cd2d 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -12,41 +12,59 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Stub for RPC request.""" + import grpc from pydgraph.meta import VERSION from pydgraph.proto import api_pb2_grpc as api_grpc __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' __version__ = VERSION __status__ = 'development' class DgraphClientStub(object): + """Stub for the Dgraph grpc client.""" + def __init__(self, addr='localhost:9080', credentials=None, options=None): if credentials is None: self.channel = grpc.insecure_channel(addr, options) else: - self.channel = grpc.secure_channel(addr, credentials, options) - + self.channel = grpc.secure_channel(addr, credentials, options) + self.stub = api_grpc.DgraphStub(self.channel) - def alter(self, op, timeout=None, metadata=None, credentials=None): - return self.stub.Alter(op, timeout=timeout, metadata=metadata, credentials=credentials) + def alter(self, operation, timeout=None, metadata=None, credentials=None): + """Runs alter operation.""" + return self.stub.Alter(operation, timeout=timeout, metadata=metadata, + credentials=credentials) def query(self, req, timeout=None, metadata=None, credentials=None): - return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) - - def mutate(self, mu, timeout=None, metadata=None, credentials=None): - return self.stub.Mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) - - def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): - return self.stub.CommitOrAbort(ctx, timeout=timeout, metadata=metadata, credentials=credentials) - - def check_version(self, check, timeout=None, metadata=None, credentials=None): - return self.stub.CheckVersion(check, timeout=timeout, metadata=metadata, credentials=credentials) + """Runs query operation.""" + return self.stub.Query(req, timeout=timeout, metadata=metadata, + credentials=credentials) + + def mutate(self, mutation, timeout=None, metadata=None, credentials=None): + """Runs mutate operation.""" + return self.stub.Mutate(mutation, timeout=timeout, metadata=metadata, + credentials=credentials) + + def commit_or_abort(self, ctx, timeout=None, metadata=None, + credentials=None): + """Runs commit or abort operation.""" + return self.stub.CommitOrAbort(ctx, timeout=timeout, metadata=metadata, + credentials=credentials) + + def check_version(self, check, timeout=None, metadata=None, + credentials=None): + """Returns the version of the Dgraph instance.""" + return self.stub.CheckVersion(check, timeout=timeout, + metadata=metadata, + credentials=credentials) def close(self): + """Deletes channel and stub.""" del self.channel del self.stub diff --git a/pydgraph/errors.py b/pydgraph/errors.py index 4f54f371..da453e2a 100644 --- a/pydgraph/errors.py +++ b/pydgraph/errors.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Errors thrown by the Dgraph client.""" + from pydgraph.meta import VERSION __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' __version__ = VERSION __status__ = 'development' class AbortedError(Exception): + """Error thrown by aborted transactions.""" + def __init__(self): - super(AbortedError, self).__init__('Transaction has been aborted. Please retry') + super(AbortedError, self).__init__( + 'Transaction has been aborted. Please retry') diff --git a/pydgraph/meta.py b/pydgraph/meta.py index c2b65535..cf544b13 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -12,4 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Metadata about this package.""" + VERSION = '1.0.0' diff --git a/pydgraph/txn.py b/pydgraph/txn.py index cfe997a9..c7232bb8 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -12,30 +12,32 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc +"""Dgraph atomic transaction support.""" + import json +import grpc from pydgraph import errors, util from pydgraph.meta import VERSION from pydgraph.proto import api_pb2 as api __author__ = 'Shailesh Kochhar ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' __version__ = VERSION __status__ = 'development' class Txn(object): """Txn is a single atomic transaction. - + A transaction lifecycle is as follows: - + 1. Created using Client.newTxn. - - 2. Various query and mutate calls made. - - 3. commit or discard used. If any mutations have been made, It's important - that at least one of these methods is called to clean up resources. discard + + 2. Modified via calls to query and mutate. + + 3. Committed or discarded. If any mutations have been made, it's important + that at least one of these methods is called to clean up resources. Discard is a no-op if commit has already been called, so it's safe to call discard after calling commit. """ @@ -50,129 +52,153 @@ def __init__(self, client): self._sequencing = api.LinRead.CLIENT_SIDE def sequencing(self, sequencing): + """Sets sequencing.""" self._sequencing = sequencing - def query(self, q, variables=None, timeout=None, metadata=None, credentials=None): - req = self._common_query(q, variables=variables) - res = self._dc.any_client().query(req, timeout=timeout, metadata=metadata, credentials=credentials) + def query(self, query, variables=None, timeout=None, metadata=None, + credentials=None): + """Adds a query operation to the transaction.""" + req = self._common_query(query, variables=variables) + res = self._dc.any_client().query(req, timeout=timeout, + metadata=metadata, + credentials=credentials) self.merge_context(res.txn) return res - - def _common_query(self, q, variables=None): + + def _common_query(self, query, variables=None): if self._finished: - raise Exception('Transaction has already been committed or discarded') + raise Exception( + 'Transaction has already been committed or discarded') lin_read = self._ctx.lin_read lin_read.sequencing = self._sequencing - req = api.Request(query=q, start_ts=self._ctx.start_ts, lin_read=lin_read) + req = api.Request(query=query, start_ts=self._ctx.start_ts, + lin_read=lin_read) if variables is not None: for key, value in variables.items(): if util.is_string(key) and util.is_string(value): req.vars[key] = value - + return req - def mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, commit_now=None, - ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): - mu = self._common_mutate(mu=mu, set_obj=set_obj, del_obj=del_obj, set_nquads=set_nquads, del_nquads=del_nquads, - commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) + def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, + del_nquads=None, commit_now=None, ignore_index_conflict=None, + timeout=None, metadata=None, credentials=None): + """Adds a mutate operation to the transaction.""" + mutation = self._common_mutate( + mutation=mutation, set_obj=set_obj, del_obj=del_obj, + set_nquads=set_nquads, del_nquads=del_nquads, + commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) try: - ag = self._dc.any_client().mutate(mu, timeout=timeout, metadata=metadata, credentials=credentials) - except Exception as e: + assigned = self._dc.any_client().mutate(mutation, timeout=timeout, + metadata=metadata, + credentials=credentials) + except Exception as error: try: - self.discard(timeout=timeout, metadata=metadata, credentials=credentials) + self.discard(timeout=timeout, metadata=metadata, + credentials=credentials) except: # Ignore error - user should see the original error. pass - self._common_except_mutate(e) + self._common_except_mutate(error) - if mu.commit_now: + if mutation.commit_now: self._finished = True - self.merge_context(ag.context) - return ag - - def _common_mutate(self, mu=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, + self.merge_context(assigned.context) + return assigned + + def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, + set_nquads=None, del_nquads=None, commit_now=None, ignore_index_conflict=None): - if not mu: - mu = api.Mutation() + if not mutation: + mutation = api.Mutation() if set_obj: - mu.set_json = json.dumps(set_obj).encode('utf8') + mutation.set_json = json.dumps(set_obj).encode('utf8') if del_obj: - mu.delete_json = json.dumps(del_obj).encode('utf8') + mutation.delete_json = json.dumps(del_obj).encode('utf8') if set_nquads: - mu.set_nquads = set_nquads.encode('utf8') + mutation.set_nquads = set_nquads.encode('utf8') if del_nquads: - mu.del_nquads = del_nquads.encode('utf8') + mutation.del_nquads = del_nquads.encode('utf8') if commit_now: - mu.commit_now = True + mutation.commit_now = True if ignore_index_conflict: - mu.ignore_index_conflict = True - + mutation.ignore_index_conflict = True + if self._finished: - raise Exception('Transaction has already been committed or discarded') - + raise Exception( + 'Transaction has already been committed or discarded') + self._mutated = True - mu.start_ts = self._ctx.start_ts - return mu + mutation.start_ts = self._ctx.start_ts + return mutation @staticmethod - def _common_except_mutate(e): - if isinstance(e, grpc._channel._Rendezvous): - e.details() - status_code = e.code() - if status_code == grpc.StatusCode.ABORTED or status_code == grpc.StatusCode.FAILED_PRECONDITION: + def _common_except_mutate(error): + if isinstance(error, grpc._channel._Rendezvous): + error.details() + status_code = error.code() + if (status_code == grpc.StatusCode.ABORTED or + status_code == grpc.StatusCode.FAILED_PRECONDITION): raise errors.AbortedError() - - raise e + + raise error def commit(self, timeout=None, metadata=None, credentials=None): + """Commits the transaction.""" if not self._common_commit(): return try: - self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, + self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, + metadata=metadata, credentials=credentials) - except Exception as e: - self._common_except_commit(e) - + except Exception as error: + self._common_except_commit(error) + def _common_commit(self): if self._finished: - raise Exception('Transaction has already been committed or discarded') - + raise Exception( + 'Transaction has already been committed or discarded') + self._finished = True return self._mutated @staticmethod - def _common_except_commit(e): - if isinstance(e, grpc._channel._Rendezvous): - e.details() - status_code = e.code() + def _common_except_commit(error): + if isinstance(error, grpc._channel._Rendezvous): + error.details() + status_code = error.code() if status_code == grpc.StatusCode.ABORTED: raise errors.AbortedError() - raise e + raise error def discard(self, timeout=None, metadata=None, credentials=None): + """Discards the transaction.""" if not self._common_discard(): return - self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, metadata=metadata, credentials=credentials) - + self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, + metadata=metadata, + credentials=credentials) + def _common_discard(self): if self._finished: return False - + self._finished = True if not self._mutated: return False - + self._ctx.aborted = True return True - + def merge_context(self, src=None): + """Merges context from this instance with src.""" if src is None: # This condition will be true only if the server doesn't return a # txn context after a query or mutation. diff --git a/pydgraph/util.py b/pydgraph/util.py index 273e7d96..8ba8e1c2 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -12,17 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Various utility functions.""" + import sys from pydgraph.meta import VERSION __author__ = 'Shailesh Kochhar ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' __version__ = VERSION __status__ = 'development' def merge_lin_reads(target, src): + """Merger src linread map into target linread map.""" if src is None: return target @@ -37,8 +40,9 @@ def merge_lin_reads(target, src): return target -def is_string(s): +def is_string(string): + """Checks if argument is a string. Compatible with Python 2 and 3.""" if sys.version_info[0] < 3: - return isinstance(s, basestring) + return isinstance(string, basestring) - return isinstance(s, str) + return isinstance(string, str) diff --git a/tests/helper.py b/tests/helper.py index 0e44ae1a..569c57ca 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Utilities used by tests.""" + __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest @@ -21,23 +23,25 @@ def create_lin_read(src_ids): - lr = pydgraph.LinRead() - ids = lr.ids + """Creates a linread map using src_ids.""" + lin_read = pydgraph.LinRead() + ids = lin_read.ids for key, value in src_ids.items(): ids[key] = value - return lr + return lin_read -def are_lin_reads_equal(a, b): - a_ids = a.ids - b_ids = b.ids +def are_lin_reads_equal(lin_read1, lin_read2): + """Returns True if both linread maps are equal.""" + ids1 = lin_read1.ids + ids2 = lin_read2.ids - if len(a_ids) != len(b_ids): + if len(ids1) != len(ids2): return False - for (key, value) in a_ids.items(): - if key not in b_ids or b.ids[key] != value: + for (key, value) in ids1.items(): + if key not in ids2 or lin_read2.ids[key] != value: return False return True @@ -47,21 +51,25 @@ def are_lin_reads_equal(a, b): def create_client(addr=SERVER_ADDR): + """Creates a new client object using the given address.""" return pydgraph.DgraphClient(pydgraph.DgraphClientStub(addr)) -def set_schema(c, schema): - return c.alter(pydgraph.Operation(schema=schema)) +def set_schema(client, schema): + """Sets the schema in the given client.""" + return client.alter(pydgraph.Operation(schema=schema)) -def drop_all(c): - return c.alter(pydgraph.Operation(drop_all=True)) +def drop_all(client): + """Drops all data in the given client.""" + return client.alter(pydgraph.Operation(drop_all=True)) def setup(): - c = create_client() - drop_all(c) - return c + """Creates a new client and drops all existing data.""" + client = create_client() + drop_all(client) + return client class ClientIntegrationTestCase(unittest.TestCase): diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index c299c5c4..3201101b 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests to verify upsert directive.""" + __author__ = 'Shailesh Kochhar ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest import logging @@ -33,6 +35,7 @@ class TestAccountUpsert(helper.ClientIntegrationTestCase): + """Tests to verify upsert directive.""" def setUp(self): super(TestAccountUpsert, self).setUp() @@ -63,7 +66,8 @@ def do_upserts(self, account_list, concurrency): retry_ctr = multiprocessing.Value('i', 0, lock=True) def _updater(acct): - upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, success_ctr=success_ctr, retry_ctr=retry_ctr) + upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, + success_ctr=success_ctr, retry_ctr=retry_ctr) pool = mpd.Pool(concurrency) results = [ @@ -71,21 +75,21 @@ def _updater(acct): for acct in account_list for _ in range(concurrency) ] - [res.get() for res in results] + _ = [res.get() for res in results] pool.close() def assert_changes(self, firsts, accounts): """Will check to see changes have been made.""" - q = """{{ + query = """{{ all(func: anyofterms(first, "{}")) {{ first last age }} }}""".format(' '.join(firsts)) - logging.debug(q) - result = json.loads(self.client.query(q=q).json) + logging.debug(query) + result = json.loads(self.client.query(query).json) account_set = set() for acct in result['all']: @@ -100,8 +104,9 @@ def assert_changes(self, firsts, accounts): def upsert_account(addr, account, success_ctr, retry_ctr): - c = helper.create_client(addr) - q = """{{ + """Runs upsert operation.""" + client = helper.create_client(addr) + query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid }} @@ -110,12 +115,13 @@ def upsert_account(addr, account, success_ctr, retry_ctr): last_update_time = time.time() - 10000 while True: if time.time() > last_update_time + 10000: - logging.debug('Success: %d Retries: %d', success_ctr.value, retry_ctr.value) + logging.debug('Success: %d Retries: %d', success_ctr.value, + retry_ctr.value) last_update_time = time.time() - txn = c.txn() + txn = client.txn() try: - result = json.loads(txn.query(q=q).json) + result = json.loads(txn.query(query).json) assert len(result['acct']) <= 1, ('Lookup of account %s found ' 'multiple accounts' % account) @@ -135,7 +141,8 @@ def upsert_account(addr, account, success_ctr, retry_ctr): uid = acct['uid'] assert uid is not None, 'Account with uid None' - updatequads = '<{0}> "{1:d}"^^ .'.format(uid, int(time.time())) + updatequads = '<{0}> "{1:d}"^^ .'.format( + uid, int(time.time())) txn.mutate(set_nquads=updatequads) txn.commit() @@ -153,9 +160,10 @@ def upsert_account(addr, account, success_ctr, retry_ctr): def suite(): - s = unittest.TestSuite() - s.addTest(TestAccountUpsert()) - return s + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestAccountUpsert()) + return suite_obj if __name__ == '__main__': diff --git a/tests/test_bank.py b/tests/test_bank.py index e9f09192..4cf0b54d 100644 --- a/tests/test_bank.py +++ b/tests/test_bank.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Test that runs through example bank transactions.""" + __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest import logging @@ -31,6 +33,8 @@ class TestBank(helper.ClientIntegrationTestCase): + """Test that runs through example bank transactions.""" + def setUp(self): super(TestBank, self).setUp() @@ -54,10 +58,11 @@ def test_bank_transfer(self): pool = mpd.Pool(CONCURRENCY) results = [pool.apply_async( run_transfers, - (self.TEST_SERVER_ADDR, TRANSFER_COUNT, self.uids, success_ctr, retry_ctr) + (self.TEST_SERVER_ADDR, TRANSFER_COUNT, self.uids, success_ctr, + retry_ctr) ) for _ in range(CONCURRENCY)] - [res.get() for res in results] + _ = [res.get() for res in results] pool.close() finally: total_watcher.terminate() @@ -85,6 +90,7 @@ def start_total_watcher(self): def looper(func, *args, **kwargs): + """Returns a function that runs func in an infinite loop.""" def _looper(): while True: func(*args, **kwargs) @@ -93,10 +99,10 @@ def _looper(): return _looper -def run_total(c, uids): +def run_total(client, uids): """Calculates the total amount in the accounts.""" - q = """{{ + query = """{{ var(func: uid("{uids:s}")) {{ b as bal }} @@ -105,16 +111,17 @@ def run_total(c, uids): }} }}""".format(uids='", "'.join(uids)) - resp = c.query(q) + resp = client.query(query) total = json.loads(resp.json)['total'] logging.info('Response: %s', total) assert total[0]['bal'] == 10000 def run_transfers(addr, transfer_count, account_ids, success_ctr, retry_ctr): + """Runs transfers between the given accounts.""" pname = mpd.current_process().name log = logging.getLogger('test_bank.run_transfers[%s]' % (pname,)) - c = helper.create_client(addr) + client = helper.create_client(addr) while True: from_acc, to_acc = select_account_pair(account_ids) @@ -125,7 +132,7 @@ def run_transfers(addr, transfer_count, account_ids, success_ctr, retry_ctr): }} }}""".format(uid1=from_acc, uid2=to_acc) - txn = c.txn() + txn = client.txn() try: accounts = load_from_query(txn, query, 'me') accounts[0]['bal'] += 5 @@ -135,10 +142,11 @@ def run_transfers(addr, transfer_count, account_ids, success_ctr, retry_ctr): success_ctr.value += 1 if not success_ctr.value % 100: - log.info('Runs %d. Aborts: %d', success_ctr.value, retry_ctr.value) + log.info('Runs %d. Aborts: %d', success_ctr.value, + retry_ctr.value) if success_ctr.value >= transfer_count: break - except: + except BaseException: with retry_ctr.get_lock(): retry_ctr.value += 1 @@ -163,6 +171,7 @@ def load_from_query(txn, query, field): def dump_from_obj(txn, obj, commit=False): + """Dumps the given object into the transaction.""" assigned = txn.mutate(set_obj=obj) if not commit: @@ -171,9 +180,10 @@ def dump_from_obj(txn, obj, commit=False): def suite(): - s = unittest.TestSuite() - s.addTest(TestBank()) - return s + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestBank()) + return suite_obj if __name__ == '__main__': diff --git a/tests/test_client.py b/tests/test_client.py index cbf1c5d1..1fc040c4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests construction of Dgraph client.""" + __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest @@ -21,15 +23,17 @@ class TestDgraphClient(unittest.TestCase): + """Tests construction of Dgraph client.""" def test_constructor(self): with self.assertRaises(ValueError): pydgraph.DgraphClient() def suite(): - s = unittest.TestSuite() - s.addTest(TestDgraphClient()) - return s + """Returns a tests suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestDgraphClient()) + return suite_obj if __name__ == '__main__': diff --git a/tests/test_client_stub.py b/tests/test_client_stub.py index 254698a3..61e0a1a4 100644 --- a/tests/test_client_stub.py +++ b/tests/test_client_stub.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests client stub.""" + __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest import sys @@ -22,6 +24,7 @@ from . import helper class TestDgraphClientStub(helper.ClientIntegrationTestCase): + """Tests client stub.""" def validate_version_object(self, version): tag = version.tag @@ -51,9 +54,10 @@ def test_close(self): def suite(): - s = unittest.TestSuite() - s.addTest(TestDgraphClientStub()) - return s + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestDgraphClientStub()) + return suite_obj if __name__ == '__main__': diff --git a/tests/test_essentials.py b/tests/test_essentials.py index 022e3b49..7f0555a4 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests mutation after query behavior.""" + __author__ = 'Shailesh Kochhar ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest import logging @@ -23,6 +25,8 @@ class TestEssentials(helper.ClientIntegrationTestCase): + """Tests mutation after query behavior.""" + def testMutationAfterQuery(self): """Tests what happens when making a mutation on a txn after querying on the client.""" @@ -44,9 +48,10 @@ def testMutationAfterQuery(self): def suite(): - s = unittest.TestSuite() - s.addTest(TestEssentials()) - return s + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestEssentials()) + return suite_obj if __name__ == '__main__': diff --git a/tests/test_queries.py b/tests/test_queries.py index 68b7e5b1..8a61a5dd 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests behavior of queries after mutation in the same transaction.""" + __author__ = 'Mohit Ranka ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest import sys @@ -26,6 +28,8 @@ class TestQueries(helper.ClientIntegrationTestCase): + """Tests behavior of queries after mutation in the same transaction.""" + def setUp(self): super(TestQueries, self).setUp() @@ -33,6 +37,8 @@ def setUp(self): helper.set_schema(self.client, 'name: string @index(term) .') def test_mutation_and_query(self): + """Runs mutation and verifies queries see the results.""" + txn = self.client.txn() _ = txn.mutate(pydgraph.Mutation(commit_now=True), set_nquads=""" <_:alice> \"Alice\" . @@ -52,23 +58,29 @@ def test_mutation_and_query(self): }""" response = self.client.query(query, variables={'$a': 'Alice'}) - self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], json.loads(response.json).get('me')) - self.assertTrue(is_number(response.latency.parsing_ns), 'Parsing latency is not available') - self.assertTrue(is_number(response.latency.processing_ns), 'Processing latency is not available') - self.assertTrue(is_number(response.latency.encoding_ns), 'Encoding latency is not available') - - -def is_number(n): + self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], + json.loads(response.json).get('me')) + self.assertTrue(is_number(response.latency.parsing_ns), + 'Parsing latency is not available') + self.assertTrue(is_number(response.latency.processing_ns), + 'Processing latency is not available') + self.assertTrue(is_number(response.latency.encoding_ns), + 'Encoding latency is not available') + + +def is_number(number): + """Returns true if object is a number. Compatible with Python 2 and 3.""" if sys.version_info[0] < 3: - return isinstance(n, (int, long)) + return isinstance(number, (int, long)) - return isinstance(n, int) + return isinstance(number, int) def suite(): - s = unittest.TestSuite() - s.addTest(TestQueries()) - return s + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestQueries()) + return suite_obj if __name__ == '__main__': diff --git a/tests/test_util.py b/tests/test_util.py index e6d900cb..2480272c 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests utility functions.""" + __author__ = 'Garvit Pahal ' -__maintainer__ = 'Garvit Pahal ' +__maintainer__ = 'Martin Martinez Rivera ' import unittest @@ -24,6 +26,8 @@ class TestMergeLinReads(unittest.TestCase): + """Tests merge_lin_reads utility function.""" + def common_test(self, lr1, lr2, expected): self.assertTrue(helper.are_lin_reads_equal(util.merge_lin_reads(lr1, lr2), expected)) self.assertTrue(helper.are_lin_reads_equal(lr1, expected)) @@ -33,37 +37,37 @@ def test_disjoint(self): lr2 = helper.create_lin_read({2: 2, 3: 3}) res = helper.create_lin_read({1: 1, 2: 2, 3: 3}) self.common_test(lr1, lr2, res) - + def test_lower_value(self): lr1 = helper.create_lin_read({1: 2}) lr2 = helper.create_lin_read({1: 1}) res = helper.create_lin_read({1: 2}) self.common_test(lr1, lr2, res) - + def test_higher_value(self): lr1 = helper.create_lin_read({1: 1}) lr2 = helper.create_lin_read({1: 2}) res = helper.create_lin_read({1: 2}) self.common_test(lr1, lr2, res) - + def test_equal_value(self): lr1 = helper.create_lin_read({1: 1}) lr2 = helper.create_lin_read({1: 1}) res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) - + def test_none(self): lr1 = helper.create_lin_read({1: 1}) lr2 = None res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) - + def test_no_src_ids(self): lr1 = helper.create_lin_read({1: 1}) lr2 = pydgraph.LinRead() res = helper.create_lin_read({1: 1}) self.common_test(lr1, lr2, res) - + def test_no_target_ids(self): lr1 = pydgraph.LinRead() lr2 = helper.create_lin_read({1: 1}) @@ -72,6 +76,8 @@ def test_no_target_ids(self): class TestIsString(unittest.TestCase): + """Tests is_string utility function.""" + def test_is_string(self): self.assertTrue(util.is_string('')) self.assertTrue(util.is_string('a')) @@ -80,10 +86,11 @@ def test_is_string(self): def suite(): - s = unittest.TestSuite() - s.addTest(TestMergeLinReads()) - s.addTest(TestIsString()) - return s + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestMergeLinReads()) + suite_obj.addTest(TestIsString()) + return suite_obj if __name__ == '__main__': From ae1065668966ca5231d5fad166ade80d47c237af Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 3 Jan 2019 12:13:29 -0800 Subject: [PATCH 113/435] Remove linreadmap. (#39) linreadmap was removed from Dgraph and is no longer needed in clients. --- pydgraph/__init__.py | 2 +- pydgraph/client.py | 9 -------- pydgraph/txn.py | 14 ++++-------- pydgraph/util.py | 16 -------------- tests/helper.py | 25 ---------------------- tests/test_util.py | 51 -------------------------------------------- 6 files changed, 5 insertions(+), 112 deletions(-) diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py index eaafc0f2..d88816e6 100755 --- a/pydgraph/__init__.py +++ b/pydgraph/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. from pydgraph.proto.api_pb2 import Operation, Payload, Request, Response, Mutation, Assigned, TxnContext, Check,\ - Version, NQuad, Value, Facet, SchemaNode, LinRead, Latency + Version, NQuad, Value, Facet, SchemaNode, Latency from pydgraph.client_stub import * from pydgraph.client import * from pydgraph.txn import * diff --git a/pydgraph/client.py b/pydgraph/client.py index d12e5daa..56c183a0 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -38,7 +38,6 @@ def __init__(self, *clients): raise ValueError('No clients provided in DgraphClient constructor') self._clients = clients[:] - self._lin_read = api.LinRead() def alter(self, operation, timeout=None, metadata=None, credentials=None): """Runs a modification via this client.""" @@ -56,14 +55,6 @@ def txn(self): """Creates a transaction.""" return txn.Txn(self) - def set_lin_read(self, ctx): - """Sets linread map in ctx to the one from this instace.""" - ctx.lin_read.MergeFrom(self._lin_read) - - def merge_lin_reads(self, src): - """Merges linread map in this instance with src.""" - util.merge_lin_reads(self._lin_read, src) - def any_client(self): """Returns a random client.""" return random.choice(self._clients) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index c7232bb8..70590f39 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -45,15 +45,15 @@ class Txn(object): def __init__(self, client): self._dc = client self._ctx = api.TxnContext() - self._dc.set_lin_read(self._ctx) self._finished = False self._mutated = False - self._sequencing = api.LinRead.CLIENT_SIDE def sequencing(self, sequencing): """Sets sequencing.""" - self._sequencing = sequencing + # This method is obsolete since sequencing is no longer used. This + # method is being kept for backwards-compatibility. + pass def query(self, query, variables=None, timeout=None, metadata=None, credentials=None): @@ -70,10 +70,7 @@ def _common_query(self, query, variables=None): raise Exception( 'Transaction has already been committed or discarded') - lin_read = self._ctx.lin_read - lin_read.sequencing = self._sequencing - req = api.Request(query=query, start_ts=self._ctx.start_ts, - lin_read=lin_read) + req = api.Request(query=query, start_ts=self._ctx.start_ts) if variables is not None: for key, value in variables.items(): if util.is_string(key) and util.is_string(value): @@ -204,9 +201,6 @@ def merge_context(self, src=None): # txn context after a query or mutation. return - util.merge_lin_reads(self._ctx.lin_read, src.lin_read) - self._dc.merge_lin_reads(src.lin_read) - if self._ctx.start_ts == 0: self._ctx.start_ts = src.start_ts elif self._ctx.start_ts != src.start_ts: diff --git a/pydgraph/util.py b/pydgraph/util.py index 8ba8e1c2..2d083eb2 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -24,22 +24,6 @@ __status__ = 'development' -def merge_lin_reads(target, src): - """Merger src linread map into target linread map.""" - if src is None: - return target - - # cache for the loop - target_ids = target.ids - target_ids_get = target_ids.get - - for key, src_value in src.ids.items(): - if target_ids_get(key, 0) <= src_value: - target_ids[key] = src_value - - return target - - def is_string(string): """Checks if argument is a string. Compatible with Python 2 and 3.""" if sys.version_info[0] < 3: diff --git a/tests/helper.py b/tests/helper.py index 569c57ca..51704628 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -22,31 +22,6 @@ import pydgraph -def create_lin_read(src_ids): - """Creates a linread map using src_ids.""" - lin_read = pydgraph.LinRead() - ids = lin_read.ids - for key, value in src_ids.items(): - ids[key] = value - - return lin_read - - -def are_lin_reads_equal(lin_read1, lin_read2): - """Returns True if both linread maps are equal.""" - ids1 = lin_read1.ids - ids2 = lin_read2.ids - - if len(ids1) != len(ids2): - return False - - for (key, value) in ids1.items(): - if key not in ids2 or lin_read2.ids[key] != value: - return False - - return True - - SERVER_ADDR = 'localhost:9180' diff --git a/tests/test_util.py b/tests/test_util.py index 2480272c..1e3fd30d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -25,56 +25,6 @@ from . import helper -class TestMergeLinReads(unittest.TestCase): - """Tests merge_lin_reads utility function.""" - - def common_test(self, lr1, lr2, expected): - self.assertTrue(helper.are_lin_reads_equal(util.merge_lin_reads(lr1, lr2), expected)) - self.assertTrue(helper.are_lin_reads_equal(lr1, expected)) - - def test_disjoint(self): - lr1 = helper.create_lin_read({1: 1}) - lr2 = helper.create_lin_read({2: 2, 3: 3}) - res = helper.create_lin_read({1: 1, 2: 2, 3: 3}) - self.common_test(lr1, lr2, res) - - def test_lower_value(self): - lr1 = helper.create_lin_read({1: 2}) - lr2 = helper.create_lin_read({1: 1}) - res = helper.create_lin_read({1: 2}) - self.common_test(lr1, lr2, res) - - def test_higher_value(self): - lr1 = helper.create_lin_read({1: 1}) - lr2 = helper.create_lin_read({1: 2}) - res = helper.create_lin_read({1: 2}) - self.common_test(lr1, lr2, res) - - def test_equal_value(self): - lr1 = helper.create_lin_read({1: 1}) - lr2 = helper.create_lin_read({1: 1}) - res = helper.create_lin_read({1: 1}) - self.common_test(lr1, lr2, res) - - def test_none(self): - lr1 = helper.create_lin_read({1: 1}) - lr2 = None - res = helper.create_lin_read({1: 1}) - self.common_test(lr1, lr2, res) - - def test_no_src_ids(self): - lr1 = helper.create_lin_read({1: 1}) - lr2 = pydgraph.LinRead() - res = helper.create_lin_read({1: 1}) - self.common_test(lr1, lr2, res) - - def test_no_target_ids(self): - lr1 = pydgraph.LinRead() - lr2 = helper.create_lin_read({1: 1}) - res = helper.create_lin_read({1: 1}) - self.common_test(lr1, lr2, res) - - class TestIsString(unittest.TestCase): """Tests is_string utility function.""" @@ -88,7 +38,6 @@ def test_is_string(self): def suite(): """Returns a test suite object.""" suite_obj = unittest.TestSuite() - suite_obj.addTest(TestMergeLinReads()) suite_obj.addTest(TestIsString()) return suite_obj From 12875273f50c9a7b6d85a7973ff4502a56ef4ffd Mon Sep 17 00:00:00 2001 From: Daniel Mai Date: Thu, 3 Jan 2019 12:23:28 -0800 Subject: [PATCH 114/435] Add support for read-only transactions. (#40) Read-only transactions can be created by using client.txn(read_only=True) instead of client.txn(). Read-only transactions improve performance of queries by not going through the Raft proposal system when advancing the start ts. The start ts stays the same across the lifetime of the transaction. Mutations and commits are not allowed in read-only transactions. The client.query() helper method uses read-only transactions now. This addresses #27. Closes #30. --- pydgraph/client.py | 9 +++++---- pydgraph/txn.py | 18 ++++++++++++------ tests/test_txn.py | 26 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index 56c183a0..a302556d 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -48,12 +48,13 @@ def alter(self, operation, timeout=None, metadata=None, credentials=None): def query(self, query, variables=None, timeout=None, metadata=None, credentials=None): """Runs a query via this client.""" - return self.txn().query(query, variables=variables, timeout=timeout, - metadata=metadata, credentials=credentials) + txn = self.txn(read_only=True) + return txn.query(query, variables=variables, timeout=timeout, + metadata=metadata, credentials=credentials) - def txn(self): + def txn(self, read_only=False): """Creates a transaction.""" - return txn.Txn(self) + return txn.Txn(self, read_only=read_only) def any_client(self): """Returns a random client.""" diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 70590f39..fd2fd190 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -42,7 +42,7 @@ class Txn(object): after calling commit. """ - def __init__(self, client): + def __init__(self, client, read_only=False): self._dc = client self._ctx = api.TxnContext() @@ -70,7 +70,8 @@ def _common_query(self, query, variables=None): raise Exception( 'Transaction has already been committed or discarded') - req = api.Request(query=query, start_ts=self._ctx.start_ts) + req = api.Request(query=query, start_ts=self._ctx.start_ts, + read_only=self._read_only) if variables is not None: for key, value in variables.items(): if util.is_string(key) and util.is_string(value): @@ -110,6 +111,12 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, commit_now=None, ignore_index_conflict=None): + if self._read_only: + raise Exception( + 'Readonly transaction cannot run mutations or be committed') + if self._finished: + raise Exception( + 'Transaction has already been committed or discarded') if not mutation: mutation = api.Mutation() if set_obj: @@ -125,10 +132,6 @@ def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, if ignore_index_conflict: mutation.ignore_index_conflict = True - if self._finished: - raise Exception( - 'Transaction has already been committed or discarded') - self._mutated = True mutation.start_ts = self._ctx.start_ts return mutation @@ -157,6 +160,9 @@ def commit(self, timeout=None, metadata=None, credentials=None): self._common_except_commit(error) def _common_commit(self): + if self._read_only: + raise Exception( + 'Readonly transaction cannot run mutations or be committed') if self._finished: raise Exception( 'Transaction has already been committed or discarded') diff --git a/tests/test_txn.py b/tests/test_txn.py index c6d1affc..7b091bf5 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -230,6 +230,32 @@ def test_read_from_new_client(self): resp = self.client.query(query) self.assertEqual([{'name': 'Manish2'}], json.loads(resp.json).get('me')) + def test_read_only_txn(self): + """Tests read-only transactions. Read-only transactions should + not advance the start ts nor should allow mutations or commits.""" + + query = '{ me() {} }' + + # Using client.query helper method + resp1 = self.client.query(query) + start_ts1 = resp1.txn.start_ts + resp2 = self.client.query(query) + start_ts2 = resp2.txn.start_ts + self.assertEqual(start_ts1, start_ts2) + + # Using client.txn method + txn = self.client.txn(read_only=True) + resp1 = txn.query(query) + start_ts1 = resp1.txn.start_ts + resp2 = txn.query(query) + start_ts2 = resp2.txn.start_ts + self.assertEqual(start_ts1, start_ts2) + + with self.assertRaises(Exception): + txn.mutate(set_obj={'name': 'Manish'}) + with self.assertRaises(Exception): + txn.commit() + def test_conflict(self): """Tests committing two transactions which conflict.""" From 6b6d4a10f17d99fd84eb5011b55f189e3f4cc056 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 3 Jan 2019 13:38:30 -0800 Subject: [PATCH 115/435] Add preds support in pydgraph. (#41) Also fix bug introduced in previous commit (_read_only was not being set during initialization). --- pydgraph/txn.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index fd2fd190..99318a11 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -43,11 +43,13 @@ class Txn(object): """ def __init__(self, client, read_only=False): - self._dc = client + self._dg = client + self._dc = client.any_client() self._ctx = api.TxnContext() self._finished = False self._mutated = False + self._read_only = read_only def sequencing(self, sequencing): """Sets sequencing.""" @@ -59,9 +61,9 @@ def query(self, query, variables=None, timeout=None, metadata=None, credentials=None): """Adds a query operation to the transaction.""" req = self._common_query(query, variables=variables) - res = self._dc.any_client().query(req, timeout=timeout, - metadata=metadata, - credentials=credentials) + res = self._dc.query(req, timeout=timeout, + metadata=metadata, + credentials=credentials) self.merge_context(res.txn) return res @@ -89,9 +91,9 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) try: - assigned = self._dc.any_client().mutate(mutation, timeout=timeout, - metadata=metadata, - credentials=credentials) + assigned = self._dc.mutate(mutation, timeout=timeout, + metadata=metadata, + credentials=credentials) except Exception as error: try: self.discard(timeout=timeout, metadata=metadata, @@ -153,9 +155,9 @@ def commit(self, timeout=None, metadata=None, credentials=None): return try: - self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, - metadata=metadata, - credentials=credentials) + self._dc.commit_or_abort(self._ctx, timeout=timeout, + metadata=metadata, + credentials=credentials) except Exception as error: self._common_except_commit(error) @@ -185,9 +187,9 @@ def discard(self, timeout=None, metadata=None, credentials=None): if not self._common_discard(): return - self._dc.any_client().commit_or_abort(self._ctx, timeout=timeout, - metadata=metadata, - credentials=credentials) + self._dc.commit_or_abort(self._ctx, timeout=timeout, + metadata=metadata, + credentials=credentials) def _common_discard(self): if self._finished: @@ -213,4 +215,5 @@ def merge_context(self, src=None): # This condition should never be true. raise Exception('StartTs mismatch') - self._ctx.keys.extend(src.keys[:]) + self._ctx.keys.extend(src.keys) + self._ctx.preds.extend(src.preds) From ae7e384464a5b69b6d9882587c1a3d760423117f Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 3 Jan 2019 14:07:38 -0800 Subject: [PATCH 116/435] Add section on how to set auth tokens to README. (#42) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index e5e99e4e..273fbc0e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ and understand how to run and work with Dgraph. - [Run a query](#run-a-query) - [Commit a transaction](#commit-a-transaction) - [Cleanup Resources](#cleanup-resources) + - [Setting Metadata Headers](#setting-metadata-headers) - [Development](#development) - [Building the source](#building-the-source) - [Running tests](#running-tests) @@ -273,6 +274,16 @@ stub1.close() stub2.close() ``` +### Setting Metadata Headers +Metadata headers such as authentication tokens can be set through the metadata of gRPC methods. Below is an example of how to set a header named "auth-token". +```python +# The following piece of code shows how one can set metadata with +# auth-token, to allow Alter operation, if the server requires it. +# metadata is a list of arbritary key-value pairs. +metadata = [("auth-token", "the-auth-token-value")] +dg.alter(op, metadata=metadata) +``` + ## Development ### Building the source From 1b613e7f93f555ff2c13df753c75796277d88105 Mon Sep 17 00:00:00 2001 From: Daniel Mai Date: Thu, 3 Jan 2019 14:28:03 -0800 Subject: [PATCH 117/435] Add docs about read-only transactions. (#43) --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 273fbc0e..2f7d07b4 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ client.alter(op) To create a transaction, call `DgraphClient#txn()` method, which returns a new `Txn` object. This operation incurs no network overhead. -It is good practise to call `Txn#discard()` in a `finally` block after running +It is good practice to call `Txn#discard()` in a `finally` block after running the transaction. Calling `Txn#discard()` after `Txn#commit()` is a no-op and you can call `Txn#discard()` multiple times with no additional side-effects. @@ -106,6 +106,22 @@ finally: # ... ``` +To create a read-only transaction, call `DgraphClient#txn(read_only=True)`. +Read-only transactions are ideal for transactions which only involve queries. +Mutations and commits are not allowed. + +```python +txn = client.txn(read_only=True) +try: + # Do some queries here + # ... +finally: + txn.discard() + # ... +``` + +`client.query()` uses a read-only transaction to execute the query. + ### Run a mutation `Txn#mutate(mu=Mutation)` runs a mutation. It takes in a `Mutation` object, From b9a8759660c817903cfcc122482442cee7856314 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 3 Jan 2019 15:28:39 -0800 Subject: [PATCH 118/435] Update version and add it to the changelog. (#44) --- CHANGELOG.md | 14 ++++++++++++-- pydgraph/meta.py | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c19fb30..e29eaea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.0.1] - 2019-01-03 + +### Added +- Full compatibility with Dgraph v1.0.11 +- Added support for read-only transactions. +- Fixed dependencies. +- Support for predicate tracking. +- Remove linread map and sequencing. + ## [v1.0.0] - 2018-05-16 ### Added - Full compatibility with Dgraph v1.0.0 -[Unreleased]: https://github.com/dgraph-io/dgraph-js/compare/v1.0.0...HEAD -[v1.0.0]: https://github.com/dgraph-io/dgraph-js/tree/v1.0.0 +[Unreleased]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...HEAD +[v1.0.1]: https://github.com/dgraph-io/pydgraph/tree/v1.0.1 +[v1.0.0]: https://github.com/dgraph-io/pydgraph/tree/v1.0.0 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index cf544b13..15c3a1f8 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.0.0' +VERSION = '1.0.1' From fd65f9af4cff91eee8fb9efd91966ba62fcdfc1d Mon Sep 17 00:00:00 2001 From: Malware Utkonos Date: Wed, 16 Jan 2019 16:31:30 -0500 Subject: [PATCH 119/435] Update grpcio to 1.18.0 (#45) --- .gitignore | 3 +++ requirements.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d4bc70b7..86cbf74b 100755 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ MANIFEST # IntelliJ .idea pydgraph.iml + +# Python Virtual Environments +venv diff --git a/requirements.txt b/requirements.txt index 67013147..0c1cac3a 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -grpcio==1.17.1 +grpcio==1.18.0 protobuf==3.6.1 From ad0942164a193d7bccf253b634f7bca5e8364a40 Mon Sep 17 00:00:00 2001 From: Malware Utkonos Date: Wed, 16 Jan 2019 20:43:28 -0500 Subject: [PATCH 120/435] Update CI to cover python 3.7 (#47) --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd101fc7..e7b75e63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: python python: - - "3.6" + - "3.7" - "2.7" sudo: required -dist: trusty +dist: xenial before_install: - scripts/install_dgraph.sh install: @@ -11,4 +11,4 @@ install: - pip install coveralls script: - scripts/build.sh -after_success: coveralls \ No newline at end of file +after_success: coveralls From 3b5d6fbede0c34d5f7f3c9149d19b32f071fd995 Mon Sep 17 00:00:00 2001 From: Daniel Mai Date: Wed, 20 Feb 2019 18:11:23 -0800 Subject: [PATCH 121/435] Add mutual TLS example code. * Add examples sections in README linking to simple and tls examples. * Update example links in README to be relative paths instead of GitHub URLs direct to the master branch. * Update simple.py example README to work with Dgraph v1.0.10+ (Server -> Alpha rename). --- README.md | 12 ++- examples/simple/README.md | 12 ++- examples/tls/.gitignore | 6 ++ examples/tls/README.md | 119 ++++++++++++++++++++++++++++++ examples/tls/mutualtls_example.py | 64 ++++++++++++++++ 5 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 examples/tls/.gitignore create mode 100644 examples/tls/README.md create mode 100644 examples/tls/mutualtls_example.py diff --git a/README.md b/README.md index 2f7d07b4..ae76d0a7 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ and understand how to run and work with Dgraph. - [Commit a transaction](#commit-a-transaction) - [Cleanup Resources](#cleanup-resources) - [Setting Metadata Headers](#setting-metadata-headers) +- [Examples](#examples) - [Development](#development) - [Building the source](#building-the-source) - [Running tests](#running-tests) @@ -41,11 +42,11 @@ pip install pydgraph ## Quickstart -Build and run the [simple] project in the `examples` folder, which +Build and run the [simple][] project in the `examples` folder, which contains an end-to-end example of using the Dgraph python client. Follow the instructions in the README of that project. -[simple]: https://github.com/dgraph-io/pydgraph/tree/master/examples/simple +[simple]: ./examples/simple ## Using a client @@ -300,6 +301,13 @@ metadata = [("auth-token", "the-auth-token-value")] dg.alter(op, metadata=metadata) ``` +## Examples + +- [simple][]: Quickstart example of using pydgraph. +- [tls][]: Example of using pydgraph with a Dgraph cluster secured with TLS. + +[tls]: ./examples/tls + ## Development ### Building the source diff --git a/examples/simple/README.md b/examples/simple/README.md index cafa5add..1a78451e 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -9,14 +9,14 @@ for Dgraph. ### Start Dgraph server -You will need to install [Dgraph v1.0.0 or above][releases] and run it. +You will need to install [Dgraph v1.0.10 or above][releases] and run it. [releases]: https://github.com/dgraph-io/dgraph/releases You can run the commands below to start a clean Dgraph server every time, for testing and exploration. -First, create two separate directories for `dgraph zero` and `dgraph server`. +First, create two separate directories for `dgraph zero` and `dgraph alpha`. ```sh mkdir -p dgraphdata/zero dgraphdata/data @@ -27,18 +27,16 @@ Then start `dgraph zero`: ```sh cd dgraphdata/zero rm -r zw; dgraph zero -# If running Dgraph version <= 1.0.2, use the following command instead: -# rm -r zw; dgraph zero --port_offset -2000 ``` -Finally, start the `dgraph server`: +Finally, start the `dgraph alpha`: ```sh cd dgraphdata/data -rm -r p w; dgraph server --memory_mb=1024 --zero localhost:5080 +rm -r p w; dgraph server --lru_mb=1024 --zero localhost:5080 ``` -For more configuration options, and other details, refer to +For more configuration options and other details, refer to [docs.dgraph.io](https://docs.dgraph.io) ## Install dependencies diff --git a/examples/tls/.gitignore b/examples/tls/.gitignore new file mode 100644 index 00000000..65148c12 --- /dev/null +++ b/examples/tls/.gitignore @@ -0,0 +1,6 @@ +tls/ + +p/ +w/ +zw/ + diff --git a/examples/tls/README.md b/examples/tls/README.md new file mode 100644 index 00000000..959eccad --- /dev/null +++ b/examples/tls/README.md @@ -0,0 +1,119 @@ +# Mutual TLS example project + +Project demonstrating the use of pydgraph and Dgraph set up with client-server +mutual TLS. The following guide shows how to set up a single-group two-node +cluster (1 Dgraph Zero and 1 Dgraph Alpha) configured with mutual TLS. + +## Running + +### Install Dgraph + +You will need to [install Dgraph v1.0.0 or +above](https://docs.dgraph.io/get-started/#step-1-install-dgraph). + +A quick-start installation script is available for Linux and Mac: + +```sh +curl -sSf https://get.dgraph.io | bash +``` + +### Create TLS certificates + +Dgraph provides a `dgraph cert` tool to create and manage self-signed +server and client certificates using a generated Dgraph Root CA. See the [TLS +documentation](https://docs.dgraph.io/deploy/#tls-configuration) for more +information. + +Create the root CA. All certificates and keys are created in the `tls` directory. + +```sh +dgraph cert +``` + +Now create the Alpha server certificate (node.crt) and key (node.key) and client +certificate (client.user.crt) key (client.user.key). + +```sh +dgraph cert -n localhost +``` + +```sh +dgraph cert -c user +``` + +The following files should now be in the `tls` directory: + +```sh +$ ls tls +ca.crt ca.key client.user.crt client.user.key node.crt node.key +``` + +Using `dgraph cert ls` provides more details about each file. For instance, it +shows that the `node.crt` is valid only for the host named `localhost` and the +corresponding file permissions. + +```sh +$ dgraph cert ls +-rw-r--r-- ca.crt - Dgraph Root CA certificate + Issuer: Dgraph Labs, Inc. + S/N: 3dfb9c54929d703b +Expiration: 19 Feb 29 00:57 UTC + MD5 hash: C82CF5D4C344668E34A61D590D6A4B77 + +-r-------- ca.key - Dgraph Root CA key + MD5 hash: C82CF5D4C344668E34A61D590D6A4B77 + +-rw-r--r-- client.user.crt - Dgraph client certificate: user + Issuer: Dgraph Labs, Inc. + CA Verify: PASSED + S/N: 5991417e75ba14c7 +Expiration: 21 Feb 24 01:04 UTC + MD5 hash: BA35D4ABD8DFF1ED137E8D8E5D921D06 + +-rw------- client.user.key - Dgraph Client key + MD5 hash: BA35D4ABD8DFF1ED137E8D8E5D921D06 + +-rw-r--r-- node.crt - Dgraph Node certificate + Issuer: Dgraph Labs, Inc. + CA Verify: PASSED + S/N: 51d53048b6845d8c +Expiration: 21 Feb 24 01:00 UTC + Hosts: localhost + MD5 hash: 5D71F59AAEE294F1CFDA9E3232761018 + +-rw------- node.key - Dgraph Node key + MD5 hash: 5D71F59AAEE294F1CFDA9E3232761018 +``` + +### Start Dgraph cluster + +Start Dgraph Zero: + +```sh +dgraph zero +``` + +Start Dgraph Alpha with TLS options. `REQUIREANDVERIFY` sets mutual TLS (server authentication and client authentication): + +```sh +dgraph alpha --lru_mb=1024 --zero=localhost:5080 --tls_dir=./tls --tls_client_auth=REQUIREANDVERIFY +``` + +### Run example + +Ensure the pydgraph client is installed: + +```sh +pip install pydgraph +``` + +Then run the example, which connects to the Dgraph Alpha via TLS using the +generated root CA cert and client cert and key in the `tls` directory. The +example first deletes all the data in Dgraph via drop all, updates the schema, +runs a mutation, and finally runs a query. The result of the query is printed +out. + +```sh +python mutualtls_example.py +``` + diff --git a/examples/tls/mutualtls_example.py b/examples/tls/mutualtls_example.py new file mode 100644 index 00000000..0dd7d5dd --- /dev/null +++ b/examples/tls/mutualtls_example.py @@ -0,0 +1,64 @@ +#!/usr/bin/python + +import pydgraph +import grpc + +def create_client(addr='localhost:9080'): + # Read certs + with open('./tls/ca.crt', 'rb') as f: + root_ca_cert = f.read() + with open('./tls/client.user.key', 'rb') as f: + client_cert_key = f.read() + with open('./tls/client.user.crt', 'rb') as f: + client_cert = f.read() + + # Connect to Dgraph via gRPC with mutual TLS. + creds = grpc.ssl_channel_credentials(root_certificates=root_ca_cert, + private_key=client_cert_key, + certificate_chain=client_cert) + client_stub = pydgraph.DgraphClientStub(addr, credentials=creds) + return pydgraph.DgraphClient(client_stub) + +def main(): + client = create_client('localhost:9080') + + # Drop all + client.alter(pydgraph.Operation(drop_all=True)) + + # Update schema + schema = ''' +name: string @index(exact) . +description: string . +url: string . +''' + op = pydgraph.Operation(schema=schema) + client.alter(op) + + # Mutate + dgraph = { + "name": "Dgraph", + "description": "Scalable, Distributed, Low Latency Graph Database", + "url": "https://dgraph.io" + } + txn = client.txn() + try: + txn.mutate(set_obj=dgraph) + txn.commit() + finally: + txn.discard() + + # Query + res = client.query(''' +query dgraph($name: string) { + data(func: eq(name, $name)) { + uid + name + description + url + } +} +''', variables={"$name": "Dgraph"}) + print(res.json); + +if __name__ == '__main__': + main() From 4602bfe7ebce09aa8cb73d5d23a8d91af1848c0c Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 11 Mar 2019 15:05:38 -0700 Subject: [PATCH 122/435] Reject non-string keys or values in variable map. --- pydgraph/txn.py | 3 +++ tests/test_txn.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 99318a11..c00c38ca 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -78,6 +78,9 @@ def _common_query(self, query, variables=None): for key, value in variables.items(): if util.is_string(key) and util.is_string(value): req.vars[key] = value + else: + raise Exception( + 'Values and keys in variable map must be strings') return req diff --git a/tests/test_txn.py b/tests/test_txn.py index 7b091bf5..5baed689 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -413,6 +413,23 @@ def test_read_index_key_same_txn(self): self.assertEqual([], json.loads(resp.json).get('me'), "Expected 0 nodes read from index") + def test_non_string_variable(self): + helper.drop_all(self.client) + helper.set_schema(self.client, 'name: string @index(exact) .') + + txn = self.client.txn() + query = """ + query node($a: string) { + node(func: eq(name, $a)) + { + expand(_all_) + } + } + """ + variables = {"$a": 1234} + with self.assertRaises(Exception): + _ = txn.query(query, variables=variables) + class TestSPStar(helper.ClientIntegrationTestCase): def setUp(self): From d2ddf82a61e0839e438dc38b9cf9c43659097ac1 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 11 Mar 2019 15:14:03 -0700 Subject: [PATCH 123/435] Add comment. --- tests/test_txn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_txn.py b/tests/test_txn.py index 5baed689..627ae9b6 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -414,6 +414,8 @@ def test_read_index_key_same_txn(self): "Expected 0 nodes read from index") def test_non_string_variable(self): + """Tests sending a variable map with non-string values or keys results + in an Exception.""" helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') @@ -520,7 +522,7 @@ def test_sp_star2(self): 'uid': uid1, 'friend': [{'name': 'Jan2', 'uid': uid2}] }], json.loads(resp.json).get('me')) - + deleted2 = txn.mutate(del_obj={'uid': uid1, 'friend': None}) self.assertEqual(0, len(deleted2.uids)) resp = txn.query(query) From cd7f5110336906bf41d54a46802da8e2fb90fe01 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 12 Mar 2019 14:18:25 -0700 Subject: [PATCH 124/435] Set version of grpcio using >= instead of ==. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0c1cac3a..b27eb0c4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -grpcio==1.18.0 +grpcio>=1.18.0 protobuf==3.6.1 From 38d6684734045566fd3159f4f9092806fcbdfef9 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 19 Mar 2019 12:12:27 -0700 Subject: [PATCH 125/435] Change version to 1.0.2 (#52) --- CHANGELOG.md | 12 ++++++++++-- pydgraph/meta.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e29eaea9..64657855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.0.2] - 2019-03-19 + +### Added +- During queries, passing a map with non-string keys or values as a variable + map will result in an error instead of continuing silently. +- Fixed dependencies. + ## [v1.0.1] - 2019-01-03 ### Added @@ -22,5 +29,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Full compatibility with Dgraph v1.0.0 [Unreleased]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...HEAD -[v1.0.1]: https://github.com/dgraph-io/pydgraph/tree/v1.0.1 -[v1.0.0]: https://github.com/dgraph-io/pydgraph/tree/v1.0.0 +[v1.0.2]: https://github.com/dgraph-io/pydgraph/compare/v1.0.1...v1.0.2 +[v1.0.1]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/dgraph-io/pydgraph/releases/tag/v1.0.0 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 15c3a1f8..0429baae 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.0.1' +VERSION = '1.0.2' From 202f7f2e3722bf7ce592a0ea41f3cc4ef69f378c Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Wed, 20 Mar 2019 14:08:14 -0700 Subject: [PATCH 126/435] Add support for best-effort queries. (#53) --- pydgraph/client.py | 4 +- pydgraph/proto/api.proto | 1 + pydgraph/proto/api_pb2.py | 99 +++++++++++++++++++++------------------ pydgraph/txn.py | 9 +++- tests/test_txn.py | 23 +++++++++ 5 files changed, 86 insertions(+), 50 deletions(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index a302556d..2be27b8e 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -52,9 +52,9 @@ def query(self, query, variables=None, timeout=None, metadata=None, return txn.query(query, variables=variables, timeout=timeout, metadata=metadata, credentials=credentials) - def txn(self, read_only=False): + def txn(self, read_only=False, best_effort=False): """Creates a transaction.""" - return txn.Txn(self, read_only=read_only) + return txn.Txn(self, read_only=read_only, best_effort=best_effort) def any_client(self): """Returns a random client.""" diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index c6189cef..912f97d4 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -50,6 +50,7 @@ message Request { uint64 start_ts = 13; LinRead lin_read = 14; bool read_only = 15; + bool best_effort = 16; } message Response { diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 84976061..5138ad24 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -19,7 +19,7 @@ package='api', syntax='proto3', serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xb0\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xc5\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x10 \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -41,8 +41,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1080, - serialized_end=1126, + serialized_start=1101, + serialized_end=1147, ) _sym_db.RegisterEnumDescriptor(_LINREAD_SEQUENCING) @@ -75,8 +75,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1713, - serialized_end=1778, + serialized_start=1734, + serialized_end=1799, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -114,8 +114,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=152, - serialized_end=195, + serialized_start=173, + serialized_end=216, ) _REQUEST = _descriptor.Descriptor( @@ -160,6 +160,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='best_effort', full_name='api.Request.best_effort', index=5, + number=16, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -173,7 +180,7 @@ oneofs=[ ], serialized_start=19, - serialized_end=195, + serialized_end=216, ) @@ -224,8 +231,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=197, - serialized_end=315, + serialized_start=218, + serialized_end=336, ) @@ -262,8 +269,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=434, - serialized_end=477, + serialized_start=455, + serialized_end=498, ) _ASSIGNED = _descriptor.Descriptor( @@ -306,8 +313,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=318, - serialized_end=477, + serialized_start=339, + serialized_end=498, ) @@ -393,8 +400,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=480, - serialized_end=688, + serialized_start=501, + serialized_end=709, ) @@ -438,8 +445,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=690, - serialized_end=754, + serialized_start=711, + serialized_end=775, ) @@ -469,8 +476,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=756, - serialized_end=779, + serialized_start=777, + serialized_end=800, ) @@ -535,8 +542,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=781, - serialized_end=908, + serialized_start=802, + serialized_end=929, ) @@ -559,8 +566,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=910, - serialized_end=917, + serialized_start=931, + serialized_end=938, ) @@ -590,8 +597,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=919, - serialized_end=941, + serialized_start=940, + serialized_end=962, ) @@ -628,8 +635,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1036, - serialized_end=1078, + serialized_start=1057, + serialized_end=1099, ) _LINREAD = _descriptor.Descriptor( @@ -666,8 +673,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=944, - serialized_end=1126, + serialized_start=965, + serialized_end=1147, ) @@ -711,8 +718,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1128, - serialized_end=1201, + serialized_start=1149, + serialized_end=1222, ) @@ -784,8 +791,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1204, - serialized_end=1357, + serialized_start=1225, + serialized_end=1378, ) @@ -888,8 +895,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1360, - serialized_end=1604, + serialized_start=1381, + serialized_end=1625, ) @@ -948,8 +955,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1607, - serialized_end=1778, + serialized_start=1628, + serialized_end=1799, ) @@ -1035,8 +1042,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1781, - serialized_end=1936, + serialized_start=1802, + serialized_end=1957, ) @@ -1080,8 +1087,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1938, - serialized_end=2009, + serialized_start=1959, + serialized_end=2030, ) @@ -1118,8 +1125,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2011, - serialized_end=2057, + serialized_start=2032, + serialized_end=2078, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1350,8 +1357,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=2060, - serialized_end=2333, + serialized_start=2081, + serialized_end=2354, methods=[ _descriptor.MethodDescriptor( name='Login', diff --git a/pydgraph/txn.py b/pydgraph/txn.py index c00c38ca..613e375f 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -42,7 +42,11 @@ class Txn(object): after calling commit. """ - def __init__(self, client, read_only=False): + def __init__(self, client, read_only=False, best_effort=False): + if not read_only and best_effort: + raise Exception('Best effort transactions are only compatible with ' + 'read-only transactions') + self._dg = client self._dc = client.any_client() self._ctx = api.TxnContext() @@ -50,6 +54,7 @@ def __init__(self, client, read_only=False): self._finished = False self._mutated = False self._read_only = read_only + self._best_effort = best_effort def sequencing(self, sequencing): """Sets sequencing.""" @@ -73,7 +78,7 @@ def _common_query(self, query, variables=None): 'Transaction has already been committed or discarded') req = api.Request(query=query, start_ts=self._ctx.start_ts, - read_only=self._read_only) + read_only=self._read_only, best_effort=self._best_effort) if variables is not None: for key, value in variables.items(): if util.is_string(key) and util.is_string(value): diff --git a/tests/test_txn.py b/tests/test_txn.py index 627ae9b6..f06b818e 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -256,6 +256,29 @@ def test_read_only_txn(self): with self.assertRaises(Exception): txn.commit() + def test_best_effort_txn(self): + """Tests best-effort transactions.""" + + helper.drop_all(self.client) + helper.set_schema(self.client, 'name: string @index(exact) .') + + txn = self.client.txn() + _ = txn.mutate(set_obj={'name': 'Manish'}) + txn.commit() + + query = '{ me(func: eq(name, Manish)) {name} }' + with self.assertRaises(Exception): + txn = self.client.txn(read_only=False, best_effort=True) + + txn = self.client.txn(read_only=True, best_effort=True) + resp = txn.query(query) + self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) + + with self.assertRaises(Exception): + txn.mutate(set_obj={'name': 'Manish'}) + with self.assertRaises(Exception): + txn.commit() + def test_conflict(self): """Tests committing two transactions which conflict.""" From 9af423c45ee50a8e9246dd66ea86a133f9cfa6e9 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Wed, 20 Mar 2019 14:25:56 -0700 Subject: [PATCH 127/435] Documentation for best-effort queries. (#54) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ae76d0a7..114aa3a6 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,12 @@ finally: `client.query()` uses a read-only transaction to execute the query. +To create a read-only transaction that executes best-effort queries, call +`DgraphClient#txn(read_only=True, best_effort=True)`. Best-effort queries are +faster than normal queries because they bypass the normal consensus protocol. +For this same reason, best-effort queries cannot guarantee to return the latest +data. Best-effort queries are only supported by read-only transactions. + ### Run a mutation `Txn#mutate(mu=Mutation)` runs a mutation. It takes in a `Mutation` object, From 7df6506a958deb68b043e40cce1e41f815d09c34 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Wed, 20 Mar 2019 14:47:49 -0700 Subject: [PATCH 128/435] Changelog for v1.0.3 (#55) --- CHANGELOG.md | 6 ++++++ pydgraph/meta.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64657855..faa65a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.0.3] - 2019-03-20 + +### Added +- Support for best-effort queries. + ## [v1.0.2] - 2019-03-19 ### Added @@ -29,6 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Full compatibility with Dgraph v1.0.0 [Unreleased]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...HEAD +[v1.0.3]: https://github.com/dgraph-io/pydgraph/compare/v1.0.2...v1.0.3 [v1.0.2]: https://github.com/dgraph-io/pydgraph/compare/v1.0.1...v1.0.2 [v1.0.1]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...v1.0.1 [v1.0.0]: https://github.com/dgraph-io/pydgraph/releases/tag/v1.0.0 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 0429baae..710d79e7 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.0.2' +VERSION = '1.0.3' From 7a69b44cb683b1debba4b8696d74a603f7d7d358 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 25 Mar 2019 10:37:28 -0700 Subject: [PATCH 129/435] Fix N-quad example. (#57) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 114aa3a6..92a334ec 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ txn.mutate(set_obj=p) # txn.mutate(mu) # If you want to use N-Quads, use this instead: -# txn.mutate(set_nquads='_:alice "Alice"') +# txn.mutate(set_nquads='_:alice "Alice" .') ``` ```python From 698107b53de3ed8ac0ba0fabc5c11c879d9ddb7d Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 15 Apr 2019 17:15:17 -0700 Subject: [PATCH 130/435] Add ACL support (#60) --- pydgraph/client.py | 70 +++++++++++++- pydgraph/client_stub.py | 4 + pydgraph/proto/api.proto | 2 +- pydgraph/proto/api_pb2.py | 87 ++++++++--------- pydgraph/txn.py | 66 +++++++++++-- pydgraph/util.py | 4 + setup.py | 6 +- tests/helper.py | 1 + tests/test_bank.py | 192 -------------------------------------- tests/test_txn.py | 6 +- 10 files changed, 183 insertions(+), 255 deletions(-) delete mode 100644 tests/test_bank.py diff --git a/pydgraph/client.py b/pydgraph/client.py index 2be27b8e..5af09a23 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -38,19 +38,72 @@ def __init__(self, *clients): raise ValueError('No clients provided in DgraphClient constructor') self._clients = clients[:] + self._jwt = api.Jwt() + self._login_metadata = [] + + def login(self, userid, password, timeout=None, metadata=None, + credentials=None): + login_req = api.LoginRequest() + login_req.userid = userid + login_req.password = password + + response = self.any_client().login(login_req, timeout=timeout, + metadata=metadata, + credentials=credentials) + self._jwt = api.Jwt() + self._jwt.ParseFromString(response.json) + self._login_metadata = [("accessjwt", self._jwt.access_jwt)] + + def retry_login(self, timeout=None, metadata=None, credentials=None): + if len(self._jwt.refresh_jwt) == 0: + raise ValueError('refresh jwt should not be empty') + + login_req = api.LoginRequest() + login_req.refresh_token = self._jwt.refresh_jwt + + response = self.any_client().login(login_req, timeout=timeout, + metadata=metadata, + credentials=credentials) + self._jwt = api.Jwt() + self._jwt.ParseFromString(response.json) + self._login_metadata = [("accessjwt", self._jwt.access_jwt)] def alter(self, operation, timeout=None, metadata=None, credentials=None): """Runs a modification via this client.""" - return self.any_client().alter(operation, timeout=timeout, - metadata=metadata, - credentials=credentials) + new_metadata = self.add_login_metadata(metadata) + + try: + return self.any_client().alter(operation, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + except Exception as error: + if util.is_jwt_expired(error): + self.retry_login() + new_metadata = self.add_login_metadata(metadata) + return self.any_client().alter(operation, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + else: + raise error + def query(self, query, variables=None, timeout=None, metadata=None, credentials=None): """Runs a query via this client.""" + new_metadata = self.add_login_metadata(metadata) txn = self.txn(read_only=True) - return txn.query(query, variables=variables, timeout=timeout, - metadata=metadata, credentials=credentials) + + try: + return txn.query(query, variables=variables, timeout=timeout, + metadata=new_metadata, credentials=credentials) + except Exception as error: + if util.is_jwt_expired(error): + self.retry_login() + new_metadata = self.add_login_metadata(metadata) + return txn.query(query, variables=variables, timeout=timeout, + metadata=new_metadata, credentials=credentials) + else: + raise error def txn(self, read_only=False, best_effort=False): """Creates a transaction.""" @@ -59,3 +112,10 @@ def txn(self, read_only=False, best_effort=False): def any_client(self): """Returns a random client.""" return random.choice(self._clients) + + def add_login_metadata(self, metadata): + new_metadata = list(self._login_metadata) + if not metadata: + return new_metadata + new_metadata.extend(metadata) + return new_metadata diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 1e16cd2d..41a8e657 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -36,6 +36,10 @@ def __init__(self, addr='localhost:9080', credentials=None, options=None): self.stub = api_grpc.DgraphStub(self.channel) + def login(self, login_req, timeout=None, metadata=None, credentials=None): + return self.stub.Login(login_req, timeout=timeout, metadata=metadata, + credentials=credentials) + def alter(self, operation, timeout=None, metadata=None, credentials=None): """Runs alter operation.""" return self.stub.Alter(operation, timeout=timeout, metadata=metadata, diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index 912f97d4..8788622e 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -55,7 +55,7 @@ message Request { message Response { bytes json = 1; - repeated SchemaNode schema = 2; + repeated SchemaNode schema = 2 [deprecated=true]; TxnContext txn = 3; Latency latency = 12; } diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 5138ad24..c92b6074 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -19,7 +19,7 @@ package='api', syntax='proto3', serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xc5\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x10 \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"v\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1f\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNode\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xc5\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x10 \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"z\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12#\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNodeB\x02\x18\x01\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -41,8 +41,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1101, - serialized_end=1147, + serialized_start=1105, + serialized_end=1151, ) _sym_db.RegisterEnumDescriptor(_LINREAD_SEQUENCING) @@ -75,8 +75,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1734, - serialized_end=1799, + serialized_start=1738, + serialized_end=1803, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -204,7 +204,7 @@ has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=_b('\030\001'), file=DESCRIPTOR), _descriptor.FieldDescriptor( name='txn', full_name='api.Response.txn', index=2, number=3, type=11, cpp_type=10, label=1, @@ -232,7 +232,7 @@ oneofs=[ ], serialized_start=218, - serialized_end=336, + serialized_end=340, ) @@ -269,8 +269,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=455, - serialized_end=498, + serialized_start=459, + serialized_end=502, ) _ASSIGNED = _descriptor.Descriptor( @@ -313,8 +313,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=339, - serialized_end=498, + serialized_start=343, + serialized_end=502, ) @@ -400,8 +400,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=501, - serialized_end=709, + serialized_start=505, + serialized_end=713, ) @@ -445,8 +445,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=711, - serialized_end=775, + serialized_start=715, + serialized_end=779, ) @@ -476,8 +476,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=777, - serialized_end=800, + serialized_start=781, + serialized_end=804, ) @@ -542,8 +542,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=802, - serialized_end=929, + serialized_start=806, + serialized_end=933, ) @@ -566,8 +566,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=931, - serialized_end=938, + serialized_start=935, + serialized_end=942, ) @@ -597,8 +597,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=940, - serialized_end=962, + serialized_start=944, + serialized_end=966, ) @@ -635,8 +635,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1057, - serialized_end=1099, + serialized_start=1061, + serialized_end=1103, ) _LINREAD = _descriptor.Descriptor( @@ -673,8 +673,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=965, - serialized_end=1147, + serialized_start=969, + serialized_end=1151, ) @@ -718,8 +718,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1149, - serialized_end=1222, + serialized_start=1153, + serialized_end=1226, ) @@ -791,8 +791,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1225, - serialized_end=1378, + serialized_start=1229, + serialized_end=1382, ) @@ -895,8 +895,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1381, - serialized_end=1625, + serialized_start=1385, + serialized_end=1629, ) @@ -955,8 +955,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1628, - serialized_end=1799, + serialized_start=1632, + serialized_end=1803, ) @@ -1042,8 +1042,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1802, - serialized_end=1957, + serialized_start=1806, + serialized_end=1961, ) @@ -1087,8 +1087,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1959, - serialized_end=2030, + serialized_start=1963, + serialized_end=2034, ) @@ -1125,8 +1125,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2032, - serialized_end=2078, + serialized_start=2036, + serialized_end=2082, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1348,6 +1348,7 @@ DESCRIPTOR._options = None _REQUEST_VARSENTRY._options = None +_RESPONSE.fields_by_name['schema']._options = None _ASSIGNED_UIDSENTRY._options = None _LINREAD_IDSENTRY._options = None @@ -1357,8 +1358,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=2081, - serialized_end=2354, + serialized_start=2085, + serialized_end=2358, methods=[ _descriptor.MethodDescriptor( name='Login', diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 613e375f..cb2d2b40 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -65,10 +65,22 @@ def sequencing(self, sequencing): def query(self, query, variables=None, timeout=None, metadata=None, credentials=None): """Adds a query operation to the transaction.""" + new_metadata = self._dg.add_login_metadata(metadata) req = self._common_query(query, variables=variables) - res = self._dc.query(req, timeout=timeout, - metadata=metadata, - credentials=credentials) + try: + res = self._dc.query(req, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + except Exception as error: + if util.is_jwt_expired(error): + self._dg.retry_login() + new_metadata = self._dg.add_login_metadata(metadata) + res = self._dc.query(req, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + else: + raise error + self.merge_context(res.txn) return res @@ -98,11 +110,27 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, set_nquads=set_nquads, del_nquads=del_nquads, commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) + new_metadata = self._dg.add_login_metadata(metadata) + mutate_error = None + try: assigned = self._dc.mutate(mutation, timeout=timeout, - metadata=metadata, + metadata=new_metadata, credentials=credentials) except Exception as error: + if util.is_jwt_expired(error): + self._dg.retry_login() + new_metadata = self._dg.add_login_metadata(metadata) + try: + assigned = self._dc.mutate(mutation, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + except Exception as error: + mutate_error = error + else: + mutate_error = error + + if mutate_error is not None: try: self.discard(timeout=timeout, metadata=metadata, credentials=credentials) @@ -162,11 +190,22 @@ def commit(self, timeout=None, metadata=None, credentials=None): if not self._common_commit(): return + new_metadata = self._dg.add_login_metadata(metadata) try: self._dc.commit_or_abort(self._ctx, timeout=timeout, - metadata=metadata, + metadata=new_metadata, credentials=credentials) except Exception as error: + if util.is_jwt_expired(error): + self._dg.retry_login() + new_metadata = self._dg.add_login_metadata(metadata) + try: + self._dc.commit_or_abort(self._ctx, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + except Exception as error: + return self._common_except_commit(error) + self._common_except_commit(error) def _common_commit(self): @@ -195,9 +234,20 @@ def discard(self, timeout=None, metadata=None, credentials=None): if not self._common_discard(): return - self._dc.commit_or_abort(self._ctx, timeout=timeout, - metadata=metadata, - credentials=credentials) + new_metadata = self._dg.add_login_metadata(metadata) + try: + self._dc.commit_or_abort(self._ctx, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + except Exception as error: + if util.is_jwt_expired(error): + self._dg.retry_login() + new_metadata = self._dg.add_login_metadata(metadata) + self._dc.commit_or_abort(self._ctx, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + else: + raise error def _common_discard(self): if self._finished: diff --git a/pydgraph/util.py b/pydgraph/util.py index 2d083eb2..d8af8529 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -30,3 +30,7 @@ def is_string(string): return isinstance(string, basestring) return isinstance(string, str) + + +def is_jwt_expired(exception): + return 'Token is expired' in str(exception) diff --git a/setup.py b/setup.py index 119abaf3..62a40361 100755 --- a/setup.py +++ b/setup.py @@ -21,13 +21,13 @@ from distutils.core import setup try: - from pypandoc import convert + from pypandoc import convert_file except ImportError as e: # NOTE: error is thrown only for package build steps if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv: raise e - def convert(f, _): + def convert_file(f, _): return open(f, 'r').read() from pydgraph.meta import VERSION @@ -38,7 +38,7 @@ def convert(f, _): name='pydgraph', version=VERSION, description='Official Dgraph client implementation for Python', - long_description=convert(README, 'rst'), + long_description=convert_file(README, 'rst'), license='Apache License, Version 2.0', author='Dgraph Labs', author_email='contact@dgraph.io', diff --git a/tests/helper.py b/tests/helper.py index 51704628..9937e6f1 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -58,3 +58,4 @@ def setUp(self): """Sets up the client.""" self.client = create_client(self.TEST_SERVER_ADDR) + self.client.login("groot", "password") diff --git a/tests/test_bank.py b/tests/test_bank.py deleted file mode 100644 index 4cf0b54d..00000000 --- a/tests/test_bank.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright 2018 Dgraph Labs, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Test that runs through example bank transactions.""" - -__author__ = 'Garvit Pahal ' -__maintainer__ = 'Martin Martinez Rivera ' - -import unittest -import logging -import json -import random -import time -import multiprocessing as mp -import multiprocessing.dummy as mpd - -from . import helper - -USERS = 100 -CONCURRENCY = 10 -TRANSFER_COUNT = 1000 - - -class TestBank(helper.ClientIntegrationTestCase): - """Test that runs through example bank transactions.""" - - def setUp(self): - super(TestBank, self).setUp() - - self.accounts = [ - {'bal': 100} for _ in range(USERS) - ] - self.uids = [] - - logging.debug(len(self.accounts)) - - def test_bank_transfer(self): - """Run transfers concurrently.""" - self.create_accounts() - - try: - total_watcher = self.start_total_watcher() - - success_ctr = mp.Value('i', 0, lock=True) - retry_ctr = mp.Value('i', 0, lock=True) - - pool = mpd.Pool(CONCURRENCY) - results = [pool.apply_async( - run_transfers, - (self.TEST_SERVER_ADDR, TRANSFER_COUNT, self.uids, success_ctr, - retry_ctr) - ) for _ in range(CONCURRENCY)] - - _ = [res.get() for res in results] - pool.close() - finally: - total_watcher.terminate() - time.sleep(0.1) - - def create_accounts(self): - """Creates the default set of accounts.""" - - helper.drop_all(self.client) - helper.set_schema(self.client, 'bal: int .') - - txn = self.client.txn() - assigned = txn.mutate(set_obj=self.accounts) - txn.commit() - - self.uids.extend(assigned.uids.values()) - logging.debug('Created %d accounts', len(assigned.uids)) - - def start_total_watcher(self): - """Watcher keeps an eye on the total account balances.""" - total_watch = looper(run_total, self.client, self.uids) - process = mp.Process(target=total_watch, name='total_watcher') - process.start() - return process - - -def looper(func, *args, **kwargs): - """Returns a function that runs func in an infinite loop.""" - def _looper(): - while True: - func(*args, **kwargs) - time.sleep(1) - - return _looper - - -def run_total(client, uids): - """Calculates the total amount in the accounts.""" - - query = """{{ - var(func: uid("{uids:s}")) {{ - b as bal - }} - total() {{ - bal: sum(val(b)) - }} - }}""".format(uids='", "'.join(uids)) - - resp = client.query(query) - total = json.loads(resp.json)['total'] - logging.info('Response: %s', total) - assert total[0]['bal'] == 10000 - - -def run_transfers(addr, transfer_count, account_ids, success_ctr, retry_ctr): - """Runs transfers between the given accounts.""" - pname = mpd.current_process().name - log = logging.getLogger('test_bank.run_transfers[%s]' % (pname,)) - client = helper.create_client(addr) - - while True: - from_acc, to_acc = select_account_pair(account_ids) - query = """{{ - me(func: uid("{uid1:s}", "{uid2:s}")) {{ - uid, - bal - }} - }}""".format(uid1=from_acc, uid2=to_acc) - - txn = client.txn() - try: - accounts = load_from_query(txn, query, 'me') - accounts[0]['bal'] += 5 - accounts[1]['bal'] -= 5 - dump_from_obj(txn, accounts) - with success_ctr.get_lock(): - success_ctr.value += 1 - - if not success_ctr.value % 100: - log.info('Runs %d. Aborts: %d', success_ctr.value, - retry_ctr.value) - if success_ctr.value >= transfer_count: - break - except BaseException: - with retry_ctr.get_lock(): - retry_ctr.value += 1 - - with success_ctr.get_lock(), retry_ctr.get_lock(): - log.info('success: %d, retries: %d', success_ctr.value, retry_ctr.value) - - -def select_account_pair(accounts): - """Selects a pair of accounts at random from accounts ensuring they are not - the same.""" - while True: - from_acc = random.choice(accounts) - to_acc = random.choice(accounts) - if from_acc != to_acc: - return from_acc, to_acc - - -def load_from_query(txn, query, field): - """Loads a field from the results of a query executed in a txn.""" - resp = txn.query(query) - return json.loads(resp.json)[field] - - -def dump_from_obj(txn, obj, commit=False): - """Dumps the given object into the transaction.""" - assigned = txn.mutate(set_obj=obj) - - if not commit: - return assigned - return txn.commit() - - -def suite(): - """Returns a test suite object.""" - suite_obj = unittest.TestSuite() - suite_obj.addTest(TestBank()) - return suite_obj - - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO) - runner = unittest.TextTestRunner() - runner.run(suite()) diff --git a/tests/test_txn.py b/tests/test_txn.py index f06b818e..3bde7318 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -266,13 +266,13 @@ def test_best_effort_txn(self): _ = txn.mutate(set_obj={'name': 'Manish'}) txn.commit() - query = '{ me(func: eq(name, Manish)) {name} }' + query = '{ me(func: has(name)) {name} }' with self.assertRaises(Exception): txn = self.client.txn(read_only=False, best_effort=True) txn = self.client.txn(read_only=True, best_effort=True) resp = txn.query(query) - self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) + self.assertEqual([], json.loads(resp.json).get('me')) with self.assertRaises(Exception): txn.mutate(set_obj={'name': 'Manish'}) @@ -461,7 +461,7 @@ def setUp(self): super(TestSPStar, self).setUp() helper.drop_all(self.client) - helper.set_schema(self.client, 'friend: uid .') + helper.set_schema(self.client, 'friend: [uid] .') def test_sp_star(self): """Tests a Subject Predicate Star query.""" From d333a84270c7caaea22f0c1630ad0ef900526ec6 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 16 Apr 2019 14:40:16 -0700 Subject: [PATCH 131/435] Remove query method from client.py (#61) Other clients do not have the query as part of the client interface, only the transaction interface can do that. This change removes query from the client interface so that it matches the rest of the clients. --- examples/simple/simple.py | 6 +++--- examples/tls/mutualtls_example.py | 4 ++-- pydgraph/client.py | 19 ------------------- tests/test_acct_upsert.py | 2 +- tests/test_essentials.py | 7 +++---- tests/test_queries.py | 2 +- tests/test_txn.py | 24 +++++++++++------------- 7 files changed, 21 insertions(+), 43 deletions(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 375653d1..2009c968 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -99,7 +99,7 @@ def delete_data(client): } }""" variables1 = {'$a': 'Bob'} - res1 = client.query(query1, variables=variables1) + res1 = client.txn(read_only=True).query(query1, variables=variables1) ppl1 = json.loads(res1.json) for person in ppl1['all']: print('Query to find Uid for Bob :') @@ -142,7 +142,7 @@ def query_data(client): }""" variables = {'$a': 'Alice'} - res = client.query(query, variables=variables) + res = client.txn(read_only=True).query(query, variables=variables) ppl = json.loads(res.json) # Print results. @@ -178,7 +178,7 @@ def query_data01(client): }""" variables01 = {'$b': 'Bob'} - res01 = client.query(query01, variables=variables01) + res01 = client.txn(read_only=True).query(query01, variables=variables01) ppl01 = json.loads(res01.json) print('Number of people named "Bob": {}'.format(len(ppl01['all']))) diff --git a/examples/tls/mutualtls_example.py b/examples/tls/mutualtls_example.py index 0dd7d5dd..ac045605 100644 --- a/examples/tls/mutualtls_example.py +++ b/examples/tls/mutualtls_example.py @@ -46,9 +46,9 @@ def main(): txn.commit() finally: txn.discard() - + # Query - res = client.query(''' + res = client.txn(read_only=True).query(''' query dgraph($name: string) { data(func: eq(name, $name)) { uid diff --git a/pydgraph/client.py b/pydgraph/client.py index 5af09a23..ea3b8ae0 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -86,25 +86,6 @@ def alter(self, operation, timeout=None, metadata=None, credentials=None): else: raise error - - def query(self, query, variables=None, timeout=None, metadata=None, - credentials=None): - """Runs a query via this client.""" - new_metadata = self.add_login_metadata(metadata) - txn = self.txn(read_only=True) - - try: - return txn.query(query, variables=variables, timeout=timeout, - metadata=new_metadata, credentials=credentials) - except Exception as error: - if util.is_jwt_expired(error): - self.retry_login() - new_metadata = self.add_login_metadata(metadata) - return txn.query(query, variables=variables, timeout=timeout, - metadata=new_metadata, credentials=credentials) - else: - raise error - def txn(self, read_only=False, best_effort=False): """Creates a transaction.""" return txn.Txn(self, read_only=read_only, best_effort=best_effort) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 3201101b..2a9bbdd1 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -89,7 +89,7 @@ def assert_changes(self, firsts, accounts): }} }}""".format(' '.join(firsts)) logging.debug(query) - result = json.loads(self.client.query(query).json) + result = json.loads(self.client.txn(read_only=True).query(query).json) account_set = set() for acct in result['all']: diff --git a/tests/test_essentials.py b/tests/test_essentials.py index 7f0555a4..8b14453c 100644 --- a/tests/test_essentials.py +++ b/tests/test_essentials.py @@ -28,10 +28,9 @@ class TestEssentials(helper.ClientIntegrationTestCase): """Tests mutation after query behavior.""" def testMutationAfterQuery(self): - """Tests what happens when making a mutation on a txn after querying - on the client.""" + """Tests what happens when making a mutation on a txn after querying.""" - _ = self.client.query('{firsts(func: has(first)) { uid first }}') + _ = self.client.txn(read_only=True).query('{firsts(func: has(first)) { uid first }}') txn = self.client.txn() mutation = txn.mutate(set_nquads='_:node "Node name first" .') @@ -43,7 +42,7 @@ def testMutationAfterQuery(self): txn.commit() query = '{{node(func: uid({uid:s})) {{ uid }} }}'.format(uid=created) - reread = self.client.query(query) + reread = self.client.txn(read_only=True).query(query) self.assertEqual(created, json.loads(reread.json).get('node')[0]['uid']) diff --git a/tests/test_queries.py b/tests/test_queries.py index 8a61a5dd..82d5bf51 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -57,7 +57,7 @@ def test_mutation_and_query(self): } }""" - response = self.client.query(query, variables={'$a': 'Alice'}) + response = self.client.txn().query(query, variables={'$a': 'Alice'}) self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], json.loads(response.json).get('me')) self.assertTrue(is_number(response.latency.parsing_ns), diff --git a/tests/test_txn.py b/tests/test_txn.py index 3bde7318..7bcd60a4 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -75,7 +75,7 @@ def test_commit_now(self): name }} }}""".format(uid=uid) - resp = self.client.query(query) + resp = self.client.txn(read_only=True).query(query) self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) def test_discard(self): @@ -98,7 +98,7 @@ def test_discard(self): name }} }}""".format(uid=uid) - resp = self.client.query(query) + resp = self.client.txn(read_only=True).query(query) self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) def test_mutate_error(self): @@ -143,7 +143,7 @@ def test_read_before_start_ts(self): }} }}""".format(uid=uid) - resp = self.client.query(query) + resp = self.client.txn(read_only=True).query(query) self.assertEqual([], json.loads(resp.json).get('me')) def test_read_after_start_ts(self): @@ -163,7 +163,7 @@ def test_read_after_start_ts(self): }} }}""".format(uid=uid) - resp = self.client.query(query) + resp = self.client.txn(read_only=True).query(query) self.assertEqual([{'name': 'Manish'}], json.loads(resp.json).get('me')) def test_read_before_and_after_start_ts(self): @@ -197,7 +197,7 @@ def test_read_before_and_after_start_ts(self): # once txn3 is committed, other txns observe the update txn3.commit() - resp4 = self.client.query(query) + resp4 = self.client.txn(read_only=True).query(query) self.assertEqual([{'name': 'Manish2'}], json.loads(resp4.json).get('me')) def test_read_from_new_client(self): @@ -218,7 +218,7 @@ def test_read_from_new_client(self): }} }}""".format(uid=uid) - resp2 = client2.query(query) + resp2 = client2.txn(read_only=True).query(query) self.assertEqual([{'name': 'Manish'}], json.loads(resp2.json).get('me')) self.assertTrue(resp2.txn.start_ts > 0) @@ -227,7 +227,7 @@ def test_read_from_new_client(self): self.assertTrue(assigned.context.start_ts > 0) txn2.commit() - resp = self.client.query(query) + resp = self.client.txn(read_only=True).query(query) self.assertEqual([{'name': 'Manish2'}], json.loads(resp.json).get('me')) def test_read_only_txn(self): @@ -236,14 +236,12 @@ def test_read_only_txn(self): query = '{ me() {} }' - # Using client.query helper method - resp1 = self.client.query(query) + resp1 = self.client.txn(read_only=True).query(query) start_ts1 = resp1.txn.start_ts - resp2 = self.client.query(query) + resp2 = self.client.txn(read_only=True).query(query) start_ts2 = resp2.txn.start_ts self.assertEqual(start_ts1, start_ts2) - # Using client.txn method txn = self.client.txn(read_only=True) resp1 = txn.query(query) start_ts1 = resp1.txn.start_ts @@ -361,7 +359,7 @@ def test_commit_conflict(self): }} }}""".format(uid=uid) - resp4 = self.client.query(query) + resp4 = self.client.txn(read_only=True).query(query) self.assertEqual([{'name': 'Jan the man'}], json.loads(resp4.json).get('me')) def test_mutate_conflict(self): @@ -408,7 +406,7 @@ def test_conflict_ignore(self): } }""" - resp = self.client.query(query) + resp = self.client.txn(read_only=True).query(query) self.assertEqual([{'uid': uid1}, {'uid': uid2}], json.loads(resp.json).get('me')) def test_read_index_key_same_txn(self): From ba9957ff7afa470c9c684da2da4dc00a07b50250 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 16 Apr 2019 15:06:17 -0700 Subject: [PATCH 132/435] Create version 1.1 (#62) --- CHANGELOG.md | 10 ++++++++++ pydgraph/meta.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faa65a66..325d4cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.1] - 2019-04-16 + +### Added +- Support for ACL (Access Control List). + +### Removed +- The query method from the client class has been deprecated. This was done in + order to match the rest of the clients and to make it explicit that creating a + transaction is required to query Dgraph. + ## [v1.0.3] - 2019-03-20 ### Added diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 710d79e7..2ad3e23e 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.0.3' +VERSION = '1.1' From d41678a86a0c68000f06a80ee80ceea6bcde0fee Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Wed, 17 Apr 2019 13:30:23 -0700 Subject: [PATCH 133/435] Remove references to client.query() method from README. (#63) --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 92a334ec..42689c04 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,6 @@ finally: # ... ``` -`client.query()` uses a read-only transaction to execute the query. - To create a read-only transaction that executes best-effort queries, call `DgraphClient#txn(read_only=True, best_effort=True)`. Best-effort queries are faster than normal queries because they bypass the normal consensus protocol. @@ -172,7 +170,7 @@ query1 = """query all($a: string) variables1 = {'$a': 'Bob'} -res1 = client.query(query1, variables=variables1) +res1 = txn.query(query1, variables=variables1) ppl1 = json.loads(res1.json) @@ -225,9 +223,9 @@ query = """query all($a: string) { }""" variables = {'$a': 'Alice'} -res = client.txn().query(query, variables=variables) +res = txn.query(query, variables=variables) # If not doing a mutation in the same transaction, simply use: -# res = client.query(query, variables=variables) +# res = client.txn(read_only=True).query(query, variables=variables) ppl = json.loads(res.json); From 541eed04be06031434df062954d70fe118bc11bb Mon Sep 17 00:00:00 2001 From: Christoph Brand Date: Thu, 25 Apr 2019 20:52:41 +0200 Subject: [PATCH 134/435] Fix error reference in transaction mutate function (#65) In Python 3.7 the error variable here is not set and thus a wrong exception gets thrown. Switching this to the variable "mutate_error" fixes it. --- pydgraph/txn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/txn.py b/pydgraph/txn.py index cb2d2b40..4c6a516b 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -138,7 +138,7 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, # Ignore error - user should see the original error. pass - self._common_except_mutate(error) + self._common_except_mutate(mutate_error) if mutation.commit_now: self._finished = True From 386575d8a47b2856f06b8d7b0f5a7c60f12d7cda Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 26 Apr 2019 17:03:30 -0700 Subject: [PATCH 135/435] Create version 1.1.1 (#66) --- CHANGELOG.md | 5 +++++ pydgraph/meta.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 325d4cb1..be815a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.1.1] - 2019-04-26 + +### Added +- Bug fix + ## [v1.1] - 2019-04-16 ### Added diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 2ad3e23e..b429cea2 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.1' +VERSION = '1.1.1' From 86778b6219315aed2e6a1ffe76d114f3d48566f2 Mon Sep 17 00:00:00 2001 From: jimyx17 Date: Fri, 7 Jun 2019 02:47:51 +0200 Subject: [PATCH 136/435] Free GRPC resources on stub close (#69) Lastest versions of GRPC does not include the shutdown and destroy channel on _del_ call. (https://github.com/grpc/grpc/issues/12531). --- pydgraph/client_stub.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 41a8e657..08ac8535 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -70,5 +70,9 @@ def check_version(self, check, timeout=None, metadata=None, def close(self): """Deletes channel and stub.""" + try: + self.channel.close() + except: + pass del self.channel del self.stub From 85b4c90cefe3b027f0a44417b303385dcfb5ee48 Mon Sep 17 00:00:00 2001 From: Kevin Orr Date: Fri, 7 Jun 2019 17:12:03 -0400 Subject: [PATCH 137/435] Don't pin dependency versions in requirements.txt (#70) (#71) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b27eb0c4..80f4e682 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ grpcio>=1.18.0 -protobuf==3.6.1 +protobuf>=3.6.1 From 53e08b2f350fb818a96446a00a2b27f16d1218ef Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 7 Jun 2019 15:19:02 -0700 Subject: [PATCH 138/435] Bump version to 1.1.2 (#72) --- CHANGELOG.md | 5 +++++ pydgraph/meta.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be815a2b..250aec74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.1.2] - 2019-06-07 + +### Added +- Updated requirements.txt to unpin protobuf version. + ## [v1.1.1] - 2019-04-26 ### Added diff --git a/pydgraph/meta.py b/pydgraph/meta.py index b429cea2..e25fa25d 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.1.1' +VERSION = '1.1.2' From b4ce1de6fe9d7f9e50209d8d56934b6d69ed7488 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 7 Jun 2019 15:24:49 -0700 Subject: [PATCH 139/435] Update changelog for v1.1.2 (#73) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 250aec74..6a2670f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Updated requirements.txt to unpin protobuf version. +- Manually free Grpc resources on stub close. ## [v1.1.1] - 2019-04-26 @@ -54,6 +55,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Full compatibility with Dgraph v1.0.0 [Unreleased]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...HEAD +[v1.1.2]: https://github.com/dgraph-io/pydgraph/compare/v1.1.1...v1.1.2 +[v1.1.1]: https://github.com/dgraph-io/pydgraph/compare/v1.1...v1.1.1 +[v1.1]: https://github.com/dgraph-io/pydgraph/compare/v1.0.3...v1.1 [v1.0.3]: https://github.com/dgraph-io/pydgraph/compare/v1.0.2...v1.0.3 [v1.0.2]: https://github.com/dgraph-io/pydgraph/compare/v1.0.1...v1.0.2 [v1.0.1]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...v1.0.1 From b8effde9ed86405880ed09ce4c5209e270cc9325 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Mon, 17 Jun 2019 20:57:46 +0530 Subject: [PATCH 140/435] Copy api.proto from dgo --- pydgraph/proto/api.proto | 15 +++- pydgraph/proto/api_pb2.py | 141 +++++++++++++++++++++++++++----------- 2 files changed, 114 insertions(+), 42 deletions(-) diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index 8788622e..3c9fae7b 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -71,6 +71,7 @@ message Mutation { bytes delete_json = 2; bytes set_nquads = 3; bytes del_nquads = 4; + string query = 5; repeated NQuad set = 10; repeated NQuad del = 11; @@ -79,11 +80,23 @@ message Mutation { bool ignore_index_conflict = 15; // this field is not parsed and used by the server anymore. } - message Operation { string schema = 1; string drop_attr = 2; bool drop_all = 3; + + enum DropOp { + NONE = 0; + ALL = 1; + DATA = 2; + ATTR = 3; + TYPE = 4; + } + DropOp drop_op = 4; + + // If drop_op is ATTR or TYPE, drop_value holds the name of the predicate or + // type to delete. + string drop_value = 5; } // Worker services. diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index c92b6074..6dfa2056 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: api.proto @@ -19,11 +20,45 @@ package='api', syntax='proto3', serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xc5\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x10 \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"z\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12#\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNodeB\x02\x18\x01\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd0\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"@\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xc5\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x10 \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"z\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12#\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNodeB\x02\x18\x01\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xdf\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\r\n\x05query\x18\x05 \x01(\t\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"\xb7\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) +_OPERATION_DROPOP = _descriptor.EnumDescriptor( + name='DropOp', + full_name='api.Operation.DropOp', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ALL', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DATA', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ATTR', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='TYPE', index=4, number=4, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=857, + serialized_end=914, +) +_sym_db.RegisterEnumDescriptor(_OPERATION_DROPOP) + _LINREAD_SEQUENCING = _descriptor.EnumDescriptor( name='Sequencing', full_name='api.LinRead.Sequencing', @@ -41,8 +76,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1105, - serialized_end=1151, + serialized_start=1240, + serialized_end=1286, ) _sym_db.RegisterEnumDescriptor(_LINREAD_SEQUENCING) @@ -75,8 +110,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1738, - serialized_end=1803, + serialized_start=1873, + serialized_end=1938, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -354,35 +389,42 @@ is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='set', full_name='api.Mutation.set', index=4, + name='query', full_name='api.Mutation.query', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='set', full_name='api.Mutation.set', index=5, number=10, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='del', full_name='api.Mutation.del', index=5, + name='del', full_name='api.Mutation.del', index=6, number=11, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='start_ts', full_name='api.Mutation.start_ts', index=6, + name='start_ts', full_name='api.Mutation.start_ts', index=7, number=13, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='commit_now', full_name='api.Mutation.commit_now', index=7, + name='commit_now', full_name='api.Mutation.commit_now', index=8, number=14, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='ignore_index_conflict', full_name='api.Mutation.ignore_index_conflict', index=8, + name='ignore_index_conflict', full_name='api.Mutation.ignore_index_conflict', index=9, number=15, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -401,7 +443,7 @@ oneofs=[ ], serialized_start=505, - serialized_end=713, + serialized_end=728, ) @@ -433,11 +475,26 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='drop_op', full_name='api.Operation.drop_op', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='drop_value', full_name='api.Operation.drop_value', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ + _OPERATION_DROPOP, ], serialized_options=None, is_extendable=False, @@ -445,8 +502,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=715, - serialized_end=779, + serialized_start=731, + serialized_end=914, ) @@ -476,8 +533,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=781, - serialized_end=804, + serialized_start=916, + serialized_end=939, ) @@ -542,8 +599,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=806, - serialized_end=933, + serialized_start=941, + serialized_end=1068, ) @@ -566,8 +623,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=935, - serialized_end=942, + serialized_start=1070, + serialized_end=1077, ) @@ -597,8 +654,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=944, - serialized_end=966, + serialized_start=1079, + serialized_end=1101, ) @@ -635,8 +692,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1061, - serialized_end=1103, + serialized_start=1196, + serialized_end=1238, ) _LINREAD = _descriptor.Descriptor( @@ -673,8 +730,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=969, - serialized_end=1151, + serialized_start=1104, + serialized_end=1286, ) @@ -718,8 +775,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1153, - serialized_end=1226, + serialized_start=1288, + serialized_end=1361, ) @@ -791,8 +848,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1229, - serialized_end=1382, + serialized_start=1364, + serialized_end=1517, ) @@ -895,8 +952,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1385, - serialized_end=1629, + serialized_start=1520, + serialized_end=1764, ) @@ -955,8 +1012,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1632, - serialized_end=1803, + serialized_start=1767, + serialized_end=1938, ) @@ -1042,8 +1099,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1806, - serialized_end=1961, + serialized_start=1941, + serialized_end=2096, ) @@ -1087,8 +1144,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1963, - serialized_end=2034, + serialized_start=2098, + serialized_end=2169, ) @@ -1125,8 +1182,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2036, - serialized_end=2082, + serialized_start=2171, + serialized_end=2217, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1141,6 +1198,8 @@ _ASSIGNED.fields_by_name['latency'].message_type = _LATENCY _MUTATION.fields_by_name['set'].message_type = _NQUAD _MUTATION.fields_by_name['del'].message_type = _NQUAD +_OPERATION.fields_by_name['drop_op'].enum_type = _OPERATION_DROPOP +_OPERATION_DROPOP.containing_type = _OPERATION _TXNCONTEXT.fields_by_name['lin_read'].message_type = _LINREAD _LINREAD_IDSENTRY.containing_type = _LINREAD _LINREAD.fields_by_name['ids'].message_type = _LINREAD_IDSENTRY @@ -1358,8 +1417,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=2085, - serialized_end=2358, + serialized_start=2220, + serialized_end=2493, methods=[ _descriptor.MethodDescriptor( name='Login', From 254b0b7a8cce88bd0e1ed0be587008045e76c53d Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 18 Jun 2019 16:07:31 +0530 Subject: [PATCH 141/435] Fix trailing space --- examples/simple/simple.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 2009c968..6e1c9a09 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -93,10 +93,10 @@ def delete_data(client): try: query1 = """query all($a: string) { - all(func: eq(name, $a)) + all(func: eq(name, $a)) { uid - } + } }""" variables1 = {'$a': 'Bob'} res1 = client.txn(read_only=True).query(query1, variables=variables1) @@ -158,23 +158,23 @@ def query_data(client): #Query to check for deleted node def query_data01(client): query01 = """query all($b: string) - { all(func: eq(name, $b)) - { uid, + { all(func: eq(name, $b)) + { uid, name, age - friend - { + friend + { uid, name, - age + age } - ~friend - { + ~friend + { uid, name, - age + age } - } + } }""" variables01 = {'$b': 'Bob'} From 9d6250c483af191bf3f6d3cd41e0edb6e528dfca Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 18 Jun 2019 17:29:28 +0530 Subject: [PATCH 142/435] Add support for Upsert Block --- pydgraph/txn.py | 8 +- tests/test_upsert_block.py | 158 +++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 tests/test_upsert_block.py diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 4c6a516b..9d75c952 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -102,12 +102,12 @@ def _common_query(self, query, variables=None): return req def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, - del_nquads=None, commit_now=None, ignore_index_conflict=None, + del_nquads=None, query=None, commit_now=None, ignore_index_conflict=None, timeout=None, metadata=None, credentials=None): """Adds a mutate operation to the transaction.""" mutation = self._common_mutate( mutation=mutation, set_obj=set_obj, del_obj=del_obj, - set_nquads=set_nquads, del_nquads=del_nquads, + set_nquads=set_nquads, del_nquads=del_nquads, query=query, commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) new_metadata = self._dg.add_login_metadata(metadata) @@ -147,7 +147,7 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, return assigned def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, - set_nquads=None, del_nquads=None, + set_nquads=None, del_nquads=None, query=None, commit_now=None, ignore_index_conflict=None): if self._read_only: raise Exception( @@ -165,6 +165,8 @@ def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, mutation.set_nquads = set_nquads.encode('utf8') if del_nquads: mutation.del_nquads = del_nquads.encode('utf8') + if query: + mutation.query = query.encode('utf8') if commit_now: mutation.commit_now = True if ignore_index_conflict: diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py new file mode 100644 index 00000000..2d5d1a29 --- /dev/null +++ b/tests/test_upsert_block.py @@ -0,0 +1,158 @@ +# Copyright 2019 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests to verify upsert block.""" + +__author__ = 'Aman Mangal ' +__maintainer__ = 'Aman Mangal ' + +import unittest +import logging +import json +import time +import multiprocessing +import multiprocessing.dummy as mpd + +import pydgraph + +from . import helper + +CONCURRENCY = 5 +FIRSTS = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] +LASTS = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] +AGES = [20, 25, 30, 35] + + +class TestAccountUpsert(helper.ClientIntegrationTestCase): + """Tests to verify upsert directive.""" + def setUp(self): + super(TestAccountUpsert, self).setUp() + + self.accounts = [ + {'first': f, 'last': l, 'age': a} + for f in FIRSTS for l in LASTS for a in AGES + ] + logging.info(len(self.accounts)) + + helper.drop_all(self.client) + helper.set_schema(self.client, """ + first: string @index(term) @upsert . + last: string @index(hash) @upsert . + age: int @index(int) @upsert . + when: int . + """) + + def test_account_upsert(self): + """Run upserts concurrently using upsert block.""" + self.do_upserts(self.accounts, CONCURRENCY) + self.assert_changes(FIRSTS, self.accounts) + + def do_upserts(self, account_list, concurrency): + """Runs the upsert command for the accounts in `account_list`. Execution + happens in concurrent processes.""" + + success_ctr = multiprocessing.Value('i', 0, lock=True) + retry_ctr = multiprocessing.Value('i', 0, lock=True) + + def _updater(acct): + upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, + success_ctr=success_ctr, retry_ctr=retry_ctr) + + pool = mpd.Pool(concurrency) + results = [ + pool.apply_async(_updater, (acct,)) + for acct in account_list for _ in range(concurrency) + ] + + _ = [res.get() for res in results] + pool.close() + + def assert_changes(self, firsts, accounts): + """Will check to see changes have been made.""" + + query = """{{ + all(func: anyofterms(first, "{}")) {{ + first + last + age + }} + }}""".format(' '.join(firsts)) + logging.debug(query) + result = json.loads(self.client.txn(read_only=True).query(query).json) + + account_set = set() + for acct in result['all']: + self.assertTrue(acct['first'] is not None) + self.assertTrue(acct['last'] is not None) + self.assertTrue(acct['age'] is not None) + account_set.add('{first}_{last}_{age}'.format(**acct)) + + self.assertEqual(len(account_set), len(accounts)) + for acct in accounts: + self.assertTrue('{first}_{last}_{age}'.format(**acct) in account_set) + + +def upsert_account(addr, account, success_ctr, retry_ctr): + """Runs upsert operation.""" + client = helper.create_client(addr) + query = """{{ + acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ + u as uid + }} + }}""".format(**account) + + last_update_time = time.time() - 10000 + while True: + if time.time() > last_update_time + 10000: + logging.debug('Success: %d Retries: %d', success_ctr.value, + retry_ctr.value) + last_update_time = time.time() + + txn = client.txn() + try: + nquads = """ + uid(u) "{first}" . + uid(u) "{last}" . + uid(u) "{age}"^^ . + """.format(**account) + created = txn.mutate(query=query, set_nquads=nquads) + + updatequads = 'uid(u) "{0:d}"^^ .'.format(int(time.time())) + txn.mutate(query=query, set_nquads=updatequads) + txn.commit() + + with success_ctr.get_lock(): + success_ctr.value += 1 + + # txn successful, break the loop + return + except pydgraph.AbortedError: + with retry_ctr.get_lock(): + retry_ctr.value += 1 + # txn failed, retry the loop + finally: + txn.discard() + + +def suite(): + """Returns a test suite object.""" + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestAccountUpsert()) + return suite_obj + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + runner = unittest.TextTestRunner() + runner.run(suite()) From 4e5658a5e813c4fa1b2652a924823ef56feae31e Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 18 Jun 2019 17:33:30 +0530 Subject: [PATCH 143/435] Bump version to 1.2.0 --- CHANGELOG.md | 6 ++++++ pydgraph/meta.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2670f3..6a09da80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v1.2.0] - 2019-06-24 + +### Added +- Added support for Upsert Block + ## [v1.1.2] - 2019-06-07 ### Added @@ -55,6 +60,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Full compatibility with Dgraph v1.0.0 [Unreleased]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...HEAD +[v1.2.0] https://github.com/dgraph-io/pydgraph/compare/v1.1.2...v1.2.0 [v1.1.2]: https://github.com/dgraph-io/pydgraph/compare/v1.1.1...v1.1.2 [v1.1.1]: https://github.com/dgraph-io/pydgraph/compare/v1.1...v1.1.1 [v1.1]: https://github.com/dgraph-io/pydgraph/compare/v1.0.3...v1.1 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index e25fa25d..582ff2e2 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.1.2' +VERSION = '1.2.0' From 8e0a3c6e3f7ec99235ca31b978c8d24e40604e06 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 3 Sep 2019 19:00:19 +0530 Subject: [PATCH 144/435] Update API to support Conditional Upsert (and Multiple Mutations) (#77) --- README.md | 5 - examples/simple/simple.py | 31 +- pydgraph/__init__.py | 4 +- pydgraph/client_stub.py | 7 +- pydgraph/proto/api.proto | 62 +--- pydgraph/proto/api_pb2.py | 626 +++++++++------------------------ pydgraph/proto/api_pb2_grpc.py | 19 +- pydgraph/txn.py | 139 +++----- tests/test_txn.py | 151 ++++---- tests/test_upsert_block.py | 11 +- 10 files changed, 330 insertions(+), 725 deletions(-) diff --git a/README.md b/README.md index 42689c04..c3708386 100644 --- a/README.md +++ b/README.md @@ -185,11 +185,6 @@ Sometimes, you only want to commit a mutation, without querying anything further In such cases, you can set the keyword argument `commit_now=True` to indicate that the mutation must be immediately committed. -Keyword argument `ignore_index_conflict=True` can be used to not run conflict -detection over the index, which would decrease the number of transaction -conflicts and aborts. However, this would come at the cost of potentially -inconsistent upsert operations. - ### Run a query You can run a query by calling `Txn#query(string)`. You will need to pass in a diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 6e1c9a09..e174ae41 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -6,7 +6,7 @@ # Create a client stub. def create_client_stub(): - return pydgraph.DgraphClientStub('localhost:9080') + return pydgraph.DgraphClientStub('localhost:9180') # Create a client. @@ -23,7 +23,7 @@ def drop_all(client): def set_schema(client): schema = """ name: string @index(exact) . - friend: uid @reverse . + friend: [uid] @reverse . age: int . married: bool . loc: geo . @@ -39,6 +39,7 @@ def create_data(client): try: # Create data. p = { + 'uid': '_:alice', 'name': 'Alice', 'age': 26, 'married': True, @@ -49,10 +50,12 @@ def create_data(client): 'dob': datetime.datetime(1980, 1, 1, 23, 0, 0, 0).isoformat(), 'friend': [ { + 'uid': '_:bob', 'name': 'Bob', 'age': 24, }, { + 'uid': '_:charlie', 'name': 'Charlie', 'age': 29, } @@ -65,20 +68,18 @@ def create_data(client): } # Run mutation. - assigned = txn.mutate(set_obj=p) + response = txn.mutate(set_obj=p) # Commit transaction. txn.commit() # Get uid of the outermost object (person named "Alice"). - # assigned.uids returns a map from blank node names to uids. - # For a json mutation, blank node names "blank-0", "blank-1", ... are used - # for all the created nodes. - print('Created person named "Alice" with uid = {}\n'.format(assigned.uids['blank-0'])) - - print('All created nodes (map from blank node names to uids):') - for uid in assigned.uids: - print('{} => {}'.format(uid, assigned.uids[uid])) + # response.uids returns a map from blank node names to uids. + print('Created person named "Alice" with uid = {}\n'.format(response.uids['alice'])) + + print('All created nodes (map from node names to uids):') + for uid in response.uids: + print('{} => {}'.format(uid, response.uids[uid])) finally: # Clean up. Calling this after txn.commit() is a no-op # and hence safe. @@ -86,7 +87,7 @@ def create_data(client): print('\n') -#Deleting a data +# Deleting a data def delete_data(client): # Create a new transaction. txn = client.txn() @@ -111,9 +112,7 @@ def delete_data(client): print('Bob deleted') print('\n') - - assigned = txn.mutate(del_obj= person) - + txn.mutate(del_obj= person) txn.commit() finally: @@ -155,7 +154,7 @@ def query_data(client): print(person) print('\n') -#Query to check for deleted node +# Query to check for deleted node def query_data01(client): query01 = """query all($b: string) { all(func: eq(name, $b)) diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py index d88816e6..a9d2588e 100755 --- a/pydgraph/__init__.py +++ b/pydgraph/__init__.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pydgraph.proto.api_pb2 import Operation, Payload, Request, Response, Mutation, Assigned, TxnContext, Check,\ - Version, NQuad, Value, Facet, SchemaNode, Latency +from pydgraph.proto.api_pb2 import Operation, Payload, Request, Response, Mutation, TxnContext,\ + Check, Version, NQuad, Value, Facet, Latency from pydgraph.client_stub import * from pydgraph.client import * from pydgraph.txn import * diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index 08ac8535..c8cfe883 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -46,15 +46,10 @@ def alter(self, operation, timeout=None, metadata=None, credentials=None): credentials=credentials) def query(self, req, timeout=None, metadata=None, credentials=None): - """Runs query operation.""" + """Runs query or mutate operation.""" return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) - def mutate(self, mutation, timeout=None, metadata=None, credentials=None): - """Runs mutate operation.""" - return self.stub.Mutate(mutation, timeout=timeout, metadata=metadata, - credentials=credentials) - def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): """Runs commit or abort operation.""" diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index 3c9fae7b..842122fb 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -37,33 +37,28 @@ option java_outer_classname = "DgraphProto"; service Dgraph { rpc Login (LoginRequest) returns (Response) {} rpc Query (Request) returns (Response) {} - rpc Mutate (Mutation) returns (Assigned) {} rpc Alter (Operation) returns (Payload) {} rpc CommitOrAbort (TxnContext) returns (TxnContext) {} rpc CheckVersion(Check) returns (Version) {} } message Request { - string query = 1; - map vars = 2; // Support for GraphQL like variables. + uint64 start_ts = 1; + + string query = 4; + map vars = 5; // Support for GraphQL like variables. + bool read_only = 6; + bool best_effort = 7; - uint64 start_ts = 13; - LinRead lin_read = 14; - bool read_only = 15; - bool best_effort = 16; + repeated Mutation mutations = 12; + bool commit_now = 13; } message Response { bytes json = 1; - repeated SchemaNode schema = 2 [deprecated=true]; - TxnContext txn = 3; - Latency latency = 12; -} - -message Assigned { - map uids = 1; - TxnContext context = 2; - Latency latency = 12; + TxnContext txn = 2; + Latency latency = 3; + map uids = 12; } message Mutation { @@ -71,13 +66,14 @@ message Mutation { bytes delete_json = 2; bytes set_nquads = 3; bytes del_nquads = 4; - string query = 5; + repeated NQuad set = 5; + repeated NQuad del = 6; + + // This is being used for upserts. + string cond = 9; - repeated NQuad set = 10; - repeated NQuad del = 11; - uint64 start_ts = 13; + // This field is a duplicate of the one in Request and placed here for convenience. bool commit_now = 14; - bool ignore_index_conflict = 15; // this field is not parsed and used by the server anymore. } message Operation { @@ -110,7 +106,6 @@ message TxnContext { bool aborted = 3; repeated string keys = 4; // List of keys to be used for conflict detection. repeated string preds = 5; // List of predicates involved in this transaction. - LinRead lin_read = 13; } message Check {} @@ -119,20 +114,11 @@ message Version { string tag = 1; } -message LinRead { - enum Sequencing { - CLIENT_SIDE = 0; - SERVER_SIDE = 1; - } - - map ids = 1; - Sequencing sequencing = 2; -} - message Latency { uint64 parsing_ns = 1; uint64 processing_ns = 2; uint64 encoding_ns = 3; + uint64 assign_timestamp_ns = 4; } message NQuad { @@ -177,18 +163,6 @@ message Facet { string alias = 5; // not stored, only used for query. } -message SchemaNode { - string predicate = 1; - string type = 2; - bool index = 3; - repeated string tokenizer = 4; - bool reverse = 5; - bool count = 6; - bool list = 7; - bool upsert = 8; - bool lang = 9; -} - message LoginRequest { string userid = 1; string password = 2; diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 6dfa2056..9ffaaa5b 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -20,7 +20,7 @@ package='api', syntax='proto3', serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xc5\x01\n\x07Request\x12\r\n\x05query\x18\x01 \x01(\t\x12$\n\x04vars\x18\x02 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x1e\n\x08lin_read\x18\x0e \x01(\x0b\x32\x0c.api.LinRead\x12\x11\n\tread_only\x18\x0f \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x10 \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"z\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12#\n\x06schema\x18\x02 \x03(\x0b\x32\x0f.api.SchemaNodeB\x02\x18\x01\x12\x1c\n\x03txn\x18\x03 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\"\x9f\x01\n\x08\x41ssigned\x12%\n\x04uids\x18\x01 \x03(\x0b\x32\x17.api.Assigned.UidsEntry\x12 \n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x0c \x01(\x0b\x32\x0c.api.Latency\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xdf\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\r\n\x05query\x18\x05 \x01(\t\x12\x17\n\x03set\x18\n \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x0b \x03(\x0b\x32\n.api.NQuad\x12\x10\n\x08start_ts\x18\r \x01(\x04\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\x12\x1d\n\x15ignore_index_conflict\x18\x0f \x01(\x08\"\xb7\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"\x7f\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\x12\x1e\n\x08lin_read\x18\r \x01(\x0b\x32\x0c.api.LinRead\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"\xb6\x01\n\x07LinRead\x12\"\n\x03ids\x18\x01 \x03(\x0b\x32\x15.api.LinRead.IdsEntry\x12+\n\nsequencing\x18\x02 \x01(\x0e\x32\x17.api.LinRead.Sequencing\x1a*\n\x08IdsEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\".\n\nSequencing\x12\x0f\n\x0b\x43LIENT_SIDE\x10\x00\x12\x0f\n\x0bSERVER_SIDE\x10\x01\"I\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"\x9b\x01\n\nSchemaNode\x12\x11\n\tpredicate\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\r\n\x05index\x18\x03 \x01(\x08\x12\x11\n\ttokenizer\x18\x04 \x03(\t\x12\x0f\n\x07reverse\x18\x05 \x01(\x08\x12\r\n\x05\x63ount\x18\x06 \x01(\x08\x12\x0c\n\x04list\x18\x07 \x01(\x08\x12\x0e\n\x06upsert\x18\x08 \x01(\x08\x12\x0c\n\x04lang\x18\t \x01(\x08\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\x91\x02\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12(\n\x06Mutate\x12\r.api.Mutation\x1a\r.api.Assigned\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xdb\x01\n\x07Request\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\r\n\x05query\x18\x04 \x01(\t\x12$\n\x04vars\x18\x05 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x11\n\tread_only\x18\x06 \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x07 \x01(\x08\x12 \n\tmutations\x18\x0c \x03(\x0b\x32\r.api.Mutation\x12\x12\n\ncommit_now\x18\r \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa9\x01\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1c\n\x03txn\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x03 \x01(\x0b\x32\x0c.api.Latency\x12%\n\x04uids\x18\x0c \x03(\x0b\x32\x17.api.Response.UidsEntry\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xad\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\x05 \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x06 \x03(\x0b\x32\n.api.NQuad\x12\x0c\n\x04\x63ond\x18\t \x01(\t\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\"\xb7\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"_\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"f\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\x12\x1b\n\x13\x61ssign_timestamp_ns\x18\x04 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\xe7\x01\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -54,33 +54,11 @@ ], containing_type=None, serialized_options=None, - serialized_start=857, - serialized_end=914, + serialized_start=715, + serialized_end=772, ) _sym_db.RegisterEnumDescriptor(_OPERATION_DROPOP) -_LINREAD_SEQUENCING = _descriptor.EnumDescriptor( - name='Sequencing', - full_name='api.LinRead.Sequencing', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='CLIENT_SIDE', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SERVER_SIDE', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=1240, - serialized_end=1286, -) -_sym_db.RegisterEnumDescriptor(_LINREAD_SEQUENCING) - _FACET_VALTYPE = _descriptor.EnumDescriptor( name='ValType', full_name='api.Facet.ValType', @@ -110,8 +88,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1873, - serialized_end=1938, + serialized_start=1543, + serialized_end=1608, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -149,8 +127,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=173, - serialized_end=216, + serialized_start=195, + serialized_end=238, ) _REQUEST = _descriptor.Descriptor( @@ -161,103 +139,58 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='query', full_name='api.Request.query', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='vars', full_name='api.Request.vars', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='start_ts', full_name='api.Request.start_ts', index=2, - number=13, type=4, cpp_type=4, label=1, + name='start_ts', full_name='api.Request.start_ts', index=0, + number=1, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='lin_read', full_name='api.Request.lin_read', index=3, - number=14, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, + name='query', full_name='api.Request.query', index=1, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='read_only', full_name='api.Request.read_only', index=4, - number=15, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, + name='vars', full_name='api.Request.vars', index=2, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='best_effort', full_name='api.Request.best_effort', index=5, - number=16, type=8, cpp_type=7, label=1, + name='read_only', full_name='api.Request.read_only', index=3, + number=6, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_REQUEST_VARSENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=19, - serialized_end=216, -) - - -_RESPONSE = _descriptor.Descriptor( - name='Response', - full_name='api.Response', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ _descriptor.FieldDescriptor( - name='json', full_name='api.Response.json', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + name='best_effort', full_name='api.Request.best_effort', index=4, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='schema', full_name='api.Response.schema', index=1, - number=2, type=11, cpp_type=10, label=3, + name='mutations', full_name='api.Request.mutations', index=5, + number=12, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='txn', full_name='api.Response.txn', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='latency', full_name='api.Response.latency', index=3, - number=12, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, + name='commit_now', full_name='api.Request.commit_now', index=6, + number=13, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], - nested_types=[], + nested_types=[_REQUEST_VARSENTRY, ], enum_types=[ ], serialized_options=None, @@ -266,27 +199,27 @@ extension_ranges=[], oneofs=[ ], - serialized_start=218, - serialized_end=340, + serialized_start=19, + serialized_end=238, ) -_ASSIGNED_UIDSENTRY = _descriptor.Descriptor( +_RESPONSE_UIDSENTRY = _descriptor.Descriptor( name='UidsEntry', - full_name='api.Assigned.UidsEntry', + full_name='api.Response.UidsEntry', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='key', full_name='api.Assigned.UidsEntry.key', index=0, + name='key', full_name='api.Response.UidsEntry.key', index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='value', full_name='api.Assigned.UidsEntry.value', index=1, + name='value', full_name='api.Response.UidsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, @@ -304,42 +237,49 @@ extension_ranges=[], oneofs=[ ], - serialized_start=459, - serialized_end=502, + serialized_start=367, + serialized_end=410, ) -_ASSIGNED = _descriptor.Descriptor( - name='Assigned', - full_name='api.Assigned', +_RESPONSE = _descriptor.Descriptor( + name='Response', + full_name='api.Response', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='uids', full_name='api.Assigned.uids', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], + name='json', full_name='api.Response.json', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='context', full_name='api.Assigned.context', index=1, + name='txn', full_name='api.Response.txn', index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='latency', full_name='api.Assigned.latency', index=2, - number=12, type=11, cpp_type=10, label=1, + name='latency', full_name='api.Response.latency', index=2, + number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='uids', full_name='api.Response.uids', index=3, + number=12, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], - nested_types=[_ASSIGNED_UIDSENTRY, ], + nested_types=[_RESPONSE_UIDSENTRY, ], enum_types=[ ], serialized_options=None, @@ -348,8 +288,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=343, - serialized_end=502, + serialized_start=241, + serialized_end=410, ) @@ -389,47 +329,33 @@ is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='query', full_name='api.Mutation.query', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='set', full_name='api.Mutation.set', index=5, - number=10, type=11, cpp_type=10, label=3, + name='set', full_name='api.Mutation.set', index=4, + number=5, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='del', full_name='api.Mutation.del', index=6, - number=11, type=11, cpp_type=10, label=3, + name='del', full_name='api.Mutation.del', index=5, + number=6, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='start_ts', full_name='api.Mutation.start_ts', index=7, - number=13, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, + name='cond', full_name='api.Mutation.cond', index=6, + number=9, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='commit_now', full_name='api.Mutation.commit_now', index=8, + name='commit_now', full_name='api.Mutation.commit_now', index=7, number=14, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='ignore_index_conflict', full_name='api.Mutation.ignore_index_conflict', index=9, - number=15, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -442,8 +368,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=505, - serialized_end=728, + serialized_start=413, + serialized_end=586, ) @@ -502,8 +428,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=731, - serialized_end=914, + serialized_start=589, + serialized_end=772, ) @@ -533,8 +459,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=916, - serialized_end=939, + serialized_start=774, + serialized_end=797, ) @@ -580,13 +506,6 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='lin_read', full_name='api.TxnContext.lin_read', index=5, - number=13, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -599,8 +518,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=941, - serialized_end=1068, + serialized_start=799, + serialized_end=894, ) @@ -623,8 +542,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1070, - serialized_end=1077, + serialized_start=896, + serialized_end=903, ) @@ -654,84 +573,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1079, - serialized_end=1101, -) - - -_LINREAD_IDSENTRY = _descriptor.Descriptor( - name='IdsEntry', - full_name='api.LinRead.IdsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='api.LinRead.IdsEntry.key', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='api.LinRead.IdsEntry.value', index=1, - number=2, type=4, cpp_type=4, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1196, - serialized_end=1238, -) - -_LINREAD = _descriptor.Descriptor( - name='LinRead', - full_name='api.LinRead', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='ids', full_name='api.LinRead.ids', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='sequencing', full_name='api.LinRead.sequencing', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_LINREAD_IDSENTRY, ], - enum_types=[ - _LINREAD_SEQUENCING, - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1104, - serialized_end=1286, + serialized_start=905, + serialized_end=927, ) @@ -763,6 +606,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='assign_timestamp_ns', full_name='api.Latency.assign_timestamp_ns', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -775,8 +625,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1288, - serialized_end=1361, + serialized_start=929, + serialized_end=1031, ) @@ -848,8 +698,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1364, - serialized_end=1517, + serialized_start=1034, + serialized_end=1187, ) @@ -952,8 +802,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1520, - serialized_end=1764, + serialized_start=1190, + serialized_end=1434, ) @@ -1012,95 +862,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1767, - serialized_end=1938, -) - - -_SCHEMANODE = _descriptor.Descriptor( - name='SchemaNode', - full_name='api.SchemaNode', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='predicate', full_name='api.SchemaNode.predicate', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='api.SchemaNode.type', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='index', full_name='api.SchemaNode.index', index=2, - number=3, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='tokenizer', full_name='api.SchemaNode.tokenizer', index=3, - number=4, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='reverse', full_name='api.SchemaNode.reverse', index=4, - number=5, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='count', full_name='api.SchemaNode.count', index=5, - number=6, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='list', full_name='api.SchemaNode.list', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='upsert', full_name='api.SchemaNode.upsert', index=7, - number=8, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='lang', full_name='api.SchemaNode.lang', index=8, - number=9, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1941, - serialized_end=2096, + serialized_start=1437, + serialized_end=1608, ) @@ -1144,8 +907,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2098, - serialized_end=2169, + serialized_start=1610, + serialized_end=1681, ) @@ -1182,29 +945,21 @@ extension_ranges=[], oneofs=[ ], - serialized_start=2171, - serialized_end=2217, + serialized_start=1683, + serialized_end=1729, ) _REQUEST_VARSENTRY.containing_type = _REQUEST _REQUEST.fields_by_name['vars'].message_type = _REQUEST_VARSENTRY -_REQUEST.fields_by_name['lin_read'].message_type = _LINREAD -_RESPONSE.fields_by_name['schema'].message_type = _SCHEMANODE +_REQUEST.fields_by_name['mutations'].message_type = _MUTATION +_RESPONSE_UIDSENTRY.containing_type = _RESPONSE _RESPONSE.fields_by_name['txn'].message_type = _TXNCONTEXT _RESPONSE.fields_by_name['latency'].message_type = _LATENCY -_ASSIGNED_UIDSENTRY.containing_type = _ASSIGNED -_ASSIGNED.fields_by_name['uids'].message_type = _ASSIGNED_UIDSENTRY -_ASSIGNED.fields_by_name['context'].message_type = _TXNCONTEXT -_ASSIGNED.fields_by_name['latency'].message_type = _LATENCY +_RESPONSE.fields_by_name['uids'].message_type = _RESPONSE_UIDSENTRY _MUTATION.fields_by_name['set'].message_type = _NQUAD _MUTATION.fields_by_name['del'].message_type = _NQUAD _OPERATION.fields_by_name['drop_op'].enum_type = _OPERATION_DROPOP _OPERATION_DROPOP.containing_type = _OPERATION -_TXNCONTEXT.fields_by_name['lin_read'].message_type = _LINREAD -_LINREAD_IDSENTRY.containing_type = _LINREAD -_LINREAD.fields_by_name['ids'].message_type = _LINREAD_IDSENTRY -_LINREAD.fields_by_name['sequencing'].enum_type = _LINREAD_SEQUENCING -_LINREAD_SEQUENCING.containing_type = _LINREAD _NQUAD.fields_by_name['object_value'].message_type = _VALUE _NQUAD.fields_by_name['facets'].message_type = _FACET _VALUE.oneofs_by_name['val'].fields.append( @@ -1244,172 +999,138 @@ _FACET_VALTYPE.containing_type = _FACET DESCRIPTOR.message_types_by_name['Request'] = _REQUEST DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE -DESCRIPTOR.message_types_by_name['Assigned'] = _ASSIGNED DESCRIPTOR.message_types_by_name['Mutation'] = _MUTATION DESCRIPTOR.message_types_by_name['Operation'] = _OPERATION DESCRIPTOR.message_types_by_name['Payload'] = _PAYLOAD DESCRIPTOR.message_types_by_name['TxnContext'] = _TXNCONTEXT DESCRIPTOR.message_types_by_name['Check'] = _CHECK DESCRIPTOR.message_types_by_name['Version'] = _VERSION -DESCRIPTOR.message_types_by_name['LinRead'] = _LINREAD DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY DESCRIPTOR.message_types_by_name['NQuad'] = _NQUAD DESCRIPTOR.message_types_by_name['Value'] = _VALUE DESCRIPTOR.message_types_by_name['Facet'] = _FACET -DESCRIPTOR.message_types_by_name['SchemaNode'] = _SCHEMANODE DESCRIPTOR.message_types_by_name['LoginRequest'] = _LOGINREQUEST DESCRIPTOR.message_types_by_name['Jwt'] = _JWT _sym_db.RegisterFileDescriptor(DESCRIPTOR) -Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), dict( +Request = _reflection.GeneratedProtocolMessageType('Request', (_message.Message,), { - VarsEntry = _reflection.GeneratedProtocolMessageType('VarsEntry', (_message.Message,), dict( - DESCRIPTOR = _REQUEST_VARSENTRY, - __module__ = 'api_pb2' + 'VarsEntry' : _reflection.GeneratedProtocolMessageType('VarsEntry', (_message.Message,), { + 'DESCRIPTOR' : _REQUEST_VARSENTRY, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Request.VarsEntry) - )) + }) , - DESCRIPTOR = _REQUEST, - __module__ = 'api_pb2' + 'DESCRIPTOR' : _REQUEST, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Request) - )) + }) _sym_db.RegisterMessage(Request) _sym_db.RegisterMessage(Request.VarsEntry) -Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), dict( - DESCRIPTOR = _RESPONSE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.Response) - )) -_sym_db.RegisterMessage(Response) +Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), { -Assigned = _reflection.GeneratedProtocolMessageType('Assigned', (_message.Message,), dict( - - UidsEntry = _reflection.GeneratedProtocolMessageType('UidsEntry', (_message.Message,), dict( - DESCRIPTOR = _ASSIGNED_UIDSENTRY, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.Assigned.UidsEntry) - )) + 'UidsEntry' : _reflection.GeneratedProtocolMessageType('UidsEntry', (_message.Message,), { + 'DESCRIPTOR' : _RESPONSE_UIDSENTRY, + '__module__' : 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Response.UidsEntry) + }) , - DESCRIPTOR = _ASSIGNED, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.Assigned) - )) -_sym_db.RegisterMessage(Assigned) -_sym_db.RegisterMessage(Assigned.UidsEntry) + 'DESCRIPTOR' : _RESPONSE, + '__module__' : 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Response) + }) +_sym_db.RegisterMessage(Response) +_sym_db.RegisterMessage(Response.UidsEntry) -Mutation = _reflection.GeneratedProtocolMessageType('Mutation', (_message.Message,), dict( - DESCRIPTOR = _MUTATION, - __module__ = 'api_pb2' +Mutation = _reflection.GeneratedProtocolMessageType('Mutation', (_message.Message,), { + 'DESCRIPTOR' : _MUTATION, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Mutation) - )) + }) _sym_db.RegisterMessage(Mutation) -Operation = _reflection.GeneratedProtocolMessageType('Operation', (_message.Message,), dict( - DESCRIPTOR = _OPERATION, - __module__ = 'api_pb2' +Operation = _reflection.GeneratedProtocolMessageType('Operation', (_message.Message,), { + 'DESCRIPTOR' : _OPERATION, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Operation) - )) + }) _sym_db.RegisterMessage(Operation) -Payload = _reflection.GeneratedProtocolMessageType('Payload', (_message.Message,), dict( - DESCRIPTOR = _PAYLOAD, - __module__ = 'api_pb2' +Payload = _reflection.GeneratedProtocolMessageType('Payload', (_message.Message,), { + 'DESCRIPTOR' : _PAYLOAD, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Payload) - )) + }) _sym_db.RegisterMessage(Payload) -TxnContext = _reflection.GeneratedProtocolMessageType('TxnContext', (_message.Message,), dict( - DESCRIPTOR = _TXNCONTEXT, - __module__ = 'api_pb2' +TxnContext = _reflection.GeneratedProtocolMessageType('TxnContext', (_message.Message,), { + 'DESCRIPTOR' : _TXNCONTEXT, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.TxnContext) - )) + }) _sym_db.RegisterMessage(TxnContext) -Check = _reflection.GeneratedProtocolMessageType('Check', (_message.Message,), dict( - DESCRIPTOR = _CHECK, - __module__ = 'api_pb2' +Check = _reflection.GeneratedProtocolMessageType('Check', (_message.Message,), { + 'DESCRIPTOR' : _CHECK, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Check) - )) + }) _sym_db.RegisterMessage(Check) -Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), dict( - DESCRIPTOR = _VERSION, - __module__ = 'api_pb2' +Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), { + 'DESCRIPTOR' : _VERSION, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Version) - )) + }) _sym_db.RegisterMessage(Version) -LinRead = _reflection.GeneratedProtocolMessageType('LinRead', (_message.Message,), dict( - - IdsEntry = _reflection.GeneratedProtocolMessageType('IdsEntry', (_message.Message,), dict( - DESCRIPTOR = _LINREAD_IDSENTRY, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.LinRead.IdsEntry) - )) - , - DESCRIPTOR = _LINREAD, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.LinRead) - )) -_sym_db.RegisterMessage(LinRead) -_sym_db.RegisterMessage(LinRead.IdsEntry) - -Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), dict( - DESCRIPTOR = _LATENCY, - __module__ = 'api_pb2' +Latency = _reflection.GeneratedProtocolMessageType('Latency', (_message.Message,), { + 'DESCRIPTOR' : _LATENCY, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Latency) - )) + }) _sym_db.RegisterMessage(Latency) -NQuad = _reflection.GeneratedProtocolMessageType('NQuad', (_message.Message,), dict( - DESCRIPTOR = _NQUAD, - __module__ = 'api_pb2' +NQuad = _reflection.GeneratedProtocolMessageType('NQuad', (_message.Message,), { + 'DESCRIPTOR' : _NQUAD, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.NQuad) - )) + }) _sym_db.RegisterMessage(NQuad) -Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), dict( - DESCRIPTOR = _VALUE, - __module__ = 'api_pb2' +Value = _reflection.GeneratedProtocolMessageType('Value', (_message.Message,), { + 'DESCRIPTOR' : _VALUE, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Value) - )) + }) _sym_db.RegisterMessage(Value) -Facet = _reflection.GeneratedProtocolMessageType('Facet', (_message.Message,), dict( - DESCRIPTOR = _FACET, - __module__ = 'api_pb2' +Facet = _reflection.GeneratedProtocolMessageType('Facet', (_message.Message,), { + 'DESCRIPTOR' : _FACET, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Facet) - )) + }) _sym_db.RegisterMessage(Facet) -SchemaNode = _reflection.GeneratedProtocolMessageType('SchemaNode', (_message.Message,), dict( - DESCRIPTOR = _SCHEMANODE, - __module__ = 'api_pb2' - # @@protoc_insertion_point(class_scope:api.SchemaNode) - )) -_sym_db.RegisterMessage(SchemaNode) - -LoginRequest = _reflection.GeneratedProtocolMessageType('LoginRequest', (_message.Message,), dict( - DESCRIPTOR = _LOGINREQUEST, - __module__ = 'api_pb2' +LoginRequest = _reflection.GeneratedProtocolMessageType('LoginRequest', (_message.Message,), { + 'DESCRIPTOR' : _LOGINREQUEST, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.LoginRequest) - )) + }) _sym_db.RegisterMessage(LoginRequest) -Jwt = _reflection.GeneratedProtocolMessageType('Jwt', (_message.Message,), dict( - DESCRIPTOR = _JWT, - __module__ = 'api_pb2' +Jwt = _reflection.GeneratedProtocolMessageType('Jwt', (_message.Message,), { + 'DESCRIPTOR' : _JWT, + '__module__' : 'api_pb2' # @@protoc_insertion_point(class_scope:api.Jwt) - )) + }) _sym_db.RegisterMessage(Jwt) DESCRIPTOR._options = None _REQUEST_VARSENTRY._options = None -_RESPONSE.fields_by_name['schema']._options = None -_ASSIGNED_UIDSENTRY._options = None -_LINREAD_IDSENTRY._options = None +_RESPONSE_UIDSENTRY._options = None _DGRAPH = _descriptor.ServiceDescriptor( name='Dgraph', @@ -1417,8 +1138,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=2220, - serialized_end=2493, + serialized_start=1732, + serialized_end=1963, methods=[ _descriptor.MethodDescriptor( name='Login', @@ -1438,19 +1159,10 @@ output_type=_RESPONSE, serialized_options=None, ), - _descriptor.MethodDescriptor( - name='Mutate', - full_name='api.Dgraph.Mutate', - index=2, - containing_service=None, - input_type=_MUTATION, - output_type=_ASSIGNED, - serialized_options=None, - ), _descriptor.MethodDescriptor( name='Alter', full_name='api.Dgraph.Alter', - index=3, + index=2, containing_service=None, input_type=_OPERATION, output_type=_PAYLOAD, @@ -1459,7 +1171,7 @@ _descriptor.MethodDescriptor( name='CommitOrAbort', full_name='api.Dgraph.CommitOrAbort', - index=4, + index=3, containing_service=None, input_type=_TXNCONTEXT, output_type=_TXNCONTEXT, @@ -1468,7 +1180,7 @@ _descriptor.MethodDescriptor( name='CheckVersion', full_name='api.Dgraph.CheckVersion', - index=5, + index=4, containing_service=None, input_type=_CHECK, output_type=_VERSION, diff --git a/pydgraph/proto/api_pb2_grpc.py b/pydgraph/proto/api_pb2_grpc.py index c8f57620..1f70ead5 100644 --- a/pydgraph/proto/api_pb2_grpc.py +++ b/pydgraph/proto/api_pb2_grpc.py @@ -1,7 +1,7 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! import grpc -from . import api_pb2 as api__pb2 +import api_pb2 as api__pb2 class DgraphStub(object): @@ -24,11 +24,6 @@ def __init__(self, channel): request_serializer=api__pb2.Request.SerializeToString, response_deserializer=api__pb2.Response.FromString, ) - self.Mutate = channel.unary_unary( - '/api.Dgraph/Mutate', - request_serializer=api__pb2.Mutation.SerializeToString, - response_deserializer=api__pb2.Assigned.FromString, - ) self.Alter = channel.unary_unary( '/api.Dgraph/Alter', request_serializer=api__pb2.Operation.SerializeToString, @@ -64,13 +59,6 @@ def Query(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') - def Mutate(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - def Alter(self, request, context): # missing associated documentation comment in .proto file pass @@ -105,11 +93,6 @@ def add_DgraphServicer_to_server(servicer, server): request_deserializer=api__pb2.Request.FromString, response_serializer=api__pb2.Response.SerializeToString, ), - 'Mutate': grpc.unary_unary_rpc_method_handler( - servicer.Mutate, - request_deserializer=api__pb2.Mutation.FromString, - response_serializer=api__pb2.Assigned.SerializeToString, - ), 'Alter': grpc.unary_unary_rpc_method_handler( servicer.Alter, request_deserializer=api__pb2.Operation.FromString, diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 9d75c952..aeaa481f 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -56,81 +56,50 @@ def __init__(self, client, read_only=False, best_effort=False): self._read_only = read_only self._best_effort = best_effort - def sequencing(self, sequencing): - """Sets sequencing.""" - # This method is obsolete since sequencing is no longer used. This - # method is being kept for backwards-compatibility. - pass - - def query(self, query, variables=None, timeout=None, metadata=None, - credentials=None): - """Adds a query operation to the transaction.""" - new_metadata = self._dg.add_login_metadata(metadata) - req = self._common_query(query, variables=variables) - try: - res = self._dc.query(req, timeout=timeout, - metadata=new_metadata, - credentials=credentials) - except Exception as error: - if util.is_jwt_expired(error): - self._dg.retry_login() - new_metadata = self._dg.add_login_metadata(metadata) - res = self._dc.query(req, timeout=timeout, - metadata=new_metadata, - credentials=credentials) - else: - raise error - - self.merge_context(res.txn) - return res + def query(self, query, variables=None, timeout=None, metadata=None, credentials=None): + """Executes a query operation.""" + req = self.create_request(query=query, variables=variables) + return self.do_request(req, timeout=timeout, metadata=metadata, credentials=credentials) - def _common_query(self, query, variables=None): + def mutate(self, mutation=None, set_obj=None, del_obj=None, + set_nquads=None, del_nquads=None, cond=None, commit_now=None, + timeout=None, metadata=None, credentials=None): + """Executes a mutate operation.""" + mutation = self.create_mutation(mutation, set_obj, del_obj, set_nquads, del_nquads, cond) + commit_now = commit_now or mutation.commit_now + req = self.create_request(mutations=[mutation], commit_now=commit_now) + return self.do_request(req, timeout=timeout, metadata=metadata, credentials=credentials) + + def do_request(self, request, timeout=None, metadata=None, credentials=None): + """Executes a query/mutate operation on the server.""" if self._finished: - raise Exception( - 'Transaction has already been committed or discarded') - - req = api.Request(query=query, start_ts=self._ctx.start_ts, - read_only=self._read_only, best_effort=self._best_effort) - if variables is not None: - for key, value in variables.items(): - if util.is_string(key) and util.is_string(value): - req.vars[key] = value - else: - raise Exception( - 'Values and keys in variable map must be strings') - - return req + raise Exception('Transaction has already been committed or discarded') - def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, - del_nquads=None, query=None, commit_now=None, ignore_index_conflict=None, - timeout=None, metadata=None, credentials=None): - """Adds a mutate operation to the transaction.""" - mutation = self._common_mutate( - mutation=mutation, set_obj=set_obj, del_obj=del_obj, - set_nquads=set_nquads, del_nquads=del_nquads, query=query, - commit_now=commit_now, ignore_index_conflict=ignore_index_conflict) + if len(request.mutations) > 0: + if self._read_only: + raise Exception('Readonly transaction cannot run mutations') + self._mutated = True new_metadata = self._dg.add_login_metadata(metadata) - mutate_error = None - + query_error = None try: - assigned = self._dc.mutate(mutation, timeout=timeout, - metadata=new_metadata, - credentials=credentials) + response = self._dc.query(request, timeout=timeout, + metadata=new_metadata, + credentials=credentials) except Exception as error: if util.is_jwt_expired(error): self._dg.retry_login() new_metadata = self._dg.add_login_metadata(metadata) try: - assigned = self._dc.mutate(mutation, timeout=timeout, - metadata=new_metadata, - credentials=credentials) + response = self._dc.query(request, timeout=timeout, + metadata=new_metadata, + credentials=credentials) except Exception as error: - mutate_error = error + query_error = error else: - mutate_error = error + query_error = error - if mutate_error is not None: + if query_error is not None: try: self.discard(timeout=timeout, metadata=metadata, credentials=credentials) @@ -138,23 +107,16 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, # Ignore error - user should see the original error. pass - self._common_except_mutate(mutate_error) + self._common_except_mutate(query_error) - if mutation.commit_now: + if request.commit_now: self._finished = True - self.merge_context(assigned.context) - return assigned + self.merge_context(response.txn) + return response - def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, - set_nquads=None, del_nquads=None, query=None, - commit_now=None, ignore_index_conflict=None): - if self._read_only: - raise Exception( - 'Readonly transaction cannot run mutations or be committed') - if self._finished: - raise Exception( - 'Transaction has already been committed or discarded') + def create_mutation(self, mutation=None, set_obj=None, del_obj=None, + set_nquads=None, del_nquads=None, cond=None): if not mutation: mutation = api.Mutation() if set_obj: @@ -165,17 +127,28 @@ def _common_mutate(self, mutation=None, set_obj=None, del_obj=None, mutation.set_nquads = set_nquads.encode('utf8') if del_nquads: mutation.del_nquads = del_nquads.encode('utf8') - if query: - mutation.query = query.encode('utf8') - if commit_now: - mutation.commit_now = True - if ignore_index_conflict: - mutation.ignore_index_conflict = True - - self._mutated = True - mutation.start_ts = self._ctx.start_ts + if cond: + mutation.cond = cond.encode('utf8') return mutation + def create_request(self, query=None, variables=None, mutations=None, commit_now=None): + """Creates a request object""" + request = api.Request(start_ts = self._ctx.start_ts, commit_now=commit_now, + read_only=self._read_only, best_effort=self._best_effort) + + if variables is not None: + for key, value in variables.items(): + if util.is_string(key) and util.is_string(value): + request.vars[key] = value + else: + raise Exception('Values and keys in variable map must be strings') + if query: + request.query = query.encode('utf8') + if mutations: + for mutation in mutations: + request.mutations.append(mutation) + return request + @staticmethod def _common_except_mutate(error): if isinstance(error, grpc._channel._Rendezvous): diff --git a/tests/test_txn.py b/tests/test_txn.py index 7bcd60a4..9e0ce3c5 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -33,10 +33,10 @@ def setUp(self): def test_query_after_commit(self): txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn.commit() @@ -52,8 +52,8 @@ def test_query_after_commit(self): def test_mutate_after_commit(self): txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') txn.commit() @@ -62,10 +62,10 @@ def test_mutate_after_commit(self): def test_commit_now(self): txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}, commit_now=True) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}, commit_now=True) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid self.assertRaises(Exception, txn.commit) @@ -80,11 +80,11 @@ def test_commit_now(self): def test_discard(self): txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') txn.commit() - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn2 = self.client.txn() @@ -113,10 +113,10 @@ def test_read_at_start_ts(self): """Tests read after write when readTs == startTs""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid query = """{{ @@ -131,10 +131,10 @@ def test_read_before_start_ts(self): """Tests read before write when readTs < startTs""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid query = """{{ @@ -150,10 +150,10 @@ def test_read_after_start_ts(self): """Tests read after committing a write when readTs > startTs""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn.commit() @@ -171,10 +171,10 @@ def test_read_before_and_after_start_ts(self): readTs1 < startTs and readTs2 > startTs""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn.commit() @@ -204,10 +204,10 @@ def test_read_from_new_client(self): """Tests committed reads from a new client with startTs == 0.""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn.commit() @@ -223,8 +223,8 @@ def test_read_from_new_client(self): self.assertTrue(resp2.txn.start_ts > 0) txn2 = client2.txn() - assigned = txn2.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) - self.assertTrue(assigned.context.start_ts > 0) + response = txn2.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) + self.assertTrue(response.txn.start_ts > 0) txn2.commit() resp = self.client.txn(read_only=True).query(query) @@ -266,7 +266,7 @@ def test_best_effort_txn(self): query = '{ me(func: has(name)) {name} }' with self.assertRaises(Exception): - txn = self.client.txn(read_only=False, best_effort=True) + self.client.txn(read_only=False, best_effort=True) txn = self.client.txn(read_only=True, best_effort=True) resp = txn.query(query) @@ -283,10 +283,10 @@ def test_conflict(self): helper.drop_all(self.client) txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn2 = self.client.txn() @@ -310,10 +310,10 @@ def test_conflict_reverse_order(self): committed.""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn2 = self.client.txn() @@ -337,10 +337,10 @@ def test_conflict_reverse_order(self): def test_commit_conflict(self): txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn2 = self.client.txn() @@ -364,10 +364,10 @@ def test_commit_conflict(self): def test_mutate_conflict(self): txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid txn2 = self.client.txn() @@ -378,37 +378,6 @@ def test_mutate_conflict(self): _ = txn.mutate(set_obj={'uid': uid, 'name': 'Manish2'}) self.assertRaises(pydgraph.AbortedError, txn.commit) - def test_conflict_ignore(self): - """Tests a mutation with ignore index conflict.""" - - helper.set_schema(self.client, 'name: string @index(fulltext) .') - - txn = self.client.txn() - assigned1 = txn.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) - self.assertEqual(1, len(assigned1.uids), 'Nothing was assigned') - - for _, uid in assigned1.uids.items(): - uid1 = uid - - txn2 = self.client.txn() - assigned2 = txn2.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) - self.assertEqual(1, len(assigned2.uids), 'Nothing was assigned') - - for _, uid in assigned2.uids.items(): - uid2 = uid - - txn.commit() - txn2.commit() - - query = """{ - me(func: eq(name, "Manish")) { - uid - } - }""" - - resp = self.client.txn(read_only=True).query(query) - self.assertEqual([{'uid': uid1}, {'uid': uid2}], json.loads(resp.json).get('me')) - def test_read_index_key_same_txn(self): """Tests reading an indexed field within a transaction. The read should return the results from before any writes of the same @@ -418,10 +387,10 @@ def test_read_index_key_same_txn(self): helper.set_schema(self.client, 'name: string @index(exact) .') txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish'}, ignore_index_conflict=True) - self.assertEqual(1, len(assigned.uids), 'Nothing was assigned') + response = txn.mutate(set_obj={'name': 'Manish'}) + self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in assigned.uids.items(): + for _, uid in response.uids.items(): uid = uid query = """{ @@ -465,23 +434,23 @@ def test_sp_star(self): """Tests a Subject Predicate Star query.""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish', 'friend': [{'name': 'Jan'}]}) - uid1 = assigned.uids['blank-0'] - self.assertEqual(2, len(assigned.uids), 'Expected 2 nodes to be created') + response = txn.mutate(set_obj={'uid': '_:manish', 'name': 'Manish', 'friend': [{'name': 'Jan'}]}) + uid1 = response.uids['manish'] + self.assertEqual(2, len(response.uids), 'Expected 2 nodes to be created') txn.commit() txn2 = self.client.txn() - assigned2 = txn2.mutate(del_obj={'uid': uid1, 'friend': None}) - self.assertEqual(0, len(assigned2.uids)) + response2 = txn2.mutate(del_obj={'uid': uid1, 'friend': None}) + self.assertEqual(0, len(response2.uids)) - assigned3 = txn2.mutate(set_obj={ + response3 = txn2.mutate(set_obj={ 'uid': uid1, 'name': 'Manish', - 'friend': [{'name': 'Jan2'}] + 'friend': [{'uid': '_:jan2', 'name': 'Jan2'}] }) - self.assertEqual(1, len(assigned3.uids)) - uid2 = assigned3.uids['blank-0'] + self.assertEqual(1, len(response3.uids)) + uid2 = response3.uids['jan2'] query = """{{ me(func: uid("{uid:s}")) {{ @@ -503,9 +472,9 @@ def test_sp_star2(self): """Second test of Subject Predicate Star""" txn = self.client.txn() - assigned = txn.mutate(set_obj={'name': 'Manish', 'friend': [{'name': 'Jan'}]}) - self.assertEqual(2, len(assigned.uids)) - uid1, uid2 = assigned.uids['blank-0'], assigned.uids['blank-1'] + response = txn.mutate(set_obj={'uid': '_:manish', 'name': 'Manish', 'friend': [{'uid': '_:jan', 'name': 'Jan'}]}) + self.assertEqual(2, len(response.uids)) + uid1, uid2 = response.uids['manish'], response.uids['jan'] query = """{{ me(func: uid("{uid:s}")) {{ @@ -530,13 +499,13 @@ def test_sp_star2(self): self.assertEqual([{'uid': uid1}], json.loads(resp.json).get('me')) # add an edge to Jan2 - assigned2 = txn.mutate(set_obj={ + response2 = txn.mutate(set_obj={ 'uid': uid1, 'name': 'Manish', - 'friend': [{'name': 'Jan2'}] + 'friend': [{'uid': '_:jan2', 'name': 'Jan2'}] }) - self.assertEqual(1, len(assigned2.uids)) - uid2 = assigned2.uids['blank-0'] + self.assertEqual(1, len(response2.uids)) + uid2 = response2.uids['jan2'] resp = txn.query(query) self.assertEqual([{ diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index 2d5d1a29..57d25fc4 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -25,6 +25,7 @@ import multiprocessing.dummy as mpd import pydgraph +from pydgraph.proto import api_pb2 as api from . import helper @@ -126,11 +127,15 @@ def upsert_account(addr, account, success_ctr, retry_ctr): uid(u) "{last}" . uid(u) "{age}"^^ . """.format(**account) - created = txn.mutate(query=query, set_nquads=nquads) + mutation = txn.create_mutation(set_nquads=nquads) + request = txn.create_request(query=query, mutations=[mutation], commit_now=True) + txn.do_request(request) updatequads = 'uid(u) "{0:d}"^^ .'.format(int(time.time())) - txn.mutate(query=query, set_nquads=updatequads) - txn.commit() + txn = client.txn() + mutation = txn.create_mutation(set_nquads=updatequads) + request = txn.create_request(query=query, mutations=[mutation], commit_now=True) + txn.do_request(request) with success_ctr.get_lock(): success_ctr.value += 1 From 72af080ce0ee43363ba88553e37cd80766a51c30 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Wed, 4 Sep 2019 10:15:05 -0700 Subject: [PATCH 145/435] Fix misc issues. (#78) - Fix the generated grpc file since the generated import statements don't work correctly with python 3. - Also use extend instead of calling append on every list item. --- pydgraph/proto/api_pb2_grpc.py | 2 +- pydgraph/txn.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pydgraph/proto/api_pb2_grpc.py b/pydgraph/proto/api_pb2_grpc.py index 1f70ead5..42b7159c 100644 --- a/pydgraph/proto/api_pb2_grpc.py +++ b/pydgraph/proto/api_pb2_grpc.py @@ -1,7 +1,7 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! import grpc -import api_pb2 as api__pb2 +from . import api_pb2 as api__pb2 class DgraphStub(object): diff --git a/pydgraph/txn.py b/pydgraph/txn.py index aeaa481f..4d486f61 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -145,8 +145,7 @@ def create_request(self, query=None, variables=None, mutations=None, commit_now= if query: request.query = query.encode('utf8') if mutations: - for mutation in mutations: - request.mutations.append(mutation) + request.mutations.extend(mutations) return request @staticmethod From d04003f90a400651eddafe99a22856f14582fe80 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Thu, 5 Sep 2019 16:16:15 +0530 Subject: [PATCH 146/435] Add docs for upsert block (#79) --- README.md | 134 +++++++++++++++++------------- examples/simple/README.md | 76 +++++------------ examples/simple/simple.py | 95 +++++++-------------- examples/tls/.gitignore | 6 -- examples/tls/README.md | 119 -------------------------- examples/tls/mutualtls_example.py | 64 -------------- tests/test_upsert_block.py | 1 - 7 files changed, 127 insertions(+), 368 deletions(-) delete mode 100644 examples/tls/.gitignore delete mode 100644 examples/tls/README.md delete mode 100644 examples/tls/mutualtls_example.py diff --git a/README.md b/README.md index c3708386..d31867cc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# pydgraph [![Build Status](https://img.shields.io/travis/dgraph-io/pydgraph/master.svg?style=flat)](https://travis-ci.org/dgraph-io/pydgraph) [![Coverage Status](https://img.shields.io/coveralls/github/dgraph-io/pydgraph/master.svg?style=flat)](https://coveralls.io/github/dgraph-io/pydgraph?branch=master) +# pydgraph Official Dgraph client implementation for Python (Python >= v2.7 and >= v3.5), using [grpc]. @@ -19,13 +19,13 @@ and understand how to run and work with Dgraph. - [Install](#install) - [Quickstart](#quickstart) - [Using a client](#using-a-client) - - [Create a client](#create-a-client) - - [Alter the database](#alter-the-database) - - [Create a transaction](#create-a-transaction) - - [Run a mutation](#run-a-mutation) - - [Run a query](#run-a-query) - - [Commit a transaction](#commit-a-transaction) - - [Cleanup Resources](#cleanup-resources) + - [Creating a client](#creating-a-client) + - [Altering the database](#altering-the-database) + - [Creating a transaction](#creating-a-transaction) + - [Running a mutation](#running-a-mutation) + - [Running a query](#running-a-query) + - [Committing a transaction](#committing-a-transaction) + - [Cleaning up Resources](#cleaning-up-resources) - [Setting Metadata Headers](#setting-metadata-headers) - [Examples](#examples) - [Development](#development) @@ -50,9 +50,9 @@ instructions in the README of that project. ## Using a client -### Create a client +### Creating a client -A `DgraphClient` object can be initialised by passing it a list of +You can initialize a `DgraphClient` object by passing it a list of `DgraphClientStub` clients as variadic arguments. Connecting to multiple Dgraph servers in the same cluster allows for better distribution of workload. @@ -65,7 +65,7 @@ client_stub = pydgraph.DgraphClientStub('localhost:9080') client = pydgraph.DgraphClient(client_stub) ``` -### Alter the database +### Altering the database To set the schema, create an `Operation` object, set the schema and pass it to `DgraphClient#alter(Operation)` method. @@ -81,14 +81,13 @@ Drop all is useful if you wish to discard all the data, and start from a clean slate, without bringing the instance down. ```python -# Drop all data including schema from the Dgraph instance. This is useful -# for small examples such as this, since it puts Dgraph into a clean -# state. +# Drop all data including schema from the Dgraph instance. This is a useful +# for small examples such as this since it puts Dgraph into a clean state. op = pydgraph.Operation(drop_all=True) client.alter(op) ``` -### Create a transaction +### Creating a transaction To create a transaction, call `DgraphClient#txn()` method, which returns a new `Txn` object. This operation incurs no network overhead. @@ -127,12 +126,11 @@ faster than normal queries because they bypass the normal consensus protocol. For this same reason, best-effort queries cannot guarantee to return the latest data. Best-effort queries are only supported by read-only transactions. -### Run a mutation +### Running a mutation `Txn#mutate(mu=Mutation)` runs a mutation. It takes in a `Mutation` object, which provides two main ways to set data: JSON and RDF N-Quad. You can choose -whichever way is convenient. Most users won't need to create a `Mutation` -object themselves. +whichever way is convenient. `Txn#mutate()` provides convenience keyword arguments `set_obj` and `del_obj` for setting JSON values and `set_nquads` and `del_nquads` for setting N-Quad @@ -160,53 +158,49 @@ txn.mutate(set_obj=p) ```python # Delete data. -query1 = """query all($a: string) +query = """query all($a: string) { - all(func: eq(name, $a)) + all(func: eq(name, $a)) { uid - } + } }""" - -variables1 = {'$a': 'Bob'} -res1 = txn.query(query1, variables=variables1) +variables = {'$a': 'Bob'} -ppl1 = json.loads(res1.json) +res = txn.query(query, variables=variables) +ppl = json.loads(res.json) -#For mutation to delete node, use this: -txn.mutate(del_obj= person) +# For a mutation to delete a node, use this: +txn.mutate(del_obj=person) ``` -For a more complete example with multiple fields and relationships, look at the +For a complete example with multiple fields and relationships, look at the [simple] project in the `examples` folder. Sometimes, you only want to commit a mutation, without querying anything further. In such cases, you can set the keyword argument `commit_now=True` to indicate that the mutation must be immediately committed. -### Run a query +A mutation can be executed using `txn.do_request` as well. + +```python +mutation = txn.create_mutation(set_nquads='_:alice "Alice" .') +request = txn.create_request(mutations=[mutation], commit_now=True) +txn.do_request(request) +``` + +### Running a query You can run a query by calling `Txn#query(string)`. You will need to pass in a GraphQL+- query string. If you want to pass an additional dictionary of any variables that you might want to set in the query, call `Txn#query(string, variables=d)` with the variables dictionary `d`. -The response would contain the field `json`, which returns the response -JSON. - -Let’s run the following query with a variable $a: - -```console -query all($a: string) { - all(func: eq(name, $a)) - { - name - } -} -``` +The response would contain the field `json`, which returns the response JSON. -Run the query, deserialize the result from JSON and print it out: +Let’s run a query with a variable `$a`, deserialize the result from JSON and +print it out: ```python # Run query. @@ -219,10 +213,11 @@ query = """query all($a: string) { variables = {'$a': 'Alice'} res = txn.query(query, variables=variables) + # If not doing a mutation in the same transaction, simply use: # res = client.txn(read_only=True).query(query, variables=variables) -ppl = json.loads(res.json); +ppl = json.loads(res.json) # Print results. print('Number of people named "Alice": {}'.format(len(ppl['all']))) @@ -237,18 +232,46 @@ Number of people named "Alice": 1 Alice ``` -### Commit a transaction +You can also use `txn.do_request` function to run the query. + +```python +request = txn.create_request(query=query) +txn.do_request(request) +``` + +### Running an Upsert: Query + Mutation + +The `txn.do_request` function allows you to run upserts consisting of one query and +one mutation. Query variables could be defined and can then be used in the mutation. + +To know more about upsert, we highly recommend going through the docs at +https://docs.dgraph.io/mutations/#upsert-block. + +```python +query = """{ + u as var(func: eq(name, "Alice")) +}""" +nquad = """ + uid(u) "25" . +""" +cond = "@if(eq(len(u), 1))" +mutation = txn.create_mutation(set_nquads=nquad, cond=cond) +request = txn.create_request(query=query, mutations=[mutation], commit_now=True) +txn.do_request(request) +``` + +### Committing a transaction A transaction can be committed using the `Txn#commit()` method. If your transaction consisted solely of calls to `Txn#query` or `Txn#queryWithVars`, and no calls to `Txn#mutate`, then calling `Txn#commit()` is not necessary. -An error will be raised if other transactions running concurrently modify the same -data that was modified in this transaction. It is up to the user to retry -transactions when they fail. +An error is raised if another transaction(s) modify the same data concurrently that was +modified in the current transaction. It is up to the user to retry transactions +when they fail. ```python -txn = client.txn(); +txn = client.txn() try: # ... # Perform any number of queries and mutations @@ -266,9 +289,9 @@ finally: txn.discard() ``` -### Cleanup Resources +### Cleaning Up Resources -To cleanup resources, you have to call `DgraphClientStub#close()` individually for +To clean up resources, you have to call `DgraphClientStub#close()` individually for all the instances of `DgraphClientStub`. ```python @@ -285,7 +308,7 @@ client = pydgraph.DgraphClient(stub1, stub2) # Use client # ... -# Cleanup resources by closing all client stubs. +# Clean up resources by closing all client stubs. stub1.close() stub2.close() ``` @@ -295,7 +318,7 @@ Metadata headers such as authentication tokens can be set through the metadata o ```python # The following piece of code shows how one can set metadata with # auth-token, to allow Alter operation, if the server requires it. -# metadata is a list of arbritary key-value pairs. +# metadata is a list of arbitrary key-value pairs. metadata = [("auth-token", "the-auth-token-value")] dg.alter(op, metadata=metadata) ``` @@ -303,9 +326,6 @@ dg.alter(op, metadata=metadata) ## Examples - [simple][]: Quickstart example of using pydgraph. -- [tls][]: Example of using pydgraph with a Dgraph cluster secured with TLS. - -[tls]: ./examples/tls ## Development @@ -330,7 +350,7 @@ python scripts/protogen.py ### Running tests -To run the tests in your local machine you can run the script +To run the tests in your local machine, you can run the script `scripts/local-tests.sh`. This script assumes Dgraph and dgo (Go client) are already built on the local machine. The script will take care of bringing up a Dgraph cluster and bringing it down after the tests are executed. The script diff --git a/examples/simple/README.md b/examples/simple/README.md index 1a78451e..5db089dc 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -1,78 +1,42 @@ -# Simple example project +# Simple Example Project -Simple project demonstrating the use of [pydgraph], the official python client -for Dgraph. +Simple project demonstrating the use of [pydgraph], the official python client for Dgraph. [pydgraph]:https://github.com/dgraph-io/pydgraph ## Running -### Start Dgraph server +### Start Dgraph -You will need to install [Dgraph v1.0.10 or above][releases] and run it. +Store the following content in `docker-compose.yml`. Then, run `docker-compose up` to +set up the Dgraph cluster: -[releases]: https://github.com/dgraph-io/dgraph/releases - -You can run the commands below to start a clean Dgraph server every time, for testing -and exploration. - -First, create two separate directories for `dgraph zero` and `dgraph alpha`. - -```sh -mkdir -p dgraphdata/zero dgraphdata/data ``` - -Then start `dgraph zero`: - -```sh -cd dgraphdata/zero -rm -r zw; dgraph zero -``` - -Finally, start the `dgraph alpha`: - -```sh -cd dgraphdata/data -rm -r p w; dgraph server --lru_mb=1024 --zero localhost:5080 +version: "3.2" +services: + zero: + image: dgraph/dgraph:v1.1.0 + restart: on-failure + command: dgraph zero --my=zero:5080 + server: + image: dgraph/dgraph:v1.1.0 + ports: + - 8080:8080 + - 9080:9080 + restart: on-failure + command: dgraph alpha --my=server:7080 --lru_mb=2048 --zero=zero:5080 ``` -For more configuration options and other details, refer to -[docs.dgraph.io](https://docs.dgraph.io) - -## Install dependencies +## Install the Dependencies ```sh pip install -r requirements.txt ``` -## Run the sample code +## Run the Sample Code ```sh python simple.py ``` -Your output should look something like this (uid values may be different): - -```console -Created person named "Alice" with uid = 0x7569 - -All created nodes (map from blank node names to uids): -blank-0: 0x7569 -blank-1: 0x756a -blank-2: 0x756b -blank-3: 0x756c - -Number of people named "Alice": 1 -{ uid: '0x7569', - name: 'Alice', - age: 26, - married: true, - loc: { type: 'Point', coordinates: [ 1.1, 2 ] }, - dob: '1980-02-01T17:30:00Z', - friend: [ { name: 'Bob', age: 24 }, { name: 'Charlie', age: 29 } ], - school: [ { name: 'Crown Public School' } ] } - -DONE! -``` - You can explore the source code in the `simple.py` file. diff --git a/examples/simple/simple.py b/examples/simple/simple.py index e174ae41..c25fdc13 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -6,7 +6,7 @@ # Create a client stub. def create_client_stub(): - return pydgraph.DgraphClientStub('localhost:9180') + return pydgraph.DgraphClientStub('localhost:9080') # Create a client. @@ -53,11 +53,6 @@ def create_data(client): 'uid': '_:bob', 'name': 'Bob', 'age': 24, - }, - { - 'uid': '_:charlie', - 'name': 'Charlie', - 'age': 29, } ], 'school': [ @@ -75,16 +70,11 @@ def create_data(client): # Get uid of the outermost object (person named "Alice"). # response.uids returns a map from blank node names to uids. - print('Created person named "Alice" with uid = {}\n'.format(response.uids['alice'])) + print('Created person named "Alice" with uid = {}'.format(response.uids['alice'])) - print('All created nodes (map from node names to uids):') - for uid in response.uids: - print('{} => {}'.format(uid, response.uids[uid])) finally: - # Clean up. Calling this after txn.commit() is a no-op - # and hence safe. + # Clean up. Calling this after txn.commit() is a no-op and hence safe. txn.discard() - print('\n') # Deleting a data @@ -92,10 +82,8 @@ def delete_data(client): # Create a new transaction. txn = client.txn() try: - query1 = """query all($a: string) - { - all(func: eq(name, $a)) - { + query1 = """query all($a: string) { + all(func: eq(name, $a)) { uid } }""" @@ -103,16 +91,9 @@ def delete_data(client): res1 = client.txn(read_only=True).query(query1, variables=variables1) ppl1 = json.loads(res1.json) for person in ppl1['all']: - print('Query to find Uid for Bob :') - print(query1) - print('\n') - print("Bob's UID : ") - print(person) - print('\n') - print('Bob deleted') - print('\n') - - txn.mutate(del_obj= person) + print("Bob's UID: " + person['uid']) + print(txn.mutate(del_obj=person)) + print('Bob deleted') txn.commit() finally: @@ -120,7 +101,7 @@ def delete_data(client): # Query for data. -def query_data(client): +def query_alice(client): # Run query. query = """query all($a: string) { all(func: eq(name, $a)) { @@ -146,49 +127,33 @@ def query_data(client): # Print results. print('Number of people named "Alice": {}'.format(len(ppl['all']))) - print('\n') - for person in ppl['all']: - print('Query for Alice : \n' +query) - print('\n') - print('Result :') - print(person) - print('\n') # Query to check for deleted node -def query_data01(client): - query01 = """query all($b: string) - { all(func: eq(name, $b)) - { uid, - name, +def query_bob(client): + query = """query all($b: string) { + all(func: eq(name, $b)) { + uid + name age - friend - { - uid, - name, + friend { + uid + name age } - ~friend - { - uid, - name, + ~friend { + uid + name age } } }""" - variables01 = {'$b': 'Bob'} - res01 = client.txn(read_only=True).query(query01, variables=variables01) - ppl01 = json.loads(res01.json) - - print('Number of people named "Bob": {}'.format(len(ppl01['all']))) - print('\n') - for person in ppl01['all']: - print('Query for Bob :\n' + query01) - print('\n') - print('Result :') - print(person) - print('\n') + variables = {'$b': 'Bob'} + res = client.txn(read_only=True).query(query, variables=variables) + ppl = json.loads(res.json) + # Print results. + print('Number of people named "Bob": {}'.format(len(ppl['all']))) def main(): client_stub = create_client_stub() @@ -196,11 +161,11 @@ def main(): drop_all(client) set_schema(client) create_data(client) - query_data(client) # query for Alice - query_data01(client) # query for Bob + query_alice(client) # query for Alice + query_bob(client) # query for Bob delete_data(client) # delete Bob - query_data(client) # query for Alice - query_data01(client) # query for Bob + query_alice(client) # query for Alice + query_bob(client) # query for Bob # Close the client stub. client_stub.close() @@ -209,6 +174,6 @@ def main(): if __name__ == '__main__': try: main() - print('\nDONE!') + print('DONE!') except Exception as e: print('Error: {}'.format(e)) diff --git a/examples/tls/.gitignore b/examples/tls/.gitignore deleted file mode 100644 index 65148c12..00000000 --- a/examples/tls/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -tls/ - -p/ -w/ -zw/ - diff --git a/examples/tls/README.md b/examples/tls/README.md deleted file mode 100644 index 959eccad..00000000 --- a/examples/tls/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Mutual TLS example project - -Project demonstrating the use of pydgraph and Dgraph set up with client-server -mutual TLS. The following guide shows how to set up a single-group two-node -cluster (1 Dgraph Zero and 1 Dgraph Alpha) configured with mutual TLS. - -## Running - -### Install Dgraph - -You will need to [install Dgraph v1.0.0 or -above](https://docs.dgraph.io/get-started/#step-1-install-dgraph). - -A quick-start installation script is available for Linux and Mac: - -```sh -curl -sSf https://get.dgraph.io | bash -``` - -### Create TLS certificates - -Dgraph provides a `dgraph cert` tool to create and manage self-signed -server and client certificates using a generated Dgraph Root CA. See the [TLS -documentation](https://docs.dgraph.io/deploy/#tls-configuration) for more -information. - -Create the root CA. All certificates and keys are created in the `tls` directory. - -```sh -dgraph cert -``` - -Now create the Alpha server certificate (node.crt) and key (node.key) and client -certificate (client.user.crt) key (client.user.key). - -```sh -dgraph cert -n localhost -``` - -```sh -dgraph cert -c user -``` - -The following files should now be in the `tls` directory: - -```sh -$ ls tls -ca.crt ca.key client.user.crt client.user.key node.crt node.key -``` - -Using `dgraph cert ls` provides more details about each file. For instance, it -shows that the `node.crt` is valid only for the host named `localhost` and the -corresponding file permissions. - -```sh -$ dgraph cert ls --rw-r--r-- ca.crt - Dgraph Root CA certificate - Issuer: Dgraph Labs, Inc. - S/N: 3dfb9c54929d703b -Expiration: 19 Feb 29 00:57 UTC - MD5 hash: C82CF5D4C344668E34A61D590D6A4B77 - --r-------- ca.key - Dgraph Root CA key - MD5 hash: C82CF5D4C344668E34A61D590D6A4B77 - --rw-r--r-- client.user.crt - Dgraph client certificate: user - Issuer: Dgraph Labs, Inc. - CA Verify: PASSED - S/N: 5991417e75ba14c7 -Expiration: 21 Feb 24 01:04 UTC - MD5 hash: BA35D4ABD8DFF1ED137E8D8E5D921D06 - --rw------- client.user.key - Dgraph Client key - MD5 hash: BA35D4ABD8DFF1ED137E8D8E5D921D06 - --rw-r--r-- node.crt - Dgraph Node certificate - Issuer: Dgraph Labs, Inc. - CA Verify: PASSED - S/N: 51d53048b6845d8c -Expiration: 21 Feb 24 01:00 UTC - Hosts: localhost - MD5 hash: 5D71F59AAEE294F1CFDA9E3232761018 - --rw------- node.key - Dgraph Node key - MD5 hash: 5D71F59AAEE294F1CFDA9E3232761018 -``` - -### Start Dgraph cluster - -Start Dgraph Zero: - -```sh -dgraph zero -``` - -Start Dgraph Alpha with TLS options. `REQUIREANDVERIFY` sets mutual TLS (server authentication and client authentication): - -```sh -dgraph alpha --lru_mb=1024 --zero=localhost:5080 --tls_dir=./tls --tls_client_auth=REQUIREANDVERIFY -``` - -### Run example - -Ensure the pydgraph client is installed: - -```sh -pip install pydgraph -``` - -Then run the example, which connects to the Dgraph Alpha via TLS using the -generated root CA cert and client cert and key in the `tls` directory. The -example first deletes all the data in Dgraph via drop all, updates the schema, -runs a mutation, and finally runs a query. The result of the query is printed -out. - -```sh -python mutualtls_example.py -``` - diff --git a/examples/tls/mutualtls_example.py b/examples/tls/mutualtls_example.py deleted file mode 100644 index ac045605..00000000 --- a/examples/tls/mutualtls_example.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python - -import pydgraph -import grpc - -def create_client(addr='localhost:9080'): - # Read certs - with open('./tls/ca.crt', 'rb') as f: - root_ca_cert = f.read() - with open('./tls/client.user.key', 'rb') as f: - client_cert_key = f.read() - with open('./tls/client.user.crt', 'rb') as f: - client_cert = f.read() - - # Connect to Dgraph via gRPC with mutual TLS. - creds = grpc.ssl_channel_credentials(root_certificates=root_ca_cert, - private_key=client_cert_key, - certificate_chain=client_cert) - client_stub = pydgraph.DgraphClientStub(addr, credentials=creds) - return pydgraph.DgraphClient(client_stub) - -def main(): - client = create_client('localhost:9080') - - # Drop all - client.alter(pydgraph.Operation(drop_all=True)) - - # Update schema - schema = ''' -name: string @index(exact) . -description: string . -url: string . -''' - op = pydgraph.Operation(schema=schema) - client.alter(op) - - # Mutate - dgraph = { - "name": "Dgraph", - "description": "Scalable, Distributed, Low Latency Graph Database", - "url": "https://dgraph.io" - } - txn = client.txn() - try: - txn.mutate(set_obj=dgraph) - txn.commit() - finally: - txn.discard() - - # Query - res = client.txn(read_only=True).query(''' -query dgraph($name: string) { - data(func: eq(name, $name)) { - uid - name - description - url - } -} -''', variables={"$name": "Dgraph"}) - print(res.json); - -if __name__ == '__main__': - main() diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index 57d25fc4..3eab9874 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -25,7 +25,6 @@ import multiprocessing.dummy as mpd import pydgraph -from pydgraph.proto import api_pb2 as api from . import helper From 50c9a5a4e11efeb2d122c6efe492432cfba020cf Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Thu, 5 Sep 2019 16:18:42 +0530 Subject: [PATCH 147/435] minor change --- examples/simple/simple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index c25fdc13..341b47f0 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -92,7 +92,7 @@ def delete_data(client): ppl1 = json.loads(res1.json) for person in ppl1['all']: print("Bob's UID: " + person['uid']) - print(txn.mutate(del_obj=person)) + txn.mutate(del_obj=person) print('Bob deleted') txn.commit() From df54c0715cbb19b335134a77a513fd79f203dab3 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Thu, 5 Sep 2019 16:18:49 +0530 Subject: [PATCH 148/435] Bump version to 2.0.0 --- CHANGELOG.md | 6 ++++++ pydgraph/meta.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a09da80..d5dfdd49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v2.0.0] - 2019-09-05 + +### Added +- Update internal grpc API to talk to dgraph v1.1.0 + ## [v1.2.0] - 2019-06-24 ### Added @@ -60,6 +65,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Full compatibility with Dgraph v1.0.0 [Unreleased]: https://github.com/dgraph-io/pydgraph/compare/v1.0.0...HEAD +[v2.0.0] https://github.com/dgraph-io/pydgraph/compare/v1.2.0...v2.0.0 [v1.2.0] https://github.com/dgraph-io/pydgraph/compare/v1.1.2...v1.2.0 [v1.1.2]: https://github.com/dgraph-io/pydgraph/compare/v1.1.1...v1.1.2 [v1.1.1]: https://github.com/dgraph-io/pydgraph/compare/v1.1...v1.1.1 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 582ff2e2..1bff63a7 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '1.2.0' +VERSION = '2.0.0' From bf256803bcbe2e03c57bf051375ea72f5bd41a5b Mon Sep 17 00:00:00 2001 From: robin-snt <53257569+robin-snt@users.noreply.github.com> Date: Fri, 6 Sep 2019 18:50:38 +0200 Subject: [PATCH 149/435] Catch pypandoc ModuleNotFoundError (#81) Python >= 3.6 doesn't receive ImportError, but instead ModuleNotFoundError. --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 62a40361..1cd4d2db 100755 --- a/setup.py +++ b/setup.py @@ -29,6 +29,13 @@ def convert_file(f, _): return open(f, 'r').read() +except ModuleNotFoundError as e: + # NOTE: error is thrown only for package build steps + if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv: + raise e + + def convert_file(f, _): + return open(f, 'r').read() from pydgraph.meta import VERSION From 246db1373ce0e78af35e82bd436755a363be3fd8 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 6 Sep 2019 10:33:04 -0700 Subject: [PATCH 150/435] Bump version to 2.0.1 (#83) --- CHANGELOG.md | 5 +++++ pydgraph/meta.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5dfdd49..47f1ced2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v2.0.1] - 2019-09-06 + +### Added +- Fix unhandled ModuleNotFoundError. + ## [v2.0.0] - 2019-09-05 ### Added diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 1bff63a7..27597d99 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '2.0.0' +VERSION = '2.0.1' From d439309ff5d62d14be150bcdf3289dffb6b63513 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 10 Sep 2019 10:27:46 -0700 Subject: [PATCH 151/435] Do not throw errors in build steps if pypandoc is not found. (#84) Throwing this error is causing issues for users that try to build packages that depend on pydgraph. See https://github.com/dgraph-io/pydgraph/issues/82 for more details. --- setup.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/setup.py b/setup.py index 1cd4d2db..65c9a7ee 100755 --- a/setup.py +++ b/setup.py @@ -23,17 +23,9 @@ try: from pypandoc import convert_file except ImportError as e: - # NOTE: error is thrown only for package build steps - if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv: - raise e - def convert_file(f, _): return open(f, 'r').read() except ModuleNotFoundError as e: - # NOTE: error is thrown only for package build steps - if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv: - raise e - def convert_file(f, _): return open(f, 'r').read() From 3928b22cec1e3c2a9bf93b686c387cda66f93aa6 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 10 Sep 2019 10:42:27 -0700 Subject: [PATCH 152/435] Bump version to 2.0.2 (#85) --- CHANGELOG.md | 5 +++++ pydgraph/meta.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f1ced2..05cf3f92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v2.0.2] - 2019-09-10 + +### Added +- Do not throw errors in build steps if pypandoc is not found. + ## [v2.0.1] - 2019-09-06 ### Added diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 27597d99..c346705c 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '2.0.1' +VERSION = '2.0.2' From a3b49ad5101230cb8ce21b14090fefe4b7e29b5c Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 12 Sep 2019 13:53:43 -0700 Subject: [PATCH 153/435] Add a note about version compatibility in the README. (#88) Versions of pydgraph >= 2 don't work with Dgraph 1.0.x and viceversa. This PR adds a note in the README. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d31867cc..56c63280 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,9 @@ Install using pip: pip install pydgraph ``` +**NOTE**: Dgraph 1.1.x and 1.0.x have incompatible APIs. 1.1.x is supported by +versions of pydgraph >=2 while 1.0.x is supported by versions <= 1.2.0. + ## Quickstart Build and run the [simple][] project in the `examples` folder, which From 84c1fd16d4cfab6edab7a2b8ac2aa03cec0107c6 Mon Sep 17 00:00:00 2001 From: Animesh Chandra Pathak Date: Tue, 17 Sep 2019 15:54:45 +0530 Subject: [PATCH 154/435] Add tests (#87) --- tests/test_acct_upsert.py | 59 +++++++- tests/test_acl.py | 145 +++++++++++++++++++ tests/test_txn.py | 8 +- tests/test_type_system.py | 101 ++++++++++++++ tests/test_upsert_block.py | 278 +++++++++++++++++++++---------------- 5 files changed, 464 insertions(+), 127 deletions(-) create mode 100644 tests/test_acl.py create mode 100644 tests/test_type_system.py diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 2a9bbdd1..06b72683 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -55,10 +55,15 @@ def setUp(self): def test_account_upsert(self): """Run upserts concurrently.""" - self.do_upserts(self.accounts, CONCURRENCY) + self.do_upserts(self.accounts, CONCURRENCY, upsert_account) self.assert_changes(FIRSTS, self.accounts) - def do_upserts(self, account_list, concurrency): + def test_account_upsert_block(self): + """Run upserts concurrently using upsert block.""" + self.do_upserts(self.accounts, CONCURRENCY, upsert_account_upsert_block) + self.assert_changes(FIRSTS, self.accounts) + + def do_upserts(self, account_list, concurrency, upsert_func): """Runs the upsert command for the accounts in `account_list`. Execution happens in concurrent processes.""" @@ -66,8 +71,8 @@ def do_upserts(self, account_list, concurrency): retry_ctr = multiprocessing.Value('i', 0, lock=True) def _updater(acct): - upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, - success_ctr=success_ctr, retry_ctr=retry_ctr) + upsert_func(addr=self.TEST_SERVER_ADDR, account=acct, + success_ctr=success_ctr, retry_ctr=retry_ctr) pool = mpd.Pool(concurrency) results = [ @@ -159,6 +164,52 @@ def upsert_account(addr, account, success_ctr, retry_ctr): txn.discard() +def upsert_account_upsert_block(addr, account, success_ctr, retry_ctr): + """Runs upsert operation.""" + client = helper.create_client(addr) + query = """{{ + acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ + u as uid + }} + }}""".format(**account) + + last_update_time = time.time() - 10000 + while True: + if time.time() > last_update_time + 10000: + logging.debug('Success: %d Retries: %d', success_ctr.value, + retry_ctr.value) + last_update_time = time.time() + + txn = client.txn() + try: + nquads = """ + uid(u) "{first}" . + uid(u) "{last}" . + uid(u) "{age}"^^ . + """.format(**account) + mutation = txn.create_mutation(set_nquads=nquads) + request = txn.create_request(query=query, mutations=[mutation], commit_now=True) + txn.do_request(request) + + updatequads = 'uid(u) "{0:d}"^^ .'.format(int(time.time())) + txn = client.txn() + mutation = txn.create_mutation(set_nquads=updatequads) + request = txn.create_request(query=query, mutations=[mutation], commit_now=True) + txn.do_request(request) + + with success_ctr.get_lock(): + success_ctr.value += 1 + + # txn successful, break the loop + return + except pydgraph.AbortedError: + with retry_ctr.get_lock(): + retry_ctr.value += 1 + # txn failed, retry the loop + finally: + txn.discard() + + def suite(): """Returns a test suite object.""" suite_obj = unittest.TestSuite() diff --git a/tests/test_acl.py b/tests/test_acl.py new file mode 100644 index 00000000..71381fc2 --- /dev/null +++ b/tests/test_acl.py @@ -0,0 +1,145 @@ +# Copyright 2019 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests to verify ACL.""" +import subprocess +import time + +__author__ = 'Animesh Pathak ' +__maintainer__ = 'Animesh Pathak ' + +import logging +import unittest + +from . import helper + + +class TestACL(helper.ClientIntegrationTestCase): + user_id = 'alice' + group_id = 'dev' + user_password = 'simplepassword' + server_addr = 'localhost:9180' + + def setUp(self): + super(TestACL, self).setUp() + helper.drop_all(self.client) + helper.set_schema(self.client, 'name: string .') + self.insert_sample_data() + self.add_user() + self.add_group() + self.add_user_to_group() + self.alice_client = helper.create_client(self.server_addr) + time.sleep(6) + self.alice_client.login(self.user_id, self.user_password) + + def test_read(self): + self.change_permission(4) + self.try_reading(True) + self.try_writing(False) + self.try_altering(False) + self.change_permission(0) + + def test_write(self): + self.change_permission(2) + self.try_reading(False) + self.try_writing(True) + self.try_altering(False) + self.change_permission(0) + + def test_alter(self): + self.change_permission(1) + self.try_reading(False) + self.try_writing(False) + self.try_altering(True) + self.change_permission(0) + + def change_permission(self, permission): + bash_command = "dgraph acl -a " + self.server_addr + " mod -g " + self.group_id + \ + " -p name -m " + str(permission) + " -x password" + self.run_command(bash_command) + + def insert_sample_data(self): + txn = self.client.txn() + txn.mutate(set_nquads='_:animesh "Animesh" .', commit_now=True) + + def add_user(self): + bash_command = "dgraph acl -a " + self.server_addr + " add -u " + self.user_id + \ + " -p " + self.user_password + " -x password" + self.run_command(bash_command) + + def add_group(self): + bash_command = "dgraph acl -a " + self.server_addr + " add -g " + self.group_id + " -x password" + self.run_command(bash_command) + + def add_user_to_group(self): + bash_command = "dgraph acl -a " + self.server_addr + " mod -u " + \ + self.user_id + " -l " + self.group_id + " -x password" + self.run_command(bash_command) + + def run_command(self, bash_command): + try: + subprocess.check_output(bash_command.split()) + except subprocess.CalledProcessError as e: + self.fail("Acl test failed: Unable to execute command " + bash_command + "\n" + str(e)) + + def try_reading(self, expected): + txn = self.alice_client.txn() + query = """ + { + me(func: has(name)) { + uid + name + } + } + """ + + try: + txn.query(query) + if not expected: + self.fail("Acl test failed: Read successful without permission") + except Exception as e: + if expected: + self.fail("Acl test failed: Read failed for readable predicate.\n" + str(e)) + + def try_writing(self, expected): + txn = self.alice_client.txn() + + try: + txn.mutate(set_nquads='_:aman "Aman" .', commit_now=True) + if not expected: + self.fail("Acl test failed: Write successful without permission") + except Exception as e: + if expected: + self.fail("Acl test failed: Write failed for writable predicate.\n" + str(e)) + + def try_altering(self, expected): + try: + helper.set_schema(self.alice_client, 'name: string @index(exact, term) .') + if not expected: + self.fail("Acl test failed: Alter successful without permission") + except Exception as e: + if expected: + self.fail("Acl test failed: Alter failed for altreble predicate.\n" + str(e)) + + +def suite(): + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestACL()) + return suite_obj + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_txn.py b/tests/test_txn.py index 9e0ce3c5..0606d9b2 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -64,7 +64,6 @@ def test_commit_now(self): txn = self.client.txn() response = txn.mutate(set_obj={'name': 'Manish'}, commit_now=True) self.assertEqual(1, len(response.uids), 'Nothing was assigned') - for _, uid in response.uids.items(): uid = uid @@ -422,6 +421,13 @@ def test_non_string_variable(self): with self.assertRaises(Exception): _ = txn.query(query, variables=variables) + def test_finished(self): + txn = self.client.txn() + txn.mutate(set_nquads='_:animesh "Animesh" .', commit_now=True) + + with self.assertRaises(Exception): + txn.mutate(set_nquads='_:aman "Aman" .', commit_now=True) + class TestSPStar(helper.ClientIntegrationTestCase): def setUp(self): diff --git a/tests/test_type_system.py b/tests/test_type_system.py new file mode 100644 index 00000000..245b98a1 --- /dev/null +++ b/tests/test_type_system.py @@ -0,0 +1,101 @@ +# Copyright 2019 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests to verify type system.""" +__author__ = 'Animesh Pathak ' +__maintainer__ = 'Animesh Pathak ' + +import json +import logging +import unittest + +from tests import helper + + +class TestTypeSystem(helper.ClientIntegrationTestCase): + def setUp(self): + super(TestTypeSystem, self).setUp() + helper.drop_all(self.client) + + schema = """ + type Person { + name: string + age: int + } + name: string @index(term, exact) . + age: int . + """ + + helper.set_schema(self.client, schema) + + def test_type_deletion_failure(self): + """It tries to delete all predicates of a node without having any type""" + + rdfs = """ + _:animesh "Animesh" . + _:animesh "24" . + """ + + self.insert_delete_and_check(rdfs, 1) + + def test_type_deletion(self): + + rdfs = """ + _:animesh "Animesh" . + _:animesh "24" . + _:animesh "Person" . + """ + + self.insert_delete_and_check(rdfs, 0) + + def insert_delete_and_check(self, rdfs, expected_result_count=0): + txn = self.client.txn() + txn.mutate(set_nquads=rdfs, commit_now=True) + + query = """ + { + u as var(func: eq(name, "Animesh")) + } + """ + + txn = self.client.txn() + mutation = txn.create_mutation(del_nquads='uid(u) * * .') + request = txn.create_request(mutations=[mutation], query=query, commit_now=True) + txn.do_request(request) + + query = """ + { + me(func: eq(name, "Animesh")) { + name + } + } + """ + + txn = self.client.txn() + response = txn.query(query=query) + data = json.loads(response.json) + if len(data["me"]) != expected_result_count: + self.fail("Type system test failed: Error while deleting predicates.") + + +def suite(): + suite_obj = unittest.TestSuite() + suite_obj.addTest(TestTypeSystem()) + return suite_obj + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + runner = unittest.TextTestRunner() + runner.run(suite()) diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index 3eab9874..363989aa 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -13,150 +13,184 @@ # limitations under the License. """Tests to verify upsert block.""" - -__author__ = 'Aman Mangal ' -__maintainer__ = 'Aman Mangal ' +__author__ = 'Animesh Pathak ' +__maintainer__ = 'Animesh Pathak ' import unittest import logging import json -import time -import multiprocessing -import multiprocessing.dummy as mpd - -import pydgraph from . import helper -CONCURRENCY = 5 -FIRSTS = ['Paul', 'Eric', 'Jack', 'John', 'Martin'] -LASTS = ['Brown', 'Smith', 'Robinson', 'Waters', 'Taylor'] -AGES = [20, 25, 30, 35] +class TestUpsertBlock(helper.ClientIntegrationTestCase): + """Tests for Upsert Block""" -class TestAccountUpsert(helper.ClientIntegrationTestCase): - """Tests to verify upsert directive.""" def setUp(self): - super(TestAccountUpsert, self).setUp() - - self.accounts = [ - {'first': f, 'last': l, 'age': a} - for f in FIRSTS for l in LASTS for a in AGES - ] - logging.info(len(self.accounts)) - + super(TestUpsertBlock, self).setUp() helper.drop_all(self.client) - helper.set_schema(self.client, """ - first: string @index(term) @upsert . - last: string @index(hash) @upsert . - age: int @index(int) @upsert . - when: int . - """) - - def test_account_upsert(self): - """Run upserts concurrently using upsert block.""" - self.do_upserts(self.accounts, CONCURRENCY) - self.assert_changes(FIRSTS, self.accounts) - - def do_upserts(self, account_list, concurrency): - """Runs the upsert command for the accounts in `account_list`. Execution - happens in concurrent processes.""" - - success_ctr = multiprocessing.Value('i', 0, lock=True) - retry_ctr = multiprocessing.Value('i', 0, lock=True) - - def _updater(acct): - upsert_account(addr=self.TEST_SERVER_ADDR, account=acct, - success_ctr=success_ctr, retry_ctr=retry_ctr) - - pool = mpd.Pool(concurrency) - results = [ - pool.apply_async(_updater, (acct,)) - for acct in account_list for _ in range(concurrency) - ] - - _ = [res.get() for res in results] - pool.close() - - def assert_changes(self, firsts, accounts): - """Will check to see changes have been made.""" - - query = """{{ - all(func: anyofterms(first, "{}")) {{ - first - last - age - }} - }}""".format(' '.join(firsts)) - logging.debug(query) - result = json.loads(self.client.txn(read_only=True).query(query).json) - - account_set = set() - for acct in result['all']: - self.assertTrue(acct['first'] is not None) - self.assertTrue(acct['last'] is not None) - self.assertTrue(acct['age'] is not None) - account_set.add('{first}_{last}_{age}'.format(**acct)) - - self.assertEqual(len(account_set), len(accounts)) - for acct in accounts: - self.assertTrue('{first}_{last}_{age}'.format(**acct) in account_set) - - -def upsert_account(addr, account, success_ctr, retry_ctr): - """Runs upsert operation.""" - client = helper.create_client(addr) - query = """{{ - acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ - u as uid - }} - }}""".format(**account) - - last_update_time = time.time() - 10000 - while True: - if time.time() > last_update_time + 10000: - logging.debug('Success: %d Retries: %d', success_ctr.value, - retry_ctr.value) - last_update_time = time.time() - - txn = client.txn() + helper.set_schema(self.client, 'name: string @index(term) @upsert .') + + def test_upsert_block_one_mutation(self): + txn = self.client.txn() + mutation = txn.create_mutation(set_nquads='_:animesh "Animesh" .') + request = txn.create_request(mutations=[mutation], commit_now=True) + txn.do_request(request) + + def test_upsert_block_multiple_mutation(self): + txn = self.client.txn() + mutation1 = txn.create_mutation(set_nquads='_:animesh "Animesh" .') + mutation2 = txn.create_mutation(set_nquads='_:aman "Aman" .') + request = txn.create_request(mutations=[mutation1, mutation2], commit_now=True) try: - nquads = """ - uid(u) "{first}" . - uid(u) "{last}" . - uid(u) "{age}"^^ . - """.format(**account) - mutation = txn.create_mutation(set_nquads=nquads) - request = txn.create_request(query=query, mutations=[mutation], commit_now=True) txn.do_request(request) - - updatequads = 'uid(u) "{0:d}"^^ .'.format(int(time.time())) - txn = client.txn() - mutation = txn.create_mutation(set_nquads=updatequads) - request = txn.create_request(query=query, mutations=[mutation], commit_now=True) + self.fail("Upsert block test failed: Multiple mutations succeeded") + except Exception as e: + txn.discard() + self.assertTrue("Only 1 mutation per request is supported" in str(e)) + + def test_one_mutation_one_query(self): + txn = self.client.txn() + mutation = txn.create_mutation(set_nquads='uid(u) "Animesh" .') + + query = """ + { + me(func: eq(name, "Animesh")) { + u as uid + } + } + """ + + request = txn.create_request(mutations=[mutation], query=query, commit_now=True) + txn.do_request(request) + + def test_one_query(self): + self.insert_sample_data() + txn = self.client.txn() + + query = """ + { + me(func: eq(name, "Animesh")) { + name + uid + } + } + """ + + request = txn.create_request(query=query) + response = txn.do_request(request) + data = json.loads(response.json) + if len(data["me"]) <= 0: + self.fail("Upsert block test failed: No data found in query") + + def test_no_query_no_mutation(self): + txn = self.client.txn() + request = txn.create_request() + try: txn.do_request(request) - - with success_ctr.get_lock(): - success_ctr.value += 1 - - # txn successful, break the loop - return - except pydgraph.AbortedError: - with retry_ctr.get_lock(): - retry_ctr.value += 1 - # txn failed, retry the loop - finally: + self.fail("Upsert block test failed: Empty query succeeded") + except Exception as e: txn.discard() + self.assertTrue("Empty query" in str(e)) + + def test_conditional_upsert(self): + self.insert_sample_data() + txn = self.client.txn() + + query = """ + { + u as var(func: eq(name, "Animesh")) + } + """ + + mutation = txn.create_mutation(cond="@if(gt(len(u), 0))", set_nquads='uid(u) "Ashish" .') + request = txn.create_request(mutations=[mutation], query=query, commit_now=True) + txn.do_request(request) + self.was_upsert_successful() + + def test_bulk_set(self): + rdfs = """ + _:animesh "Animesh" . + _:aman "Aman" . + _:ashish "Ashish" . + """ + + txn = self.client.txn() + txn.mutate(set_nquads=rdfs, commit_now=True) + + query = """ + { + me(func: has(name)) { + u as uid + } + } + """ + + txn = self.client.txn() + mutation = txn.create_mutation(set_nquads='uid(u) "Random" .') + request = txn.create_request(mutations=[mutation], query=query, commit_now=True) + txn.do_request(request) + + query = """ + { + me(func: eq(name, "Animesh")) { + uid + } + } + """ + + txn = self.client.txn() + response = txn.query(query) + data = json.loads(response.json)['me'] + if len(data) > 0: + self.fail("Upsert block test failed: Couldn't do bulk set") + + def test_json(self): + txn = self.client.txn() + data = {"uid": "_:animesh", "name": "Pathak"} + txn.mutate(set_obj=data, commit_now=True) + + def insert_sample_data(self): + txn = self.client.txn() + txn.mutate(set_nquads='_:animesh "Animesh" .', commit_now=True) + + def was_upsert_successful(self): + query = """ + { + me(func: eq(name, "Animesh")) { + uid + } + } + """ + + txn = self.client.txn() + response = txn.query(query) + data = json.loads(response.json) + if len(data["me"]) != 0: + self.fail("Upsert block test failed: Couldn't delete data.") + + query = """ + { + me(func: eq(name, "Ashish")) { + uid + } + } + """ + + txn = self.client.txn() + response = txn.query(query) + data = json.loads(response.json) + if len(data["me"]) != 1: + self.fail("Upsert block test failed: Couldn't update data.") def suite(): - """Returns a test suite object.""" suite_obj = unittest.TestSuite() - suite_obj.addTest(TestAccountUpsert()) + suite_obj.addTest(TestUpsertBlock()) return suite_obj -if __name__ == '__main__': +if __name__ == "__main__": logging.basicConfig(level=logging.INFO) runner = unittest.TextTestRunner() runner.run(suite()) From 0dab1245d3f1bf6aa85acf02bfa51ef5368ad3ee Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Wed, 18 Sep 2019 16:38:15 +0530 Subject: [PATCH 155/435] Update README with Conditional Upsert example (#90) --- README.md | 91 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 56c63280..6328e470 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,15 @@ and understand how to run and work with Dgraph. - [Install](#install) - [Quickstart](#quickstart) -- [Using a client](#using-a-client) - - [Creating a client](#creating-a-client) - - [Altering the database](#altering-the-database) - - [Creating a transaction](#creating-a-transaction) - - [Running a mutation](#running-a-mutation) - - [Running a query](#running-a-query) - - [Committing a transaction](#committing-a-transaction) +- [Using a Client](#using-a-client) + - [Creating a Client](#creating-a-client) + - [Altering the Database](#altering-the-database) + - [Creating a Transaction](#creating-a-transaction) + - [Running a Mutation](#running-a-mutation) + - [Committing a Transaction](#committing-a-transaction) + * [Running a Query](#running-a-query) + * [Running an Upsert: Query + Mutation](#running-an-upsert-query--mutation) + * [Running a Conditional Upsert](#running-a-conditional-upsert) - [Cleaning up Resources](#cleaning-up-resources) - [Setting Metadata Headers](#setting-metadata-headers) - [Examples](#examples) @@ -53,7 +55,7 @@ instructions in the README of that project. ## Using a client -### Creating a client +### Creating a Client You can initialize a `DgraphClient` object by passing it a list of `DgraphClientStub` clients as variadic arguments. Connecting to multiple Dgraph @@ -68,7 +70,7 @@ client_stub = pydgraph.DgraphClientStub('localhost:9080') client = pydgraph.DgraphClient(client_stub) ``` -### Altering the database +### Altering the Database To set the schema, create an `Operation` object, set the schema and pass it to `DgraphClient#alter(Operation)` method. @@ -90,7 +92,7 @@ op = pydgraph.Operation(drop_all=True) client.alter(op) ``` -### Creating a transaction +### Creating a Transaction To create a transaction, call `DgraphClient#txn()` method, which returns a new `Txn` object. This operation incurs no network overhead. @@ -129,7 +131,7 @@ faster than normal queries because they bypass the normal consensus protocol. For this same reason, best-effort queries cannot guarantee to return the latest data. Best-effort queries are only supported by read-only transactions. -### Running a mutation +### Running a Mutation `Txn#mutate(mu=Mutation)` runs a mutation. It takes in a `Mutation` object, which provides two main ways to set data: JSON and RDF N-Quad. You can choose @@ -193,7 +195,36 @@ request = txn.create_request(mutations=[mutation], commit_now=True) txn.do_request(request) ``` -### Running a query +### Committing a Transaction + +A transaction can be committed using the `Txn#commit()` method. If your transaction +consisted solely of calls to `Txn#query` or `Txn#queryWithVars`, and no calls to +`Txn#mutate`, then calling `Txn#commit()` is not necessary. + +An error is raised if another transaction(s) modify the same data concurrently that was +modified in the current transaction. It is up to the user to retry transactions +when they fail. + +```python +txn = client.txn() +try: + # ... + # Perform any number of queries and mutations + # ... + # and finally... + txn.commit() +except Exception as e: + if isinstance(e, pydgraph.AbortedError): + # Retry or handle exception. + else: + raise e +finally: + # Clean up. Calling this after txn.commit() is a no-op + # and hence safe. + txn.discard() +``` + +### Running a Query You can run a query by calling `Txn#query(string)`. You will need to pass in a GraphQL+- query string. If you want to pass an additional dictionary of any @@ -263,33 +294,23 @@ request = txn.create_request(query=query, mutations=[mutation], commit_now=True) txn.do_request(request) ``` -### Committing a transaction +### Running a Conditional Upsert -A transaction can be committed using the `Txn#commit()` method. If your transaction -consisted solely of calls to `Txn#query` or `Txn#queryWithVars`, and no calls to -`Txn#mutate`, then calling `Txn#commit()` is not necessary. +The upsert block also allows specifying a conditional mutation block using an `@if` directive. The mutation is executed +only when the specified condition is true. If the condition is false, the mutation is silently ignored. -An error is raised if another transaction(s) modify the same data concurrently that was -modified in the current transaction. It is up to the user to retry transactions -when they fail. +See more about Conditional Upsert [Here](https://docs.dgraph.io/mutations/#conditional-upsert). ```python -txn = client.txn() -try: - # ... - # Perform any number of queries and mutations - # ... - # and finally... - txn.commit() -except Exception as e: - if isinstance(e, pydgraph.AbortedError): - # Retry or handle exception. - else: - raise e -finally: - # Clean up. Calling this after txn.commit() is a no-op - # and hence safe. - txn.discard() +query = """ + { + user as var(func: eq(email, "wrong_email@dgraph.io")) + } + """ +mutation = txn.create_mutation(cond="@if(eq(len(user), 1))", + set_nquads="uid(user) \"correct_email@dgraph.io\" .") +request = txn.create_request(mutations=[mutation], query=query, commit_now=True) +txn.do_request(request) ``` ### Cleaning Up Resources From c423e135c18eccbaf9945cc7b103acd269e017d1 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 1 Oct 2019 19:34:17 +0530 Subject: [PATCH 156/435] Fix simple example to use type system --- examples/simple/simple.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 341b47f0..0abe44ce 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -6,7 +6,7 @@ # Create a client stub. def create_client_stub(): - return pydgraph.DgraphClientStub('localhost:9080') + return pydgraph.DgraphClientStub('localhost:9180') # Create a client. @@ -28,6 +28,15 @@ def set_schema(client): married: bool . loc: geo . dob: datetime . + + type Person { + name: string + friend: [uid] + age: int + married: bool + loc: geo + dob: datetime + } """ return client.alter(pydgraph.Operation(schema=schema)) @@ -40,6 +49,7 @@ def create_data(client): # Create data. p = { 'uid': '_:alice', + 'dgraph.type': 'Person', 'name': 'Alice', 'age': 26, 'married': True, @@ -51,6 +61,7 @@ def create_data(client): 'friend': [ { 'uid': '_:bob', + 'dgraph.type': 'Person', 'name': 'Bob', 'age': 24, } @@ -92,8 +103,8 @@ def delete_data(client): ppl1 = json.loads(res1.json) for person in ppl1['all']: print("Bob's UID: " + person['uid']) - txn.mutate(del_obj=person) - print('Bob deleted') + txn.mutate(del_obj=person) + print('Bob deleted') txn.commit() finally: @@ -128,6 +139,7 @@ def query_alice(client): # Print results. print('Number of people named "Alice": {}'.format(len(ppl['all']))) + # Query to check for deleted node def query_bob(client): query = """query all($b: string) { @@ -155,17 +167,18 @@ def query_bob(client): # Print results. print('Number of people named "Bob": {}'.format(len(ppl['all']))) + def main(): client_stub = create_client_stub() client = create_client(client_stub) drop_all(client) set_schema(client) create_data(client) - query_alice(client) # query for Alice - query_bob(client) # query for Bob - delete_data(client) # delete Bob - query_alice(client) # query for Alice - query_bob(client) # query for Bob + query_alice(client) # query for Alice + query_bob(client) # query for Bob + delete_data(client) # delete Bob + query_alice(client) # query for Alice + query_bob(client) # query for Bob # Close the client stub. client_stub.close() From 65016433303df85efa7c686365a78bcf73ffad85 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Thu, 3 Oct 2019 00:44:33 +0530 Subject: [PATCH 157/435] Simplify the type system test cases (#92) Ref: https://github.com/dgraph-io/dgraph/pull/4017 --- tests/test_type_system.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_type_system.py b/tests/test_type_system.py index 245b98a1..484c97ae 100644 --- a/tests/test_type_system.py +++ b/tests/test_type_system.py @@ -30,8 +30,8 @@ def setUp(self): schema = """ type Person { - name: string - age: int + name + age } name: string @index(term, exact) . age: int . From f32f8c9d29049d4bedd039161b4ffae83e444b2c Mon Sep 17 00:00:00 2001 From: Daniel Mai Date: Thu, 10 Oct 2019 12:09:56 -0700 Subject: [PATCH 158/435] Change simple example to connect to Dgraph's default port. (#93) --- examples/simple/simple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 0abe44ce..148e81fc 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -6,7 +6,7 @@ # Create a client stub. def create_client_stub(): - return pydgraph.DgraphClientStub('localhost:9180') + return pydgraph.DgraphClientStub('localhost:9080') # Create a client. From b174ebee5017f44ccb5de1bbf40b623635b658c7 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Fri, 11 Oct 2019 06:04:40 +0530 Subject: [PATCH 159/435] Update Simple Example to use simplified type definitions in schema --- examples/simple/simple.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 148e81fc..29240240 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -30,12 +30,12 @@ def set_schema(client): dob: datetime . type Person { - name: string - friend: [uid] - age: int - married: bool - loc: geo - dob: datetime + name + friend + age + married + loc + dob } """ return client.alter(pydgraph.Operation(schema=schema)) From 9b7db05a57e8e00f614e7ec3947404dc24275e55 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 15 Nov 2019 11:01:15 -0800 Subject: [PATCH 160/435] Revert "Update Simple Example to use simplified type definitions in schema" (#98) This reverts commit b174ebee5017f44ccb5de1bbf40b623635b658c7. --- examples/simple/simple.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 29240240..148e81fc 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -30,12 +30,12 @@ def set_schema(client): dob: datetime . type Person { - name - friend - age - married - loc - dob + name: string + friend: [uid] + age: int + married: bool + loc: geo + dob: datetime } """ return client.alter(pydgraph.Operation(schema=schema)) From a3a1a012e848eca7971045345c22d00e43073fcc Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Sat, 7 Dec 2019 02:57:01 +0530 Subject: [PATCH 161/435] Sync proto files with dgo (#104) --- pydgraph/proto/api.proto | 14 ++++++++++++++ tests/test_upsert_block.py | 9 ++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index 842122fb..d11e469e 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -54,10 +54,18 @@ message Request { bool commit_now = 13; } +message Uids { + repeated string uids = 1; +} + message Response { bytes json = 1; TxnContext txn = 2; Latency latency = 3; + // Metrics contains all metrics related to the query. + Metrics metrics = 4; + // uids contains a mapping of blank_node => uid for the node. It only returns uids + // that were created as part of a mutation. map uids = 12; } @@ -119,6 +127,12 @@ message Latency { uint64 processing_ns = 2; uint64 encoding_ns = 3; uint64 assign_timestamp_ns = 4; + uint64 total_ns = 5; +} + +message Metrics { + // num_uids is the map of number of uids processed by each attribute. + map num_uids = 1; } message NQuad { diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index 363989aa..32b9e7d6 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -42,12 +42,7 @@ def test_upsert_block_multiple_mutation(self): mutation1 = txn.create_mutation(set_nquads='_:animesh "Animesh" .') mutation2 = txn.create_mutation(set_nquads='_:aman "Aman" .') request = txn.create_request(mutations=[mutation1, mutation2], commit_now=True) - try: - txn.do_request(request) - self.fail("Upsert block test failed: Multiple mutations succeeded") - except Exception as e: - txn.discard() - self.assertTrue("Only 1 mutation per request is supported" in str(e)) + txn.do_request(request) def test_one_mutation_one_query(self): txn = self.client.txn() @@ -91,7 +86,7 @@ def test_no_query_no_mutation(self): self.fail("Upsert block test failed: Empty query succeeded") except Exception as e: txn.discard() - self.assertTrue("Empty query" in str(e)) + self.assertTrue("empty request" in str(e)) def test_conditional_upsert(self): self.insert_sample_data() From 2ddba43dc91afef8a57c8a3fca52607d1026c20f Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 17 Dec 2019 11:43:47 -0800 Subject: [PATCH 162/435] Update Simple Example to use simplified type definitions in schema (#99) --- examples/simple/simple.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 148e81fc..29240240 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -30,12 +30,12 @@ def set_schema(client): dob: datetime . type Person { - name: string - friend: [uid] - age: int - married: bool - loc: geo - dob: datetime + name + friend + age + married + loc + dob } """ return client.alter(pydgraph.Operation(schema=schema)) From ec09dfa820f7d0b280ce97e95d9fbe26ddbf4b52 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Thu, 19 Dec 2019 00:49:20 +0530 Subject: [PATCH 163/435] Updating upsert documentation (#106) - Removing condition from the upsert operation example - Other minor code cleanup from examples --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6328e470..5e48892e 100644 --- a/README.md +++ b/README.md @@ -286,10 +286,10 @@ query = """{ u as var(func: eq(name, "Alice")) }""" nquad = """ + uid(u) "Alice" . uid(u) "25" . """ -cond = "@if(eq(len(u), 1))" -mutation = txn.create_mutation(set_nquads=nquad, cond=cond) +mutation = txn.create_mutation(set_nquads=nquad) request = txn.create_request(query=query, mutations=[mutation], commit_now=True) txn.do_request(request) ``` @@ -303,12 +303,15 @@ See more about Conditional Upsert [Here](https://docs.dgraph.io/mutations/#condi ```python query = """ - { - user as var(func: eq(email, "wrong_email@dgraph.io")) - } - """ -mutation = txn.create_mutation(cond="@if(eq(len(user), 1))", - set_nquads="uid(user) \"correct_email@dgraph.io\" .") + { + user as var(func: eq(email, "wrong_email@dgraph.io")) + } +""" +cond = "@if(eq(len(user), 1))" +nquads = """ + uid(user) "correct_email@dgraph.io" . +""" +mutation = txn.create_mutation(cond=cond, set_nquads=nquads) request = txn.create_request(mutations=[mutation], query=query, commit_now=True) txn.do_request(request) ``` From 56efe5236a4980e55088430798607b366e691730 Mon Sep 17 00:00:00 2001 From: sleto-it <31849787+sleto-it@users.noreply.github.com> Date: Wed, 5 Feb 2020 14:33:17 +0100 Subject: [PATCH 164/435] Update README.md --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e48892e..1f0e29ed 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ and understand how to run and work with Dgraph. ## Table of contents - [Install](#install) +- [Supported Versions](#supported-versions) - [Quickstart](#quickstart) - [Using a Client](#using-a-client) - [Creating a Client](#creating-a-client) @@ -42,8 +43,16 @@ Install using pip: pip install pydgraph ``` -**NOTE**: Dgraph 1.1.x and 1.0.x have incompatible APIs. 1.1.x is supported by -versions of pydgraph >=2 while 1.0.x is supported by versions <= 1.2.0. +## Supported Versions + +Depending on the version of Dgraph that you are connecting to, you will have to +use a different version of this client. + +| Dgraph version | pydgraph version | +|:--------------:|:--------------------:| +| 1.0.X | <= *1.2.0* | +| 1.1.X | >= *2.0.0* | +| 1.2.X | >= *2.0.0* | ## Quickstart From c8cc4128127d0488a3d93d6f6eb004413d2f061f Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 27 Feb 2020 16:34:05 -0800 Subject: [PATCH 165/435] Regenerate protos. (#115) --- pydgraph/proto/api_pb2.py | 293 ++++++++++++++++++++++++++++---------- 1 file changed, 216 insertions(+), 77 deletions(-) diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 9ffaaa5b..64b892c8 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -2,8 +2,6 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: api.proto -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -19,8 +17,8 @@ name='api.proto', package='api', syntax='proto3', - serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), - serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xdb\x01\n\x07Request\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\r\n\x05query\x18\x04 \x01(\t\x12$\n\x04vars\x18\x05 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x11\n\tread_only\x18\x06 \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x07 \x01(\x08\x12 \n\tmutations\x18\x0c \x03(\x0b\x32\r.api.Mutation\x12\x12\n\ncommit_now\x18\r \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa9\x01\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1c\n\x03txn\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x03 \x01(\x0b\x32\x0c.api.Latency\x12%\n\x04uids\x18\x0c \x03(\x0b\x32\x17.api.Response.UidsEntry\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xad\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\x05 \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x06 \x03(\x0b\x32\n.api.NQuad\x12\x0c\n\x04\x63ond\x18\t \x01(\t\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\"\xb7\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"_\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"f\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\x12\x1b\n\x13\x61ssign_timestamp_ns\x18\x04 \x01(\x04\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\xe7\x01\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') + serialized_options=b'\n\tio.dgraphB\013DgraphProto', + serialized_pb=b'\n\tapi.proto\x12\x03\x61pi\"\xdb\x01\n\x07Request\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\r\n\x05query\x18\x04 \x01(\t\x12$\n\x04vars\x18\x05 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x11\n\tread_only\x18\x06 \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x07 \x01(\x08\x12 \n\tmutations\x18\x0c \x03(\x0b\x32\r.api.Mutation\x12\x12\n\ncommit_now\x18\r \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x14\n\x04Uids\x12\x0c\n\x04uids\x18\x01 \x03(\t\"\xc8\x01\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1c\n\x03txn\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x03 \x01(\x0b\x32\x0c.api.Latency\x12\x1d\n\x07metrics\x18\x04 \x01(\x0b\x32\x0c.api.Metrics\x12%\n\x04uids\x18\x0c \x03(\x0b\x32\x17.api.Response.UidsEntry\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xad\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\x05 \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x06 \x03(\x0b\x32\n.api.NQuad\x12\x0c\n\x04\x63ond\x18\t \x01(\t\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\"\xb7\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"_\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"x\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\x12\x1b\n\x13\x61ssign_timestamp_ns\x18\x04 \x01(\x04\x12\x10\n\x08total_ns\x18\x05 \x01(\x04\"f\n\x07Metrics\x12+\n\x08num_uids\x18\x01 \x03(\x0b\x32\x19.api.Metrics.NumUidsEntry\x1a.\n\x0cNumUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\xe7\x01\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3' ) @@ -54,8 +52,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=715, - serialized_end=772, + serialized_start=768, + serialized_end=825, ) _sym_db.RegisterEnumDescriptor(_OPERATION_DROPOP) @@ -88,8 +86,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1543, - serialized_end=1608, + serialized_start=1718, + serialized_end=1783, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -104,14 +102,14 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Request.VarsEntry.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Request.VarsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -121,7 +119,7 @@ nested_types=[], enum_types=[ ], - serialized_options=_b('8\001'), + serialized_options=b'8\001', is_extendable=False, syntax='proto3', extension_ranges=[], @@ -148,7 +146,7 @@ _descriptor.FieldDescriptor( name='query', full_name='api.Request.query', index=1, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -204,6 +202,37 @@ ) +_UIDS = _descriptor.Descriptor( + name='Uids', + full_name='api.Uids', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='uids', full_name='api.Uids.uids', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=240, + serialized_end=260, +) + + _RESPONSE_UIDSENTRY = _descriptor.Descriptor( name='UidsEntry', full_name='api.Response.UidsEntry', @@ -214,14 +243,14 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Response.UidsEntry.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Response.UidsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -231,14 +260,14 @@ nested_types=[], enum_types=[ ], - serialized_options=_b('8\001'), + serialized_options=b'8\001', is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=367, - serialized_end=410, + serialized_start=420, + serialized_end=463, ) _RESPONSE = _descriptor.Descriptor( @@ -251,7 +280,7 @@ _descriptor.FieldDescriptor( name='json', full_name='api.Response.json', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -270,7 +299,14 @@ is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='uids', full_name='api.Response.uids', index=3, + name='metrics', full_name='api.Response.metrics', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='uids', full_name='api.Response.uids', index=4, number=12, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -288,8 +324,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=241, - serialized_end=410, + serialized_start=263, + serialized_end=463, ) @@ -303,28 +339,28 @@ _descriptor.FieldDescriptor( name='set_json', full_name='api.Mutation.set_json', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='delete_json', full_name='api.Mutation.delete_json', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='set_nquads', full_name='api.Mutation.set_nquads', index=2, number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='del_nquads', full_name='api.Mutation.del_nquads', index=3, number=4, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -345,7 +381,7 @@ _descriptor.FieldDescriptor( name='cond', full_name='api.Mutation.cond', index=6, number=9, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -368,8 +404,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=413, - serialized_end=586, + serialized_start=466, + serialized_end=639, ) @@ -383,14 +419,14 @@ _descriptor.FieldDescriptor( name='schema', full_name='api.Operation.schema', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='drop_attr', full_name='api.Operation.drop_attr', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -411,7 +447,7 @@ _descriptor.FieldDescriptor( name='drop_value', full_name='api.Operation.drop_value', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -428,8 +464,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=589, - serialized_end=772, + serialized_start=642, + serialized_end=825, ) @@ -443,7 +479,7 @@ _descriptor.FieldDescriptor( name='Data', full_name='api.Payload.Data', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -459,8 +495,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=774, - serialized_end=797, + serialized_start=827, + serialized_end=850, ) @@ -518,8 +554,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=799, - serialized_end=894, + serialized_start=852, + serialized_end=947, ) @@ -542,8 +578,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=896, - serialized_end=903, + serialized_start=949, + serialized_end=956, ) @@ -557,7 +593,7 @@ _descriptor.FieldDescriptor( name='tag', full_name='api.Version.tag', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -573,8 +609,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=905, - serialized_end=927, + serialized_start=958, + serialized_end=980, ) @@ -613,20 +649,95 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='total_ns', full_name='api.Latency.total_ns', index=4, + number=5, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=982, + serialized_end=1102, +) + + +_METRICS_NUMUIDSENTRY = _descriptor.Descriptor( + name='NumUidsEntry', + full_name='api.Metrics.NumUidsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='api.Metrics.NumUidsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='api.Metrics.NumUidsEntry.value', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], nested_types=[], enum_types=[ ], + serialized_options=b'8\001', + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1160, + serialized_end=1206, +) + +_METRICS = _descriptor.Descriptor( + name='Metrics', + full_name='api.Metrics', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='num_uids', full_name='api.Metrics.num_uids', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_METRICS_NUMUIDSENTRY, ], + enum_types=[ + ], serialized_options=None, is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=929, - serialized_end=1031, + serialized_start=1104, + serialized_end=1206, ) @@ -640,21 +751,21 @@ _descriptor.FieldDescriptor( name='subject', full_name='api.NQuad.subject', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='predicate', full_name='api.NQuad.predicate', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='object_id', full_name='api.NQuad.object_id', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -668,14 +779,14 @@ _descriptor.FieldDescriptor( name='label', full_name='api.NQuad.label', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='lang', full_name='api.NQuad.lang', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -698,8 +809,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1034, - serialized_end=1187, + serialized_start=1209, + serialized_end=1362, ) @@ -713,14 +824,14 @@ _descriptor.FieldDescriptor( name='default_val', full_name='api.Value.default_val', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='bytes_val', full_name='api.Value.bytes_val', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -741,7 +852,7 @@ _descriptor.FieldDescriptor( name='str_val', full_name='api.Value.str_val', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -755,28 +866,28 @@ _descriptor.FieldDescriptor( name='geo_val', full_name='api.Value.geo_val', index=6, number=7, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='date_val', full_name='api.Value.date_val', index=7, number=8, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='datetime_val', full_name='api.Value.datetime_val', index=8, number=9, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='password_val', full_name='api.Value.password_val', index=9, number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -802,8 +913,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1190, - serialized_end=1434, + serialized_start=1365, + serialized_end=1609, ) @@ -817,14 +928,14 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Facet.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Facet.value', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -845,7 +956,7 @@ _descriptor.FieldDescriptor( name='alias', full_name='api.Facet.alias', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -862,8 +973,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1437, - serialized_end=1608, + serialized_start=1612, + serialized_end=1783, ) @@ -877,21 +988,21 @@ _descriptor.FieldDescriptor( name='userid', full_name='api.LoginRequest.userid', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='password', full_name='api.LoginRequest.password', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='refresh_token', full_name='api.LoginRequest.refresh_token', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -907,8 +1018,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1610, - serialized_end=1681, + serialized_start=1785, + serialized_end=1856, ) @@ -922,14 +1033,14 @@ _descriptor.FieldDescriptor( name='access_jwt', full_name='api.Jwt.access_jwt', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='refresh_jwt', full_name='api.Jwt.refresh_jwt', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -945,8 +1056,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1683, - serialized_end=1729, + serialized_start=1858, + serialized_end=1904, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -955,11 +1066,14 @@ _RESPONSE_UIDSENTRY.containing_type = _RESPONSE _RESPONSE.fields_by_name['txn'].message_type = _TXNCONTEXT _RESPONSE.fields_by_name['latency'].message_type = _LATENCY +_RESPONSE.fields_by_name['metrics'].message_type = _METRICS _RESPONSE.fields_by_name['uids'].message_type = _RESPONSE_UIDSENTRY _MUTATION.fields_by_name['set'].message_type = _NQUAD _MUTATION.fields_by_name['del'].message_type = _NQUAD _OPERATION.fields_by_name['drop_op'].enum_type = _OPERATION_DROPOP _OPERATION_DROPOP.containing_type = _OPERATION +_METRICS_NUMUIDSENTRY.containing_type = _METRICS +_METRICS.fields_by_name['num_uids'].message_type = _METRICS_NUMUIDSENTRY _NQUAD.fields_by_name['object_value'].message_type = _VALUE _NQUAD.fields_by_name['facets'].message_type = _FACET _VALUE.oneofs_by_name['val'].fields.append( @@ -998,6 +1112,7 @@ _FACET.fields_by_name['val_type'].enum_type = _FACET_VALTYPE _FACET_VALTYPE.containing_type = _FACET DESCRIPTOR.message_types_by_name['Request'] = _REQUEST +DESCRIPTOR.message_types_by_name['Uids'] = _UIDS DESCRIPTOR.message_types_by_name['Response'] = _RESPONSE DESCRIPTOR.message_types_by_name['Mutation'] = _MUTATION DESCRIPTOR.message_types_by_name['Operation'] = _OPERATION @@ -1006,6 +1121,7 @@ DESCRIPTOR.message_types_by_name['Check'] = _CHECK DESCRIPTOR.message_types_by_name['Version'] = _VERSION DESCRIPTOR.message_types_by_name['Latency'] = _LATENCY +DESCRIPTOR.message_types_by_name['Metrics'] = _METRICS DESCRIPTOR.message_types_by_name['NQuad'] = _NQUAD DESCRIPTOR.message_types_by_name['Value'] = _VALUE DESCRIPTOR.message_types_by_name['Facet'] = _FACET @@ -1028,6 +1144,13 @@ _sym_db.RegisterMessage(Request) _sym_db.RegisterMessage(Request.VarsEntry) +Uids = _reflection.GeneratedProtocolMessageType('Uids', (_message.Message,), { + 'DESCRIPTOR' : _UIDS, + '__module__' : 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Uids) + }) +_sym_db.RegisterMessage(Uids) + Response = _reflection.GeneratedProtocolMessageType('Response', (_message.Message,), { 'UidsEntry' : _reflection.GeneratedProtocolMessageType('UidsEntry', (_message.Message,), { @@ -1092,6 +1215,21 @@ }) _sym_db.RegisterMessage(Latency) +Metrics = _reflection.GeneratedProtocolMessageType('Metrics', (_message.Message,), { + + 'NumUidsEntry' : _reflection.GeneratedProtocolMessageType('NumUidsEntry', (_message.Message,), { + 'DESCRIPTOR' : _METRICS_NUMUIDSENTRY, + '__module__' : 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Metrics.NumUidsEntry) + }) + , + 'DESCRIPTOR' : _METRICS, + '__module__' : 'api_pb2' + # @@protoc_insertion_point(class_scope:api.Metrics) + }) +_sym_db.RegisterMessage(Metrics) +_sym_db.RegisterMessage(Metrics.NumUidsEntry) + NQuad = _reflection.GeneratedProtocolMessageType('NQuad', (_message.Message,), { 'DESCRIPTOR' : _NQUAD, '__module__' : 'api_pb2' @@ -1131,6 +1269,7 @@ DESCRIPTOR._options = None _REQUEST_VARSENTRY._options = None _RESPONSE_UIDSENTRY._options = None +_METRICS_NUMUIDSENTRY._options = None _DGRAPH = _descriptor.ServiceDescriptor( name='Dgraph', @@ -1138,8 +1277,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=1732, - serialized_end=1963, + serialized_start=1907, + serialized_end=2138, methods=[ _descriptor.MethodDescriptor( name='Login', From f18cc25f2113325dcf0d489c0c96458d98097917 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 28 Feb 2020 13:36:23 -0800 Subject: [PATCH 166/435] Document timeout and credentials keyword arguments. (#119) --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 1f0e29ed..088291bc 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,19 @@ metadata = [("auth-token", "the-auth-token-value")] dg.alter(op, metadata=metadata) ``` +### Setting a timeout. + +A timeout value representing the number of seconds can be passed to the `login`, +`alter`, `query`, and `mutate` methods using the `timeout` keyword argument. + +For example, the following alters the schema with a timeout of ten seconds: +`dg.alter(op, timeout=10)` + +### Passing credentials + +A `CallCredentials` object can be passed to the `login`, `alter`, `query`, and +`mutate` methods using the `credentials` keyword argument. + ## Examples - [simple][]: Quickstart example of using pydgraph. From f5f712e7d98259efa7103d22c7ebd569435e0aa5 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Thu, 5 Mar 2020 11:27:54 +0200 Subject: [PATCH 167/435] Add example for TLS (#114) --- examples/tls/.gitignore | 1 + examples/tls/README.md | 101 +++++++++++++++++++++++++++++ examples/tls/docker-compose.yml | 108 ++++++++++++++++++++++++++++++++ examples/tls/hmac-secret | 1 + examples/tls/requirements.txt | 3 + examples/tls/tls_example.py | 65 +++++++++++++++++++ 6 files changed, 279 insertions(+) create mode 100644 examples/tls/.gitignore create mode 100644 examples/tls/README.md create mode 100644 examples/tls/docker-compose.yml create mode 100644 examples/tls/hmac-secret create mode 100644 examples/tls/requirements.txt create mode 100644 examples/tls/tls_example.py diff --git a/examples/tls/.gitignore b/examples/tls/.gitignore new file mode 100644 index 00000000..18d93f80 --- /dev/null +++ b/examples/tls/.gitignore @@ -0,0 +1 @@ +tls/* diff --git a/examples/tls/README.md b/examples/tls/README.md new file mode 100644 index 00000000..3d43c686 --- /dev/null +++ b/examples/tls/README.md @@ -0,0 +1,101 @@ +## TLS Example Project + +Project demonstrating the use of [pydgraph][] and Dgraph set up with client-server +mutual TLS. The following guide shows how to set up a single-group six-node +cluster (3 Dgraph Zero and 3 Dgraph Alpha) configured with mutual TLS. + +### Install Dgraph + +You will need to [install Dgraph v1.1.0 or +above](https://github.com/dgraph-io/dgraph/releases) and run it. + +A quick-start installation script is available for Linux and Mac: + +```sh +curl -sSf https://get.dgraph.io | bash +``` + +### Create TLS certificates + +Dgraph provides a `dgraph cert` tool to create and manage self-signed +server and client certificates using a generated Dgraph Root CA. See the [TLS +documentation](https://docs.dgraph.io/deploy/#tls-configuration) for more +information. + +Create the root CA. All certificates and keys are created in the `tls` directory. + +```sh +dgraph cert +``` + +Now create the Alpha server certificate (node.crt) and key (node.key) and client +certificate (client.user.crt) key (client.user.key). + +```sh +dgraph cert -n localhost +``` + +```sh +dgraph cert -c user +``` + +The following files should now be in the `tls` directory: + +```sh +$ ls tls +ca.crt ca.key client.user.crt client.user.key node.crt node.key +``` + +Using `dgraph cert ls` provides more details about each file. For instance, it +shows that the `node.crt` is valid only for the host named `localhost` and the +corresponding file permissions. + +```sh +$ dgraph cert ls +-rw-r--r-- ca.crt - Dgraph Root CA certificate + Issuer: Dgraph Labs, Inc. + S/N: 3dfb9c54929d703b +Expiration: 19 Feb 29 00:57 UTC + MD5 hash: C82CF5D4C344668E34A61D590D6A4B77 + +-r-------- ca.key - Dgraph Root CA key + MD5 hash: C82CF5D4C344668E34A61D590D6A4B77 + +-rw-r--r-- client.user.crt - Dgraph client certificate: user + Issuer: Dgraph Labs, Inc. + CA Verify: PASSED + S/N: 5991417e75ba14c7 +Expiration: 21 Feb 24 01:04 UTC + MD5 hash: BA35D4ABD8DFF1ED137E8D8E5D921D06 + +-rw------- client.user.key - Dgraph Client key + MD5 hash: BA35D4ABD8DFF1ED137E8D8E5D921D06 + +-rw-r--r-- node.crt - Dgraph Node certificate + Issuer: Dgraph Labs, Inc. + CA Verify: PASSED + S/N: 51d53048b6845d8c +Expiration: 21 Feb 24 01:00 UTC + Hosts: localhost + MD5 hash: 5D71F59AAEE294F1CFDA9E3232761018 + +-rw------- node.key - Dgraph Node key + MD5 hash: 5D71F59AAEE294F1CFDA9E3232761018 +``` + +### Run Dgraph Cluster +``` +docker-compose up +``` + +### Run TLS Python Example + +#### Install Dependencies +``` +pip3 install -r requirements.txt +``` + +#### Run Example Code +``` +python3 tls_example.py +``` diff --git a/examples/tls/docker-compose.yml b/examples/tls/docker-compose.yml new file mode 100644 index 00000000..5f4ccca8 --- /dev/null +++ b/examples/tls/docker-compose.yml @@ -0,0 +1,108 @@ +version: "3.5" +services: + zero1: + image: dgraph/dgraph:latest + container_name: zero1 + working_dir: /data/zero1 + ports: + - 5080:5080 + - 6080:6080 + labels: + cluster: test + service: zero + command: dgraph zero -o 0 --my=zero1:5080 --replicas 3 --idx 1 --logtostderr -v=2 --bindall --expose_trace --profile_mode block --block_rate 10 + + zero2: + image: dgraph/dgraph:latest + container_name: zero2 + working_dir: /data/zero2 + depends_on: + - zero1 + ports: + - 5082:5082 + - 6082:6082 + labels: + cluster: test + service: zero + command: dgraph zero -o 2 --my=zero2:5082 --replicas 3 --idx 2 --logtostderr -v=2 --peer=zero1:5080 + + zero3: + image: dgraph/dgraph:latest + container_name: zero3 + working_dir: /data/zero3 + depends_on: + - zero2 + ports: + - 5083:5083 + - 6083:6083 + labels: + cluster: test + service: zero + command: dgraph zero -o 3 --my=zero3:5083 --replicas 3 --idx 3 --logtostderr -v=2 --peer=zero1:5080 + + alpha1: + image: dgraph/dgraph:latest + container_name: alpha1 + working_dir: /data/alpha1 + volumes: + - type: bind + source: ./hmac-secret + target: /dgraph-acl/hmac-secret + read_only: true + - type: bind + source: ./tls + target: /dgraph-tls + read_only: true + ports: + - 8080:8080 + - 9080:9080 + labels: + cluster: test + service: alpha + command: dgraph alpha -o 0 --my=alpha1:7080 --lru_mb=1024 --zero=zero1:5080 --expose_trace --trace 1.0 --profile_mode block --block_rate 10 --logtostderr -v=2 --whitelist 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 --acl_secret_file /dgraph-acl/hmac-secret --acl_access_ttl 3s --acl_cache_ttl 5s --tls_dir /dgraph-tls --tls_client_auth=REQUIREANDVERIFY + + alpha2: + image: dgraph/dgraph:latest + container_name: alpha2 + working_dir: /data/alpha2 + depends_on: + - alpha1 + volumes: + - type: bind + source: ./hmac-secret + target: /dgraph-acl/hmac-secret + read_only: true + - type: bind + source: ./tls + target: /dgraph-tls + read_only: true + ports: + - 8082:8082 + - 9082:9082 + labels: + cluster: test + service: alpha + command: dgraph alpha -o 2 --my=alpha2:7082 --lru_mb=1024 --zero=zero1:5080 --expose_trace --trace 1.0 --profile_mode block --block_rate 10 --logtostderr -v=2 --whitelist 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 --acl_secret_file /dgraph-acl/hmac-secret --acl_access_ttl 3s --acl_cache_ttl 5s --tls_dir /dgraph-tls --tls_client_auth=REQUIREANDVERIFY + + alpha3: + image: dgraph/dgraph:latest + container_name: alpha3 + working_dir: /data/alpha3 + depends_on: + - alpha2 + volumes: + - type: bind + source: ./hmac-secret + target: /dgraph-acl/hmac-secret + read_only: true + - type: bind + source: ./tls + target: /dgraph-tls + read_only: true + ports: + - 8083:8083 + - 9083:9083 + labels: + cluster: test + service: alpha + command: dgraph alpha -o 3 --my=alpha3:7083 --lru_mb=1024 --zero=zero1:5080 --expose_trace --trace 1.0 --profile_mode block --block_rate 10 --logtostderr -v=2 --whitelist 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 --acl_secret_file /dgraph-acl/hmac-secret --acl_access_ttl 3s --acl_cache_ttl 5s --tls_dir /dgraph-tls --tls_client_auth=REQUIREANDVERIFY diff --git a/examples/tls/hmac-secret b/examples/tls/hmac-secret new file mode 100644 index 00000000..2add0c57 --- /dev/null +++ b/examples/tls/hmac-secret @@ -0,0 +1 @@ +1234567890123456789012345678901 diff --git a/examples/tls/requirements.txt b/examples/tls/requirements.txt new file mode 100644 index 00000000..8923e859 --- /dev/null +++ b/examples/tls/requirements.txt @@ -0,0 +1,3 @@ +grpcio==1.23.0 +pydgraph==2.0.2 +grpc==0.3-19 diff --git a/examples/tls/tls_example.py b/examples/tls/tls_example.py new file mode 100644 index 00000000..d2f5b88a --- /dev/null +++ b/examples/tls/tls_example.py @@ -0,0 +1,65 @@ +#!/usr/bin/python3 + +import pydgraph +import grpc + +def create_client(addr='localhost:9080'): + # Read certs + with open('./tls/ca.crt', 'rb') as f: + root_ca_cert = f.read() + with open('./tls/client.user.key', 'rb') as f: + client_cert_key = f.read() + with open('./tls/client.user.crt', 'rb') as f: + client_cert = f.read() + + # Connect to Dgraph via gRPC with mutual TLS. + creds = grpc.ssl_channel_credentials(root_certificates=root_ca_cert, + private_key=client_cert_key, + certificate_chain=client_cert) + client_stub = pydgraph.DgraphClientStub(addr, credentials=creds) + return pydgraph.DgraphClient(client_stub) + +def main(): + client = create_client('localhost:9080') + client.login("groot", "password") + + # Drop all + client.alter(pydgraph.Operation(drop_all=True)) + + # Update schema + schema = ''' +name: string @index(exact) . +description: string . +url: string . +''' + op = pydgraph.Operation(schema=schema) + client.alter(op) + + # Mutate + dgraph = { + "name": "Dgraph", + "description": "Scalable, Distributed, Low Latency Graph Database", + "url": "https://dgraph.io" + } + txn = client.txn() + try: + txn.mutate(set_obj=dgraph) + txn.commit() + finally: + txn.discard() + + # Query + res = client.txn(read_only=True).query(''' +query dgraph($name: string) { + data(func: eq(name, $name)) { + uid + name + description + url + } +} +''', variables={"$name": "Dgraph"}) + print(res.json); + +if __name__ == '__main__': + main() From 12ddde620f16a981848fff5e2a6bb7e7e0b48038 Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Mon, 16 Mar 2020 22:30:16 +0000 Subject: [PATCH 168/435] Fix tests now that we do indexing in background (#120) --- README.md | 8 ++++++++ examples/simple/README.md | 4 ++-- examples/simple/requirements.txt | 2 +- examples/simple/simple.py | 6 ++++-- examples/tls/tls_example.py | 1 + pydgraph/util.py | 31 +++++++++++++++++++++++++++++++ tests/helper.py | 10 +++++++++- tests/test_acct_upsert.py | 4 ++++ tests/test_acl.py | 7 +++++-- tests/test_queries.py | 1 + tests/test_txn.py | 5 +++++ tests/test_type_system.py | 5 +++-- tests/test_upsert_block.py | 3 ++- 13 files changed, 76 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 088291bc..04d50237 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,14 @@ op = pydgraph.Operation(schema=schema) client.alter(op) ``` +Starting Dgraph version `20.3.0`, indexes are computed in the background. +This requires that you wait for indexing to complete before running queries. +You could do that using following code. + +```python +pydgraph.util.wait_for_indexing(client, "name", ["exact"], False, False) +``` + `Operation` contains other fields as well, including drop predicate and drop all. Drop all is useful if you wish to discard all the data, and start from a clean slate, without bringing the instance down. diff --git a/examples/simple/README.md b/examples/simple/README.md index 5db089dc..9631ed0a 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -15,11 +15,11 @@ set up the Dgraph cluster: version: "3.2" services: zero: - image: dgraph/dgraph:v1.1.0 + image: dgraph/dgraph:latest restart: on-failure command: dgraph zero --my=zero:5080 server: - image: dgraph/dgraph:v1.1.0 + image: dgraph/dgraph:latest ports: - 8080:8080 - 9080:9080 diff --git a/examples/simple/requirements.txt b/examples/simple/requirements.txt index 63a374a8..882dc01f 100755 --- a/examples/simple/requirements.txt +++ b/examples/simple/requirements.txt @@ -1 +1 @@ -pydgraph>=1.0a1.0 +pydgraph>=2.0.2 diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 29240240..7e0af482 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -16,7 +16,7 @@ def create_client(client_stub): # Drop All - discard all data and start from a clean slate. def drop_all(client): - return client.alter(pydgraph.Operation(drop_all=True)) + client.alter(pydgraph.Operation(drop_all=True)) # Set schema. @@ -38,7 +38,9 @@ def set_schema(client): dob } """ - return client.alter(pydgraph.Operation(schema=schema)) + client.alter(pydgraph.Operation(schema=schema)) + pydgraph.util.wait_for_indexing(client, "name", ["exact"], False, False) + pydgraph.util.wait_for_indexing(client, "friend", [], False, True) # Create data using JSON. diff --git a/examples/tls/tls_example.py b/examples/tls/tls_example.py index d2f5b88a..b9a26e6c 100644 --- a/examples/tls/tls_example.py +++ b/examples/tls/tls_example.py @@ -34,6 +34,7 @@ def main(): ''' op = pydgraph.Operation(schema=schema) client.alter(op) + pydgraph.util.wait_for_indexing(client, "name", ["exact"], False, False) # Mutate dgraph = { diff --git a/pydgraph/util.py b/pydgraph/util.py index d8af8529..e6d63839 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -14,7 +14,9 @@ """Various utility functions.""" +import json import sys +import time from pydgraph.meta import VERSION @@ -34,3 +36,32 @@ def is_string(string): def is_jwt_expired(exception): return 'Token is expired' in str(exception) + + +def wait_for_indexing(client, pred, tokenizers, count, reverse): + """Waits for indexes to be built.""" + tokenizers.sort() + query = "schema(pred: [{}]){{tokenizer reverse count}}".format(pred) + while True: + resp = client.txn(read_only=True).query(query) + if has_indexes(resp, tokenizers, count, reverse): + break + time.sleep(0.1) + +def has_indexes(resp, tokenizers, count, reverse): + schema = json.loads(resp.json) + if len(schema["schema"]) != 1: + return False + index = schema["schema"][0] + if len(index.get("tokenizer", [])) != len(tokenizers): + return False + if index.get("count", False) != count or index.get("reverse", False) != reverse: + return False + # if no tokenizer is expected + if len(index.get("tokenizer", [])) == 0: + return True + index["tokenizer"].sort() + for i in range(len(tokenizers)): + if tokenizers[i] != index["tokenizer"][i]: + return False + return True diff --git a/tests/helper.py b/tests/helper.py index 9937e6f1..7a567ede 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -17,6 +17,7 @@ __author__ = 'Garvit Pahal ' __maintainer__ = 'Martin Martinez Rivera ' +import time import unittest import pydgraph @@ -58,4 +59,11 @@ def setUp(self): """Sets up the client.""" self.client = create_client(self.TEST_SERVER_ADDR) - self.client.login("groot", "password") + while True: + try: + self.client.login("groot", "password") + break + except Exception as e: + if not "user not found" in str(e): + raise e + time.sleep(0.1) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 06b72683..8af595bb 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -52,6 +52,8 @@ def setUp(self): age: int @index(int) @upsert . when: int . """) + pydgraph.util.wait_for_indexing(self.client, "first", ["term"], False, False) + pydgraph.util.wait_for_indexing(self.client, "last", ["hash"], False, False) def test_account_upsert(self): """Run upserts concurrently.""" @@ -111,6 +113,7 @@ def assert_changes(self, firsts, accounts): def upsert_account(addr, account, success_ctr, retry_ctr): """Runs upsert operation.""" client = helper.create_client(addr) + client.login("groot", "password") query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid @@ -167,6 +170,7 @@ def upsert_account(addr, account, success_ctr, retry_ctr): def upsert_account_upsert_block(addr, account, success_ctr, retry_ctr): """Runs upsert operation.""" client = helper.create_client(addr) + client.login("groot", "password") query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ u as uid diff --git a/tests/test_acl.py b/tests/test_acl.py index 71381fc2..140bb97b 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -23,7 +23,7 @@ import unittest from . import helper - +import pydgraph class TestACL(helper.ClientIntegrationTestCase): user_id = 'alice' @@ -68,6 +68,8 @@ def change_permission(self, permission): bash_command = "dgraph acl -a " + self.server_addr + " mod -g " + self.group_id + \ " -p name -m " + str(permission) + " -x password" self.run_command(bash_command) + # wait for ACL cache to be refreshed. + time.sleep(6) def insert_sample_data(self): txn = self.client.txn() @@ -126,11 +128,12 @@ def try_writing(self, expected): def try_altering(self, expected): try: helper.set_schema(self.alice_client, 'name: string @index(exact, term) .') + pydgraph.util.wait_for_indexing(self.alice_client, "name", ["exact", "term"], False, False) if not expected: self.fail("Acl test failed: Alter successful without permission") except Exception as e: if expected: - self.fail("Acl test failed: Alter failed for altreble predicate.\n" + str(e)) + self.fail("Acl test failed: Alter failed for alterable predicate.\n" + str(e)) def suite(): diff --git a/tests/test_queries.py b/tests/test_queries.py index 82d5bf51..b9873151 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -35,6 +35,7 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(term) .') + pydgraph.util.wait_for_indexing(self.client, "name", ["term"], False, False) def test_mutation_and_query(self): """Runs mutation and verifies queries see the results.""" diff --git a/tests/test_txn.py b/tests/test_txn.py index 0606d9b2..36ba02fc 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -30,6 +30,7 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(fulltext) @upsert .') + pydgraph.util.wait_for_indexing(self.client, "name", ["fulltext"], False, False) def test_query_after_commit(self): txn = self.client.txn() @@ -211,6 +212,7 @@ def test_read_from_new_client(self): txn.commit() client2 = helper.create_client(self.TEST_SERVER_ADDR) + client2.login("groot", "password") query = """{{ me(func: uid("{uid:s}")) {{ name @@ -258,6 +260,7 @@ def test_best_effort_txn(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') + pydgraph.util.wait_for_indexing(self.client, "name", ["exact"], False, False) txn = self.client.txn() _ = txn.mutate(set_obj={'name': 'Manish'}) @@ -384,6 +387,7 @@ def test_read_index_key_same_txn(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') + pydgraph.util.wait_for_indexing(self.client, "name", ["exact"], False, False) txn = self.client.txn() response = txn.mutate(set_obj={'name': 'Manish'}) @@ -407,6 +411,7 @@ def test_non_string_variable(self): in an Exception.""" helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') + pydgraph.util.wait_for_indexing(self.client, "name", ["exact"], False, False) txn = self.client.txn() query = """ diff --git a/tests/test_type_system.py b/tests/test_type_system.py index 484c97ae..3db421d5 100644 --- a/tests/test_type_system.py +++ b/tests/test_type_system.py @@ -20,8 +20,8 @@ import logging import unittest -from tests import helper - +from . import helper +import pydgraph class TestTypeSystem(helper.ClientIntegrationTestCase): def setUp(self): @@ -38,6 +38,7 @@ def setUp(self): """ helper.set_schema(self.client, schema) + pydgraph.util.wait_for_indexing(self.client, "name", ["term", "exact"], False, False) def test_type_deletion_failure(self): """It tries to delete all predicates of a node without having any type""" diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index 32b9e7d6..ef6c3661 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -21,7 +21,7 @@ import json from . import helper - +import pydgraph class TestUpsertBlock(helper.ClientIntegrationTestCase): """Tests for Upsert Block""" @@ -30,6 +30,7 @@ def setUp(self): super(TestUpsertBlock, self).setUp() helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(term) @upsert .') + pydgraph.util.wait_for_indexing(self.client, "name", ["term"], False, False) def test_upsert_block_one_mutation(self): txn = self.client.txn() From bf2e074e6c6202db4c5a1f4163e9d473b95ca4b4 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 24 Mar 2020 14:10:26 -0700 Subject: [PATCH 169/435] Release new minor version with new protobuf changes. (#121) --- CHANGELOG.md | 5 +++++ pydgraph/meta.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05cf3f92..f850b42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v2.0.3] - 2020-03-24 + +### Added +- Updated protobufs to latest version. + ## [v2.0.2] - 2019-09-10 ### Added diff --git a/pydgraph/meta.py b/pydgraph/meta.py index c346705c..47a80ea9 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '2.0.2' +VERSION = '2.0.3' From f0406af89628de82882877b7b4692c7b77f2772f Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 24 Mar 2020 14:46:27 -0700 Subject: [PATCH 170/435] Use markdown as the description format. (#123) The format of the README file must be specified as markdown or pypi will reject the package. --- setup.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 65c9a7ee..480881ef 100755 --- a/setup.py +++ b/setup.py @@ -20,15 +20,6 @@ except ImportError: from distutils.core import setup -try: - from pypandoc import convert_file -except ImportError as e: - def convert_file(f, _): - return open(f, 'r').read() -except ModuleNotFoundError as e: - def convert_file(f, _): - return open(f, 'r').read() - from pydgraph.meta import VERSION README = os.path.join(os.path.dirname(__file__), 'README.md') @@ -37,7 +28,8 @@ def convert_file(f, _): name='pydgraph', version=VERSION, description='Official Dgraph client implementation for Python', - long_description=convert_file(README, 'rst'), + long_description=open(README, 'r').read(), + long_description_content_type='text/markdown', license='Apache License, Version 2.0', author='Dgraph Labs', author_email='contact@dgraph.io', From 2d7a4c091f2ef668a901c4a2abae1c1ff5d0633e Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 24 Mar 2020 20:08:38 +0530 Subject: [PATCH 171/435] Revert "Fix tests now that we do indexing in background (#120)" This reverts commit 12ddde620f16a981848fff5e2a6bb7e7e0b48038. --- README.md | 8 -------- examples/simple/README.md | 4 ++-- examples/simple/requirements.txt | 2 +- examples/simple/simple.py | 6 ++---- examples/tls/tls_example.py | 1 - pydgraph/util.py | 31 ------------------------------- tests/helper.py | 10 +--------- tests/test_acct_upsert.py | 4 ---- tests/test_acl.py | 7 ++----- tests/test_queries.py | 1 - tests/test_txn.py | 5 ----- tests/test_type_system.py | 5 ++--- tests/test_upsert_block.py | 3 +-- 13 files changed, 11 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 04d50237..088291bc 100644 --- a/README.md +++ b/README.md @@ -90,14 +90,6 @@ op = pydgraph.Operation(schema=schema) client.alter(op) ``` -Starting Dgraph version `20.3.0`, indexes are computed in the background. -This requires that you wait for indexing to complete before running queries. -You could do that using following code. - -```python -pydgraph.util.wait_for_indexing(client, "name", ["exact"], False, False) -``` - `Operation` contains other fields as well, including drop predicate and drop all. Drop all is useful if you wish to discard all the data, and start from a clean slate, without bringing the instance down. diff --git a/examples/simple/README.md b/examples/simple/README.md index 9631ed0a..5db089dc 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -15,11 +15,11 @@ set up the Dgraph cluster: version: "3.2" services: zero: - image: dgraph/dgraph:latest + image: dgraph/dgraph:v1.1.0 restart: on-failure command: dgraph zero --my=zero:5080 server: - image: dgraph/dgraph:latest + image: dgraph/dgraph:v1.1.0 ports: - 8080:8080 - 9080:9080 diff --git a/examples/simple/requirements.txt b/examples/simple/requirements.txt index 882dc01f..63a374a8 100755 --- a/examples/simple/requirements.txt +++ b/examples/simple/requirements.txt @@ -1 +1 @@ -pydgraph>=2.0.2 +pydgraph>=1.0a1.0 diff --git a/examples/simple/simple.py b/examples/simple/simple.py index 7e0af482..29240240 100644 --- a/examples/simple/simple.py +++ b/examples/simple/simple.py @@ -16,7 +16,7 @@ def create_client(client_stub): # Drop All - discard all data and start from a clean slate. def drop_all(client): - client.alter(pydgraph.Operation(drop_all=True)) + return client.alter(pydgraph.Operation(drop_all=True)) # Set schema. @@ -38,9 +38,7 @@ def set_schema(client): dob } """ - client.alter(pydgraph.Operation(schema=schema)) - pydgraph.util.wait_for_indexing(client, "name", ["exact"], False, False) - pydgraph.util.wait_for_indexing(client, "friend", [], False, True) + return client.alter(pydgraph.Operation(schema=schema)) # Create data using JSON. diff --git a/examples/tls/tls_example.py b/examples/tls/tls_example.py index b9a26e6c..d2f5b88a 100644 --- a/examples/tls/tls_example.py +++ b/examples/tls/tls_example.py @@ -34,7 +34,6 @@ def main(): ''' op = pydgraph.Operation(schema=schema) client.alter(op) - pydgraph.util.wait_for_indexing(client, "name", ["exact"], False, False) # Mutate dgraph = { diff --git a/pydgraph/util.py b/pydgraph/util.py index e6d63839..d8af8529 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -14,9 +14,7 @@ """Various utility functions.""" -import json import sys -import time from pydgraph.meta import VERSION @@ -36,32 +34,3 @@ def is_string(string): def is_jwt_expired(exception): return 'Token is expired' in str(exception) - - -def wait_for_indexing(client, pred, tokenizers, count, reverse): - """Waits for indexes to be built.""" - tokenizers.sort() - query = "schema(pred: [{}]){{tokenizer reverse count}}".format(pred) - while True: - resp = client.txn(read_only=True).query(query) - if has_indexes(resp, tokenizers, count, reverse): - break - time.sleep(0.1) - -def has_indexes(resp, tokenizers, count, reverse): - schema = json.loads(resp.json) - if len(schema["schema"]) != 1: - return False - index = schema["schema"][0] - if len(index.get("tokenizer", [])) != len(tokenizers): - return False - if index.get("count", False) != count or index.get("reverse", False) != reverse: - return False - # if no tokenizer is expected - if len(index.get("tokenizer", [])) == 0: - return True - index["tokenizer"].sort() - for i in range(len(tokenizers)): - if tokenizers[i] != index["tokenizer"][i]: - return False - return True diff --git a/tests/helper.py b/tests/helper.py index 7a567ede..9937e6f1 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -17,7 +17,6 @@ __author__ = 'Garvit Pahal ' __maintainer__ = 'Martin Martinez Rivera ' -import time import unittest import pydgraph @@ -59,11 +58,4 @@ def setUp(self): """Sets up the client.""" self.client = create_client(self.TEST_SERVER_ADDR) - while True: - try: - self.client.login("groot", "password") - break - except Exception as e: - if not "user not found" in str(e): - raise e - time.sleep(0.1) + self.client.login("groot", "password") diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 8af595bb..06b72683 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -52,8 +52,6 @@ def setUp(self): age: int @index(int) @upsert . when: int . """) - pydgraph.util.wait_for_indexing(self.client, "first", ["term"], False, False) - pydgraph.util.wait_for_indexing(self.client, "last", ["hash"], False, False) def test_account_upsert(self): """Run upserts concurrently.""" @@ -113,7 +111,6 @@ def assert_changes(self, firsts, accounts): def upsert_account(addr, account, success_ctr, retry_ctr): """Runs upsert operation.""" client = helper.create_client(addr) - client.login("groot", "password") query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid @@ -170,7 +167,6 @@ def upsert_account(addr, account, success_ctr, retry_ctr): def upsert_account_upsert_block(addr, account, success_ctr, retry_ctr): """Runs upsert operation.""" client = helper.create_client(addr) - client.login("groot", "password") query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ u as uid diff --git a/tests/test_acl.py b/tests/test_acl.py index 140bb97b..71381fc2 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -23,7 +23,7 @@ import unittest from . import helper -import pydgraph + class TestACL(helper.ClientIntegrationTestCase): user_id = 'alice' @@ -68,8 +68,6 @@ def change_permission(self, permission): bash_command = "dgraph acl -a " + self.server_addr + " mod -g " + self.group_id + \ " -p name -m " + str(permission) + " -x password" self.run_command(bash_command) - # wait for ACL cache to be refreshed. - time.sleep(6) def insert_sample_data(self): txn = self.client.txn() @@ -128,12 +126,11 @@ def try_writing(self, expected): def try_altering(self, expected): try: helper.set_schema(self.alice_client, 'name: string @index(exact, term) .') - pydgraph.util.wait_for_indexing(self.alice_client, "name", ["exact", "term"], False, False) if not expected: self.fail("Acl test failed: Alter successful without permission") except Exception as e: if expected: - self.fail("Acl test failed: Alter failed for alterable predicate.\n" + str(e)) + self.fail("Acl test failed: Alter failed for altreble predicate.\n" + str(e)) def suite(): diff --git a/tests/test_queries.py b/tests/test_queries.py index b9873151..82d5bf51 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -35,7 +35,6 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(term) .') - pydgraph.util.wait_for_indexing(self.client, "name", ["term"], False, False) def test_mutation_and_query(self): """Runs mutation and verifies queries see the results.""" diff --git a/tests/test_txn.py b/tests/test_txn.py index 36ba02fc..0606d9b2 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -30,7 +30,6 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(fulltext) @upsert .') - pydgraph.util.wait_for_indexing(self.client, "name", ["fulltext"], False, False) def test_query_after_commit(self): txn = self.client.txn() @@ -212,7 +211,6 @@ def test_read_from_new_client(self): txn.commit() client2 = helper.create_client(self.TEST_SERVER_ADDR) - client2.login("groot", "password") query = """{{ me(func: uid("{uid:s}")) {{ name @@ -260,7 +258,6 @@ def test_best_effort_txn(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') - pydgraph.util.wait_for_indexing(self.client, "name", ["exact"], False, False) txn = self.client.txn() _ = txn.mutate(set_obj={'name': 'Manish'}) @@ -387,7 +384,6 @@ def test_read_index_key_same_txn(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') - pydgraph.util.wait_for_indexing(self.client, "name", ["exact"], False, False) txn = self.client.txn() response = txn.mutate(set_obj={'name': 'Manish'}) @@ -411,7 +407,6 @@ def test_non_string_variable(self): in an Exception.""" helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(exact) .') - pydgraph.util.wait_for_indexing(self.client, "name", ["exact"], False, False) txn = self.client.txn() query = """ diff --git a/tests/test_type_system.py b/tests/test_type_system.py index 3db421d5..484c97ae 100644 --- a/tests/test_type_system.py +++ b/tests/test_type_system.py @@ -20,8 +20,8 @@ import logging import unittest -from . import helper -import pydgraph +from tests import helper + class TestTypeSystem(helper.ClientIntegrationTestCase): def setUp(self): @@ -38,7 +38,6 @@ def setUp(self): """ helper.set_schema(self.client, schema) - pydgraph.util.wait_for_indexing(self.client, "name", ["term", "exact"], False, False) def test_type_deletion_failure(self): """It tries to delete all predicates of a node without having any type""" diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index ef6c3661..32b9e7d6 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -21,7 +21,7 @@ import json from . import helper -import pydgraph + class TestUpsertBlock(helper.ClientIntegrationTestCase): """Tests for Upsert Block""" @@ -30,7 +30,6 @@ def setUp(self): super(TestUpsertBlock, self).setUp() helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(term) @upsert .') - pydgraph.util.wait_for_indexing(self.client, "name", ["term"], False, False) def test_upsert_block_one_mutation(self): txn = self.client.txn() From 6bbbaf99387e4d24ce4de30638c186654dddddae Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 24 Mar 2020 20:53:43 +0530 Subject: [PATCH 172/435] Update api.proto and add docs for indexing in background --- README.md | 11 +++ pydgraph/proto/api.proto | 3 + pydgraph/proto/api_pb2.py | 155 ++++++++++++++++++++------------------ 3 files changed, 96 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 088291bc..ad92c3f5 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,17 @@ op = pydgraph.Operation(schema=schema) client.alter(op) ``` +Starting Dgraph version 20.03.0, indexes can be computed in the background. +You can set `run_in_background` field of the `pydgraph.Operation` to `True` +before passing it to the `Alter` function. You can find more details +[here](https://docs.dgraph.io/master/query-language/#indexes-in-background). + +```python +schema = 'name: string @index(exact) .' +op = pydgraph.Operation(schema=schema, run_in_background=True) +client.alter(op) +``` + `Operation` contains other fields as well, including drop predicate and drop all. Drop all is useful if you wish to discard all the data, and start from a clean slate, without bringing the instance down. diff --git a/pydgraph/proto/api.proto b/pydgraph/proto/api.proto index d11e469e..6a486fb1 100644 --- a/pydgraph/proto/api.proto +++ b/pydgraph/proto/api.proto @@ -101,6 +101,9 @@ message Operation { // If drop_op is ATTR or TYPE, drop_value holds the name of the predicate or // type to delete. string drop_value = 5; + + // run indexes in background. + bool run_in_background = 6; } // Worker services. diff --git a/pydgraph/proto/api_pb2.py b/pydgraph/proto/api_pb2.py index 64b892c8..e57d3913 100644 --- a/pydgraph/proto/api_pb2.py +++ b/pydgraph/proto/api_pb2.py @@ -2,6 +2,8 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: api.proto +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -17,8 +19,8 @@ name='api.proto', package='api', syntax='proto3', - serialized_options=b'\n\tio.dgraphB\013DgraphProto', - serialized_pb=b'\n\tapi.proto\x12\x03\x61pi\"\xdb\x01\n\x07Request\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\r\n\x05query\x18\x04 \x01(\t\x12$\n\x04vars\x18\x05 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x11\n\tread_only\x18\x06 \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x07 \x01(\x08\x12 \n\tmutations\x18\x0c \x03(\x0b\x32\r.api.Mutation\x12\x12\n\ncommit_now\x18\r \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x14\n\x04Uids\x12\x0c\n\x04uids\x18\x01 \x03(\t\"\xc8\x01\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1c\n\x03txn\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x03 \x01(\x0b\x32\x0c.api.Latency\x12\x1d\n\x07metrics\x18\x04 \x01(\x0b\x32\x0c.api.Metrics\x12%\n\x04uids\x18\x0c \x03(\x0b\x32\x17.api.Response.UidsEntry\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xad\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\x05 \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x06 \x03(\x0b\x32\n.api.NQuad\x12\x0c\n\x04\x63ond\x18\t \x01(\t\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\"\xb7\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"_\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"x\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\x12\x1b\n\x13\x61ssign_timestamp_ns\x18\x04 \x01(\x04\x12\x10\n\x08total_ns\x18\x05 \x01(\x04\"f\n\x07Metrics\x12+\n\x08num_uids\x18\x01 \x03(\x0b\x32\x19.api.Metrics.NumUidsEntry\x1a.\n\x0cNumUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\xe7\x01\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3' + serialized_options=_b('\n\tio.dgraphB\013DgraphProto'), + serialized_pb=_b('\n\tapi.proto\x12\x03\x61pi\"\xdb\x01\n\x07Request\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\r\n\x05query\x18\x04 \x01(\t\x12$\n\x04vars\x18\x05 \x03(\x0b\x32\x16.api.Request.VarsEntry\x12\x11\n\tread_only\x18\x06 \x01(\x08\x12\x13\n\x0b\x62\x65st_effort\x18\x07 \x01(\x08\x12 \n\tmutations\x18\x0c \x03(\x0b\x32\r.api.Mutation\x12\x12\n\ncommit_now\x18\r \x01(\x08\x1a+\n\tVarsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x14\n\x04Uids\x12\x0c\n\x04uids\x18\x01 \x03(\t\"\xc8\x01\n\x08Response\x12\x0c\n\x04json\x18\x01 \x01(\x0c\x12\x1c\n\x03txn\x18\x02 \x01(\x0b\x32\x0f.api.TxnContext\x12\x1d\n\x07latency\x18\x03 \x01(\x0b\x32\x0c.api.Latency\x12\x1d\n\x07metrics\x18\x04 \x01(\x0b\x32\x0c.api.Metrics\x12%\n\x04uids\x18\x0c \x03(\x0b\x32\x17.api.Response.UidsEntry\x1a+\n\tUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xad\x01\n\x08Mutation\x12\x10\n\x08set_json\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65lete_json\x18\x02 \x01(\x0c\x12\x12\n\nset_nquads\x18\x03 \x01(\x0c\x12\x12\n\ndel_nquads\x18\x04 \x01(\x0c\x12\x17\n\x03set\x18\x05 \x03(\x0b\x32\n.api.NQuad\x12\x17\n\x03\x64\x65l\x18\x06 \x03(\x0b\x32\n.api.NQuad\x12\x0c\n\x04\x63ond\x18\t \x01(\t\x12\x12\n\ncommit_now\x18\x0e \x01(\x08\"\xd2\x01\n\tOperation\x12\x0e\n\x06schema\x18\x01 \x01(\t\x12\x11\n\tdrop_attr\x18\x02 \x01(\t\x12\x10\n\x08\x64rop_all\x18\x03 \x01(\x08\x12&\n\x07\x64rop_op\x18\x04 \x01(\x0e\x32\x15.api.Operation.DropOp\x12\x12\n\ndrop_value\x18\x05 \x01(\t\x12\x19\n\x11run_in_background\x18\x06 \x01(\x08\"9\n\x06\x44ropOp\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03\x41LL\x10\x01\x12\x08\n\x04\x44\x41TA\x10\x02\x12\x08\n\x04\x41TTR\x10\x03\x12\x08\n\x04TYPE\x10\x04\"\x17\n\x07Payload\x12\x0c\n\x04\x44\x61ta\x18\x01 \x01(\x0c\"_\n\nTxnContext\x12\x10\n\x08start_ts\x18\x01 \x01(\x04\x12\x11\n\tcommit_ts\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x62orted\x18\x03 \x01(\x08\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\r\n\x05preds\x18\x05 \x03(\t\"\x07\n\x05\x43heck\"\x16\n\x07Version\x12\x0b\n\x03tag\x18\x01 \x01(\t\"x\n\x07Latency\x12\x12\n\nparsing_ns\x18\x01 \x01(\x04\x12\x15\n\rprocessing_ns\x18\x02 \x01(\x04\x12\x13\n\x0b\x65ncoding_ns\x18\x03 \x01(\x04\x12\x1b\n\x13\x61ssign_timestamp_ns\x18\x04 \x01(\x04\x12\x10\n\x08total_ns\x18\x05 \x01(\x04\"f\n\x07Metrics\x12+\n\x08num_uids\x18\x01 \x03(\x0b\x32\x19.api.Metrics.NumUidsEntry\x1a.\n\x0cNumUidsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"\x99\x01\n\x05NQuad\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x11\n\tpredicate\x18\x02 \x01(\t\x12\x11\n\tobject_id\x18\x03 \x01(\t\x12 \n\x0cobject_value\x18\x04 \x01(\x0b\x32\n.api.Value\x12\r\n\x05label\x18\x05 \x01(\t\x12\x0c\n\x04lang\x18\x06 \x01(\t\x12\x1a\n\x06\x66\x61\x63\x65ts\x18\x07 \x03(\x0b\x32\n.api.Facet\"\xf4\x01\n\x05Value\x12\x15\n\x0b\x64\x65\x66\x61ult_val\x18\x01 \x01(\tH\x00\x12\x13\n\tbytes_val\x18\x02 \x01(\x0cH\x00\x12\x11\n\x07int_val\x18\x03 \x01(\x03H\x00\x12\x12\n\x08\x62ool_val\x18\x04 \x01(\x08H\x00\x12\x11\n\x07str_val\x18\x05 \x01(\tH\x00\x12\x14\n\ndouble_val\x18\x06 \x01(\x01H\x00\x12\x11\n\x07geo_val\x18\x07 \x01(\x0cH\x00\x12\x12\n\x08\x64\x61te_val\x18\x08 \x01(\x0cH\x00\x12\x16\n\x0c\x64\x61tetime_val\x18\t \x01(\x0cH\x00\x12\x16\n\x0cpassword_val\x18\n \x01(\tH\x00\x12\x11\n\x07uid_val\x18\x0b \x01(\x04H\x00\x42\x05\n\x03val\"\xab\x01\n\x05\x46\x61\x63\x65t\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12$\n\x08val_type\x18\x03 \x01(\x0e\x32\x12.api.Facet.ValType\x12\x0e\n\x06tokens\x18\x04 \x03(\t\x12\r\n\x05\x61lias\x18\x05 \x01(\t\"A\n\x07ValType\x12\n\n\x06STRING\x10\x00\x12\x07\n\x03INT\x10\x01\x12\t\n\x05\x46LOAT\x10\x02\x12\x08\n\x04\x42OOL\x10\x03\x12\x0c\n\x08\x44\x41TETIME\x10\x04\"G\n\x0cLoginRequest\x12\x0e\n\x06userid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rrefresh_token\x18\x03 \x01(\t\".\n\x03Jwt\x12\x12\n\naccess_jwt\x18\x01 \x01(\t\x12\x13\n\x0brefresh_jwt\x18\x02 \x01(\t2\xe7\x01\n\x06\x44graph\x12+\n\x05Login\x12\x11.api.LoginRequest\x1a\r.api.Response\"\x00\x12&\n\x05Query\x12\x0c.api.Request\x1a\r.api.Response\"\x00\x12\'\n\x05\x41lter\x12\x0e.api.Operation\x1a\x0c.api.Payload\"\x00\x12\x33\n\rCommitOrAbort\x12\x0f.api.TxnContext\x1a\x0f.api.TxnContext\"\x00\x12*\n\x0c\x43heckVersion\x12\n.api.Check\x1a\x0c.api.Version\"\x00\x42\x18\n\tio.dgraphB\x0b\x44graphProtob\x06proto3') ) @@ -52,8 +54,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=768, - serialized_end=825, + serialized_start=795, + serialized_end=852, ) _sym_db.RegisterEnumDescriptor(_OPERATION_DROPOP) @@ -86,8 +88,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1718, - serialized_end=1783, + serialized_start=1745, + serialized_end=1810, ) _sym_db.RegisterEnumDescriptor(_FACET_VALTYPE) @@ -102,14 +104,14 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Request.VarsEntry.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Request.VarsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -119,7 +121,7 @@ nested_types=[], enum_types=[ ], - serialized_options=b'8\001', + serialized_options=_b('8\001'), is_extendable=False, syntax='proto3', extension_ranges=[], @@ -146,7 +148,7 @@ _descriptor.FieldDescriptor( name='query', full_name='api.Request.query', index=1, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -243,14 +245,14 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Response.UidsEntry.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Response.UidsEntry.value', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -260,7 +262,7 @@ nested_types=[], enum_types=[ ], - serialized_options=b'8\001', + serialized_options=_b('8\001'), is_extendable=False, syntax='proto3', extension_ranges=[], @@ -280,7 +282,7 @@ _descriptor.FieldDescriptor( name='json', full_name='api.Response.json', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -339,28 +341,28 @@ _descriptor.FieldDescriptor( name='set_json', full_name='api.Mutation.set_json', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='delete_json', full_name='api.Mutation.delete_json', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='set_nquads', full_name='api.Mutation.set_nquads', index=2, number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='del_nquads', full_name='api.Mutation.del_nquads', index=3, number=4, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -381,7 +383,7 @@ _descriptor.FieldDescriptor( name='cond', full_name='api.Mutation.cond', index=6, number=9, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -419,14 +421,14 @@ _descriptor.FieldDescriptor( name='schema', full_name='api.Operation.schema', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='drop_attr', full_name='api.Operation.drop_attr', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -447,7 +449,14 @@ _descriptor.FieldDescriptor( name='drop_value', full_name='api.Operation.drop_value', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='run_in_background', full_name='api.Operation.run_in_background', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -465,7 +474,7 @@ oneofs=[ ], serialized_start=642, - serialized_end=825, + serialized_end=852, ) @@ -479,7 +488,7 @@ _descriptor.FieldDescriptor( name='Data', full_name='api.Payload.Data', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -495,8 +504,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=827, - serialized_end=850, + serialized_start=854, + serialized_end=877, ) @@ -554,8 +563,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=852, - serialized_end=947, + serialized_start=879, + serialized_end=974, ) @@ -578,8 +587,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=949, - serialized_end=956, + serialized_start=976, + serialized_end=983, ) @@ -593,7 +602,7 @@ _descriptor.FieldDescriptor( name='tag', full_name='api.Version.tag', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -609,8 +618,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=958, - serialized_end=980, + serialized_start=985, + serialized_end=1007, ) @@ -668,8 +677,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=982, - serialized_end=1102, + serialized_start=1009, + serialized_end=1129, ) @@ -683,7 +692,7 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Metrics.NumUidsEntry.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -700,14 +709,14 @@ nested_types=[], enum_types=[ ], - serialized_options=b'8\001', + serialized_options=_b('8\001'), is_extendable=False, syntax='proto3', extension_ranges=[], oneofs=[ ], - serialized_start=1160, - serialized_end=1206, + serialized_start=1187, + serialized_end=1233, ) _METRICS = _descriptor.Descriptor( @@ -736,8 +745,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1104, - serialized_end=1206, + serialized_start=1131, + serialized_end=1233, ) @@ -751,21 +760,21 @@ _descriptor.FieldDescriptor( name='subject', full_name='api.NQuad.subject', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='predicate', full_name='api.NQuad.predicate', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='object_id', full_name='api.NQuad.object_id', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -779,14 +788,14 @@ _descriptor.FieldDescriptor( name='label', full_name='api.NQuad.label', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='lang', full_name='api.NQuad.lang', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -809,8 +818,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1209, - serialized_end=1362, + serialized_start=1236, + serialized_end=1389, ) @@ -824,14 +833,14 @@ _descriptor.FieldDescriptor( name='default_val', full_name='api.Value.default_val', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='bytes_val', full_name='api.Value.bytes_val', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -852,7 +861,7 @@ _descriptor.FieldDescriptor( name='str_val', full_name='api.Value.str_val', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -866,28 +875,28 @@ _descriptor.FieldDescriptor( name='geo_val', full_name='api.Value.geo_val', index=6, number=7, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='date_val', full_name='api.Value.date_val', index=7, number=8, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='datetime_val', full_name='api.Value.datetime_val', index=8, number=9, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='password_val', full_name='api.Value.password_val', index=9, number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -913,8 +922,8 @@ name='val', full_name='api.Value.val', index=0, containing_type=None, fields=[]), ], - serialized_start=1365, - serialized_end=1609, + serialized_start=1392, + serialized_end=1636, ) @@ -928,14 +937,14 @@ _descriptor.FieldDescriptor( name='key', full_name='api.Facet.key', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='value', full_name='api.Facet.value', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=b"", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -956,7 +965,7 @@ _descriptor.FieldDescriptor( name='alias', full_name='api.Facet.alias', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -973,8 +982,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1612, - serialized_end=1783, + serialized_start=1639, + serialized_end=1810, ) @@ -988,21 +997,21 @@ _descriptor.FieldDescriptor( name='userid', full_name='api.LoginRequest.userid', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='password', full_name='api.LoginRequest.password', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='refresh_token', full_name='api.LoginRequest.refresh_token', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -1018,8 +1027,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1785, - serialized_end=1856, + serialized_start=1812, + serialized_end=1883, ) @@ -1033,14 +1042,14 @@ _descriptor.FieldDescriptor( name='access_jwt', full_name='api.Jwt.access_jwt', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='refresh_jwt', full_name='api.Jwt.refresh_jwt', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -1056,8 +1065,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1858, - serialized_end=1904, + serialized_start=1885, + serialized_end=1931, ) _REQUEST_VARSENTRY.containing_type = _REQUEST @@ -1277,8 +1286,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=1907, - serialized_end=2138, + serialized_start=1934, + serialized_end=2165, methods=[ _descriptor.MethodDescriptor( name='Login', From b0f90b2f21bdffb8bea66723e5662f0fc694dbba Mon Sep 17 00:00:00 2001 From: Aman Mangal Date: Tue, 24 Mar 2020 21:00:29 +0530 Subject: [PATCH 173/435] Fix failing tests due to recent ACL changes --- examples/simple/README.md | 4 ++-- examples/simple/requirements.txt | 2 +- tests/helper.py | 10 +++++++++- tests/test_acct_upsert.py | 2 ++ tests/test_acl.py | 6 ++++-- tests/test_txn.py | 1 + tests/test_type_system.py | 1 - tests/test_upsert_block.py | 1 - 8 files changed, 19 insertions(+), 8 deletions(-) diff --git a/examples/simple/README.md b/examples/simple/README.md index 5db089dc..9631ed0a 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -15,11 +15,11 @@ set up the Dgraph cluster: version: "3.2" services: zero: - image: dgraph/dgraph:v1.1.0 + image: dgraph/dgraph:latest restart: on-failure command: dgraph zero --my=zero:5080 server: - image: dgraph/dgraph:v1.1.0 + image: dgraph/dgraph:latest ports: - 8080:8080 - 9080:9080 diff --git a/examples/simple/requirements.txt b/examples/simple/requirements.txt index 63a374a8..882dc01f 100755 --- a/examples/simple/requirements.txt +++ b/examples/simple/requirements.txt @@ -1 +1 @@ -pydgraph>=1.0a1.0 +pydgraph>=2.0.2 diff --git a/tests/helper.py b/tests/helper.py index 9937e6f1..7a567ede 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -17,6 +17,7 @@ __author__ = 'Garvit Pahal ' __maintainer__ = 'Martin Martinez Rivera ' +import time import unittest import pydgraph @@ -58,4 +59,11 @@ def setUp(self): """Sets up the client.""" self.client = create_client(self.TEST_SERVER_ADDR) - self.client.login("groot", "password") + while True: + try: + self.client.login("groot", "password") + break + except Exception as e: + if not "user not found" in str(e): + raise e + time.sleep(0.1) diff --git a/tests/test_acct_upsert.py b/tests/test_acct_upsert.py index 06b72683..b79a2f6a 100644 --- a/tests/test_acct_upsert.py +++ b/tests/test_acct_upsert.py @@ -111,6 +111,7 @@ def assert_changes(self, firsts, accounts): def upsert_account(addr, account, success_ctr, retry_ctr): """Runs upsert operation.""" client = helper.create_client(addr) + client.login("groot", "password") query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ uid @@ -167,6 +168,7 @@ def upsert_account(addr, account, success_ctr, retry_ctr): def upsert_account_upsert_block(addr, account, success_ctr, retry_ctr): """Runs upsert operation.""" client = helper.create_client(addr) + client.login("groot", "password") query = """{{ acct(func:eq(first, "{first}")) @filter(eq(last, "{last}") AND eq(age, {age})) {{ u as uid diff --git a/tests/test_acl.py b/tests/test_acl.py index 71381fc2..5ee94367 100644 --- a/tests/test_acl.py +++ b/tests/test_acl.py @@ -23,7 +23,7 @@ import unittest from . import helper - +import pydgraph class TestACL(helper.ClientIntegrationTestCase): user_id = 'alice' @@ -68,6 +68,8 @@ def change_permission(self, permission): bash_command = "dgraph acl -a " + self.server_addr + " mod -g " + self.group_id + \ " -p name -m " + str(permission) + " -x password" self.run_command(bash_command) + # wait for ACL cache to be refreshed. + time.sleep(6) def insert_sample_data(self): txn = self.client.txn() @@ -130,7 +132,7 @@ def try_altering(self, expected): self.fail("Acl test failed: Alter successful without permission") except Exception as e: if expected: - self.fail("Acl test failed: Alter failed for altreble predicate.\n" + str(e)) + self.fail("Acl test failed: Alter failed for alterable predicate.\n" + str(e)) def suite(): diff --git a/tests/test_txn.py b/tests/test_txn.py index 0606d9b2..645ac07f 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -211,6 +211,7 @@ def test_read_from_new_client(self): txn.commit() client2 = helper.create_client(self.TEST_SERVER_ADDR) + client2.login("groot", "password") query = """{{ me(func: uid("{uid:s}")) {{ name diff --git a/tests/test_type_system.py b/tests/test_type_system.py index 484c97ae..b46c41ba 100644 --- a/tests/test_type_system.py +++ b/tests/test_type_system.py @@ -22,7 +22,6 @@ from tests import helper - class TestTypeSystem(helper.ClientIntegrationTestCase): def setUp(self): super(TestTypeSystem, self).setUp() diff --git a/tests/test_upsert_block.py b/tests/test_upsert_block.py index 32b9e7d6..08f4472e 100644 --- a/tests/test_upsert_block.py +++ b/tests/test_upsert_block.py @@ -22,7 +22,6 @@ from . import helper - class TestUpsertBlock(helper.ClientIntegrationTestCase): """Tests for Upsert Block""" From a87e1dae7006e4342aa5c1f909bb279a80a8e013 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 31 Mar 2020 17:19:19 -0700 Subject: [PATCH 174/435] Change version and add changelog. (#125) --- CHANGELOG.md | 9 +++++++++ pydgraph/meta.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f850b42a..10755ece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +=================== + +## [v20.03.0] - 2020-03-31 + +Starting with this release, the release number has changed to match the Dgraph release +to make it easier to identify which version of Dgraph a client version supports. + +### Added +- Use RunInBackground flag for computing indexes in background. ## [v2.0.3] - 2020-03-24 diff --git a/pydgraph/meta.py b/pydgraph/meta.py index 47a80ea9..e4d8fbc2 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '2.0.3' +VERSION = '20.03.0' From 5d47a99073fd3124899650fe4f8f5d073cb2b706 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 1 May 2020 11:53:25 -0700 Subject: [PATCH 175/435] Clarify documenation of any_client. (#130) --- pydgraph/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydgraph/client.py b/pydgraph/client.py index ea3b8ae0..61c32252 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -91,7 +91,7 @@ def txn(self, read_only=False, best_effort=False): return txn.Txn(self, read_only=read_only, best_effort=best_effort) def any_client(self): - """Returns a random client.""" + """Returns a random gRPC client so that requests are distributed evenly among them.""" return random.choice(self._clients) def add_login_metadata(self, metadata): From c37feffad2d7a01121cbafdbb5df87603d859b51 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Fri, 1 May 2020 14:54:43 -0700 Subject: [PATCH 176/435] Expose check_version client call. (#131) The check_version call is in the client_stub but has not been exposed in the client. Fixes #128 --- pydgraph/client.py | 22 ++++++++++++++++++++++ tests/test_queries.py | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/pydgraph/client.py b/pydgraph/client.py index 61c32252..11e721dd 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -41,6 +41,28 @@ def __init__(self, *clients): self._jwt = api.Jwt() self._login_metadata = [] + def check_version(self, timeout=None, metadata=None, credentials=None): + """Returns the version of Dgraph if the server is ready to accept requests.""" + + new_metadata = self.add_login_metadata(metadata) + check_req = api.Check() + + try: + response = self.any_client().check_version(check_req, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + return response.tag + except Exception as error: + if util.is_jwt_expired(error): + self.retry_login() + new_metadata = self.add_login_metadata(metadata) + response = self.any_client().check_version(check_req, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + return response.tag + else: + raise error + def login(self, userid, password, timeout=None, metadata=None, credentials=None): login_req = api.LoginRequest() diff --git a/tests/test_queries.py b/tests/test_queries.py index 82d5bf51..f5a375ee 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -36,6 +36,18 @@ def setUp(self): helper.drop_all(self.client) helper.set_schema(self.client, 'name: string @index(term) .') + def test_check_version(self): + """Verifies the check_version method correctly returns the cluster version""" + success = 0 + for i in range(3): + try: + tag = self.client.check_version() + self.assertGreater(len(tag), 0) + success += 1 + except Exception as e: + continue + self.assertGreater(success, 0) + def test_mutation_and_query(self): """Runs mutation and verifies queries see the results.""" From 3c4f38535e7f254841bb76549696108fcdf16dfb Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 1 Jun 2020 15:51:49 -0700 Subject: [PATCH 177/435] Improve readme. (#137) Mentioned changes to the import of the protobuf and clarify testing dependency on Dgraph. --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ad92c3f5..f95c1cf3 100644 --- a/README.md +++ b/README.md @@ -408,16 +408,25 @@ command: python scripts/protogen.py ``` +The generated file `api_pb2_grpc.py` needs to be changed in recent versions of python. +The required change is outlined below as a diff. + +``` +-import api_pb2 as api__pb2 ++from . import api_pb2 as api__pb2 +``` + ### Running tests To run the tests in your local machine, you can run the script `scripts/local-tests.sh`. This script assumes Dgraph and dgo (Go client) are -already built on the local machine. The script will take care of bringing up a -Dgraph cluster and bringing it down after the tests are executed. The script -uses the port 9180 by default to prevent interference with clusters running on -the default port. Docker and docker-compose need to be installed before running -the script. Refer to the official Docker documentation for instructions on how -to install those packages. +already built on the local machine and that their code is in `$GOPATH/src`. + +The script will take care of bringing up a Dgraph cluster and bringing it down +after the tests are executed. The script uses the port 9180 by default to +prevent interference with clusters running on the default port. Docker and +docker-compose need to be installed before running the script. Refer to the +official Docker documentation for instructions on how to install those packages. The `test.sh` script downloads and installs Dgraph. It is meant for use by our CI systems and using it for local development is not recommended. From b57ea480ab5c6b025c18e8bca8392f9b446cd5e7 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 1 Jun 2020 16:12:40 -0700 Subject: [PATCH 178/435] Stop cluster if running tests fails. (#138) --- scripts/local-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/local-test.sh b/scripts/local-test.sh index 5965e7f8..af171502 100755 --- a/scripts/local-test.sh +++ b/scripts/local-test.sh @@ -14,6 +14,6 @@ pip install coveralls pushd $(dirname $SRCDIR) source $GOPATH/src/github.com/dgraph-io/dgraph/contrib/scripts/functions.sh restartCluster -coverage run --source=pydgraph --omit=pydgraph/proto/* setup.py test +coverage run --source=pydgraph --omit=pydgraph/proto/* setup.py test || stopCluster stopCluster popd From 24cdccafb2963b88ad4bf9928d27a42130fa941a Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 1 Jun 2020 16:13:46 -0700 Subject: [PATCH 179/435] Update requirements.txt to include all required dependencies. (#140) --- requirements.txt | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 80f4e682..b15aa68f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,28 @@ -grpcio>=1.18.0 -protobuf>=3.6.1 +attrs==19.3.0 +bcrypt==3.1.7 +cached-property==1.5.1 +certifi==2020.4.5.1 +cffi==1.14.0 +chardet==3.0.4 +coverage==5.1 +coveralls==2.0.0 +cryptography==2.9.2 +docker==4.2.0 +docker-compose==1.25.5 +dockerpty==0.4.1 +docopt==0.6.2 +grpcio==1.29.0 +grpcio-tools==1.29.0 +idna==2.9 +jsonschema==3.2.0 +paramiko==2.7.1 +protobuf==3.12.2 +pycparser==2.20 +PyNaCl==1.4.0 +pyrsistent==0.16.0 +PyYAML==5.3.1 +requests==2.23.0 +six==1.15.0 +texttable==1.6.2 +urllib3==1.25.9 +websocket-client==0.57.0 From 2cfc1f30765a6a736c43bf4ba928b0c1b73c8252 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 1 Jun 2020 16:19:23 -0700 Subject: [PATCH 180/435] Include mention of docker in README test instructions. (#141) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f95c1cf3..8f9cc0aa 100644 --- a/README.md +++ b/README.md @@ -421,6 +421,7 @@ The required change is outlined below as a diff. To run the tests in your local machine, you can run the script `scripts/local-tests.sh`. This script assumes Dgraph and dgo (Go client) are already built on the local machine and that their code is in `$GOPATH/src`. +It also requires that docker and docker-compose are installed in your machine. The script will take care of bringing up a Dgraph cluster and bringing it down after the tests are executed. The script uses the port 9180 by default to From b20db7500c69d5207c48156625b9117388b1eb51 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 1 Jun 2020 16:37:59 -0700 Subject: [PATCH 181/435] Better error handling. (#135) This change adds more custom error types for connection errors and retriable errors. It also refactors some of the code to avoid duplication. --- pydgraph/errors.py | 23 +++++++++++++++++++++++ pydgraph/txn.py | 33 ++++++++++++++++----------------- pydgraph/util.py | 22 +++++++++++++++++++++- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/pydgraph/errors.py b/pydgraph/errors.py index da453e2a..10bf25cd 100644 --- a/pydgraph/errors.py +++ b/pydgraph/errors.py @@ -28,3 +28,26 @@ class AbortedError(Exception): def __init__(self): super(AbortedError, self).__init__( 'Transaction has been aborted. Please retry') + +class RetriableError(Exception): + """Error thrown when the error return by Dgraph indicates the op should be retried.""" + + def __init__(self, exception): + self.exception = exception + + def __str__(self): + return str(self.exception) + +class ConnectionError(Exception): + """Error thrown when the error return when the client has trouble connecting to Dgraph.""" + def __init__(self, exception): + self.exception = exception + + def __str__(self): + return str(self.exception) + +class TransactionError(Exception): + """Error thrown when the transaction is invalid (e.g trying to mutate in read-only mode).""" + def __init__(self, msg): + super(TransactionError, self).__init__(msg) + diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 4d486f61..1f530c40 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -73,11 +73,11 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, def do_request(self, request, timeout=None, metadata=None, credentials=None): """Executes a query/mutate operation on the server.""" if self._finished: - raise Exception('Transaction has already been committed or discarded') + raise errors.TransactionError('Transaction has already been committed or discarded') if len(request.mutations) > 0: if self._read_only: - raise Exception('Readonly transaction cannot run mutations') + raise errors.TransactionError('Readonly transaction cannot run mutations') self._mutated = True new_metadata = self._dg.add_login_metadata(metadata) @@ -141,7 +141,7 @@ def create_request(self, query=None, variables=None, mutations=None, commit_now= if util.is_string(key) and util.is_string(value): request.vars[key] = value else: - raise Exception('Values and keys in variable map must be strings') + raise errors.TransactionError('Values and keys in variable map must be strings') if query: request.query = query.encode('utf8') if mutations: @@ -150,12 +150,14 @@ def create_request(self, query=None, variables=None, mutations=None, commit_now= @staticmethod def _common_except_mutate(error): - if isinstance(error, grpc._channel._Rendezvous): - error.details() - status_code = error.code() - if (status_code == grpc.StatusCode.ABORTED or - status_code == grpc.StatusCode.FAILED_PRECONDITION): - raise errors.AbortedError() + if util.is_aborted_error(error): + raise errors.AbortedError() + + if util.is_retriable_error(error): + raise errors.RetriableError(error) + + if util.is_connection_error(error): + raise errors.ConnectionError(error) raise error @@ -184,10 +186,10 @@ def commit(self, timeout=None, metadata=None, credentials=None): def _common_commit(self): if self._read_only: - raise Exception( + raise errors.TransactionError( 'Readonly transaction cannot run mutations or be committed') if self._finished: - raise Exception( + raise errors.TransactionError( 'Transaction has already been committed or discarded') self._finished = True @@ -195,11 +197,8 @@ def _common_commit(self): @staticmethod def _common_except_commit(error): - if isinstance(error, grpc._channel._Rendezvous): - error.details() - status_code = error.code() - if status_code == grpc.StatusCode.ABORTED: - raise errors.AbortedError() + if util.is_aborted_error(error): + raise errors.AbortedError() raise error @@ -245,7 +244,7 @@ def merge_context(self, src=None): self._ctx.start_ts = src.start_ts elif self._ctx.start_ts != src.start_ts: # This condition should never be true. - raise Exception('StartTs mismatch') + raise errors.TransactionError('StartTs mismatch') self._ctx.keys.extend(src.keys) self._ctx.preds.extend(src.preds) diff --git a/pydgraph/util.py b/pydgraph/util.py index d8af8529..e8b97e57 100644 --- a/pydgraph/util.py +++ b/pydgraph/util.py @@ -14,6 +14,7 @@ """Various utility functions.""" +import grpc import sys from pydgraph.meta import VERSION @@ -31,6 +32,25 @@ def is_string(string): return isinstance(string, str) - def is_jwt_expired(exception): return 'Token is expired' in str(exception) + +def is_aborted_error(error): + """Returns true if the error is due to an aborted transaction.""" + if isinstance(error, grpc._channel._Rendezvous) or \ + isinstance(error, grpc._channel._InactiveRpcError): + status_code = error.code() + if (status_code == grpc.StatusCode.ABORTED or + status_code == grpc.StatusCode.FAILED_PRECONDITION): + return True + return False + +def is_retriable_error(error): + """Returns true if the error is retriable (e.g server is not ready yet).""" + msg = str(error) + return 'Please retry' in msg or 'opIndexing is already running' in msg + +def is_connection_error(error): + """Returns true if the error is caused connection issues.""" + msg = str(error) + return 'Unhealthy connection' in msg or 'No connection exists' in msg From fd02938c68fcf2589959690c48be0c4739b4e3d4 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Mon, 1 Jun 2020 17:38:17 -0700 Subject: [PATCH 182/435] Revert "Update requirements.txt to include all required dependencies. (#140)" (#142) This reverts commit 24cdccafb2963b88ad4bf9928d27a42130fa941a. --- requirements.txt | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/requirements.txt b/requirements.txt index b15aa68f..80f4e682 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,28 +1,2 @@ -attrs==19.3.0 -bcrypt==3.1.7 -cached-property==1.5.1 -certifi==2020.4.5.1 -cffi==1.14.0 -chardet==3.0.4 -coverage==5.1 -coveralls==2.0.0 -cryptography==2.9.2 -docker==4.2.0 -docker-compose==1.25.5 -dockerpty==0.4.1 -docopt==0.6.2 -grpcio==1.29.0 -grpcio-tools==1.29.0 -idna==2.9 -jsonschema==3.2.0 -paramiko==2.7.1 -protobuf==3.12.2 -pycparser==2.20 -PyNaCl==1.4.0 -pyrsistent==0.16.0 -PyYAML==5.3.1 -requests==2.23.0 -six==1.15.0 -texttable==1.6.2 -urllib3==1.25.9 -websocket-client==0.57.0 +grpcio>=1.18.0 +protobuf>=3.6.1 From 9e3d448176a3128130fd2af3e4a66fbae4b34365 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 2 Jun 2020 13:27:17 -0700 Subject: [PATCH 183/435] Async client methods (#132) --- README.md | 49 +++++++++++++++++++++++++++++++- pydgraph/client.py | 7 +++++ pydgraph/client_stub.py | 11 +++++++ pydgraph/txn.py | 63 +++++++++++++++++++++++++++++++++++++++++ tests/test_async.py | 61 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 tests/test_async.py diff --git a/README.md b/README.md index 8f9cc0aa..76d16da4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ using [grpc]. This client follows the [Dgraph Go client][goclient] closely. [goclient]: https://github.com/dgraph-io/dgo - Before using this client, we highly recommend that you go through [docs.dgraph.io], and understand how to run and work with Dgraph. @@ -383,6 +382,54 @@ For example, the following alters the schema with a timeout of ten seconds: A `CallCredentials` object can be passed to the `login`, `alter`, `query`, and `mutate` methods using the `credentials` keyword argument. +### Async methods. + +The `alter` method in the client has an asyncronous version called +`async_alter`. The async methods return a future. You can use this future by +retrieving the result with the `result` method on the future object. Below is a +short example: + +``` +alter_future = self.client.async_alter(pydgraph.Operation( + schema="name: string @index(term) .")) +response = alter_future.result() +``` + +The `query` and `mutate` methods int the `Txn` class also have async versions +called `async_query` and `async_mutation` respectively. These functions work +just like `async_alter`. + +However, retrieving the result is a little different because some extra +processing needs to be done. You can use the `handle_query_future` and +`handle_mutate_future` static methods in the `Txn` class to retrieve the result. +A short example is given below: + +``` +txn = client.txn() +query = "query body here" +future = txn.async_query() +response = pydgraph.Txn.handle_query_future(future) +``` + +A working example can be found in the `test_asycn.py` test file. + +Keep in mind that due to the nature of async calls, the async functions cannot +retry the request if the login is invalid. You will have to check for this error +and retry the login (with the function `retry_login` in both the `Txn` and +`Client` classes). A short example is given below: + +```python +client = DgraphClient(client_stubs) # client_stubs is a list of gRPC stubs. +alter_future = client.async_alter() +try: + response = alter_future.result() +except Exception as e: + # You can use this function in the util package to check for JWT + # expired errors. + if pydgraph.util.is_jwt_expired(e): + # retry your request here. +``` + ## Examples - [simple][]: Quickstart example of using pydgraph. diff --git a/pydgraph/client.py b/pydgraph/client.py index 11e721dd..a538f75b 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -108,6 +108,13 @@ def alter(self, operation, timeout=None, metadata=None, credentials=None): else: raise error + def async_alter(self, operation, timeout=None, metadata=None, credentials=None): + """The async version of alter.""" + new_metadata = self.add_login_metadata(metadata) + return self.any_client().async_alter(operation, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + def txn(self, read_only=False, best_effort=False): """Creates a transaction.""" return txn.Txn(self, read_only=read_only, best_effort=best_effort) diff --git a/pydgraph/client_stub.py b/pydgraph/client_stub.py index c8cfe883..28c76908 100644 --- a/pydgraph/client_stub.py +++ b/pydgraph/client_stub.py @@ -45,11 +45,22 @@ def alter(self, operation, timeout=None, metadata=None, credentials=None): return self.stub.Alter(operation, timeout=timeout, metadata=metadata, credentials=credentials) + def async_alter(self, operation, timeout=None, metadata=None, credentials=None): + """Async version of alter.""" + return self.stub.Alter.future(operation, timeout=timeout, metadata=metadata, + credentials=credentials) + + def query(self, req, timeout=None, metadata=None, credentials=None): """Runs query or mutate operation.""" return self.stub.Query(req, timeout=timeout, metadata=metadata, credentials=credentials) + def async_query(self, req, timeout=None, metadata=None, credentials=None): + """Async version of query.""" + return self.stub.Query.future(req, timeout=timeout, metadata=metadata, + credentials=credentials) + def commit_or_abort(self, ctx, timeout=None, metadata=None, credentials=None): """Runs commit or abort operation.""" diff --git a/pydgraph/txn.py b/pydgraph/txn.py index 1f530c40..00473c56 100644 --- a/pydgraph/txn.py +++ b/pydgraph/txn.py @@ -61,6 +61,11 @@ def query(self, query, variables=None, timeout=None, metadata=None, credentials= req = self.create_request(query=query, variables=variables) return self.do_request(req, timeout=timeout, metadata=metadata, credentials=credentials) + def async_query(self, query, variables=None, timeout=None, metadata=None, credentials=None): + """Async version of query.""" + req = self.create_request(query=query, variables=variables) + return self.async_do_request(req, timeout=timeout, metadata=metadata, credentials=credentials) + def mutate(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, cond=None, commit_now=None, timeout=None, metadata=None, credentials=None): @@ -70,6 +75,16 @@ def mutate(self, mutation=None, set_obj=None, del_obj=None, req = self.create_request(mutations=[mutation], commit_now=commit_now) return self.do_request(req, timeout=timeout, metadata=metadata, credentials=credentials) + def async_mutate(self, mutation=None, set_obj=None, del_obj=None, + set_nquads=None, del_nquads=None, cond=None, commit_now=None, + timeout=None, metadata=None, credentials=None): + """Async version of mutate.""" + mutation = self.create_mutation(mutation, set_obj, del_obj, set_nquads, del_nquads, cond) + commit_now = commit_now or mutation.commit_now + req = self.create_request(mutations=[mutation], commit_now=commit_now) + return self.async_do_request(req, timeout=timeout, metadata=metadata, + credentials=credentials) + def do_request(self, request, timeout=None, metadata=None, credentials=None): """Executes a query/mutate operation on the server.""" if self._finished: @@ -115,6 +130,51 @@ def do_request(self, request, timeout=None, metadata=None, credentials=None): self.merge_context(response.txn) return response + def async_do_request(self, request, timeout=None, metadata=None, credentials=None): + """Async version of do_request.""" + if self._finished: + raise Exception('Transaction has already been committed or discarded') + + if len(request.mutations) > 0: + if self._read_only: + raise Exception('Readonly transaction cannot run mutations') + self._mutated = True + + new_metadata = self._dg.add_login_metadata(metadata) + return self._dc.async_query(request, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + + @staticmethod + def handle_query_future(future): + """Method to call when getting the result of a future returned by async_query""" + try: + response = future.result() + except Exception as error: + txn._common_except_mutate(error) + + return response + + + @staticmethod + def handle_mutate_future(txn, future, commit_now): + """Method to call when getting the result of a future returned by async_mutate""" + try: + response = future.result() + except Exception as error: + try: + txn.discard(timeout=timeout, metadata=metadata, credentials=credentials) + except: + # Ignore error - user should see the original error. + pass + txn._common_except_mutate(error) + + if commit_now: + txn._finished = True + + txn.merge_context(response.txn) + return response + def create_mutation(self, mutation=None, set_obj=None, del_obj=None, set_nquads=None, del_nquads=None, cond=None): if not mutation: @@ -248,3 +308,6 @@ def merge_context(self, src=None): self._ctx.keys.extend(src.keys) self._ctx.preds.extend(src.preds) + + def retry_login(self): + self._dg.retry_login() diff --git a/tests/test_async.py b/tests/test_async.py new file mode 100644 index 00000000..a429a57a --- /dev/null +++ b/tests/test_async.py @@ -0,0 +1,61 @@ +# Copyright 2020 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests to verify async client methods.""" + +__author__ = 'Martin Martinez Rivera ' +__maintainer__ = 'Martin Martinez Rivera ' + +import json +import pydgraph + +from . import helper + +class TestAsync(helper.ClientIntegrationTestCase): + server_addr = 'localhost:9180' + + def setUp(self): + super(TestAsync, self).setUp() + helper.drop_all(self.client) + + def test_mutation_and_query(self): + """Runs mutation and queries asyncronously.""" + alter_future = self.client.async_alter(pydgraph.Operation( + schema="name: string @index(term) .")) + response = alter_future.result() + + txn = self.client.txn() + mutate_future = txn.async_mutate(pydgraph.Mutation(commit_now=True), set_nquads=""" + <_:alice> \"Alice\" . + <_:greg> \"Greg\" . + <_:alice> <_:greg> . + """) + _ = pydgraph.Txn.handle_mutate_future(txn, mutate_future, True) + + query = """query me($a: string) { + me(func: anyofterms(name, "Alice")) + { + name + follows + { + name + } + } + }""" + + txn = self.client.txn() + query_future = txn.async_query(query, variables={'$a': 'Alice'}) + response = pydgraph.Txn.handle_query_future(query_future) + self.assertEqual([{'name': 'Alice', 'follows': [{'name': 'Greg'}]}], + json.loads(response.json).get('me')) From 05706329c75b64f66b0ddf6dea29b24c3b70258b Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Tue, 2 Jun 2020 16:01:40 -0700 Subject: [PATCH 184/435] Handle exceptions when during an alter. (#144) --- README.md | 14 ++++++-------- pydgraph/client.py | 28 ++++++++++++++++++++++++---- tests/test_async.py | 2 +- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 76d16da4..c77872a0 100644 --- a/README.md +++ b/README.md @@ -385,24 +385,22 @@ A `CallCredentials` object can be passed to the `login`, `alter`, `query`, and ### Async methods. The `alter` method in the client has an asyncronous version called -`async_alter`. The async methods return a future. You can use this future by -retrieving the result with the `result` method on the future object. Below is a -short example: +`async_alter`. The async methods return a future. You can directly call the +`result` method on the future. However. The DgraphClient class provides a static +method `handle_alter_future` to handle any possible exception. ``` alter_future = self.client.async_alter(pydgraph.Operation( schema="name: string @index(term) .")) -response = alter_future.result() +response = pydgraph.DgraphClient.handle_alter_future(alter_future) ``` The `query` and `mutate` methods int the `Txn` class also have async versions called `async_query` and `async_mutation` respectively. These functions work just like `async_alter`. -However, retrieving the result is a little different because some extra -processing needs to be done. You can use the `handle_query_future` and -`handle_mutate_future` static methods in the `Txn` class to retrieve the result. -A short example is given below: +You can use the `handle_query_future` and `handle_mutate_future` static methods +in the `Txn` class to retrieve the result. A short example is given below: ``` txn = client.txn() diff --git a/pydgraph/client.py b/pydgraph/client.py index a538f75b..0cca97d2 100755 --- a/pydgraph/client.py +++ b/pydgraph/client.py @@ -102,11 +102,24 @@ def alter(self, operation, timeout=None, metadata=None, credentials=None): if util.is_jwt_expired(error): self.retry_login() new_metadata = self.add_login_metadata(metadata) - return self.any_client().alter(operation, timeout=timeout, - metadata=new_metadata, - credentials=credentials) + try: + return self.any_client().alter(operation, timeout=timeout, + metadata=new_metadata, + credentials=credentials) + except Exception as error: + self._common_except_alter(error) else: - raise error + self._common_except_alter(error) + + @staticmethod + def _common_except_alter(error): + if util.is_retriable_error(error): + raise errors.RetriableError(error) + + if util.is_connection_error(error): + raise errors.ConnectionError(error) + + raise error def async_alter(self, operation, timeout=None, metadata=None, credentials=None): """The async version of alter.""" @@ -115,6 +128,13 @@ def async_alter(self, operation, timeout=None, metadata=None, credentials=None): metadata=new_metadata, credentials=credentials) + @staticmethod + def handle_alter_future(future): + try: + return future.result() + except Exception as error: + DgraphClient._common_except_alter(error) + def txn(self, read_only=False, best_effort=False): """Creates a transaction.""" return txn.Txn(self, read_only=read_only, best_effort=best_effort) diff --git a/tests/test_async.py b/tests/test_async.py index a429a57a..f9f531f5 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -33,7 +33,7 @@ def test_mutation_and_query(self): """Runs mutation and queries asyncronously.""" alter_future = self.client.async_alter(pydgraph.Operation( schema="name: string @index(term) .")) - response = alter_future.result() + response = pydgraph.DgraphClient.handle_alter_future(alter_future) txn = self.client.txn() mutate_future = txn.async_mutate(pydgraph.Mutation(commit_now=True), set_nquads=""" From 3f99cdbb44707676451a87d4ddaa2f7c3352eca7 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Wed, 3 Jun 2020 13:03:13 -0700 Subject: [PATCH 185/435] Add test for mutation with facets. (#146) --- tests/test_txn.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_txn.py b/tests/test_txn.py index 645ac07f..d2a22e4c 100644 --- a/tests/test_txn.py +++ b/tests/test_txn.py @@ -429,6 +429,42 @@ def test_finished(self): with self.assertRaises(Exception): txn.mutate(set_nquads='_:aman "Aman" .', commit_now=True) + def test_mutate_facet(self): + """Tests mutations that include facets work as expected.""" + helper.drop_all(self.client) + helper.set_schema(self.client, """ +name: string . +friend: uid . +""") + + nquads = """ +_:a "aaa" (close_friend=true) . +_:b "bbb" . +_:a _:b (close_friend=true). +""" + + txn = self.client.txn() + _ = txn.mutate(set_nquads=nquads, commit_now=True) + + query = """ +{ + q1(func: has(name), orderasc: name) { + name @facets(close_friend) + } + + q2(func: has(friend)) { + friend @facets(close_friend) { + name + } + } +} +""" + txn = self.client.txn() + resp = txn.query(query) + self.assertEqual([{'name': 'aaa', 'name|close_friend': True}, {'name': 'bbb'}], + json.loads(resp.json).get('q1')) + self.assertEqual([{'friend': {'name': 'bbb', 'friend|close_friend': True}}], + json.loads(resp.json).get('q2')) class TestSPStar(helper.ClientIntegrationTestCase): def setUp(self): From 9d02a9fbcd576907aace42ce36a3b0e24c1505cc Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 4 Jun 2020 15:50:02 -0700 Subject: [PATCH 186/435] Update changelog and metadata for version 20.03.1 (#145) --- CHANGELOG.md | 8 ++++++++ pydgraph/meta.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10755ece..6aecb75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] =================== +## [v20.03.1] - 2020-06-03 + +### Added +- Added more exception classes for specific types of errors (e.g retriable +errors). Existing applications might want to update their error handling code to +take advantage of these new error classes. +- Added async versions of alter, query, and mutate functions. + ## [v20.03.0] - 2020-03-31 Starting with this release, the release number has changed to match the Dgraph release diff --git a/pydgraph/meta.py b/pydgraph/meta.py index e4d8fbc2..3f83af85 100644 --- a/pydgraph/meta.py +++ b/pydgraph/meta.py @@ -14,4 +14,4 @@ """Metadata about this package.""" -VERSION = '20.03.0' +VERSION = '20.03.1' From 95dbeea641e9f7fa0dc5806cbe92a5af533c4ce1 Mon Sep 17 00:00:00 2001 From: Manish R Jain Date: Fri, 10 Jul 2020 21:25:34 -0700 Subject: [PATCH 187/435] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c77872a0..625dc73f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ using [grpc]. This client follows the [Dgraph Go client][goclient] closely. +**Use [Discuss Issues](https://discuss.dgraph.io/tags/c/issues/35/pydgraph) for reporting issues about this repository.** + [goclient]: https://github.com/dgraph-io/dgo Before using this client, we highly recommend that you go through [docs.dgraph.io], and understand how to run and work with Dgraph. From 5b0062a11c4dc0769a34a7db75a32583261a55a9 Mon Sep 17 00:00:00 2001 From: minhaj-shakeel Date: Mon, 13 Jul 2020 16:37:30 +0530 Subject: [PATCH 188/435] Enabled Issue Auto-close --- .github/ISSUE_TEMPLATE | 1 + .github/workflows/main.yml | 24 ++++++++++++++++++++++++ ISSUE_TEMPLATE | 1 + 3 files changed, 26 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE create mode 100644 .github/workflows/main.yml create mode 100644 ISSUE_TEMPLATE diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 00000000..2e89df14 --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1 @@ +**GitHub Issues are deprecated. Use [Discuss Issues](https://discuss.dgraph.io/c/issues/pydgraph) for reporting issues about this repository.**% \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..93afe5af --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,24 @@ +# This is a basic workflow to help you get started with Actions + +name: Issue Closer + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + issues: + types: [ opened, reopened ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Close Issue + uses: peter-evans/close-issue@v1.0.1 + with: + comment: | + **Use [Discuss Issues](https://discuss.dgraph.io/c/issues/pydgraph) for reporting issues about this repository.**% \ No newline at end of file diff --git a/ISSUE_TEMPLATE b/ISSUE_TEMPLATE new file mode 100644 index 00000000..2e89df14 --- /dev/null +++ b/ISSUE_TEMPLATE @@ -0,0 +1 @@ +**GitHub Issues are deprecated. Use [Discuss Issues](https://discuss.dgraph.io/c/issues/pydgraph) for reporting issues about this repository.**% \ No newline at end of file From 2bbd47a0f2c137ffc857f7069cac87354f4292df Mon Sep 17 00:00:00 2001 From: minhaj-shakeel Date: Tue, 14 Jul 2020 02:13:40 +0530 Subject: [PATCH 189/435] Update URL and README.md --- .github/ISSUE_TEMPLATE | 4 +++- .github/workflows/main.yml | 2 +- README.md | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index 2e89df14..101f5d7c 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -1 +1,3 @@ -**GitHub Issues are deprecated. Use [Discuss Issues](https://discuss.dgraph.io/c/issues/pydgraph) for reporting issues about this repository.**% \ No newline at end of file +**GitHub Issues are deprecated. Use [Discuss Issues](https://discuss.dgraph.io/c/issues/35/clients/46) for reporting issues about this repository.** +Any issues opened or reopened would be automatically closed by the bot. + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93afe5af..38f3ebeb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,4 +21,4 @@ jobs: uses: peter-evans/close-issue@v1.0.1 with: comment: | - **Use [Discuss Issues](https://discuss.dgraph.io/c/issues/pydgraph) for reporting issues about this repository.**% \ No newline at end of file + **Use [Discuss Issues](https://discuss.dgraph.io/c/issues/35/clients/46) for reporting issues about this repository.**% diff --git a/README.md b/README.md index 625dc73f..11fad2df 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ using [grpc]. This client follows the [Dgraph Go client][goclient] closely. -**Use [Discuss Issues](https://discuss.dgraph.io/tags/c/issues/35/pydgraph) for reporting issues about this repository.** +**Use [Discuss Issues](https://discuss.dgraph.io/c/issues/35/clients/46) for reporting issues about this repository.** [goclient]: https://github.com/dgraph-io/dgo Before using this client, we highly recommend that you go through [docs.dgraph.io], From b350252eed2a04c906f2931aa5f12833113d436f Mon Sep 17 00:00:00 2001 From: minhaj-shakeel Date: Tue, 14 Jul 2020 11:30:02 +0530 Subject: [PATCH 190/435] Update comment in main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 38f3ebeb..a61c1716 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,4 +21,4 @@ jobs: uses: peter-evans/close-issue@v1.0.1 with: comment: | - **Use [Discuss Issues](https://discuss.dgraph.io/c/issues/35/clients/46) for reporting issues about this repository.**% + **Use [Discuss Issues](https://discuss.dgraph.io/c/issues/35/clients/46) for reporting issues about this repository.** From fae82c2013805fb6cbf2900fac36a1650b4c349a Mon Sep 17 00:00:00 2001 From: minhaj-shakeel Date: Wed, 15 Jul 2020 12:23:38 +0530 Subject: [PATCH 191/435] Add notification Screenshot --- .github/SetNotifications.png | Bin 0 -> 42077 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/SetNotifications.png diff --git a/.github/SetNotifications.png b/.github/SetNotifications.png new file mode 100644 index 0000000000000000000000000000000000000000..5aca6712948489ceb8efa6857de6931bf96002fa GIT binary patch literal 42077 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 Jsx)lF{||p5i4y<- literal 0 HcmV?d00001 From 8149d59cf66b25ce888e7b766579a980a8d9aa61 Mon Sep 17 00:00:00 2001 From: minhaj-shakeel Date: Thu, 16 Jul 2020 12:34:19 +0530 Subject: [PATCH 192/435] Add screenshot --- .github/Screenshot1594882817.png | Bin 0 -> 88294 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/Screenshot1594882817.png diff --git a/.github/Screenshot1594882817.png b/.github/Screenshot1594882817.png new file mode 100644 index 0000000000000000000000000000000000000000..3b08178ac9f111367571ed7032ca72710f9b1741 GIT binary patch literal 88294 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%0ArNGZmGnWi2V9Z`_%6yPoyx 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 Date: Thu, 16 Jul 2020 12:39:42 +0530 Subject: [PATCH 193/435] Add screenshot --- .github/Screenshot1594883285.png | Bin 0 -> 67928 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/Screenshot1594883285.png diff --git a/.github/Screenshot1594883285.png b/.github/Screenshot1594883285.png new file mode 100644 index 0000000000000000000000000000000000000000..ea3c06450677529b8669a6e3def1406dc1e7ed75 GIT binary patch literal 67928 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 OmMmPp;MRPDgZ~#%0CfTY literal 0 HcmV?d00001 From 08e9b4c98b0e4d69a78380aed5271d1e583a3b45 Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 16 Jul 2020 07:38:24 +0000 Subject: [PATCH 194/435] Added set notifications file after edits --- .github/SetNotifications2.png | Bin 0 -> 38272 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/SetNotifications2.png diff --git a/.github/SetNotifications2.png b/.github/SetNotifications2.png new file mode 100644 index 0000000000000000000000000000000000000000..d02e3fcde218eaabae22b94645f6530071c27375 GIT binary patch literal 38272 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 literal 0 HcmV?d00001 From e2acfab29dfc1c45e1e9961c281b0a017a84b1b1 Mon Sep 17 00:00:00 2001 From: Martin Martinez Rivera Date: Thu, 20 Aug 2020 12:49:10 -0700 Subject: [PATCH 195/435] Add section on authentication of TLS reverse proxies. (#153) Add a section on how to authenticate pydgraph when the Dgraph instance is behind a reverse TLS proxy. Taken from the gRPC section in https://graphql.dgraph.io/slash-graphql/advanced-queries/ --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 11fad2df..078be139 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,24 @@ For example, the following alters the schema with a timeout of ten seconds: A `CallCredentials` object can be passed to the `login`, `alter`, `query`, and `mutate` methods using the `credentials` keyword argument. +### Authenticating to a reverse TLS proxy + +If the Dgraph instance is behind a reverse TLS proxy, credentials can also be +passed through the methods available in the gRPC library. Note that in this case +every request will need to include the credentials. In the example below, we are +trying to add authentication to a proxy that requires an API key. This value is +expected to be included in the metadata using the key "authorization". + +``` +creds = grpc.ssl_channel_credentials() +call_credentials = grpc.metadata_call_credentials( + lambda context, callback: callback((("authorization", ""),), None)) +composite_credentials = grpc.composite_channel_credentials(creds, call_credentials) +client_stub = pydgraph.DgraphClientStub( + '{host}:{port}'.format(host=GRPC_HOST, port=GRPC_PORT), composite_credentials) +client = pydgraph.DgraphClient(client_stub) +``` + ### Async methods. The `alter` method in the client has an asyncronous version called From 8dc8de596a886265fc5b365b6196e00fd1e18101 Mon Sep 17 00:00:00 2001 From: rahulgurnani Date: Fri, 28 Aug 2020 17:39:13 +0530 Subject: [PATCH 196/435] Add information about whitelist IPs --- examples/simple/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/simple/README.md b/examples/simple/README.md index 9631ed0a..53818194 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -24,8 +24,9 @@ services: - 8080:8080 - 9080:9080 restart: on-failure - command: dgraph alpha --my=server:7080 --lru_mb=2048 --zero=zero:5080 + command: dgraph alpha --my=server:7080 --lru_mb=2048 --zero=zero:5080 --whitelist "${WHITELISTED}" ``` +The "WHITELISTED" environment variable can be intiialized as described [in this post](https://discuss.dgraph.io/t/suggestion-for-how-to-add-docker-compose-network-to-whitelist/9600). We need to whitelist these IPs because the docker container runs with it's own IP address. ## Install the Dependencies From 56e7bae20396651195091329568f33829e06300d Mon Sep 17 00:00:00 2001 From: Anurag Sharma Date: Mon, 7 Sep 2020 23:16:17 +0530 Subject: [PATCH 197/435] First commit for Slash client in python --- pydgraph/__init__.py | 1 + pydgraph/client_slash.py | 52 +++++++++++ test-grpc.py | 194 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 pydgraph/client_slash.py create mode 100644 test-grpc.py diff --git a/pydgraph/__init__.py b/pydgraph/__init__.py index a9d2588e..62edf841 100755 --- a/pydgraph/__init__.py +++ b/pydgraph/__init__.py @@ -16,5 +16,6 @@ Check, Version, NQuad, Value, Facet, Latency from pydgraph.client_stub import * from pydgraph.client import * +from pydgraph.client_slash import * from pydgraph.txn import * from pydgraph.errors import * diff --git a/pydgraph/client_slash.py b/pydgraph/client_slash.py new file mode 100644 index 00000000..3f0f3ab5 --- /dev/null +++ b/pydgraph/client_slash.py @@ -0,0 +1,52 @@ +# Copyright 2020 Dgraph Labs, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Slash GraphQL python client.""" +import grpc + +import pydgraph +from pydgraph import txn, util, DgraphClientStub +from pydgraph.meta import VERSION +from pydgraph.proto import api_pb2 as api + +__author__ = 'Anurag Sharma ' +__maintainer__ = 'Martin Martinez Rivera ' +__version__ = VERSION +__status__ = 'development' + + +class SlashGraphQLClient(object): + """Creates a new Client for interacting with the Slash GraphQL. + """ + + def __init__(self, SlashEndpoint, APIKey): + if not SlashEndpoint: + raise ValueError('No Slash endpoint provided in SlashGraphQLClient constructor') + + self._SlashEndpoint = SlashEndpoint + self._APIKey = APIKey + + def get_stub(self): + """Returns Dgraph Client stub for the Slash GraphQL endpoint""" + url = self._SlashEndpoint.replace("/graphql", "") + url = url.replace("https://", "") + url_parts = url.split(".", 1) + HOST = url_parts[0] + ".grpc." + url_parts[1] + PORT = "443" + print HOST + creds = grpc.ssl_channel_credentials() + call_credentials = grpc.metadata_call_credentials(lambda context, callback: callback((("authorization", self._APIKey),), None)) + composite_credentials = grpc.composite_channel_credentials(creds, call_credentials) + client_stub = pydgraph.DgraphClientStub('{host}:{port}'.format(host=HOST, port=PORT), composite_credentials, options=(('grpc.enable_http_proxy', 0),)) + return client_stub \ No newline at end of file diff --git a/test-grpc.py b/test-grpc.py new file mode 100644 index 00000000..89984f57 --- /dev/null +++ b/test-grpc.py @@ -0,0 +1,194 @@ +import datetime +import json + +import pydgraph + + +# Create a client stub. +def create_client_stub(): + return pydgraph.DgraphClientStub('localhost:9080') + + +# Create a client. +def create_client(client_stub): + return pydgraph.DgraphClient(client_stub) + + +# Drop All - discard all data and start from a clean slate. +def drop_all(client): + return client.alter(pydgraph.Operation(drop_all=True)) + + +# Set schema. +def set_schema(client): + schema = """ + director.film : [uid] @reverse @count . + actor.film : [uid] @count . + genre : [uid] @reverse @count . + initial_release_date : datetime @index(year) . + rating : [uid] @reverse . + country : [uid] @reverse . + loc : geo @index(geo) . + name : string @index(hash, term, trigram, fulltext) @lang . + starring : [uid] @count . + performance.character_note : string @lang . + tagline : string @lang . + cut.note : string @lang . + rated : [uid] @reverse . + email : string @index(exact) @upsert . + """ + return client.alter(pydgraph.Operation(schema=schema)) + +def create_dataTriples(client): + # Create a new transaction. + txn = client.txn() + try: + data_nquads = """ + <1> "1" . + <1>