diff --git a/docs/client.rst b/docs/client.rst index 246ba54..4b6f76d 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -5,5 +5,5 @@ Reference ~~~~~~~~~ .. automodule:: grpclib.client - :members: Channel, Stream, UnaryUnaryMethod, UnaryStreamMethod, - StreamUnaryMethod, StreamStreamMethod + :members: Channel, Stream, UnaryUnaryMethod, UnaryStreamMethod, + StreamUnaryMethod, StreamStreamMethod diff --git a/docs/conf.py b/docs/conf.py index dd6a423..94cc9cb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ autoclass_content = 'both' autodoc_member_order = 'bysource' intersphinx_mapping = { - 'python': ('https://docs.python.org/3.6', None), + 'python': ('https://docs.python.org/3.7', None), } source_suffix = '.rst' diff --git a/docs/server.rst b/docs/server.rst index de8b876..b334370 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -1,8 +1,53 @@ Server ====== +Single :py:class:`~grpclib.server.Server` can serve arbitrary +number of services: + +.. code-block:: python + + server = Server([services], loop=loop) + +To monitor health of your services you can use standard gRPC health checking +protocol, details are here: :doc:`health`. + +There is a special gRPC reflection protocol to inspect running servers and call +their methods using command-line tools, details are here: :doc:`reflection`. +It is as simple as using curl. + +And it is also important to handle server's exit properly: + +.. code-block:: python + + with graceful_exit([server]): + await server.start(host, port) + await server.wait_closed() + +:py:func:`~grpclib.utils.graceful_exit` helps you handle ``SIGINT`` +(during development) and ``SIGTERM`` (on production) signals. + +When things become complicated you can start using +:py:class:`~python:contextlib.AsyncExitStack` and +:py:func:`~python:contextlib.asynccontextmanager` to manage lifecycle of your +application: + +.. code-block:: python + + async with AsyncExitStack() as stack: + db = await stack.enter_async_context(setup_db()) + foo_svc = await stack.enter_async_context(setup_foo_svc()) + + bar_svc = BarService(db, foo_svc) + server = Server([bar_svc], loop=loop) + stack.enter_context(graceful_exit([server], loop=loop)) + await server.start(host, port) + await server.wait_closed() + Reference ~~~~~~~~~ .. automodule:: grpclib.server - :members: Server, Stream + :members: Server, Stream + +.. automodule:: grpclib.utils + :members: graceful_exit diff --git a/grpclib/utils.py b/grpclib/utils.py index 505ac0d..51e9e24 100644 --- a/grpclib/utils.py +++ b/grpclib/utils.py @@ -156,15 +156,16 @@ def graceful_exit(servers, *, loop, print('Server closed') First stage calls ``server.close()`` and ``await server.wait_closed()`` - should complete successfully without errors. + should complete successfully without errors. If server wasn't started yet, + second stage runs to prevent server start. Second stage raises ``SystemExit`` exception, but you will receive ``asyncio.CancelledError`` in your ``async def main()`` coroutine. You - can use ``try..finally`` constructs or context-managers to properly handle + can use ``try..finally`` constructs and context-managers to properly handle this error. This context-manager is designed to work in cooperation with - :py:func:`python:asyncio.run` function, introduced in Python 3.7: + :py:func:`python:asyncio.run` function: .. code-block:: python