From ac358205796b544354cb2cb5b5f18100d9a334cd Mon Sep 17 00:00:00 2001 From: Francis CLAIRICIA-ROSE-CLAIRE-JOSEPHINE Date: Sun, 5 Nov 2023 11:20:10 +0100 Subject: [PATCH] [DOCS] Internal: Simplified doc inheritance --- src/easynetwork/api_async/client/tcp.py | 3 +-- src/easynetwork/api_async/client/udp.py | 3 +-- src/easynetwork/api_async/server/tcp.py | 18 ++++++----------- src/easynetwork/api_async/server/udp.py | 18 ++++++----------- src/easynetwork/api_sync/server/_base.py | 20 +++++-------------- src/easynetwork/api_sync/server/tcp.py | 5 +++-- src/easynetwork/api_sync/server/udp.py | 5 +++-- src/easynetwork/converter.py | 6 +++--- src/easynetwork/lowlevel/_utils.py | 12 +++++++++++ .../lowlevel/api_sync/transports/socket.py | 17 ++++++++++++++++ 10 files changed, 57 insertions(+), 50 deletions(-) diff --git a/src/easynetwork/api_async/client/tcp.py b/src/easynetwork/api_async/client/tcp.py index 2ed11c7b..28e94576 100644 --- a/src/easynetwork/api_async/client/tcp.py +++ b/src/easynetwork/api_async/client/tcp.py @@ -477,11 +477,10 @@ def get_remote_address(self) -> SocketAddress: address_family = endpoint.extra(INETSocketAttribute.family) return new_socket_address(remote_address, address_family) + @_utils.inherit_doc(AbstractAsyncNetworkClient) def get_backend(self) -> AsyncBackend: return self.__backend - get_backend.__doc__ = AbstractAsyncNetworkClient.get_backend.__doc__ - async def __ensure_connected(self) -> AsyncStreamEndpoint[_SentPacketT, _ReceivedPacketT]: if self.__endpoint is None: endpoint_and_proxy = None diff --git a/src/easynetwork/api_async/client/udp.py b/src/easynetwork/api_async/client/udp.py index 7dee531e..ef892107 100644 --- a/src/easynetwork/api_async/client/udp.py +++ b/src/easynetwork/api_async/client/udp.py @@ -320,11 +320,10 @@ def get_remote_address(self) -> SocketAddress: address_family = endpoint.extra(INETSocketAttribute.family) return new_socket_address(remote_address, address_family) + @_utils.inherit_doc(AbstractAsyncNetworkClient) def get_backend(self) -> AsyncBackend: return self.__backend - get_backend.__doc__ = AbstractAsyncNetworkClient.get_backend.__doc__ - async def __ensure_connected(self) -> AsyncDatagramEndpoint[_SentPacketT, _ReceivedPacketT]: if self.__endpoint is None: endpoint_and_proxy = None diff --git a/src/easynetwork/api_async/server/tcp.py b/src/easynetwork/api_async/server/tcp.py index a8d45431..d11acc07 100644 --- a/src/easynetwork/api_async/server/tcp.py +++ b/src/easynetwork/api_async/server/tcp.py @@ -198,11 +198,10 @@ def _value_or_default(value: float | None, default: float) -> float: else: self.__client_connection_log_level = logging.DEBUG + @_utils.inherit_doc(AbstractAsyncNetworkServer) def is_serving(self) -> bool: return self.__servers is not None and all(not server.is_closing() for server in self.__servers) - is_serving.__doc__ = AbstractAsyncNetworkServer.is_serving.__doc__ - def stop_listening(self) -> None: """ Schedules the shutdown of all listener sockets. @@ -217,14 +216,13 @@ def stop_listening(self) -> None: exit_stack.callback(listener_task.cancel) del listener_task + @_utils.inherit_doc(AbstractAsyncNetworkServer) async def server_close(self) -> None: if self.__listeners_factory_scope is not None: self.__listeners_factory_scope.cancel() self.__listeners_factory = None await self.__close_servers() - server_close.__doc__ = AbstractAsyncNetworkServer.server_close.__doc__ - async def __close_servers(self) -> None: async with contextlib.AsyncExitStack() as exit_stack: servers, self.__servers = self.__servers, None @@ -240,6 +238,7 @@ async def __close_servers(self) -> None: await self.__backend.cancel_shielded_coro_yield() + @_utils.inherit_doc(AbstractAsyncNetworkServer) async def shutdown(self) -> None: if self.__mainloop_task is not None: self.__mainloop_task.cancel() @@ -252,8 +251,7 @@ async def shutdown(self) -> None: finally: self.__shutdown_asked = False - shutdown.__doc__ = AbstractAsyncNetworkServer.shutdown.__doc__ - + @_utils.inherit_doc(AbstractAsyncNetworkServer) async def serve_forever(self, *, is_up_event: SupportsEventSet | None = None) -> NoReturn: async with contextlib.AsyncExitStack() as server_exit_stack: # Wake up server @@ -330,8 +328,6 @@ async def serve_forever(self, *, is_up_event: SupportsEventSet | None = None) -> raise AssertionError("sleep_forever() does not return") - serve_forever.__doc__ = AbstractAsyncNetworkServer.serve_forever.__doc__ - async def __serve( self, server: lowlevel_stream_server.AsyncStreamServer[_RequestT, _ResponseT], @@ -449,6 +445,7 @@ def __suppress_and_log_remaining_exception(self, client_address: SocketAddress) self.__logger.error("Exception occurred during processing of request from %s", client_address, exc_info=exc) self.__logger.error("-" * 40) + @_utils.inherit_doc(AbstractAsyncNetworkServer) def get_addresses(self) -> Sequence[SocketAddress]: if (servers := self.__servers) is None: return () @@ -458,13 +455,10 @@ def get_addresses(self) -> Sequence[SocketAddress]: if not server.is_closing() ) - get_addresses.__doc__ = AbstractAsyncNetworkServer.get_addresses.__doc__ - + @_utils.inherit_doc(AbstractAsyncNetworkServer) def get_backend(self) -> AsyncBackend: return self.__backend - get_backend.__doc__ = AbstractAsyncNetworkServer.get_backend.__doc__ - @property def sockets(self) -> Sequence[SocketProxy]: """The listeners sockets. Read-only attribute.""" diff --git a/src/easynetwork/api_async/server/udp.py b/src/easynetwork/api_async/server/udp.py index ecb80b04..5bb6c805 100644 --- a/src/easynetwork/api_async/server/udp.py +++ b/src/easynetwork/api_async/server/udp.py @@ -133,19 +133,17 @@ def __init__( self.__clients_cache = defaultdict(weakref.WeakValueDictionary) self.__send_locks_cache = defaultdict(weakref.WeakValueDictionary) + @_utils.inherit_doc(AbstractAsyncNetworkServer) def is_serving(self) -> bool: return self.__servers is not None and all(not server.is_closing() for server in self.__servers) - is_serving.__doc__ = AbstractAsyncNetworkServer.is_serving.__doc__ - + @_utils.inherit_doc(AbstractAsyncNetworkServer) async def server_close(self) -> None: if self.__listeners_factory_scope is not None: self.__listeners_factory_scope.cancel() self.__listeners_factory = None await self.__close_servers() - server_close.__doc__ = AbstractAsyncNetworkServer.server_close.__doc__ - async def __close_servers(self) -> None: async with contextlib.AsyncExitStack() as exit_stack: servers, self.__servers = self.__servers, None @@ -165,6 +163,7 @@ async def __close_servers(self) -> None: await self.__backend.cancel_shielded_coro_yield() + @_utils.inherit_doc(AbstractAsyncNetworkServer) async def shutdown(self) -> None: if self.__mainloop_task is not None: self.__mainloop_task.cancel() @@ -177,8 +176,7 @@ async def shutdown(self) -> None: finally: self.__shutdown_asked = False - shutdown.__doc__ = AbstractAsyncNetworkServer.shutdown.__doc__ - + @_utils.inherit_doc(AbstractAsyncNetworkServer) async def serve_forever(self, *, is_up_event: SupportsEventSet | None = None) -> NoReturn: async with contextlib.AsyncExitStack() as server_exit_stack: # Wake up server @@ -256,8 +254,6 @@ async def serve_forever(self, *, is_up_event: SupportsEventSet | None = None) -> raise AssertionError("sleep_forever() does not return") - serve_forever.__doc__ = AbstractAsyncNetworkServer.serve_forever.__doc__ - async def __serve( self, server: lowlevel_datagram_server.AsyncDatagramServer[_RequestT, _ResponseT, tuple[Any, ...]], @@ -323,6 +319,7 @@ def __suppress_and_log_remaining_exception(self, client_address: SocketAddress) self.__logger.error("Exception occurred during processing of request from %s", client_address, exc_info=exc) self.__logger.error("-" * 40) + @_utils.inherit_doc(AbstractAsyncNetworkServer) def get_addresses(self) -> Sequence[SocketAddress]: if (servers := self.__servers) is None: return () @@ -332,13 +329,10 @@ def get_addresses(self) -> Sequence[SocketAddress]: if not server.is_closing() ) - get_addresses.__doc__ = AbstractAsyncNetworkServer.get_addresses.__doc__ - + @_utils.inherit_doc(AbstractAsyncNetworkServer) def get_backend(self) -> AsyncBackend: return self.__backend - get_backend.__doc__ = AbstractAsyncNetworkServer.get_backend.__doc__ - @property def sockets(self) -> Sequence[SocketProxy]: """The listeners sockets. Read-only attribute.""" diff --git a/src/easynetwork/api_sync/server/_base.py b/src/easynetwork/api_sync/server/_base.py index add196d1..e1a98eac 100644 --- a/src/easynetwork/api_sync/server/_base.py +++ b/src/easynetwork/api_sync/server/_base.py @@ -27,6 +27,7 @@ from ...api_async.server.abc import SupportsEventSet from ...exceptions import ServerAlreadyRunning, ServerClosedError +from ...lowlevel import _utils from ...lowlevel._lock import ForkSafeLock from ...lowlevel.api_async.backend.abc import ThreadsPortal from ...lowlevel.socket import SocketAddress @@ -56,14 +57,14 @@ def __init__(self, server: AbstractAsyncNetworkServer) -> None: self.__close_lock = ForkSafeLock() self.__bootstrap_lock = ForkSafeLock() + @_utils.inherit_doc(AbstractNetworkServer) def is_serving(self) -> bool: if (portal := self._portal) is not None: with contextlib.suppress(RuntimeError): return portal.run_sync(self.__server.is_serving) return False - is_serving.__doc__ = AbstractNetworkServer.is_serving.__doc__ - + @_utils.inherit_doc(AbstractNetworkServer) def server_close(self) -> None: with self.__close_lock.get(), contextlib.ExitStack() as stack, contextlib.suppress(RuntimeError): if (portal := self._portal) is not None: @@ -75,8 +76,7 @@ def server_close(self) -> None: backend = self.__server.get_backend() backend.bootstrap(self.__server.server_close) - server_close.__doc__ = AbstractNetworkServer.server_close.__doc__ - + @_utils.inherit_doc(AbstractNetworkServer) def shutdown(self, timeout: float | None = None) -> None: if (portal := self._portal) is not None: with contextlib.suppress(RuntimeError, concurrent.futures.CancelledError): @@ -91,8 +91,6 @@ def shutdown(self, timeout: float | None = None) -> None: timeout -= time.perf_counter() - _start self.__is_shutdown.wait(timeout) - shutdown.__doc__ = AbstractNetworkServer.shutdown.__doc__ - async def __do_shutdown_with_timeout(self, timeout_delay: float) -> None: backend = self.__server.get_backend() with backend.move_on_after(timeout_delay): @@ -151,21 +149,13 @@ async def serve_forever() -> NoReturn: backend.bootstrap(serve_forever, runner_options=runner_options) + @_utils.inherit_doc(AbstractNetworkServer) def get_addresses(self) -> Sequence[SocketAddress]: - """ - Returns all interfaces to which the listeners are bound. Thread-safe. - - Returns: - A sequence of network socket address. - If the server is not serving (:meth:`is_serving` returns :data:`False`), an empty sequence is returned. - """ if (portal := self._portal) is not None: with contextlib.suppress(RuntimeError): return portal.run_sync(self.__server.get_addresses) return () - get_addresses.__doc__ = AbstractNetworkServer.get_addresses.__doc__ - @property def _server(self) -> AbstractAsyncNetworkServer: return self.__server diff --git a/src/easynetwork/api_sync/server/tcp.py b/src/easynetwork/api_sync/server/tcp.py index d3d2bad6..ed823b7c 100644 --- a/src/easynetwork/api_sync/server/tcp.py +++ b/src/easynetwork/api_sync/server/tcp.py @@ -26,6 +26,7 @@ from ..._typevars import _RequestT, _ResponseT from ...api_async.server.tcp import AsyncTCPNetworkServer +from ...lowlevel import _utils from ...lowlevel.socket import SocketProxy from . import _base @@ -110,8 +111,8 @@ def stop_listening(self) -> None: portal.run_sync(self._server.stop_listening) @property + @_utils.inherit_doc(AsyncTCPNetworkServer) def sockets(self) -> Sequence[SocketProxy]: - """The listeners sockets. Read-only attribute.""" if (portal := self._portal) is not None: with contextlib.suppress(RuntimeError): sockets = portal.run_sync(lambda: self._server.sockets) @@ -119,8 +120,8 @@ def sockets(self) -> Sequence[SocketProxy]: return () @property + @_utils.inherit_doc(AsyncTCPNetworkServer) def logger(self) -> logging.Logger: - """The server's logger.""" return self._server.logger if TYPE_CHECKING: diff --git a/src/easynetwork/api_sync/server/udp.py b/src/easynetwork/api_sync/server/udp.py index 6c73df33..74707dba 100644 --- a/src/easynetwork/api_sync/server/udp.py +++ b/src/easynetwork/api_sync/server/udp.py @@ -26,6 +26,7 @@ from ..._typevars import _RequestT, _ResponseT from ...api_async.server.udp import AsyncUDPNetworkServer +from ...lowlevel import _utils from ...lowlevel.socket import SocketProxy from . import _base @@ -84,8 +85,8 @@ def __init__( ) @property + @_utils.inherit_doc(AsyncUDPNetworkServer) def sockets(self) -> Sequence[SocketProxy]: - """The listeners sockets. Read-only attribute.""" if (portal := self._portal) is not None: with contextlib.suppress(RuntimeError): sockets = portal.run_sync(lambda: self._server.sockets) @@ -93,8 +94,8 @@ def sockets(self) -> Sequence[SocketProxy]: return () @property + @_utils.inherit_doc(AsyncUDPNetworkServer) def logger(self) -> logging.Logger: - """The server's logger.""" return self._server.logger if TYPE_CHECKING: diff --git a/src/easynetwork/converter.py b/src/easynetwork/converter.py index 698f0a16..272ccd9f 100644 --- a/src/easynetwork/converter.py +++ b/src/easynetwork/converter.py @@ -27,6 +27,7 @@ from typing import Any, Generic, final from ._typevars import _DTOPacketT, _PacketT, _ReceivedPacketT, _SentPacketT +from .lowlevel import _utils class AbstractPacketConverterComposite(Generic[_SentPacketT, _ReceivedPacketT, _DTOPacketT], metaclass=ABCMeta): @@ -122,12 +123,11 @@ class AbstractPacketConverter(AbstractPacketConverterComposite[_PacketT, _Packet __slots__ = () @abstractmethod + @_utils.inherit_doc(AbstractPacketConverterComposite) def create_from_dto_packet(self, packet: _DTOPacketT, /) -> _PacketT: raise NotImplementedError @abstractmethod + @_utils.inherit_doc(AbstractPacketConverterComposite) def convert_to_dto_packet(self, obj: _PacketT, /) -> _DTOPacketT: raise NotImplementedError - - create_from_dto_packet.__doc__ = AbstractPacketConverterComposite.create_from_dto_packet.__doc__ - convert_to_dto_packet.__doc__ = AbstractPacketConverterComposite.convert_to_dto_packet.__doc__ diff --git a/src/easynetwork/lowlevel/_utils.py b/src/easynetwork/lowlevel/_utils.py index b1a0c0cf..7d339c08 100644 --- a/src/easynetwork/lowlevel/_utils.py +++ b/src/easynetwork/lowlevel/_utils.py @@ -64,6 +64,7 @@ _T_Arg = TypeVar("_T_Arg") _ExcType = TypeVar("_ExcType", bound=BaseException) +_FuncType = TypeVar("_FuncType", bound=Callable[..., Any]) def replace_kwargs(kwargs: dict[str, Any], keys: dict[str, str]) -> None: @@ -89,6 +90,17 @@ def decorator(func: Callable[Concatenate[_T_Arg, _P], _R], /) -> Callable[_P, _R return decorator +def inherit_doc(base_cls: type[Any]) -> Callable[[_FuncType], _FuncType]: + assert isinstance(base_cls, type) # nosec assert_used + + def decorator(dest_func: _FuncType) -> _FuncType: + ref_func: Any = getattr(base_cls, dest_func.__name__) + dest_func.__doc__ = ref_func.__doc__ + return dest_func + + return decorator + + def error_from_errno(errno: int) -> OSError: return OSError(errno, os.strerror(errno)) diff --git a/src/easynetwork/lowlevel/api_sync/transports/socket.py b/src/easynetwork/lowlevel/api_sync/transports/socket.py index 86c81178..3d254943 100644 --- a/src/easynetwork/lowlevel/api_sync/transports/socket.py +++ b/src/easynetwork/lowlevel/api_sync/transports/socket.py @@ -73,24 +73,29 @@ def __init__( self.__socket: socket.socket = sock self.__socket.setblocking(False) + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def is_closed(self) -> bool: return self.__socket.fileno() < 0 + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def close(self) -> None: _close_stream_socket(self.__socket) + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def recv_noblock(self, bufsize: int) -> bytes: try: return self.__socket.recv(bufsize) except (BlockingIOError, InterruptedError): raise base_selector.WouldBlockOnRead(self.__socket.fileno()) from None + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def send_noblock(self, data: bytes | bytearray | memoryview) -> int: try: return self.__socket.send(data) except (BlockingIOError, InterruptedError): raise base_selector.WouldBlockOnWrite(self.__socket.fileno()) from None + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def send_eof(self) -> None: if self.__socket.fileno() < 0: return @@ -106,6 +111,7 @@ def send_eof(self) -> None: raise @property + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: socket = self.__socket return socket_tools._get_socket_extra(socket) @@ -163,9 +169,11 @@ def __init__( self.__ssl_shutdown_timeout: float = shutdown_timeout self.__standard_compatible: bool = standard_compatible + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def is_closed(self) -> bool: return self.__socket.fileno() < 0 + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def close(self) -> None: try: if self.__standard_compatible: @@ -175,12 +183,15 @@ def close(self) -> None: finally: _close_stream_socket(self.__socket) + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def recv_noblock(self, bufsize: int) -> bytes: return self._try_ssl_method(self.__socket.recv, bufsize) + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def send_noblock(self, data: bytes | bytearray | memoryview) -> int: return self._try_ssl_method(self.__socket.send, data) + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def send_eof(self) -> None: # ssl.SSLSocket.shutdown() would close both read and write streams raise NotImplementedError("SSL/TLS API does not support sending EOF.") @@ -196,6 +207,7 @@ def _try_ssl_method(self, socket_method: Callable[_P, _R], /, *args: _P.args, ** raise base_selector.WouldBlockOnWrite(self.__socket.fileno()) from None @property + @_utils.inherit_doc(base_selector.SelectorStreamTransport) def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: socket = self.__socket return ChainMap( @@ -231,12 +243,15 @@ def __init__( self.__socket: socket.socket = sock self.__socket.setblocking(False) + @_utils.inherit_doc(base_selector.SelectorDatagramTransport) def is_closed(self) -> bool: return self.__socket.fileno() < 0 + @_utils.inherit_doc(base_selector.SelectorDatagramTransport) def close(self) -> None: self.__socket.close() + @_utils.inherit_doc(base_selector.SelectorDatagramTransport) def recv_noblock(self) -> bytes: max_datagram_size: int = self.__max_datagram_size try: @@ -244,6 +259,7 @@ def recv_noblock(self) -> bytes: except (BlockingIOError, InterruptedError): raise base_selector.WouldBlockOnRead(self.__socket.fileno()) from None + @_utils.inherit_doc(base_selector.SelectorDatagramTransport) def send_noblock(self, data: bytes | bytearray | memoryview) -> None: try: self.__socket.send(data) @@ -251,6 +267,7 @@ def send_noblock(self, data: bytes | bytearray | memoryview) -> None: raise base_selector.WouldBlockOnWrite(self.__socket.fileno()) from None @property + @_utils.inherit_doc(base_selector.SelectorDatagramTransport) def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: socket = self.__socket return socket_tools._get_socket_extra(socket)