[
https://issues.apache.org/jira/browse/TINKERPOP-2774?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18131224#comment-18131224
]
ASF GitHub Bot commented on TINKERPOP-2774:
-------------------------------------------
masterhugo commented on code in PR #3527:
URL: https://github.com/apache/tinkerpop/pull/3527#discussion_r4365238321
##########
gremlin-python/src/main/python/gremlin_python/driver/async_client.py:
##########
@@ -0,0 +1,290 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 asyncio
+import logging
+
+from gremlin_python.driver import protocol, request, serializer
+from gremlin_python.driver.async_connection import AsyncConnection,
InvalidatedConnectionError
+from gremlin_python.driver.async_protocol import AsyncGremlinServerWSProtocol
+from gremlin_python.driver.aiohttp.async_transport import
AsyncAiohttpWSTransport
+from gremlin_python.driver.client import Client
+from gremlin_python.process import traversal
+
+log = logging.getLogger("gremlinpython")
+
+__author__ = 'Apache TinkerPop ([email protected])'
+
+
+class AsyncClient(Client):
+ """Native-async Gremlin client using WebSocket transport.
+
+ Unlike :class:`~gremlin_python.driver.client.Client`, no
+ :class:`~concurrent.futures.ThreadPoolExecutor` is created. The
+ connection pool is an :class:`asyncio.Queue` and all submit/close
+ methods are coroutines that must be awaited.
+
+ Robustness features:
+
+ * Each connection runs a background WebSocket ping to prevent idle
+ disconnects (configurable via ``ping_interval``).
+ * On
:class:`~gremlin_python.driver.async_connection.InvalidatedConnectionError`
+ the request is automatically retried on a fresh connection up to
+ *pool_size* times.
+ * *default_request_options* are merged into every request so options
+ like ``evaluationTimeout`` can be set once per client.
+
+ Usage::
+
+ async with AsyncClient("ws://localhost:8182/gremlin", "g") as client:
+ result_set = await client.submit("g.V().count()")
+ print(await result_set.all())
+
+ Parameters
+ ----------
+ url:
+ WebSocket endpoint, e.g. ``ws://localhost:8182/gremlin``.
+ traversal_source:
+ Gremlin traversal source name (default ``"g"``).
+ protocol_factory:
+ Zero-argument callable returning an
+
:class:`~gremlin_python.driver.async_protocol.AsyncGremlinServerWSProtocol`.
+ transport_factory:
+ Zero-argument callable returning an
+
:class:`~gremlin_python.driver.aiohttp.async_transport.AsyncAiohttpWSTransport`.
+ pool_size:
+ Number of concurrent WebSocket connections (default 8; forced to 1
+ in session mode).
+ message_serializer:
+ Serializer to use (default
+ :class:`~gremlin_python.driver.serializer.GraphBinarySerializersV1`).
+ username, password:
+ Credentials for SASL/basic authentication.
+ kerberized_service:
+ Kerberos service principal for Kerberos authentication.
+ headers:
+ Extra HTTP headers included in every WebSocket handshake.
+ session:
+ Session UUID string to enable server-side sessions.
+ enable_user_agent_on_connect:
+ Send the driver user-agent header (default ``True``).
+ ping_interval:
+ Seconds between WebSocket keepalive pings (default 60, 0 to disable).
+ default_request_options:
+ Dict of request options merged into every request
+ (e.g. ``{"evaluationTimeout": 30000}``). Per-request options take
+ precedence over defaults.
+ **transport_kwargs:
+ Extra keyword arguments forwarded to the default
+
:class:`~gremlin_python.driver.aiohttp.async_transport.AsyncAiohttpWSTransport`.
+ """
+
+ def __init__(
+ self,
+ url,
+ traversal_source,
+ protocol_factory=None,
+ transport_factory=None,
+ pool_size=None,
+ max_workers=None, # accepted but unused — no thread pool
+ message_serializer=None,
+ username="",
+ password="",
+ kerberized_service="",
+ headers=None,
+ session=None,
+ enable_user_agent_on_connect=True,
+ ping_interval=60,
+ default_request_options=None,
+ **transport_kwargs,
+ ):
+ log.info("Creating AsyncClient with url '%s'", url)
+
+ # Do NOT call super().__init__() — Client.__init__ creates a
+ # ThreadPoolExecutor and a threading queue, neither of which we want.
+ self._closed = False
+ self._url = url
+ self._headers = headers
+ self._enable_user_agent_on_connect = enable_user_agent_on_connect
+ self._traversal_source = traversal_source
+ self._executor = None # no thread pool; attribute expected by Client
+ self._ping_interval = ping_interval
+ self._default_request_options = default_request_options or {}
+
+ if "max_content_length" not in transport_kwargs:
+ transport_kwargs["max_content_length"] = 10 * 1024 * 1024
+
+ if message_serializer is None:
+ message_serializer = serializer.GraphBinarySerializersV1()
+
+ self._message_serializer = message_serializer
+ self._username = username
+ self._password = password
+ self._session = session
+ self._session_enabled = session is not None and session != ""
+
+ if transport_factory is None:
+ def transport_factory():
+ return AsyncAiohttpWSTransport(**transport_kwargs)
+
+ self._transport_factory = transport_factory
+
+ if protocol_factory is None:
+ def protocol_factory():
+ return AsyncGremlinServerWSProtocol(
+ self._message_serializer,
+ username=self._username,
+ password=self._password,
+ kerberized_service=kerberized_service,
+ max_content_length=transport_kwargs["max_content_length"],
+ )
+
+ self._protocol_factory = protocol_factory
+
+ if self._session_enabled:
+ if pool_size is None:
+ pool_size = 1
+ elif pool_size != 1:
+ raise Exception("PoolSize must be 1 on session mode!")
+ if pool_size is None:
+ pool_size = 8
+ self._pool_size = pool_size
+
+ self._pool = asyncio.Queue()
+ self._fill_pool()
+
+ # ------------------------------------------------------------------
+ # Internal helpers
+ # ------------------------------------------------------------------
+
+ def _get_connection(self):
+ proto = self._protocol_factory()
+ return AsyncConnection(
+ self._url,
+ self._traversal_source,
+ proto,
+ self._transport_factory,
+ None,
+ self._pool,
+ headers=self._headers,
+ enable_user_agent_on_connect=self._enable_user_agent_on_connect,
+ ping_interval=self._ping_interval,
+ )
+
+ # ------------------------------------------------------------------
+ # Lifecycle
+ # ------------------------------------------------------------------
+
+ async def close(self):
+ """Close all pooled connections and mark the client as closed."""
+ if self._closed:
+ return
+ if self._session_enabled:
+ await self._close_session()
+ log.info("Closing AsyncClient with url '%s'", self._url)
+ conns = []
+ while not self._pool.empty():
+ conns.append(self._pool.get_nowait())
+ await asyncio.gather(*(conn.close() for conn in conns),
return_exceptions=True)
+ self._closed = True
+
+ async def _close_session(self):
+ message = request.RequestMessage(
+ processor="session",
+ op="close",
+ args={"session": str(self._session)},
+ )
+ conn = await self._pool.get()
+ try:
+ result_set = await conn.write(message)
+ await result_set.all()
+ except protocol.GremlinServerError:
+ pass
+
+ # ------------------------------------------------------------------
+ # Context manager
+ # ------------------------------------------------------------------
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, *_):
+ await self.close()
+
+ # ------------------------------------------------------------------
+ # Submit (with retry)
+ # ------------------------------------------------------------------
+
+ async def submit(self, message, bindings=None, request_options=None):
+ """Submit *message* and return an
:class:`~gremlin_python.driver.async_resultset.AsyncResultSet`.
+
+ Retries automatically up to *pool_size* times when a connection is
+ invalidated mid-request due to a network reset.
+ """
+ last_error = None
+ for _ in range(self._pool_size):
Review Comment:
Ok, I've removed them. I was originally thinking of having a DB-style retry
mechanism like some other drivers do, though I wasn't sure if the other Gremlin
drivers handled that internally or if it's always left up to the user.
> Full async/await support in gremlin-python
> ------------------------------------------
>
> Key: TINKERPOP-2774
> URL: https://issues.apache.org/jira/browse/TINKERPOP-2774
> Project: TinkerPop
> Issue Type: Improvement
> Components: python
> Affects Versions: 3.5.3
> Reporter: Ken Hu
> Priority: Minor
>
> Recently, there have been multiple instances of users asking about issues
> with event loops and aiohttp. This discussion nearly always ends up with the
> user requesting for async/await support in gremlin-python. This means full
> compatibility with asyncio and not needing to use threads.
> Async/await support is also a feature that those using newer, asynchronous
> web frameworks are asking for as gremlin-python can be a performance
> bottleneck due to its lack of support for async/await.
> My intention is that we can use this issue as a means of gauging the interest
> from the community for this feature. If you are interested in having
> gremlin-python support async/await then please leave a comment below.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)