From 60582493b5ab47d3cc866f5219631f71eef8385d Mon Sep 17 00:00:00 2001 From: Kirill Stepanishin Date: Mon, 10 Nov 2025 10:20:17 -0800 Subject: [PATCH 1/5] Integrate Python driver examples into automated build process and update for TinkerPop 4 Changes Made: - Added example execution to gremlin-python-integration-tests container in docker-compose.yml - Made server URLs configurable via environment variables - Added configurable vertex labels via VERTEX_LABEL environment variable - Updated protocol from WebSocket (ws://) to HTTP for TinkerPop 4 compatibility - Updated serializer from GraphBinarySerializersV1 to GraphBinarySerializersV4 - Fixed edge creation syntax using __.V() wrapper for TinkerPop 4 from()/to() compatibility - Migrated example authentication from parameters to auth functions (basic(), sigv4()) - Added AWS SigV4 authentication examples for permanent and temporary credentials --- .../gremlin-python/basic_gremlin.py | 14 ++-- .../gremlin-python/connections.py | 32 ++++---- gremlin-python/docker-compose.yml | 7 +- .../src/main/python/examples/basic_gremlin.py | 21 +++-- .../src/main/python/examples/connections.py | 82 ++++++++++++------- .../main/python/examples/modern_traversals.py | 12 ++- 6 files changed, 108 insertions(+), 60 deletions(-) diff --git a/gremlin-examples/gremlin-python/basic_gremlin.py b/gremlin-examples/gremlin-python/basic_gremlin.py index 256ed984416..c351270de85 100644 --- a/gremlin-examples/gremlin-python/basic_gremlin.py +++ b/gremlin-examples/gremlin-python/basic_gremlin.py @@ -23,15 +23,17 @@ from gremlin_python.process.strategies import * from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection +VERTEX_LABEL = 'person' def main(): - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g') + server_url = 'ws://localhost:8182/gremlin' + rc = DriverRemoteConnection(server_url, 'g') g = traversal().with_remote(rc) # basic Gremlin: adding and retrieving data - v1 = g.add_v('person').property('name', 'marko').next() - v2 = g.add_v('person').property('name', 'stephen').next() - v3 = g.add_v('person').property('name', 'vadas').next() + v1 = g.add_v(VERTEX_LABEL).property('name', 'marko').next() + v2 = g.add_v(VERTEX_LABEL).property('name', 'stephen').next() + v3 = g.add_v(VERTEX_LABEL).property('name', 'vadas').next() # be sure to use a terminating step like next() or iterate() so that the traversal "executes" # iterate() does not return any data and is used to just generate side-effects (i.e. write data to the database) @@ -39,11 +41,11 @@ def main(): g.V(v1).add_e('knows').to(v3).property('weight', 0.75).iterate() # retrieve the data from the "marko" vertex - marko = g.V().has('person', 'name', 'marko').values('name').next() + marko = g.V().has(VERTEX_LABEL, 'name', 'marko').values('name').next() print("name: " + marko) # find the "marko" vertex and then traverse to the people he "knows" and return their data - people_marko_knows = g.V().has('person', 'name', 'marko').out('knows').values('name').to_list() + people_marko_knows = g.V().has(VERTEX_LABEL, 'name', 'marko').out('knows').values('name').to_list() for person in people_marko_knows: print("marko knows " + person) diff --git a/gremlin-examples/gremlin-python/connections.py b/gremlin-examples/gremlin-python/connections.py index f268e6c27d5..9997a181ebf 100644 --- a/gremlin-examples/gremlin-python/connections.py +++ b/gremlin-examples/gremlin-python/connections.py @@ -24,6 +24,7 @@ from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection from gremlin_python.driver.serializer import GraphBinarySerializersV1 +VERTEX_LABEL = 'connection' def main(): with_remote() @@ -40,15 +41,13 @@ def with_remote(): # # which starts it in "console" mode with an empty in-memory TinkerGraph ready to go bound to a # variable named "g" as referenced in the following line. - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g') + server_url = 'ws://localhost:8182/gremlin' + rc = DriverRemoteConnection(server_url, 'g') g = traversal().with_remote(rc) - # drop existing vertices - g.V().drop().iterate() - # simple query to verify connection - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) # cleanup @@ -57,11 +56,12 @@ def with_remote(): # connecting with plain text authentication def with_auth(): - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g', username='stephen', password='password') + server_url = 'ws://localhost:8182/gremlin' + rc = DriverRemoteConnection(server_url, 'g', username='stephen', password='password') g = traversal().with_remote(rc) - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) rc.close() @@ -69,11 +69,12 @@ def with_auth(): # connecting with Kerberos SASL authentication def with_kerberos(): - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g', kerberized_service='gremlin@hostname.your.org') + server_url = 'ws://localhost:8182/gremlin' + rc = DriverRemoteConnection(server_url, 'g', kerberized_service='gremlin@hostname.your.org') g = traversal().with_remote(rc) - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) rc.close() @@ -81,8 +82,9 @@ def with_kerberos(): # connecting with customized configurations def with_configs(): + server_url = 'ws://localhost:8182/gremlin' rc = DriverRemoteConnection( - 'ws://localhost:8182/gremlin', 'g', + server_url, 'g', username="", password="", kerberized_service='', message_serializer=GraphBinarySerializersV1(), graphson_reader=None, graphson_writer=None, headers=None, session=None, @@ -90,8 +92,8 @@ def with_configs(): ) g = traversal().with_remote(rc) - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) rc.close() diff --git a/gremlin-python/docker-compose.yml b/gremlin-python/docker-compose.yml index bdc7f6332b3..a1baa4809b8 100644 --- a/gremlin-python/docker-compose.yml +++ b/gremlin-python/docker-compose.yml @@ -64,7 +64,12 @@ services: && radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.graphbinary-v4.0' --user-data='parameterize=true' && radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.graphbinary-v4.0' && radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.gremlin-v4.0+json' --user-data='parameterize=true' - && radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.gremlin-v4.0+json'; + && radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.gremlin-v4.0+json' + && echo 'Running examples...' + && python3 examples/basic_gremlin.py + && python3 examples/connections.py + && python3 examples/modern_traversals.py + && echo 'All examples completed successfully'; EXIT_CODE=$$?; chown -R `stat -c "%u:%g" .` .; exit $$EXIT_CODE" depends_on: gremlin-server-test-python: diff --git a/gremlin-python/src/main/python/examples/basic_gremlin.py b/gremlin-python/src/main/python/examples/basic_gremlin.py index 256ed984416..873fd0a731b 100644 --- a/gremlin-python/src/main/python/examples/basic_gremlin.py +++ b/gremlin-python/src/main/python/examples/basic_gremlin.py @@ -16,34 +16,39 @@ # under the License. import sys +import os sys.path.append("..") from gremlin_python.process.anonymous_traversal import traversal +from gremlin_python.process.graph_traversal import __ from gremlin_python.process.strategies import * from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection +VERTEX_LABEL = os.getenv('VERTEX_LABEL', 'person') def main(): - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g') + # if there is a port placeholder in the env var then we are running with docker so set appropriate port + server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) + rc = DriverRemoteConnection(server_url, 'g') g = traversal().with_remote(rc) # basic Gremlin: adding and retrieving data - v1 = g.add_v('person').property('name', 'marko').next() - v2 = g.add_v('person').property('name', 'stephen').next() - v3 = g.add_v('person').property('name', 'vadas').next() + v1 = g.add_v(VERTEX_LABEL).property('name', 'marko').next() + v2 = g.add_v(VERTEX_LABEL).property('name', 'stephen').next() + v3 = g.add_v(VERTEX_LABEL).property('name', 'vadas').next() # be sure to use a terminating step like next() or iterate() so that the traversal "executes" # iterate() does not return any data and is used to just generate side-effects (i.e. write data to the database) - g.V(v1).add_e('knows').to(v2).property('weight', 0.75).iterate() - g.V(v1).add_e('knows').to(v3).property('weight', 0.75).iterate() + g.V(v1).add_e('knows').to(__.V(v2)).property('weight', 0.75).iterate() + g.V(v1).add_e('knows').to(__.V(v3)).property('weight', 0.75).iterate() # retrieve the data from the "marko" vertex - marko = g.V().has('person', 'name', 'marko').values('name').next() + marko = g.V().has(VERTEX_LABEL, 'name', 'marko').values('name').next() print("name: " + marko) # find the "marko" vertex and then traverse to the people he "knows" and return their data - people_marko_knows = g.V().has('person', 'name', 'marko').out('knows').values('name').to_list() + people_marko_knows = g.V().has(VERTEX_LABEL, 'name', 'marko').out('knows').values('name').to_list() for person in people_marko_knows: print("marko knows " + person) diff --git a/gremlin-python/src/main/python/examples/connections.py b/gremlin-python/src/main/python/examples/connections.py index f268e6c27d5..fe23be33ae5 100644 --- a/gremlin-python/src/main/python/examples/connections.py +++ b/gremlin-python/src/main/python/examples/connections.py @@ -14,22 +14,26 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +import ssl import sys +import os sys.path.append("..") from gremlin_python.process.anonymous_traversal import traversal from gremlin_python.process.strategies import * from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection -from gremlin_python.driver.serializer import GraphBinarySerializersV1 +from gremlin_python.driver.serializer import GraphBinarySerializersV4 +from gremlin_python.driver.aiohttp.transport import AiohttpHTTPTransport +from gremlin_python.driver.auth import basic, sigv4 +VERTEX_LABEL = os.getenv('VERTEX_LABEL', 'connection') def main(): with_remote() with_auth() - with_kerberos() - with_configs() + with_sigv4() + with_sigv4_session_token() def with_remote(): @@ -40,15 +44,14 @@ def with_remote(): # # which starts it in "console" mode with an empty in-memory TinkerGraph ready to go bound to a # variable named "g" as referenced in the following line. - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g') + # if there is a port placeholder in the env var then we are running with docker so set appropriate port + server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) + rc = DriverRemoteConnection(server_url, 'g') g = traversal().with_remote(rc) - # drop existing vertices - g.V().drop().iterate() - # simple query to verify connection - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) # cleanup @@ -57,41 +60,62 @@ def with_remote(): # connecting with plain text authentication def with_auth(): - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g', username='stephen', password='password') + # if there is a port placeholder in the env var then we are running with docker so set appropriate port + server_url = os.getenv('GREMLIN_SERVER_BASIC_AUTH_URL', 'http://localhost:8182/gremlin').format(45941) + + # disable SSL certificate verification for CI environments + if ':45941' in server_url: + ssl_opts = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_opts.check_hostname = False + ssl_opts.verify_mode = ssl.CERT_NONE + rc = DriverRemoteConnection(server_url, 'g', auth=basic('stephen', 'password'), + transport_factory=lambda: AiohttpHTTPTransport(ssl_options=ssl_opts)) + else: + rc = DriverRemoteConnection(server_url, 'g', auth=basic('stephen', 'password')) + g = traversal().with_remote(rc) - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) rc.close() -# connecting with Kerberos SASL authentication -def with_kerberos(): - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g', kerberized_service='gremlin@hostname.your.org') +# connecting with AWS SigV4 authentication +def with_sigv4(): + # Only set mock credentials if not already present + os.environ.setdefault('AWS_ACCESS_KEY_ID', 'MOCK_ID') + os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'MOCK_KEY') + + # if there is a port placeholder in the env var then we are running with docker so set appropriate port + server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) + + rc = DriverRemoteConnection(server_url, 'g', auth=sigv4('gremlin-east-1', 'tinkerpop-sigv4')) g = traversal().with_remote(rc) - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) rc.close() -# connecting with customized configurations -def with_configs(): - rc = DriverRemoteConnection( - 'ws://localhost:8182/gremlin', 'g', - username="", password="", kerberized_service='', - message_serializer=GraphBinarySerializersV1(), graphson_reader=None, - graphson_writer=None, headers=None, session=None, - enable_user_agent_on_connect=True - ) +# connecting with AWS SigV4 authentication with session token +def with_sigv4_session_token(): + # Only set mock credentials if not already present + os.environ.setdefault('AWS_ACCESS_KEY_ID', 'MOCK_ID') + os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'MOCK_KEY') + os.environ.setdefault('AWS_SESSION_TOKEN', 'MOCK_TOKEN') + + # if there is a port placeholder in the env var then we are running with docker so set appropriate port + server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) + + rc = DriverRemoteConnection(server_url, 'g', auth=sigv4('gremlin-east-1', 'tinkerpop-sigv4')) g = traversal().with_remote(rc) - v = g.add_v().iterate() - count = g.V().count().next() + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() print("Vertex count: " + str(count)) rc.close() diff --git a/gremlin-python/src/main/python/examples/modern_traversals.py b/gremlin-python/src/main/python/examples/modern_traversals.py index ae757b10b4f..9feb66d7753 100644 --- a/gremlin-python/src/main/python/examples/modern_traversals.py +++ b/gremlin-python/src/main/python/examples/modern_traversals.py @@ -16,6 +16,7 @@ # under the License. import sys +import os sys.path.append("..") @@ -31,7 +32,16 @@ def main(): # This example requires the Modern toy graph to be preloaded upon launching the Gremlin server. # For details, see https://tinkerpop.apache.org/docs/current/reference/#gremlin-server-docker-image and use # conf/gremlin-server-modern.yaml. - rc = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g') + # if there is a port placeholder in the env var then we are running with docker so set appropriate port + server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) + + # CI uses port 45940 with gmodern binding, local uses 8182 with g binding + if ':45940' in server_url: + graph_binding = 'gmodern' # CI environment + else: + graph_binding = 'g' # Local environment + + rc = DriverRemoteConnection(server_url, graph_binding) g = traversal().with_remote(rc) e1 = g.V(1).both_e().to_list() # (1) From 55009fefb5f2f2b2e8b230a0bb395c9573531005 Mon Sep 17 00:00:00 2001 From: Kirill Stepanishin Date: Mon, 17 Nov 2025 09:27:35 -0800 Subject: [PATCH 2/5] remove unused imports --- gremlin-python/src/main/python/examples/connections.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gremlin-python/src/main/python/examples/connections.py b/gremlin-python/src/main/python/examples/connections.py index fe23be33ae5..40b8dd8e7de 100644 --- a/gremlin-python/src/main/python/examples/connections.py +++ b/gremlin-python/src/main/python/examples/connections.py @@ -21,9 +21,7 @@ sys.path.append("..") from gremlin_python.process.anonymous_traversal import traversal -from gremlin_python.process.strategies import * from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection -from gremlin_python.driver.serializer import GraphBinarySerializersV4 from gremlin_python.driver.aiohttp.transport import AiohttpHTTPTransport from gremlin_python.driver.auth import basic, sigv4 From 1f71af8be7a13cda35e4563e2ece88fc145d29e2 Mon Sep 17 00:00:00 2001 From: Kirill Stepanishin Date: Mon, 17 Nov 2025 15:49:43 -0800 Subject: [PATCH 3/5] remove sigv4 auth example --- .../src/main/python/examples/connections.py | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/gremlin-python/src/main/python/examples/connections.py b/gremlin-python/src/main/python/examples/connections.py index 40b8dd8e7de..a6aa1c884ef 100644 --- a/gremlin-python/src/main/python/examples/connections.py +++ b/gremlin-python/src/main/python/examples/connections.py @@ -23,15 +23,13 @@ from gremlin_python.process.anonymous_traversal import traversal from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection from gremlin_python.driver.aiohttp.transport import AiohttpHTTPTransport -from gremlin_python.driver.auth import basic, sigv4 +from gremlin_python.driver.auth import basic VERTEX_LABEL = os.getenv('VERTEX_LABEL', 'connection') def main(): with_remote() with_auth() - with_sigv4() - with_sigv4_session_token() def with_remote(): @@ -79,45 +77,5 @@ def with_auth(): rc.close() - -# connecting with AWS SigV4 authentication -def with_sigv4(): - # Only set mock credentials if not already present - os.environ.setdefault('AWS_ACCESS_KEY_ID', 'MOCK_ID') - os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'MOCK_KEY') - - # if there is a port placeholder in the env var then we are running with docker so set appropriate port - server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) - - rc = DriverRemoteConnection(server_url, 'g', auth=sigv4('gremlin-east-1', 'tinkerpop-sigv4')) - g = traversal().with_remote(rc) - - v = g.add_v(VERTEX_LABEL).iterate() - count = g.V().has_label(VERTEX_LABEL).count().next() - print("Vertex count: " + str(count)) - - rc.close() - - -# connecting with AWS SigV4 authentication with session token -def with_sigv4_session_token(): - # Only set mock credentials if not already present - os.environ.setdefault('AWS_ACCESS_KEY_ID', 'MOCK_ID') - os.environ.setdefault('AWS_SECRET_ACCESS_KEY', 'MOCK_KEY') - os.environ.setdefault('AWS_SESSION_TOKEN', 'MOCK_TOKEN') - - # if there is a port placeholder in the env var then we are running with docker so set appropriate port - server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) - - rc = DriverRemoteConnection(server_url, 'g', auth=sigv4('gremlin-east-1', 'tinkerpop-sigv4')) - g = traversal().with_remote(rc) - - v = g.add_v(VERTEX_LABEL).iterate() - count = g.V().has_label(VERTEX_LABEL).count().next() - print("Vertex count: " + str(count)) - - rc.close() - - if __name__ == "__main__": main() From 0968ca07e11a2ba9bae970186d2ec320119a7c61 Mon Sep 17 00:00:00 2001 From: Kirill Stepanishin Date: Tue, 18 Nov 2025 08:00:10 -0800 Subject: [PATCH 4/5] add changelog entry --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 456dfd807bb..eb65455576f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,6 +81,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima * Removed `minSize` setting for Gremlin Driver connection pool since connections are now short-lived HTTP connections * Added `idleConnectionTimeout` setting for Gremlin Driver and automatic closing of idle connections * Enabled TCP Keep-Alive in GremlinServer. +* Updated Python GLV examples to use HTTP and to run as part of the integration tests. == TinkerPop 3.8.0 (Grix Greven) From 61c0e7795dc27c8c0d0c60d421ff2d910ba90918 Mon Sep 17 00:00:00 2001 From: Kirill Stepanishin Date: Tue, 18 Nov 2025 10:40:02 -0800 Subject: [PATCH 5/5] add graph binary serializer config example --- .../src/main/python/examples/connections.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gremlin-python/src/main/python/examples/connections.py b/gremlin-python/src/main/python/examples/connections.py index a6aa1c884ef..f396a8d7972 100644 --- a/gremlin-python/src/main/python/examples/connections.py +++ b/gremlin-python/src/main/python/examples/connections.py @@ -24,12 +24,14 @@ from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection from gremlin_python.driver.aiohttp.transport import AiohttpHTTPTransport from gremlin_python.driver.auth import basic +from gremlin_python.driver.serializer import GraphBinarySerializersV4 VERTEX_LABEL = os.getenv('VERTEX_LABEL', 'connection') def main(): with_remote() with_auth() + with_configs() def with_remote(): @@ -77,5 +79,21 @@ def with_auth(): rc.close() +# connecting with customized configurations +def with_configs(): + server_url = os.getenv('GREMLIN_SERVER_URL', 'http://localhost:8182/gremlin').format(45940) + rc = DriverRemoteConnection( + server_url, 'g', + request_serializer=GraphBinarySerializersV4(), + headers=None, + ) + g = traversal().with_remote(rc) + + v = g.add_v(VERTEX_LABEL).iterate() + count = g.V().has_label(VERTEX_LABEL).count().next() + print("Vertex count: " + str(count)) + + rc.close() + if __name__ == "__main__": main()