From b0634555e9b4e0b2a969c34be43014d765cc1f3d Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Feb 2024 09:31:42 +0100 Subject: [PATCH 1/6] remove some old, outdated notes from docs --- docs/autogen_api.py | 50 -------- docs/source/index.md | 1 - docs/source/notes/index.md | 12 -- docs/source/notes/pyversions.md | 188 ----------------------------- docs/source/notes/unicode.md | 205 -------------------------------- 5 files changed, 456 deletions(-) delete mode 100755 docs/autogen_api.py delete mode 100644 docs/source/notes/index.md delete mode 100644 docs/source/notes/pyversions.md delete mode 100644 docs/source/notes/unicode.md diff --git a/docs/autogen_api.py b/docs/autogen_api.py deleted file mode 100755 index dbd46b7ad..000000000 --- a/docs/autogen_api.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -"""Script to auto-generate our API docs. -""" -# stdlib imports -import os -import sys - -# local imports -sys.path.append(os.path.abspath('sphinxext')) -# import sphinx_cython -from apigen import ApiDocWriter - -# ***************************************************************************** -if __name__ == '__main__': - pjoin = os.path.join - package = 'zmq' - outdir = pjoin('source', 'api', 'generated') - docwriter = ApiDocWriter(package, rst_extension='.rst') - # You have to escape the . here because . is a special char for regexps. - # You must do make clean if you change this! - docwriter.package_skip_patterns += [ - r'\.tests$', - r'\.backend$', - r'\.auth$', - r'\.eventloop\.minitornado$', - r'\.green\.eventloop$', - r'\.sugar$', - r'\.devices$', - ] - - docwriter.module_skip_patterns += [ - r'\.auth\.ioloop$', - r'\.eventloop\.ioloop$', - r'\.eventloop\.stack_context$', - r'\.eventloop\.future$', - r'\.error$', - r'\.green\..+$', - r'\.utils\.initthreads$', - r'\.utils\.constant_names$', - r'\.utils\.garbage$', - r'\.utils\.rebuffer$', - r'\.utils\.strtypes$', - r'\.zmq$', - ] - - # Now, generate the outputs - docwriter.write_api_docs(outdir) - docwriter.write_index(outdir, 'gen', relative_to=pjoin('source', 'api')) - - print('%d files written' % len(docwriter.written_modules)) diff --git a/docs/source/index.md b/docs/source/index.md index 161107e61..12241c6b9 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -48,7 +48,6 @@ maxdepth: 2 api/index.rst changelog.rst howto/index.rst -notes/index.rst ``` # Indices and tables diff --git a/docs/source/notes/index.md b/docs/source/notes/index.md deleted file mode 100644 index 2b7b16c57..000000000 --- a/docs/source/notes/index.md +++ /dev/null @@ -1,12 +0,0 @@ -# Notes from developing PyZMQ - -These are old notes from the early days of developing pyzmq, -back when we needed to support Python 2.5 and 3.1. - -```{toctree} ---- -maxdepth: 1 ---- -pyversions.rst -unicode.rst -``` diff --git a/docs/source/notes/pyversions.md b/docs/source/notes/pyversions.md deleted file mode 100644 index 4ffa07190..000000000 --- a/docs/source/notes/pyversions.md +++ /dev/null @@ -1,188 +0,0 @@ -(pyversions)= - -# PyZMQ, Python2.5, and Python3 - -```{note} -This describes early days of pyzmq development, -when we supported Python 2.5 and 3.1. -Much of this information is wildly outdated now. -``` - -PyZMQ is a fairly light, low-level library, so supporting as many versions -as is reasonable is our goal. Currently, we support at least Python 2.5-3.1. -Making the changes to the codebase required a few tricks, which are documented here -for future reference, either by us or by other developers looking to support several -versions of Python. - -```{note} -It is far simpler to support 2.6-3.x than to include 2.5. Many of the significant -syntax changes have been backported to 2.6, so just writing new-style code would work -in many cases. I will try to note these points as they come up. -``` - -## pyversion_compat.h - -Many functions we use, primarily involved in converting between C-buffers and Python -objects, are not available on all supported versions of Python. In order to resolve -missing symbols, we added a header {file}`utils/pyversion_compat.h` that defines missing -symbols with macros. Some of these macros alias new names to old functions (e.g. -`PyBytes_AsString`), so that we can call new-style functions on older versions, and some -simply define the function as an empty exception raiser. The important thing is that the -symbols are defined to prevent compiler warnings and linking errors. Everywhere we use -C-API functions that may not be available in a supported version, at the top of the file -is the code: - -```cython -cdef extern from "pyversion_compat.h": - pass -``` - -This ensures that the symbols are defined in the Cython generated C-code. Higher level -switching logic exists in the code itself, to prevent actually calling unavailable -functions, but the symbols must still be defined. - -## Bytes and Strings - -```{note} -If you are using Python >= 2.6, to prepare your PyZMQ code for Python3 you should use -the `b'message'` syntax to ensure all your string literal messages will still be -{class}`bytes` after you make the upgrade. -``` - -The most cumbersome part of PyZMQ compatibility from a user's perspective is the fact -that, since ØMQ uses C-strings, and would like to do so without copying, we must use the -Py3k {class}`bytes` object, which is backported to 2.6. In order to do this in a -Python-version independent way, we added a small utility that unambiguously defines the -string types: {class}`bytes`, {class}`unicode`, {obj}`basestring`. This is important, -because {class}`str` means different things on 2.x and 3.x, and {class}`bytes` is -undefined on 2.5, and both {class}`unicode` and {obj}`basestring` are undefined on 3.x. -All typechecking in PyZMQ is done against these types: - -| Explicit Type | 2.x | 3.x | -| ----------------- | ----------------- | ------------------- | -| {obj}`bytes` | {obj}`str` | {obj}`bytes` | -| {obj}`unicode` | {obj}`unicode` | {obj}`str` | -| {obj}`basestring` | {obj}`basestring` | {obj}`(str, bytes)` | - -```{note} -2.5 specific - -Where we really noticed the issue of {class}`bytes` vs {obj}`strings` coming up for -users was in updating the tests to run on every version. Since the `b'bytes literal'` syntax was not backported to 2.5, we must call `"message".encode()` for -*every* string in the test suite. -``` - -```{seealso} -{ref}`Unicode discussion ` for more information on strings/bytes. -``` - -### `PyBytes_*` - -The standard C-API function for turning a C-string into a Python string was a set of -functions with the prefix `PyString_*`. However, with the Unicode changes made in -Python3, this was broken into `PyBytes_*` for bytes objects and `PyUnicode_*` for -unicode objects. We changed all our `PyString_*` code to `PyBytes_*`, which was -backported to 2.6. - -````{note} -2.5 Specific: - -Since Python 2.5 doesn't support the `PyBytes_*` functions, we had to alias them to -the `PyString_*` methods in utils/pyversion_compat.h. - -```c -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_FromString PyString_FromString -#define PyBytes_AsString PyString_AsString -#define PyBytes_Size PyString_Size -``` - -```` - -## Buffers - -The layer that is most complicated for developers, but shouldn't trouble users, is the -Python C-Buffer APIs. These are the methods for converting between Python objects and C -buffers. The reason it is complicated is that it keeps changing. - -There are two buffer interfaces for converting an object to a C-buffer, known as new-style -and old-style. Old-style buffers were introduced long ago, but the new-style is only -backported to 2.6. The old-style buffer interface is not available in 3.x. There is also -an old- and new-style interface for creating Python objects that view C-memory. The -old-style object is called a {class}`buffer`, and the new-style object is -{class}`memoryview`. Unlike the new-style buffer interface for objects, -{class}`memoryview` has only been backported to *2.7*. This means that the available -buffer-related functions are not the same in any two versions of Python 2.5, 2.6, 2.7, or -3.1. - -We have a {file}`utils/buffers.pxd` file that defines our {func}`asbuffer` and -{func}`frombuffer` functions. {file}`utils/buffers.pxd` was adapted from [mpi4py]'s -{file}`asbuffer.pxi`. The {func}`frombuffer` functionality was added. These functions -internally switch based on Python version to call the appropriate C-API functions. - -```{seealso} -[Python Buffer API](https://docs.python.org/3/c-api/buffer.html) -``` - -## `__str__` - -As discussed, {class}`str` is not a platform independent type. The two places where we are -required to return native str objects are {func}`error.strerror`, and -{func}`Message.__str__`. In both of these cases, the natural return is actually a -{class}`bytes` object. In the methods, the native {class}`str` type is checked, and if the -native str is actually unicode, then we decode the bytes into unicode: - -```py -# ... -b = natural_result() -if str is unicode: - return b.decode() -else: - return b -``` - -## Exceptions - -```{note} -This section is only relevant for supporting Python 2.5 and 3.x, not for 2.6-3.x. -``` - -The syntax for handling exceptions has [changed](https://www.python.org/dev/peps/pep-3110/) in Python 3. The old syntax: - -```py -try: - s.send(msg) -except zmq.ZMQError, e: - handle(e) -``` - -is no longer valid in Python 3. Instead, the new syntax for this is: - -```py -try: - s.send(msg) -except zmq.ZMQError as e: - handle(e) -``` - -This new syntax is backported to Python 2.6, but is invalid on 2.5. For 2.6-3.x compatible -code, we could just use the new syntax. However, the only method we found to catch an -exception for handling on both 2.5 and 3.1 is to get the exception object inside the -exception block: - -```py -try: - s.send(msg) -except zmq.ZMQError: - e = sys.exc_info()[1] - handle(e) -``` - -This is certainly not as elegant as either the old or new syntax, but it's the only way we -have found to work everywhere. - -```{seealso} -[PEP 3110](https://www.python.org/dev/peps/pep-3110/) -``` - -[mpi4py]: https://mpi4py.readthedocs.io diff --git a/docs/source/notes/unicode.md b/docs/source/notes/unicode.md deleted file mode 100644 index 35d0783f3..000000000 --- a/docs/source/notes/unicode.md +++ /dev/null @@ -1,205 +0,0 @@ -(unicode)= - -# PyZMQ and Unicode - -```{note} -This describes early days of pyzmq development, -when we supported Python 2.5 and 3.1. -Much of this information is wildly outdated now. -``` - -PyZMQ is built with an eye towards an easy transition to Python 3, and part of -that is dealing with unicode strings. This is an overview of some of what we -found, and what it means for PyZMQ. - -## First, Unicode in Python 2 and 3 - -In Python \< 3, a `str` object is really a C string with some sugar - a -specific series of bytes with some fun methods like `endswith()` and -`split()`. In 2.0, the `unicode` object was added, which handles different -methods of encoding. In Python 3, however, the meaning of `str` changes. A -`str` in Python 3 is a full unicode object, with encoding and everything. If -you want a C string with some sugar, there is a new object called `bytes`, -that behaves much like the 2.x `str`. The idea is that for a user, a string is -a series of *characters*, not a series of bytes. For simple ascii, the two are -interchangeable, but if you consider accents and non-Latin characters, then the -character meaning of byte sequences can be ambiguous, since it depends on the -encoding scheme. They decided to avoid the ambiguity by forcing users who want -the actual bytes to specify the encoding every time they want to convert a -string to bytes. That way, users are aware of the difference between a series of -bytes and a collection of characters, and don't confuse the two, as happens in -Python 2.x. - -The problems (on both sides) come from the fact that regardless of the language -design, users are mostly going to use `str` objects to represent collections -of characters, and the behavior of that object is dramatically different in -certain aspects between the 2.x `bytes` approach and the 3.x `unicode` -approach. The `unicode` approach has the advantage of removing byte ambiguity -\- it's a list of characters, not bytes. However, if you really do want the -bytes, it's very inefficient to get them. The `bytes` approach has the -advantage of efficiency. A `bytes` object really is just a char\* pointer with -some methods to be used on it, so when interacting with, so interacting with C -code, etc is highly efficient and straightforward. However, understanding a -bytes object as a string with extended characters introduces ambiguity and -possibly confusion. - -To avoid ambiguity, hereafter we will refer to encoded C arrays as 'bytes' and -abstract unicode objects as 'strings'. - -### Unicode Buffers - -Since unicode objects have a wide range of representations, they are not stored -as the bytes according to their encoding, but rather in a format called UCS (an -older fixed-width Unicode format). On some platforms (macOS, Windows), the storage -is UCS-2, which is 2 bytes per character. On most \*ix systems, it is UCS-4, or -4 bytes per character. The contents of the *buffer* of a `unicode` object are -not encoding dependent (always UCS-2 or UCS-4), but they are *platform* -dependent. As a result of this, and the further insistence on not interpreting -`unicode` objects as bytes without specifying encoding, `str` objects in -Python 3 don't even provide the buffer interface. You simply cannot get the raw -bytes of a `unicode` object without specifying the encoding for the bytes. In -Python 2.x, you can get to the raw buffer, but the platform dependence and the -fact that the encoding of the buffer is not the encoding of the object makes it -very confusing, so this is probably a good move. - -The efficiency problem here comes from the fact that simple ascii strings are 4x -as big in memory as they need to be (on most Linux, 2x on other platforms). -Also, to translate to/from C code that works with char\*, you always have to copy -data and encode/decode the bytes. This really is horribly inefficient from a -memory standpoint. Essentially, Where memory efficiency matters to you, you -should never ever use strings; use bytes. The problem is that users will almost -always use `str`, and in 2.x they are efficient, but in 3.x they are not. We -want to make sure that we don't help the user make this mistake, so we ensure -that zmq methods don't try to hide what strings really are. - -## What This Means for PyZMQ - -PyZMQ is a wrapper for a C library, so it really should use bytes, since a -string is not a simple wrapper for `char *` like it used to be, but an -abstract sequence of characters. The representations of bytes in Python are -either the `bytes` object itself, or any object that provides the buffer -interface (aka memoryview). In Python 2.x, unicode objects do provide the buffer -interface, but as they do not in Python 3, where pyzmq requires bytes, we -specifically reject unicode objects. - -The relevant methods here are `socket.send/recv`, `socket.get/setsockopt`, -`socket.bind/connect`. The important consideration for send/recv and -set/getsockopt is that when you put in something, you really should get the same -object back with its partner method. We can easily coerce unicode objects to -bytes with send/setsockopt, but the problem is that the pair method of -recv/getsockopt will always be bytes, and there should be symmetry. We certainly -shouldn't try to always decode on the retrieval side, because if users just want -bytes, then we are potentially using up enormous amounts of excess memory -unnecessarily, due to copying and larger memory footprint of unicode strings. - -Still, we recognize the fact that users will quite frequently have unicode -strings that they want to send, so we have added `socket._string()` -wrappers. These methods simply wrap their bytes counterpart by encoding -to/decoding from bytes around them, and they all take an `encoding` keyword -argument that defaults to utf-8. Since encoding and decoding are necessary to -translate between unicode and bytes, it is impossible to perform non-copying -actions with these wrappers. - -`socket.bind/connect` methods are different from these, in that they are -strictly setters and there is not corresponding getter method. As a result, we -feel that we can safely coerce unicode objects to bytes (always to utf-8) in -these methods. - -```{note} -For cross-language symmetry (including Python 3), the `_unicode` methods -are now `_string`. Many languages have a notion of native strings, and -the use of `_unicode` was wedded too closely to the name of such objects -in Python 2. For the time being, anywhere you see `_string`, `_unicode` -also works, and is the only option in pyzmq ≤ 2.1.11. -``` - -### The Methods - -Overview of the relevant methods: - -```{eval-rst} -.. py:function:: socket.bind(self, addr) - - `addr` is ``bytes`` or ``unicode``. If ``unicode``, - encoded to utf-8 ``bytes`` -``` - -```{eval-rst} -.. py:function:: socket.connect(self, addr) - - `addr` is ``bytes`` or ``unicode``. If ``unicode``, - encoded to utf-8 ``bytes`` -``` - -```{eval-rst} -.. py:function:: socket.send(self, object obj, flags=0, copy=True) - - `obj` is ``bytes`` or provides buffer interface. - - if `obj` is ``unicode``, raise ``TypeError`` -``` - -```{eval-rst} -.. py:function:: socket.recv(self, flags=0, copy=True) - - returns ``bytes`` if `copy=True` - - returns ``zmq.Message`` if `copy=False`: - - `message.buffer` is a buffer view of the ``bytes`` - - `str(message)` provides the ``bytes`` - - `unicode(message)` decodes `message.buffer` with utf-8 -``` - -```{eval-rst} -.. py:function:: socket.send_string(self, unicode s, flags=0, encoding='utf-8') - - takes a ``unicode`` string `s`, and sends the ``bytes`` - after encoding without an extra copy, via: - - `socket.send(s.encode(encoding), flags, copy=False)` -``` - -```{eval-rst} -.. py:function:: socket.recv_string(self, flags=0, encoding='utf-8') - - always returns ``unicode`` string - - there will be a ``UnicodeError`` if it cannot decode the buffer - - performs non-copying `recv`, and decodes the buffer with `encoding` -``` - -```{eval-rst} -.. py:function:: socket.setsockopt(self, opt, optval) - - only accepts ``bytes`` for `optval` (or ``int``, depending on `opt`) - - ``TypeError`` if ``unicode`` or anything else -``` - -```{eval-rst} -.. py:function:: socket.getsockopt(self, opt) - - returns ``bytes`` (or ``int``), never ``unicode`` -``` - -```{eval-rst} -.. py:function:: socket.setsockopt_string(self, opt, unicode optval, encoding='utf-8') - - accepts ``unicode`` string for `optval` - - encodes `optval` with `encoding` before passing the ``bytes`` to - `setsockopt` -``` - -```{eval-rst} -.. py:function:: socket.getsockopt_string(self, opt, encoding='utf-8') - - always returns ``unicode`` string, after decoding with `encoding` - - note that `zmq.IDENTITY` is the only `sockopt` with a string value - that can be queried with `getsockopt` -``` From ca09e169255f24b2fdaf6c5ddaf5a64d568aa4da Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Feb 2024 12:45:06 +0100 Subject: [PATCH 2/6] use future annotations, fix docstring lint can use it now that we require Python 3.7 --- buildutils/constants.py | 2 +- zmq/__init__.py | 2 + zmq/_future.py | 57 ++++++---------- zmq/_typing.py | 2 + zmq/asyncio.py | 1 + zmq/auth/__init__.py | 1 - zmq/backend/cython/_zmq.py | 26 +++++--- zmq/constants.py | 5 +- zmq/decorators.py | 2 + zmq/devices/basedevice.py | 3 +- zmq/error.py | 10 +-- zmq/eventloop/future.py | 7 +- zmq/eventloop/zmqstream.py | 97 ++++++++++++--------------- zmq/green/__init__.py | 3 +- zmq/green/core.py | 5 +- zmq/green/device.py | 1 + zmq/green/poll.py | 2 + zmq/log/handlers.py | 10 +-- zmq/sugar/attrsettr.py | 1 + zmq/sugar/context.py | 72 +++++++++----------- zmq/sugar/frame.py | 11 +-- zmq/sugar/poll.py | 32 +++++---- zmq/sugar/socket.py | 133 +++++++++++++++++++------------------ zmq/sugar/tracker.py | 28 ++++---- zmq/sugar/version.py | 7 +- zmq/utils/jsonapi.py | 5 +- zmq/utils/monitor.py | 25 ++++--- zmq/utils/win32.py | 5 +- zmq/utils/z85.py | 1 + 29 files changed, 280 insertions(+), 276 deletions(-) diff --git a/buildutils/constants.py b/buildutils/constants.py index d980c63b2..f2594c5ee 100644 --- a/buildutils/constants.py +++ b/buildutils/constants.py @@ -79,7 +79,7 @@ def promoted_constants(): raise ValueError("Never encountered AUTOGENERATED_BELOW_HERE") global_assignments = [] - all_lines = ["__all__: List[str] = ["] + all_lines = ["__all__: list[str] = ["] for cls_name in sorted(dir(constants)): if cls_name.startswith("_"): continue diff --git a/zmq/__init__.py b/zmq/__init__.py index b12ddf64f..f1149c580 100644 --- a/zmq/__init__.py +++ b/zmq/__init__.py @@ -3,6 +3,8 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import os import sys from contextlib import contextmanager diff --git a/zmq/_future.py b/zmq/_future.py index b5a3bb92d..b3b8053a1 100644 --- a/zmq/_future.py +++ b/zmq/_future.py @@ -2,27 +2,14 @@ # Copyright (c) PyZMQ Developers. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import warnings from asyncio import Future from collections import deque from functools import partial from itertools import chain -from typing import ( - Any, - Awaitable, - Callable, - Dict, - List, - NamedTuple, - Optional, - Tuple, - Type, - TypeVar, - Union, - cast, - overload, -) +from typing import Any, Awaitable, Callable, NamedTuple, TypeVar, cast, overload import zmq as _zmq from zmq import EVENTS, POLLIN, POLLOUT @@ -32,7 +19,7 @@ class _FutureEvent(NamedTuple): future: Future kind: str - kwargs: Dict + kwargs: dict msg: Any timer: Any @@ -50,7 +37,7 @@ class _Async: """Mixin for common async logic""" _current_loop: Any = None - _Future: Type[Future] + _Future: type[Future] def _get_loop(self) -> Any: """Get event loop @@ -79,10 +66,10 @@ def _init_io_state(self, loop=None) -> None: class _AsyncPoller(_Async, _zmq.Poller): """Poller that returns a Future on poll, instead of blocking.""" - _socket_class: Type["_AsyncSocket"] + _socket_class: type[_AsyncSocket] _READ: int _WRITE: int - raw_sockets: List[Any] + raw_sockets: list[Any] def _watch_raw_socket(self, loop: Any, socket: Any, evt: int, f: Callable) -> None: """Schedule callback for a raw socket""" @@ -92,7 +79,7 @@ def _unwatch_raw_sockets(self, loop: Any, *sockets: Any) -> None: """Unschedule callback for a raw socket""" raise NotImplementedError() - def poll(self, timeout=-1) -> Awaitable[List[Tuple[Any, int]]]: # type: ignore + def poll(self, timeout=-1) -> Awaitable[list[tuple[Any, int]]]: # type: ignore """Return a Future for a poll event""" future = self._Future() if timeout == 0: @@ -110,7 +97,7 @@ def poll(self, timeout=-1) -> Awaitable[List[Tuple[Any, int]]]: # type: ignore watcher = self._Future() # watch raw sockets: - raw_sockets: List[Any] = [] + raw_sockets: list[Any] = [] def wake_raw(*args): if not watcher.done(): @@ -120,7 +107,7 @@ def wake_raw(*args): lambda f: self._unwatch_raw_sockets(loop, *raw_sockets) ) - wrapped_sockets: List[_AsyncSocket] = [] + wrapped_sockets: list[_AsyncSocket] = [] def _clear_wrapper_io(f): for s in wrapped_sockets: @@ -210,7 +197,7 @@ class _AsyncSocket(_Async, _zmq.Socket[Future]): _recv_futures = None _send_futures = None _state = 0 - _shadow_sock: "_zmq.Socket" + _shadow_sock: _zmq.Socket _poller_class = _AsyncPoller _fd = None @@ -219,7 +206,7 @@ def __init__( context=None, socket_type=-1, io_loop=None, - _from_socket: Optional["_zmq.Socket"] = None, + _from_socket: _zmq.Socket | None = None, **kwargs, ) -> None: if isinstance(context, _zmq.Socket): @@ -244,13 +231,13 @@ def __init__( self._fd = self._shadow_sock.FD @classmethod - def from_socket(cls: Type[T], socket: "_zmq.Socket", io_loop: Any = None) -> T: + def from_socket(cls: type[T], socket: _zmq.Socket, io_loop: Any = None) -> T: """Create an async socket from an existing Socket""" return cls(_from_socket=socket, io_loop=io_loop) - def close(self, linger: Optional[int] = None) -> None: + def close(self, linger: int | None = None) -> None: if not self.closed and self._fd is not None: - event_list: List[_FutureEvent] = list( + event_list: list[_FutureEvent] = list( chain(self._recv_futures or [], self._send_futures or []) ) for event in event_list: @@ -276,27 +263,27 @@ def get(self, key): @overload # type: ignore def recv_multipart( self, flags: int = 0, *, track: bool = False - ) -> Awaitable[List[bytes]]: ... + ) -> Awaitable[list[bytes]]: ... @overload def recv_multipart( self, flags: int = 0, *, copy: Literal[True], track: bool = False - ) -> Awaitable[List[bytes]]: ... + ) -> Awaitable[list[bytes]]: ... @overload def recv_multipart( self, flags: int = 0, *, copy: Literal[False], track: bool = False - ) -> Awaitable[List[_zmq.Frame]]: # type: ignore + ) -> Awaitable[list[_zmq.Frame]]: # type: ignore ... @overload def recv_multipart( self, flags: int = 0, copy: bool = True, track: bool = False - ) -> Awaitable[Union[List[bytes], List[_zmq.Frame]]]: ... + ) -> Awaitable[list[bytes] | list[_zmq.Frame]]: ... def recv_multipart( self, flags: int = 0, copy: bool = True, track: bool = False - ) -> Awaitable[Union[List[bytes], List[_zmq.Frame]]]: + ) -> Awaitable[list[bytes] | list[_zmq.Frame]]: """Receive a complete multipart zmq message. Returns a Future whose result will be a multipart message. @@ -307,7 +294,7 @@ def recv_multipart( def recv( # type: ignore self, flags: int = 0, copy: bool = True, track: bool = False - ) -> Awaitable[Union[bytes, _zmq.Frame]]: + ) -> Awaitable[bytes | _zmq.Frame]: """Receive a single zmq frame. Returns a Future, whose result will be the received frame. @@ -318,7 +305,7 @@ def recv( # type: ignore def send_multipart( # type: ignore self, msg_parts: Any, flags: int = 0, copy: bool = True, track=False, **kwargs - ) -> Awaitable[Optional[_zmq.MessageTracker]]: + ) -> Awaitable[_zmq.MessageTracker | None]: """Send a complete multipart zmq message. Returns a Future that resolves when sending is complete. @@ -335,7 +322,7 @@ def send( # type: ignore copy: bool = True, track: bool = False, **kwargs: Any, - ) -> Awaitable[Optional[_zmq.MessageTracker]]: + ) -> Awaitable[_zmq.MessageTracker | None]: """Send a single zmq frame. Returns a Future that resolves when sending is complete. diff --git a/zmq/_typing.py b/zmq/_typing.py index e08013605..7bb211af2 100644 --- a/zmq/_typing.py +++ b/zmq/_typing.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from typing import Any, Dict diff --git a/zmq/asyncio.py b/zmq/asyncio.py index 839a406e2..d1effb8a5 100644 --- a/zmq/asyncio.py +++ b/zmq/asyncio.py @@ -5,6 +5,7 @@ # Copyright (c) PyZMQ Developers. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import asyncio import selectors diff --git a/zmq/auth/__init__.py b/zmq/auth/__init__.py index 330ea4e84..c30ae8838 100644 --- a/zmq/auth/__init__.py +++ b/zmq/auth/__init__.py @@ -1,7 +1,6 @@ """Utilities for ZAP authentication. To run authentication in a background thread, see :mod:`zmq.auth.thread`. -For integration with the tornado eventloop, see :mod:`zmq.auth.ioloop`. For integration with the asyncio event loop, see :mod:`zmq.auth.asyncio`. Authentication examples are provided in the pyzmq codebase, under diff --git a/zmq/backend/cython/_zmq.py b/zmq/backend/cython/_zmq.py index f569ddb04..bd97837a1 100644 --- a/zmq/backend/cython/_zmq.py +++ b/zmq/backend/cython/_zmq.py @@ -3,6 +3,8 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + try: import cython @@ -334,7 +336,7 @@ def __del__(self): def __copy__(self): return self.fast_copy() - def fast_copy(self) -> "Frame": + def fast_copy(self) -> Frame: new_msg: Frame = Frame() # This does not copy the contents, but just increases the ref-count # of the zmq_msg by one. @@ -1015,7 +1017,8 @@ def monitor(self, addr, events: C.int = ZMQ_EVENT_ALL): The inproc url used for monitoring. Passing None as the addr will cause an existing socket monitor to be deregistered. - events : int [default: zmq.EVENT_ALL] + events : int + default: zmq.EVENT_ALL The zmq event bitmask for which events will be sent to the monitor. """ _check_version((3, 2), "monitor") @@ -1407,7 +1410,7 @@ def has(capability) -> bool: return bool(zmq_has(ccap)) -def curve_keypair(): +def curve_keypair() -> tuple[bytes, bytes]: """generate a Z85 key pair for use with zmq.CURVE security Requires libzmq (≥ 4.0) to have been built with CURVE support. @@ -1417,8 +1420,10 @@ def curve_keypair(): Returns ------- - (public, secret) : two bytestrings - The public and private key pair as 40 byte z85-encoded bytestrings. + public: bytes + The public key as 40 byte z85-encoded bytestring. + private: bytes + The private key as 40 byte z85-encoded bytestring. """ rc: C.int public_key = declare(char[64]) @@ -1444,7 +1449,7 @@ def curve_public(secret_key) -> bytes: Returns ------- - bytestring + bytes The public key as a 40 byte z85-encoded bytestring """ if isinstance(secret_key, str): @@ -1586,7 +1591,8 @@ def device(device_type: C.int, frontend: Socket, backend: Socket = None): Parameters ---------- - device_type : (QUEUE, FORWARDER, STREAMER) + device_type : int + one of: QUEUE, FORWARDER, STREAMER The type of device to start. frontend : Socket The Socket instance for the incoming traffic. @@ -1865,13 +1871,13 @@ def monitored_queue( Parameters ---------- - in_socket : Socket + in_socket : zmq.Socket One of the sockets to the Queue. Its messages will be prefixed with 'in'. - out_socket : Socket + out_socket : zmq.Socket One of the sockets to the Queue. Its messages will be prefixed with 'out'. The only difference between in/out socket is this prefix. - mon_socket : Socket + mon_socket : zmq.Socket This socket sends out every message received by each of the others with an in/out prefix specifying which one it was. in_prefix : str diff --git a/zmq/constants.py b/zmq/constants.py index d9859871d..cddf433a1 100644 --- a/zmq/constants.py +++ b/zmq/constants.py @@ -1,9 +1,10 @@ """zmq constants as enums""" +from __future__ import annotations + import errno import sys from enum import Enum, IntEnum, IntFlag -from typing import List _HAUSNUMERO = 156384712 @@ -709,7 +710,7 @@ class DeviceType(IntEnum): PEER: int = SocketType.PEER CHANNEL: int = SocketType.CHANNEL -__all__: List[str] = [ +__all__: list[str] = [ "ContextOption", "IO_THREADS", "MAX_SOCKETS", diff --git a/zmq/decorators.py b/zmq/decorators.py index 21b599a75..7d4832a6a 100644 --- a/zmq/decorators.py +++ b/zmq/decorators.py @@ -15,6 +15,8 @@ def work(ctx, push): ... """ +from __future__ import annotations + # Copyright (c) PyZMQ Developers. # Distributed under the terms of the Modified BSD License. diff --git a/zmq/devices/basedevice.py b/zmq/devices/basedevice.py index 6d229da00..9e8013428 100644 --- a/zmq/devices/basedevice.py +++ b/zmq/devices/basedevice.py @@ -56,7 +56,8 @@ class Device: sets whether the thread should be run as a daemon Default is true, because if it is false, the thread will not exit unless it is killed - context_factory : callable (class attribute) + context_factory : callable + This is a class attribute. Function for creating the Context. This will be Context.instance in ThreadDevices, and Context in ProcessDevices. The only reason it is not instance() in ProcessDevices is that there may be a stale diff --git a/zmq/error.py b/zmq/error.py index 9f6febf85..8023bc195 100644 --- a/zmq/error.py +++ b/zmq/error.py @@ -2,9 +2,9 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations from errno import EINTR -from typing import Optional, Tuple, Union class ZMQBaseError(Exception): @@ -19,13 +19,13 @@ class ZMQError(ZMQBaseError): errno : int The ZMQ errno or None. If None, then ``zmq_errno()`` is called and used. - msg : string + msg : str Description of the error or None. """ - errno: Optional[int] = None + errno: int | None = None - def __init__(self, errno: Optional[int] = None, msg: Optional[str] = None): + def __init__(self, errno: int | None = None, msg: str | None = None): """Wrap an errno style error. Parameters @@ -183,7 +183,7 @@ def __str__(self): def _check_version( - min_version_info: Union[Tuple[int], Tuple[int, int], Tuple[int, int, int]], + min_version_info: tuple[int] | tuple[int, int] | tuple[int, int, int], msg: str = "Feature", ): """Check for libzmq diff --git a/zmq/eventloop/future.py b/zmq/eventloop/future.py index b5a732c2b..b2fca5b7f 100644 --- a/zmq/eventloop/future.py +++ b/zmq/eventloop/future.py @@ -8,10 +8,11 @@ # Copyright (c) PyZMQ Developers. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import asyncio import warnings -from typing import Any, Type +from typing import Any from tornado.concurrent import Future from tornado.ioloop import IOLoop @@ -50,7 +51,7 @@ def cancel(self): class _AsyncTornado: - _Future: Type[asyncio.Future] = _TornadoFuture + _Future: type[asyncio.Future] = _TornadoFuture _READ = IOLoop.READ _WRITE = IOLoop.WRITE @@ -91,7 +92,7 @@ class Context(_zmq.Context[Socket]): def _socket_class(self, socket_type): return Socket(self, socket_type) - def __init__(self: "Context", *args: Any, **kwargs: Any) -> None: + def __init__(self: Context, *args: Any, **kwargs: Any) -> None: io_loop = kwargs.pop('io_loop', None) if io_loop is not None: warnings.warn( diff --git a/zmq/eventloop/zmqstream.py b/zmq/eventloop/zmqstream.py index cf25375f9..720fd2bf9 100644 --- a/zmq/eventloop/zmqstream.py +++ b/zmq/eventloop/zmqstream.py @@ -12,6 +12,7 @@ # 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 __future__ import annotations """A utility class for event-based messaging on a zmq socket using tornado. @@ -25,17 +26,7 @@ import pickle import warnings from queue import Queue -from typing import ( - Any, - Awaitable, - Callable, - List, - Optional, - Sequence, - Union, - cast, - overload, -) +from typing import Any, Awaitable, Callable, Sequence, cast, overload from tornado.ioloop import IOLoop from tornado.log import gen_log @@ -105,15 +96,15 @@ class ZMQStream: io_loop: IOLoop poller: zmq.Poller _send_queue: Queue - _recv_callback: Optional[Callable] - _send_callback: Optional[Callable] - _close_callback: Optional[Callable] + _recv_callback: Callable | None + _send_callback: Callable | None + _close_callback: Callable | None _state: int = 0 _flushed: bool = False _recv_copy: bool = False _fd: int - def __init__(self, socket: "zmq.Socket", io_loop: Optional[IOLoop] = None): + def __init__(self, socket: zmq.Socket, io_loop: IOLoop | None = None): if isinstance(socket, zmq._future._AsyncSocket): warnings.warn( f"""ZMQStream only supports the base zmq.Socket class. @@ -177,39 +168,33 @@ def on_err(self, callback: Callable): @overload def on_recv( self, - callback: Callable[[List[bytes]], Any], + callback: Callable[[list[bytes]], Any], ) -> None: ... @overload def on_recv( self, - callback: Callable[[List[bytes]], Any], + callback: Callable[[list[bytes]], Any], copy: Literal[True], ) -> None: ... @overload def on_recv( self, - callback: Callable[[List[zmq.Frame]], Any], + callback: Callable[[list[zmq.Frame]], Any], copy: Literal[False], ) -> None: ... @overload def on_recv( self, - callback: Union[ - Callable[[List[zmq.Frame]], Any], - Callable[[List[bytes]], Any], - ], + callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any], copy: bool = ..., ): ... def on_recv( self, - callback: Union[ - Callable[[List[zmq.Frame]], Any], - Callable[[List[bytes]], Any], - ], + callback: Callable[[list[zmq.Frame]], Any] | Callable[[list[bytes]], Any], copy: bool = True, ) -> None: """Register a callback for when a message is ready to recv. @@ -249,39 +234,39 @@ def on_recv( @overload def on_recv_stream( self, - callback: Callable[["ZMQStream", List[bytes]], Any], + callback: Callable[[ZMQStream, list[bytes]], Any], ) -> None: ... @overload def on_recv_stream( self, - callback: Callable[["ZMQStream", List[bytes]], Any], + callback: Callable[[ZMQStream, list[bytes]], Any], copy: Literal[True], ) -> None: ... @overload def on_recv_stream( self, - callback: Callable[["ZMQStream", List[zmq.Frame]], Any], + callback: Callable[[ZMQStream, list[zmq.Frame]], Any], copy: Literal[False], ) -> None: ... @overload def on_recv_stream( self, - callback: Union[ - Callable[["ZMQStream", List[zmq.Frame]], Any], - Callable[["ZMQStream", List[bytes]], Any], - ], + callback: ( + Callable[[ZMQStream, list[zmq.Frame]], Any] + | Callable[[ZMQStream, list[bytes]], Any] + ), copy: bool = ..., ): ... def on_recv_stream( self, - callback: Union[ - Callable[["ZMQStream", List[zmq.Frame]], Any], - Callable[["ZMQStream", List[bytes]], Any], - ], + callback: ( + Callable[[ZMQStream, list[zmq.Frame]], Any] + | Callable[[ZMQStream, list[bytes]], Any] + ), copy: bool = True, ): """Same as on_recv, but callback will get this stream as first argument @@ -302,7 +287,7 @@ def stream_callback(msg): self.on_recv(stream_callback, copy=copy) def on_send( - self, callback: Callable[[Sequence[Any], Optional[zmq.MessageTracker]], Any] + self, callback: Callable[[Sequence[Any], zmq.MessageTracker | None], Any] ): """Register a callback to be called on each send @@ -345,9 +330,7 @@ def on_send( def on_send_stream( self, - callback: Callable[ - ["ZMQStream", Sequence[Any], Optional[zmq.MessageTracker]], Any - ], + callback: Callable[[ZMQStream, Sequence[Any], zmq.MessageTracker | None], Any], ): """Same as on_send, but callback will get this stream as first argument @@ -376,7 +359,7 @@ def send_multipart( flags: int = 0, copy: bool = True, track: bool = False, - callback: Optional[Callable] = None, + callback: Callable | None = None, **kwargs: Any, ) -> None: """Send a multipart message, optionally also register a new callback for sends. @@ -397,7 +380,7 @@ def send_string( u: str, flags: int = 0, encoding: str = 'utf-8', - callback: Optional[Callable] = None, + callback: Callable | None = None, **kwargs: Any, ): """Send a unicode message with an encoding. @@ -413,7 +396,7 @@ def send_json( self, obj: Any, flags: int = 0, - callback: Optional[Callable] = None, + callback: Callable | None = None, **kwargs: Any, ): """Send json-serialized version of an object. @@ -427,7 +410,7 @@ def send_pyobj( obj: Any, flags: int = 0, protocol: int = -1, - callback: Optional[Callable] = None, + callback: Callable | None = None, **kwargs: Any, ): """Send a Python object as a message using pickle to serialize. @@ -441,7 +424,7 @@ def _finish_flush(self): """callback for unsetting _flushed flag.""" self._flushed = False - def flush(self, flag: int = zmq.POLLIN | zmq.POLLOUT, limit: Optional[int] = None): + def flush(self, flag: int = zmq.POLLIN | zmq.POLLOUT, limit: int | None = None): """Flush pending messages. This method safely handles all pending incoming and/or outgoing messages, @@ -459,18 +442,20 @@ def flush(self, flag: int = zmq.POLLIN | zmq.POLLOUT, limit: Optional[int] = Non Parameters ---------- - flag : int, default=POLLIN|POLLOUT - 0MQ poll flags. - If flag|POLLIN, recv events will be flushed. - If flag|POLLOUT, send events will be flushed. - Both flags can be set at once, which is the default. + flag : int + default=POLLIN|POLLOUT + 0MQ poll flags. + If flag|POLLIN, recv events will be flushed. + If flag|POLLOUT, send events will be flushed. + Both flags can be set at once, which is the default. limit : None or int, optional - The maximum number of messages to send or receive. - Both send and recv count against this limit. + The maximum number of messages to send or receive. + Both send and recv count against this limit. Returns ------- - int : count of events handled (both send and recv) + int : + count of events handled (both send and recv) """ self._check_closed() # unset self._flushed, so callbacks will execute, in case flush has @@ -525,11 +510,11 @@ def update_flag(): self._rebuild_io_state() return count - def set_close_callback(self, callback: Optional[Callable]): + def set_close_callback(self, callback: Callable | None): """Call the given callback when the stream is closed.""" self._close_callback = callback - def close(self, linger: Optional[int] = None) -> None: + def close(self, linger: int | None = None) -> None: """Close this stream.""" if self.socket is not None: if self.socket.closed: diff --git a/zmq/green/__init__.py b/zmq/green/__init__.py index ec9a7c152..08820b8a9 100644 --- a/zmq/green/__init__.py +++ b/zmq/green/__init__.py @@ -27,6 +27,7 @@ before any blocking operation and the ØMQ file descriptor is polled internally to trigger needed events. """ +from __future__ import annotations from typing import List @@ -41,6 +42,6 @@ from zmq.green.device import device # type: ignore -__all__: List[str] = [] +__all__: list[str] = [] # adding `__all__` to __init__.pyi gets mypy all confused __all__.extend(_zmq.__all__) # type: ignore diff --git a/zmq/green/core.py b/zmq/green/core.py index 7b8969ef1..22b6d00fe 100644 --- a/zmq/green/core.py +++ b/zmq/green/core.py @@ -10,12 +10,11 @@ """This module wraps the :class:`Socket` and :class:`Context` found in :mod:`pyzmq ` to be non blocking """ - +from __future__ import annotations import sys import time import warnings -from typing import Tuple import gevent from gevent.event import AsyncResult @@ -28,7 +27,7 @@ from .poll import _Poller if hasattr(zmq, 'RCVTIMEO'): - TIMEOS: Tuple = (zmq.RCVTIMEO, zmq.SNDTIMEO) + TIMEOS: tuple = (zmq.RCVTIMEO, zmq.SNDTIMEO) else: TIMEOS = () diff --git a/zmq/green/device.py b/zmq/green/device.py index e12fd4c24..f9beae39e 100644 --- a/zmq/green/device.py +++ b/zmq/green/device.py @@ -1,5 +1,6 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import zmq from zmq.green import Poller diff --git a/zmq/green/poll.py b/zmq/green/poll.py index c8217254e..6443a9c47 100644 --- a/zmq/green/poll.py +++ b/zmq/green/poll.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import gevent from gevent import select diff --git a/zmq/log/handlers.py b/zmq/log/handlers.py index f2c3c9916..841d80524 100644 --- a/zmq/log/handlers.py +++ b/zmq/log/handlers.py @@ -42,14 +42,16 @@ https://github.com/jtriley/StarCluster/blob/StarCluster-0.91/starcluster/logger.py """ +from __future__ import annotations + import logging from copy import copy +import zmq + # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. -from typing import Optional, Union -import zmq TOPIC_DELIM = "::" # delimiter for splitting topics on the receiving end. @@ -82,8 +84,8 @@ class PUBHandler(logging.Handler): def __init__( self, - interface_or_socket: Union[str, zmq.Socket], - context: Optional[zmq.Context] = None, + interface_or_socket: str | zmq.Socket, + context: zmq.Context | None = None, root_topic: str = '', ) -> None: logging.Handler.__init__(self) diff --git a/zmq/sugar/attrsettr.py b/zmq/sugar/attrsettr.py index 4a4558884..844fce606 100644 --- a/zmq/sugar/attrsettr.py +++ b/zmq/sugar/attrsettr.py @@ -2,6 +2,7 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import errno from typing import TypeVar, Union diff --git a/zmq/sugar/context.py b/zmq/sugar/context.py index bd172aed6..54708b1f9 100644 --- a/zmq/sugar/context.py +++ b/zmq/sugar/context.py @@ -3,24 +3,16 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import atexit import os from threading import Lock -from typing import ( - Any, - Callable, - Dict, - Generic, - List, - Optional, - Type, - TypeVar, - Union, - overload, -) +from typing import Any, Callable, Generic, TypeVar, overload from warnings import warn from weakref import WeakSet +import zmq from zmq.backend import Context as ContextBase from zmq.constants import ContextOption, Errno, SocketOption from zmq.error import ZMQError @@ -40,11 +32,11 @@ def _notice_atexit() -> None: atexit.register(_notice_atexit) -T = TypeVar('T', bound='Context') -ST = TypeVar('ST', bound='Socket', covariant=True) +_ContextType = TypeVar('_ContextType', bound='Context') +_SocketType = TypeVar('_SocketType', bound='Socket', covariant=True) -class Context(ContextBase, AttributeSetter, Generic[ST]): +class Context(ContextBase, AttributeSetter, Generic[_SocketType]): """Create a zmq Context A zmq Context creates sockets via its ``ctx.socket`` method. @@ -74,32 +66,32 @@ class Context(ContextBase, AttributeSetter, Generic[ST]): ctx = zmq.Context.shadow(async_ctx.underlying) """ - sockopts: Dict[int, Any] + sockopts: dict[int, Any] _instance: Any = None _instance_lock = Lock() - _instance_pid: Optional[int] = None + _instance_pid: int | None = None _shadow = False _shadow_obj = None _warn_destroy_close = False _sockets: WeakSet # mypy doesn't like a default value here - _socket_class: Type[ST] = Socket # type: ignore + _socket_class: type[_SocketType] = Socket # type: ignore @overload - def __init__(self: "Context[Socket]", io_threads: int = 1): ... + def __init__(self: Context[Socket], io_threads: int = 1): ... @overload - def __init__(self: "Context[Socket]", io_threads: "Context"): + def __init__(self: Context[Socket], io_threads: Context): # this should be positional-only, but that requires 3.8 ... @overload - def __init__(self: "Context[Socket]", *, shadow: Union["Context", int]): ... + def __init__(self: Context[Socket], *, shadow: Context | int): ... def __init__( - self: "Context[Socket]", - io_threads: Union[int, "Context"] = 1, - shadow: Union["Context", int] = 0, + self: Context[Socket], + io_threads: int | Context = 1, + shadow: Context | int = 0, ) -> None: if isinstance(io_threads, Context): # allow positional shadow `zmq.Context(zmq.asyncio.Context())` @@ -166,7 +158,7 @@ def __repr__(self) -> str: sockets = "" return f"<{_repr_cls}({sockets}) at {hex(id(self))}{closed}>" - def __enter__(self: T) -> T: + def __enter__(self: _ContextType) -> _ContextType: return self def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: @@ -174,14 +166,14 @@ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: self._warn_destroy_close = True self.destroy() - def __copy__(self: T, memo: Any = None) -> T: + def __copy__(self: _ContextType, memo: Any = None) -> _ContextType: """Copying a Context creates a shadow copy""" return self.__class__.shadow(self.underlying) __deepcopy__ = __copy__ @classmethod - def shadow(cls: Type[T], address: Union[int, "Context"]) -> T: + def shadow(cls: type[_ContextType], address: int | zmq.Context) -> _ContextType: """Shadow an existing libzmq context address is a zmq.Context or an integer (or FFI pointer) @@ -196,7 +188,7 @@ def shadow(cls: Type[T], address: Union[int, "Context"]) -> T: return cls(shadow=address) @classmethod - def shadow_pyczmq(cls: Type[T], ctx: Any) -> T: + def shadow_pyczmq(cls: type[_ContextType], ctx: Any) -> _ContextType: """Shadow an existing pyczmq context ctx is the FFI `zctx_t *` pointer @@ -213,7 +205,7 @@ def shadow_pyczmq(cls: Type[T], ctx: Any) -> T: # static method copied from tornado IOLoop.instance @classmethod - def instance(cls: Type[T], io_threads: int = 1) -> T: + def instance(cls: type[_ContextType], io_threads: int = 1) -> _ContextType: """Returns a global Context instance. Most single-process applications have a single, global Context. @@ -276,7 +268,7 @@ def term(self) -> None: # Hooks for ctxopt completion # ------------------------------------------------------------------------- - def __dir__(self) -> List[str]: + def __dir__(self) -> list[str]: keys = dir(self.__class__) keys.extend(ContextOption.__members__) return keys @@ -295,13 +287,13 @@ def _rm_socket(self, socket: Any) -> None: if getattr(self, "_sockets", None) is not None: self._sockets.discard(socket) - def destroy(self, linger: Optional[int] = None) -> None: + def destroy(self, linger: int | None = None) -> None: """Close all sockets associated with this context and then terminate the context. .. warning:: - destroy involves calling ``zmq_close()``, which is **NOT** threadsafe. + destroy involves calling :meth:`Socket.close`, which is **NOT** threadsafe. If there are active sockets in other threads, this must not be called. Parameters @@ -313,7 +305,7 @@ def destroy(self, linger: Optional[int] = None) -> None: if self.closed: return - sockets: List[ST] = list(getattr(self, "_sockets", None) or []) + sockets: list[_SocketType] = list(getattr(self, "_sockets", None) or []) for s in sockets: if s and not s.closed: if self._warn_destroy_close and warn is not None: @@ -331,11 +323,11 @@ def destroy(self, linger: Optional[int] = None) -> None: self.term() def socket( - self: T, + self: _ContextType, socket_type: int, - socket_class: Optional[Callable[[T, int], ST]] = None, + socket_class: Callable[[_ContextType, int], _SocketType] | None = None, **kwargs: Any, - ) -> ST: + ) -> _SocketType: """Create a Socket associated with this Context. Parameters @@ -344,7 +336,7 @@ def socket( The socket type, which can be any of the 0MQ socket types: REQ, REP, PUB, SUB, PAIR, DEALER, ROUTER, PULL, PUSH, etc. - socket_class: zmq.Socket or a subclass + socket_class: zmq.Socket The socket class to instantiate, if different from the default for this Context. e.g. for creating an asyncio socket attached to a default Context or vice versa. @@ -357,8 +349,10 @@ def socket( raise ZMQError(Errno.ENOTSUP) if socket_class is None: socket_class = self._socket_class - s: ST = socket_class( # set PYTHONTRACEMALLOC=2 to get the calling frame - self, socket_type, **kwargs + s: _SocketType = ( + socket_class( # set PYTHONTRACEMALLOC=2 to get the calling frame + self, socket_type, **kwargs + ) ) for opt, value in self.sockopts.items(): try: diff --git a/zmq/sugar/frame.py b/zmq/sugar/frame.py index 0819b334e..ccab7d673 100644 --- a/zmq/sugar/frame.py +++ b/zmq/sugar/frame.py @@ -18,8 +18,7 @@ def _draft(v, feature): class Frame(FrameBase, AttributeSetter): - """Frame(data=None, track=False, copy=None, copy_threshold=zmq.COPY_THRESHOLD) - + """ A zmq message Frame class for non-copying send/recvs and access to message properties. A ``zmq.Frame`` wraps an underlying ``zmq_msg_t``. @@ -51,15 +50,17 @@ class Frame(FrameBase, AttributeSetter): data : object, optional any object that provides the buffer interface will be used to construct the 0MQ message data. - track : bool [default: False] + track : bool whether a MessageTracker_ should be created to track this object. Tracking a message has a cost at creation, because it creates a threadsafe Event object. - copy : bool [default: use copy_threshold] + copy : bool + default: use copy_threshold Whether to create a copy of the data to pass to libzmq or share the memory with libzmq. If unspecified, copy_threshold is used. - copy_threshold: int [default: zmq.COPY_THRESHOLD] + copy_threshold: int + default: :const:`zmq.COPY_THRESHOLD` If copy is unspecified, messages smaller than this many bytes will be copied and messages larger than this will be shared with libzmq. """ diff --git a/zmq/sugar/poll.py b/zmq/sugar/poll.py index 9ca4aecf1..27baad46e 100644 --- a/zmq/sugar/poll.py +++ b/zmq/sugar/poll.py @@ -3,7 +3,9 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. -from typing import Any, Dict, List, Optional, Tuple +from __future__ import annotations + +from typing import Any from zmq.backend import zmq_poll from zmq.constants import POLLERR, POLLIN, POLLOUT @@ -16,8 +18,8 @@ class Poller: """A stateful poll interface that mirrors Python's built-in poll.""" - sockets: List[Tuple[Any, int]] - _map: Dict + sockets: list[tuple[Any, int]] + _map: dict def __init__(self) -> None: self.sockets = [] @@ -75,7 +77,7 @@ def unregister(self, socket: Any): for socket, flags in self.sockets[idx:]: self._map[socket] -= 1 - def poll(self, timeout: Optional[int] = None) -> List[Tuple[Any, int]]: + def poll(self, timeout: int | None = None) -> list[tuple[Any, int]]: """Poll the registered 0MQ or native fds for I/O. If there are currently events ready to be processed, this function will return immediately. @@ -90,7 +92,7 @@ def poll(self, timeout: Optional[int] = None) -> List[Tuple[Any, int]]: Returns ------- - events : list of tuples + events : list The list of events that are ready to be processed. This is a list of tuples of the form ``(socket, event_mask)``, where the 0MQ Socket or integer fd is the first element, and the poll event mask (POLLIN, POLLOUT) is the second. @@ -104,7 +106,9 @@ def poll(self, timeout: Optional[int] = None) -> List[Tuple[Any, int]]: return zmq_poll(self.sockets, timeout=timeout) -def select(rlist: List, wlist: List, xlist: List, timeout: Optional[float] = None): +def select( + rlist: list, wlist: list, xlist: list, timeout: float | None = None +) -> tuple[list, list, list]: """select(rlist, wlist, xlist, timeout=None) -> (rlist, wlist, xlist) Return the result of poll as a lists of sockets ready for r/w/exception. @@ -113,20 +117,24 @@ def select(rlist: List, wlist: List, xlist: List, timeout: Optional[float] = Non Parameters ---------- - timeout : float, int, optional + timeout : float, optional The timeout in seconds. If None, no timeout (infinite). This is in seconds to be compatible with ``select.select()``. - rlist : list of sockets/FDs + rlist : list sockets/FDs to be polled for read events - wlist : list of sockets/FDs + wlist : list sockets/FDs to be polled for write events - xlist : list of sockets/FDs + xlist : list sockets/FDs to be polled for error events Returns ------- - (rlist, wlist, xlist) : tuple of lists of sockets (length 3) - Lists correspond to sockets available for read/write/error events respectively. + rlist: list + list of sockets or FDs that are readable + wlist: list + list of sockets or FDs that are writable + xlist: list + list of sockets or FDs that had error events (rare) """ if timeout is None: timeout = -1 diff --git a/zmq/sugar/socket.py b/zmq/sugar/socket.py index ff6b80e2c..ec2e4ba8a 100644 --- a/zmq/sugar/socket.py +++ b/zmq/sugar/socket.py @@ -3,6 +3,7 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import errno import pickle @@ -11,12 +12,9 @@ from typing import ( Any, Callable, - Dict, Generic, List, - Optional, Sequence, - Type, TypeVar, Union, cast, @@ -40,26 +38,28 @@ except AttributeError: DEFAULT_PROTOCOL = pickle.HIGHEST_PROTOCOL -T = TypeVar("T", bound="Socket") +_SocketType = TypeVar("_SocketType", bound="Socket") -class _SocketContext(Generic[T]): +class _SocketContext(Generic[_SocketType]): """Context Manager for socket bind/unbind""" - socket: T + socket: _SocketType kind: str addr: str def __repr__(self): return f"" - def __init__(self: "_SocketContext[T]", socket: T, kind: str, addr: str): + def __init__( + self: _SocketContext[_SocketType], socket: _SocketType, kind: str, addr: str + ): assert kind in {"bind", "connect"} self.socket = socket self.kind = kind self.addr = addr - def __enter__(self: "_SocketContext[T]") -> T: + def __enter__(self: _SocketContext[_SocketType]) -> _SocketType: return self.socket def __exit__(self, *args): @@ -71,10 +71,10 @@ def __exit__(self, *args): self.socket.disconnect(self.addr) -ST = TypeVar("ST") +SocketReturnType = TypeVar("SocketReturnType") -class Socket(SocketBase, AttributeSetter, Generic[ST]): +class Socket(SocketBase, AttributeSetter, Generic[SocketReturnType]): """The ZMQ socket object To create a Socket, first create a Context:: @@ -104,34 +104,34 @@ class Socket(SocketBase, AttributeSetter, Generic[ST]): @overload def __init__( - self: "Socket[bytes]", - ctx_or_socket: "zmq.Context", + self: Socket[bytes], + ctx_or_socket: zmq.Context, socket_type: int, *, - copy_threshold: Optional[int] = None, + copy_threshold: int | None = None, ): ... @overload def __init__( - self: "Socket[bytes]", + self: Socket[bytes], *, - shadow: Union["Socket", int], - copy_threshold: Optional[int] = None, + shadow: Socket | int, + copy_threshold: int | None = None, ): ... @overload def __init__( - self: "Socket[bytes]", - ctx_or_socket: "Socket", + self: Socket[bytes], + ctx_or_socket: Socket, ): ... def __init__( - self: "Socket[bytes]", - ctx_or_socket: Optional[Union["zmq.Context", "Socket"]] = None, + self: Socket[bytes], + ctx_or_socket: zmq.Context | Socket | None = None, socket_type: int = 0, *, - shadow: Union["Socket", int] = 0, - copy_threshold: Optional[int] = None, + shadow: Socket | int = 0, + copy_threshold: int | None = None, ): if isinstance(ctx_or_socket, zmq.Socket): # positional Socket(other_socket) @@ -198,7 +198,7 @@ def __repr__(self): return f"<{_repr_cls}(zmq.{self._type_name}) at {hex(id(self))}{closed}>" # socket as context manager: - def __enter__(self: T) -> T: + def __enter__(self: _SocketType) -> _SocketType: """Sockets are context managers .. versionadded:: 14.4 @@ -212,14 +212,14 @@ def __exit__(self, *args, **kwargs): # Socket creation # ------------------------------------------------------------------------- - def __copy__(self: T, memo=None) -> T: + def __copy__(self: _SocketType, memo=None) -> _SocketType: """Copying a Socket creates a shadow copy""" return self.__class__.shadow(self.underlying) __deepcopy__ = __copy__ @classmethod - def shadow(cls: Type[T], address: Union[int, "zmq.Socket"]) -> T: + def shadow(cls: type[_SocketType], address: int | zmq.Socket) -> _SocketType: """Shadow an existing libzmq socket address is a zmq.Socket or an integer (or FFI pointer) @@ -258,21 +258,21 @@ def close(self, linger=None) -> None: # Connect/Bind context managers # ------------------------------------------------------------------------- - def _connect_cm(self: T, addr: str) -> _SocketContext[T]: + def _connect_cm(self: _SocketType, addr: str) -> _SocketContext[_SocketType]: """Context manager to disconnect on exit .. versionadded:: 20.0 """ return _SocketContext(self, 'connect', addr) - def _bind_cm(self: T, addr: str) -> _SocketContext[T]: + def _bind_cm(self: _SocketType, addr: str) -> _SocketContext[_SocketType]: """Context manager to unbind on exit .. versionadded:: 20.0 """ return _SocketContext(self, 'bind', addr) - def bind(self: T, addr: str) -> _SocketContext[T]: + def bind(self: _SocketType, addr: str) -> _SocketContext[_SocketType]: """s.bind(addr) Bind the socket to an address. @@ -302,7 +302,7 @@ def bind(self: T, addr: str) -> _SocketContext[T]: raise return self._bind_cm(addr) - def connect(self: T, addr: str) -> _SocketContext[T]: + def connect(self: _SocketType, addr: str) -> _SocketContext[_SocketType]: """s.connect(addr) Connect to a remote 0MQ socket. @@ -379,7 +379,7 @@ def fileno(self) -> int: """ return self.FD - def subscribe(self, topic: Union[str, bytes]) -> None: + def subscribe(self, topic: str | bytes) -> None: """Subscribe to a topic Only for SUB sockets. @@ -390,7 +390,7 @@ def subscribe(self, topic: Union[str, bytes]) -> None: topic = topic.encode('utf8') self.set(zmq.SUBSCRIBE, topic) - def unsubscribe(self, topic: Union[str, bytes]) -> None: + def unsubscribe(self, topic: str | bytes) -> None: """Unsubscribe from a topic Only for SUB sockets. @@ -446,7 +446,7 @@ def get_string(self, option: int, encoding='utf-8') -> str: getsockopt_unicode = getsockopt_string = get_string def bind_to_random_port( - self: T, + self: _SocketType, addr: str, min_port: int = 49152, max_port: int = 65536, @@ -573,9 +573,9 @@ def send( copy: bool = ..., *, track: Literal[True], - routing_id: Optional[int] = ..., - group: Optional[str] = ..., - ) -> "zmq.MessageTracker": ... + routing_id: int | None = ..., + group: str | None = ..., + ) -> zmq.MessageTracker: ... @overload def send( @@ -585,8 +585,8 @@ def send( copy: bool = ..., *, track: Literal[False], - routing_id: Optional[int] = ..., - group: Optional[str] = ..., + routing_id: int | None = ..., + group: str | None = ..., ) -> None: ... @overload @@ -596,8 +596,8 @@ def send( flags: int = ..., *, copy: bool = ..., - routing_id: Optional[int] = ..., - group: Optional[str] = ..., + routing_id: int | None = ..., + group: str | None = ..., ) -> None: ... @overload @@ -607,9 +607,9 @@ def send( flags: int = ..., copy: bool = ..., track: bool = ..., - routing_id: Optional[int] = ..., - group: Optional[str] = ..., - ) -> Optional["zmq.MessageTracker"]: ... + routing_id: int | None = ..., + group: str | None = ..., + ) -> zmq.MessageTracker | None: ... def send( self, @@ -617,9 +617,9 @@ def send( flags: int = 0, copy: bool = True, track: bool = False, - routing_id: Optional[int] = None, - group: Optional[str] = None, - ) -> Optional["zmq.MessageTracker"]: + routing_id: int | None = None, + group: str | None = None, + ) -> zmq.MessageTracker | None: """Send a single zmq message frame on this socket. This queues the message to be sent by the IO thread at a later time. @@ -748,24 +748,24 @@ def send_multipart( @overload def recv_multipart( self, flags: int = ..., *, copy: Literal[True], track: bool = ... - ) -> List[bytes]: ... + ) -> list[bytes]: ... @overload def recv_multipart( self, flags: int = ..., *, copy: Literal[False], track: bool = ... - ) -> List[zmq.Frame]: ... + ) -> list[zmq.Frame]: ... @overload - def recv_multipart(self, flags: int = ..., *, track: bool = ...) -> List[bytes]: ... + def recv_multipart(self, flags: int = ..., *, track: bool = ...) -> list[bytes]: ... @overload def recv_multipart( self, flags: int = 0, copy: bool = True, track: bool = False - ) -> Union[List[zmq.Frame], List[bytes]]: ... + ) -> list[zmq.Frame] | list[bytes]: ... def recv_multipart( self, flags: int = 0, copy: bool = True, track: bool = False - ) -> Union[List[zmq.Frame], List[bytes]]: + ) -> list[zmq.Frame] | list[bytes]: """Receive a multipart message as a list of bytes or Frame objects Parameters @@ -879,7 +879,7 @@ def send_string( copy: bool = True, encoding: str = 'utf-8', **kwargs, - ) -> Optional["zmq.Frame"]: + ) -> zmq.Frame | None: """Send a Python unicode string as a message with an encoding. 0MQ communicates with raw bytes, so you must encode/decode @@ -891,7 +891,7 @@ def send_string( The unicode string to send. flags : int, optional Any valid flags for :func:`Socket.send`. - encoding : str [default: 'utf-8'] + encoding : str The encoding to be used """ if not isinstance(u, str): @@ -907,7 +907,7 @@ def recv_string(self, flags: int = 0, encoding: str = 'utf-8') -> str: ---------- flags : int Any valid flags for :func:`Socket.recv`. - encoding : str [default: 'utf-8'] + encoding : str The encoding to be used Returns @@ -918,7 +918,7 @@ def recv_string(self, flags: int = 0, encoding: str = 'utf-8') -> str: Raises ------ ZMQError - for any of the reasons :func:`~Socket.recv` might fail + for any of the reasons :func:`Socket.recv` might fail """ msg = self.recv(flags=flags) return self._deserialize(msg, lambda buf: buf.decode(encoding)) @@ -927,7 +927,7 @@ def recv_string(self, flags: int = 0, encoding: str = 'utf-8') -> str: def send_pyobj( self, obj: Any, flags: int = 0, protocol: int = DEFAULT_PROTOCOL, **kwargs - ) -> Optional[zmq.Frame]: + ) -> zmq.Frame | None: """Send a Python object as a message using pickle to serialize. Parameters @@ -983,7 +983,7 @@ def send_json(self, obj: Any, flags: int = 0, **kwargs) -> None: msg = jsonapi.dumps(obj, **kwargs) return self.send(msg, flags=flags, **send_kwargs) - def recv_json(self, flags: int = 0, **kwargs) -> Union[List, str, int, float, Dict]: + def recv_json(self, flags: int = 0, **kwargs) -> list | str | int | float | dict: """Receive a Python object as a message using json to serialize. Keyword arguments are passed on to json.loads @@ -1008,16 +1008,18 @@ def recv_json(self, flags: int = 0, **kwargs) -> Union[List, str, int, float, Di _poller_class = Poller - def poll(self, timeout=None, flags=zmq.POLLIN) -> int: + def poll(self, timeout: int | None = None, flags: int = zmq.POLLIN) -> int: """Poll the socket for events. + See :class:`Poller` to wait for multiple sockets at once. Parameters ---------- - timeout : int [default: None] + timeout : int The timeout (in milliseconds) to wait for an event. If unspecified (or specified None), will wait forever for an event. - flags : int [default: POLLIN] + flags : int + default: POLLIN. POLLIN, POLLOUT, or POLLIN|POLLOUT. The event flags to poll for. Returns @@ -1037,8 +1039,8 @@ def poll(self, timeout=None, flags=zmq.POLLIN) -> int: return evts.get(self, 0) def get_monitor_socket( - self: T, events: Optional[int] = None, addr: Optional[str] = None - ) -> T: + self: _SocketType, events: int | None = None, addr: str | None = None + ) -> _SocketType: """Return a connected PAIR socket ready to receive the event notifications. .. versionadded:: libzmq-4.0 @@ -1046,15 +1048,16 @@ def get_monitor_socket( Parameters ---------- - events : int [default: ZMQ_EVENT_ALL] + events : int + default: `zmq.EVENT_ALL` The bitmask defining which events are wanted. - addr : string [default: None] + addr : str The optional endpoint for the monitoring sockets. Returns ------- - socket : (PAIR) - The socket is already connected and ready to receive messages. + socket : zmq.Socket + The PAIR socket, connected and ready to receive messages. """ # safe-guard, method only available on libzmq >= 4 if zmq.zmq_version_info() < (4,): diff --git a/zmq/sugar/tracker.py b/zmq/sugar/tracker.py index 47ab66448..1887906a3 100644 --- a/zmq/sugar/tracker.py +++ b/zmq/sugar/tracker.py @@ -3,18 +3,17 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import time from threading import Event -from typing import Set, Tuple, Union from zmq.backend import Frame from zmq.error import NotDone class MessageTracker: - """MessageTracker(*towatch) - - A class for tracking if 0MQ is done using one or more messages. + """A class for tracking if 0MQ is done using one or more messages. When you send a 0MQ message, it is not sent immediately. The 0MQ IO thread sends the message at some later time. Often you want to know when 0MQ has @@ -24,19 +23,17 @@ class MessageTracker: Parameters ---------- - towatch : Event, MessageTracker, Message instances. + towatch : Event, MessageTracker, zmq.Frame This objects to track. This class can track the low-level Events used by the Message class, other MessageTrackers or actual Messages. """ - events: Set[Event] - peers: Set["MessageTracker"] + events: set[Event] + peers: set[MessageTracker] - def __init__(self, *towatch: Tuple[Union["MessageTracker", Event, Frame]]): - """MessageTracker(*towatch) - - Create a message tracker to track a set of messages. + def __init__(self, *towatch: tuple[MessageTracker | Event | Frame]): + """Create a message tracker to track a set of messages. Parameters ---------- @@ -70,14 +67,13 @@ def done(self): return False return True - def wait(self, timeout: Union[float, int] = -1): - """mt.wait(timeout=-1) - - Wait for 0MQ to be done with the message or until `timeout`. + def wait(self, timeout: float | int = -1): + """Wait for 0MQ to be done with the message or until `timeout`. Parameters ---------- - timeout : float [default: -1, wait forever] + timeout : float + default: -1, which means wait forever. Maximum time in (s) to wait before raising NotDone. Returns diff --git a/zmq/sugar/version.py b/zmq/sugar/version.py index 01e50858f..b98861121 100644 --- a/zmq/sugar/version.py +++ b/zmq/sugar/version.py @@ -2,9 +2,10 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import re -from typing import Match, Tuple, Union, cast +from typing import Match, cast from zmq.backend import zmq_version_info @@ -18,7 +19,7 @@ VERSION_PATCH = int(_version_groups[2]) VERSION_EXTRA = _version_groups[3].lstrip(".") -version_info: Union[Tuple[int, int, int], Tuple[int, int, int, float]] = ( +version_info: tuple[int, int, int] | tuple[int, int, int, float] = ( VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, @@ -43,7 +44,7 @@ def pyzmq_version() -> str: return __version__ -def pyzmq_version_info() -> Union[Tuple[int, int, int], Tuple[int, int, int, float]]: +def pyzmq_version_info() -> tuple[int, int, int] | tuple[int, int, int, float]: """return the pyzmq version as a tuple of at least three numbers If pyzmq is a development version, `inf` will be appended after the third integer. diff --git a/zmq/utils/jsonapi.py b/zmq/utils/jsonapi.py index 0c23d15e6..6a6ee0785 100644 --- a/zmq/utils/jsonapi.py +++ b/zmq/utils/jsonapi.py @@ -8,9 +8,10 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import json -from typing import Any, Dict, List, Union +from typing import Any # backward-compatibility, unused jsonmod = json @@ -24,7 +25,7 @@ def dumps(o: Any, **kwargs) -> bytes: return json.dumps(o, **kwargs).encode("utf8") -def loads(s: Union[bytes, str], **kwargs) -> Union[Dict, List, str, int, float]: +def loads(s: bytes | str, **kwargs) -> dict | list | str | int | float: """Load object from JSON bytes (utf-8). Keyword arguments are passed along to :py:func:`json.loads`. diff --git a/zmq/utils/monitor.py b/zmq/utils/monitor.py index 4b954d6c1..1adeb53ea 100644 --- a/zmq/utils/monitor.py +++ b/zmq/utils/monitor.py @@ -3,8 +3,10 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import struct -from typing import Awaitable, List, Union, overload +from typing import Awaitable, overload import zmq import zmq.asyncio @@ -18,7 +20,7 @@ class _MonitorMessage(TypedDict): endpoint: bytes -def parse_monitor_message(msg: List[bytes]) -> _MonitorMessage: +def parse_monitor_message(msg: list[bytes]) -> _MonitorMessage: """decode zmq_monitor event messages. Parameters @@ -51,7 +53,7 @@ def parse_monitor_message(msg: List[bytes]) -> _MonitorMessage: async def _parse_monitor_msg_async( - awaitable_msg: Awaitable[List[bytes]], + awaitable_msg: Awaitable[list[bytes]], ) -> _MonitorMessage: """Like parse_monitor_msg, but awaitable @@ -65,7 +67,7 @@ async def _parse_monitor_msg_async( @overload def recv_monitor_message( - socket: "zmq.asyncio.Socket", + socket: zmq.asyncio.Socket, flags: int = 0, ) -> Awaitable[_MonitorMessage]: ... @@ -80,15 +82,18 @@ def recv_monitor_message( def recv_monitor_message( socket: zmq.Socket, flags: int = 0, -) -> Union[_MonitorMessage, Awaitable[_MonitorMessage]]: +) -> _MonitorMessage | Awaitable[_MonitorMessage]: """Receive and decode the given raw message from the monitoring socket and return a dict. Requires libzmq ≥ 4.0 The returned dict will have the following entries: - event : int, the event id as described in libzmq.zmq_socket_monitor - value : int, the event value associated with the event, see libzmq.zmq_socket_monitor - endpoint : string, the affected endpoint + event : int + the event id as described in `libzmq.zmq_socket_monitor` + value : int + the event value associated with the event, see `libzmq.zmq_socket_monitor` + endpoint : str + the affected endpoint .. versionchanged:: 23.1 Support for async sockets added. @@ -97,9 +102,9 @@ def recv_monitor_message( Parameters ---------- - socket : zmq PAIR socket + socket : zmq.Socket The PAIR socket (created by other.get_monitor_socket()) on which to recv the message - flags : bitfield (int) + flags : int standard zmq recv flags Returns diff --git a/zmq/utils/win32.py b/zmq/utils/win32.py index f21e57b7a..1d6724dd5 100644 --- a/zmq/utils/win32.py +++ b/zmq/utils/win32.py @@ -4,9 +4,10 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. # ----------------------------------------------------------------------------- +from __future__ import annotations import os -from typing import Any, Callable, Optional +from typing import Any, Callable class allow_interrupt: @@ -60,7 +61,7 @@ def stop_my_application(): unblock your I/O loop. """ - def __init__(self, action: Optional[Callable[[], Any]] = None) -> None: + def __init__(self, action: Callable[[], Any] | None = None) -> None: """Translate ``action`` into a CTRL-C handler. ``action`` is a callable that takes no arguments and returns no diff --git a/zmq/utils/z85.py b/zmq/utils/z85.py index 13f6cb89b..de05fc833 100644 --- a/zmq/utils/z85.py +++ b/zmq/utils/z85.py @@ -9,6 +9,7 @@ # Copyright (C) PyZMQ Developers # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import struct From 987ba15c9802fec1528a4c52df37d02b92cb1c69 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Feb 2024 12:45:27 +0100 Subject: [PATCH 3/6] fix lots of broken cross-references in docs --- docs/Makefile | 4 +- docs/source/api/zmq.asyncio.rst | 26 +--- docs/source/api/zmq.auth.asyncio.rst | 4 +- docs/source/api/zmq.auth.ioloop.rst | 6 +- docs/source/api/zmq.auth.rst | 4 +- docs/source/api/zmq.auth.thread.rst | 6 +- docs/source/api/zmq.eventloop.future.rst | 11 +- docs/source/api/zmq.eventloop.ioloop.rst | 8 +- docs/source/api/zmq.eventloop.zmqstream.rst | 4 +- docs/source/api/zmq.green.rst | 4 +- docs/source/api/zmq.log.handlers.rst | 4 +- docs/source/api/zmq.rst | 9 ++ docs/source/api/zmq.ssh.tunnel.rst | 10 +- docs/source/api/zmq.utils.jsonapi.rst | 4 +- docs/source/api/zmq.utils.monitor.rst | 10 +- docs/source/api/zmq.utils.z85.rst | 4 +- docs/source/changelog.md | 102 +++++++------- docs/source/conf.py | 38 ++++- docs/source/howto/devices.md | 6 +- docs/source/howto/eventloop.md | 147 +++----------------- docs/source/howto/morethanbindings.md | 40 ++---- docs/source/howto/ssh.md | 6 +- 22 files changed, 178 insertions(+), 279 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 66808f42e..6bf400995 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -n SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build @@ -11,7 +11,7 @@ SRCDIR = source # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) +ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest diff --git a/docs/source/api/zmq.asyncio.rst b/docs/source/api/zmq.asyncio.rst index 208237ecc..74a9070a5 100644 --- a/docs/source/api/zmq.asyncio.rst +++ b/docs/source/api/zmq.asyncio.rst @@ -1,5 +1,3 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - asyncio ======= @@ -13,8 +11,8 @@ Module: :mod:`zmq.asyncio` As of 15.0, pyzmq now supports :mod:`asyncio`, via :mod:`zmq.asyncio`. When imported from this module, blocking methods such as -:meth:`zmq.asyncio.Socket.recv_multipart`, :meth:`zmq.asyncio.Socket.poll`, -and :meth:`zmq.asyncio.Poller.poll` return :class:`~.asyncio.Future` s. +:meth:`Socket.recv_multipart`, :meth:`Socket.poll`, +and :meth:`Poller.poll` return :class:`~.asyncio.Future` s. .. sourcecode:: python @@ -37,19 +35,12 @@ and :meth:`zmq.asyncio.Poller.poll` return :class:`~.asyncio.Future` s. Classes ------- -:class:`ZMQEventLoop` -~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: ZMQEventLoop - - :class:`Context` ~~~~~~~~~~~~~~~~ Context class that creates Future-returning sockets. See :class:`zmq.Context` for more info. .. autoclass:: Context - :noindex: @@ -64,18 +55,17 @@ for use in coroutines and async applications. :class:`zmq.Socket` for the inherited API. .. autoclass:: Socket - :noindex: .. automethod:: recv - :noindex: + .. automethod:: recv_multipart - :noindex: + .. automethod:: send - :noindex: + .. automethod:: send_multipart - :noindex: + .. automethod:: poll - :noindex: + :class:`Poller` ~~~~~~~~~~~~~~~ @@ -88,7 +78,5 @@ for use in coroutines and async applications. :class:`zmq.Poller` for the inherited API. .. autoclass:: Poller - :noindex: .. automethod:: poll - :noindex: diff --git a/docs/source/api/zmq.auth.asyncio.rst b/docs/source/api/zmq.auth.asyncio.rst index 43d8382f0..6df9b871b 100644 --- a/docs/source/api/zmq.auth.asyncio.rst +++ b/docs/source/api/zmq.auth.asyncio.rst @@ -1,8 +1,8 @@ auth.asyncio ============ -Module: :mod:`auth.asyncio` ---------------------------- +Module: :mod:`zmq.auth.asyncio` +------------------------------- .. automodule:: zmq.auth.asyncio diff --git a/docs/source/api/zmq.auth.ioloop.rst b/docs/source/api/zmq.auth.ioloop.rst index 3b46c85ed..5137f65fe 100644 --- a/docs/source/api/zmq.auth.ioloop.rst +++ b/docs/source/api/zmq.auth.ioloop.rst @@ -1,8 +1,10 @@ auth.ioloop =========== -Module: :mod:`auth.ioloop` --------------------------- +Module: :mod}`zmq.auth.ioloop` +------------------------------ + +.. module:: zmq.auth.ioloop This module is deprecated in pyzmq 25. Use :mod:`zmq.auth.asyncio`. diff --git a/docs/source/api/zmq.auth.rst b/docs/source/api/zmq.auth.rst index 4787a5633..73c7166d6 100644 --- a/docs/source/api/zmq.auth.rst +++ b/docs/source/api/zmq.auth.rst @@ -1,8 +1,8 @@ auth ==== -Module: :mod:`auth` -------------------- +Module: :mod:`zmq.auth` +----------------------- .. automodule:: zmq.auth .. currentmodule:: zmq.auth diff --git a/docs/source/api/zmq.auth.thread.rst b/docs/source/api/zmq.auth.thread.rst index d95f8ca96..013522a38 100644 --- a/docs/source/api/zmq.auth.thread.rst +++ b/docs/source/api/zmq.auth.thread.rst @@ -3,8 +3,8 @@ auth.thread =========== -Module: :mod:`auth.thread` --------------------------- +Module: :mod:`zmq.auth.thread` +------------------------------ .. automodule:: zmq.auth.thread .. currentmodule:: zmq.auth.thread @@ -21,3 +21,5 @@ Classes :members: :undoc-members: :inherited-members: + +.. autoclass:: AuthenticationThread diff --git a/docs/source/api/zmq.eventloop.future.rst b/docs/source/api/zmq.eventloop.future.rst index 9247ac4aa..bcd4cb3c7 100644 --- a/docs/source/api/zmq.eventloop.future.rst +++ b/docs/source/api/zmq.eventloop.future.rst @@ -3,8 +3,8 @@ eventloop.future ================ -Module: :mod:`eventloop.future` -------------------------------- +Module: :mod:`zmq.eventloop.future` +----------------------------------- .. automodule:: zmq.eventloop.future .. currentmodule:: zmq.eventloop.future @@ -12,8 +12,8 @@ Module: :mod:`eventloop.future` .. versionadded:: 15.0 As of pyzmq 15, there is a new Socket subclass that returns Futures for recv methods, -which can be found at :class:`zmq.eventloop.future.Socket`. -You can create these sockets by instantiating a :class:`~zmq.eventloop.future.Context` +which can be found at :class:`Socket`. +You can create these sockets by instantiating a :class:`Context` from the same module. These sockets let you easily use zmq with tornado's coroutines. @@ -45,7 +45,6 @@ Classes Context class that creates Future-returning sockets. See :class:`zmq.Context` for more info. .. autoclass:: Context - :noindex: :class:`Socket` @@ -59,7 +58,6 @@ for use in coroutines and async applications. :class:`zmq.Socket` for the inherited API. .. autoclass:: Socket - :noindex: .. automethod:: recv :noindex: @@ -84,7 +82,6 @@ for use in coroutines and async applications. :class:`zmq.Poller` for the inherited API. .. autoclass:: Poller - :noindex: .. automethod:: poll :noindex: diff --git a/docs/source/api/zmq.eventloop.ioloop.rst b/docs/source/api/zmq.eventloop.ioloop.rst index 6ae4db33a..4c0498e05 100644 --- a/docs/source/api/zmq.eventloop.ioloop.rst +++ b/docs/source/api/zmq.eventloop.ioloop.rst @@ -2,8 +2,12 @@ eventloop.ioloop ================ -Module: :mod:`eventloop.ioloop` -------------------------------- +Module: :mod:`zmq.eventloop.ioloop` +----------------------------------- +.. currentmodule:: zmq.eventloop.ioloop + +.. module:: zmq.eventloop.ioloop + This module is deprecated in pyzmq 17. Use :py:mod:`tornado.ioloop`. diff --git a/docs/source/api/zmq.eventloop.zmqstream.rst b/docs/source/api/zmq.eventloop.zmqstream.rst index 4f445a8a7..26d8a7b17 100644 --- a/docs/source/api/zmq.eventloop.zmqstream.rst +++ b/docs/source/api/zmq.eventloop.zmqstream.rst @@ -3,8 +3,8 @@ eventloop.zmqstream =================== -Module: :mod:`eventloop.zmqstream` ----------------------------------- +Module: :mod:`zmq.eventloop.zmqstream` +-------------------------------------- .. automodule:: zmq.eventloop.zmqstream .. currentmodule:: zmq.eventloop.zmqstream diff --git a/docs/source/api/zmq.green.rst b/docs/source/api/zmq.green.rst index 6d0667a07..af1779f6f 100644 --- a/docs/source/api/zmq.green.rst +++ b/docs/source/api/zmq.green.rst @@ -1,7 +1,7 @@ green ===== -Module: :mod:`green` --------------------- +Module: :mod:`zmq.green` +------------------------ .. automodule:: zmq.green diff --git a/docs/source/api/zmq.log.handlers.rst b/docs/source/api/zmq.log.handlers.rst index fa2c56bde..4da60c4eb 100644 --- a/docs/source/api/zmq.log.handlers.rst +++ b/docs/source/api/zmq.log.handlers.rst @@ -3,8 +3,8 @@ log.handlers ============ -Module: :mod:`log.handlers` ---------------------------- +Module: :mod:`zmq.log.handlers` +------------------------------- .. automodule:: zmq.log.handlers .. currentmodule:: zmq.log.handlers diff --git a/docs/source/api/zmq.rst b/docs/source/api/zmq.rst index 9252cb27e..7dfbd51f2 100644 --- a/docs/source/api/zmq.rst +++ b/docs/source/api/zmq.rst @@ -105,6 +105,11 @@ as well as via enums (``zmq.SocketType.PUSH``, etc.). Each category of zmq constant is now available as an IntEnum. +.. data:: COPY_THRESHOLD + + The global default "small message" threshold for copying when `copy=False`. + Copying has a thread-coordination cost, so zero-copy only has a benefit for sufficiently large messages. + .. autoenum:: SocketType .. autoenum:: SocketOption @@ -123,6 +128,8 @@ as well as via enums (``zmq.SocketType.PUSH``, etc.). .. autoenum:: RouterNotify +.. autoenum:: ReconnectStop + .. autoenum:: SecurityMechanism .. autoenum:: DeviceType @@ -202,3 +209,5 @@ Functions .. autofunction:: zmq.get_includes .. autofunction:: zmq.get_library_dirs + +.. autofunction:: zmq.strerror diff --git a/docs/source/api/zmq.ssh.tunnel.rst b/docs/source/api/zmq.ssh.tunnel.rst index f7de99ba6..cd77ce1a6 100644 --- a/docs/source/api/zmq.ssh.tunnel.rst +++ b/docs/source/api/zmq.ssh.tunnel.rst @@ -3,8 +3,8 @@ ssh.tunnel ========== -Module: :mod:`ssh.tunnel` -------------------------- +Module: :mod:`zmq.ssh.tunnel` +----------------------------- .. automodule:: zmq.ssh.tunnel .. currentmodule:: zmq.ssh.tunnel @@ -16,12 +16,6 @@ Functions .. autofunction:: zmq.ssh.tunnel.open_tunnel -.. autofunction:: zmq.ssh.tunnel.openssh_tunnel - - -.. autofunction:: zmq.ssh.tunnel.paramiko_tunnel - - .. autofunction:: zmq.ssh.tunnel.select_random_ports diff --git a/docs/source/api/zmq.utils.jsonapi.rst b/docs/source/api/zmq.utils.jsonapi.rst index df03ef6b5..1a4e10f11 100644 --- a/docs/source/api/zmq.utils.jsonapi.rst +++ b/docs/source/api/zmq.utils.jsonapi.rst @@ -3,8 +3,8 @@ utils.jsonapi ============= -Module: :mod:`utils.jsonapi` ----------------------------- +Module: :mod:`zmq.utils.jsonapi` +-------------------------------- .. automodule:: zmq.utils.jsonapi .. currentmodule:: zmq.utils.jsonapi diff --git a/docs/source/api/zmq.utils.monitor.rst b/docs/source/api/zmq.utils.monitor.rst index 28647d9ac..0eeb60397 100644 --- a/docs/source/api/zmq.utils.monitor.rst +++ b/docs/source/api/zmq.utils.monitor.rst @@ -1,10 +1,8 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - utils.monitor ============= -Module: :mod:`utils.monitor` ----------------------------- +Module: :mod:`zmq.utils.monitor` +-------------------------------- .. automodule:: zmq.utils.monitor .. currentmodule:: zmq.utils.monitor @@ -13,7 +11,7 @@ Functions --------- -.. autofunction:: zmq.utils.monitor.parse_monitor_message +.. autofunction:: parse_monitor_message -.. autofunction:: zmq.utils.monitor.recv_monitor_message +.. autofunction:: recv_monitor_message diff --git a/docs/source/api/zmq.utils.z85.rst b/docs/source/api/zmq.utils.z85.rst index 802135f48..82d2368c4 100644 --- a/docs/source/api/zmq.utils.z85.rst +++ b/docs/source/api/zmq.utils.z85.rst @@ -3,8 +3,8 @@ utils.z85 ========= -Module: :mod:`utils.z85` ------------------------- +Module: :mod:`zmq.utils.z85` +---------------------------- .. automodule:: zmq.utils.z85 .. currentmodule:: zmq.utils.z85 diff --git a/docs/source/changelog.md b/docs/source/changelog.md index a321998de..a88fef311 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -120,8 +120,8 @@ Fixed: Deprecated: -- {class}`zmq.auth.ioloop.IOLoopAuthenticator` is deprecated in favor of {class}`zmq.auth.asyncio.AsyncioAuthenticator` -- As part of migrating toward modern pytest, {class}`zmq.tests.BaseZMQTestCase` is deprecated and should not be used outside pyzmq. +- `zmq.auth.ioloop.IOLoopAuthenticator` is deprecated in favor of {class}`zmq.auth.asyncio.AsyncioAuthenticator` +- As part of migrating toward modern pytest, `zmq.tests.BaseZMQTestCase` is deprecated and should not be used outside pyzmq. - `python setup.py test` is deprecated as a way to launch the tests. Just use `pytest`. @@ -197,7 +197,7 @@ Fixing some regressions in 23.0: Compatibility fixes: -- {func}`zmq.utils.monitor.recv_monitor_msg` now supports async Sockets. +- {func}`zmq.utils.monitor.recv_monitor_message` now supports async Sockets. - Fix build with mingw ## 23.0.0 @@ -431,7 +431,7 @@ Fixes: - Cython backend: Build Cython extensions with language level "3str" (requires Cython 0.29) - Cython backend: You can now `cimport zmq` - Asyncio: Fix memory leak in Poller -- Log: Much improved logging in {mod}`zmq.log` (see {doc}`howto/logging`) +- Log: Much improved logging in {mod}`zmq.log.handlers` (see {doc}`howto/logging`) - Log: add `python -m zmq.log` entrypoint - Sources generated with Cython 0.29.15 @@ -486,7 +486,7 @@ There are no code changes in this release. - Bump bundled libzmq to 4.2.5 - Improve tornado 5.0 compatibility (use {meth}`~tornado.ioloop.IOLoop.current` instead of {meth}`~tornado.ioloop.IOLoop.instance` - to get default loops in {class}`.ZMQStream` and {class}`.IOLoopAuthenticator`) + to get default loops in {class}`.ZMQStream` and `.IOLoopAuthenticator`) - Add support for {func}`.curve_public` - Remove delayed import of json in `send/recv_json` - Add {meth}`.Authenticator.configure_curve_callback` @@ -507,13 +507,13 @@ There are no code changes in this release. Tornado eventloop integration shouldn't be used without a proper tornado install since pyzmq 14. -- Allow pyzmq asyncio/tornado integration to run without installing {func}`zmq_poll` +- Allow pyzmq asyncio/tornado integration to run without installing `zmq_poll` implementation. The following methods and classes are deprecated and no longer required: - - {func}`zmq.eventloop.ioloop.install` - - {class}`zmq.eventloop.ioloop.IOLoop` - - {func}`zmq.asyncio.install` - - {class}`zmq.asyncio.ZMQEventLoop` + - `zmq.eventloop.ioloop.install` + - `zmq.eventloop.ioloop.IOLoop` + - `zmq.asyncio.install` + - `zmq.asyncio.ZMQEventLoop` - Set RPATH correctly when building on macOS. @@ -552,13 +552,13 @@ There are no code changes in this release. - Include zmq.h - Deprecate `zmq.Stopwatch`. Native Python timing tools can be used instead. - Better support for using pyzmq as a Cython library - \- bundle zmq.h when pyzmq bundles libzmq as an extension - \- add {func}`zmq.get_library_dirs` to find bundled libzmq + - bundle zmq.h when pyzmq bundles libzmq as an extension + - add {func}`zmq.get_library_dirs` to find bundled libzmq - Updates to setup.py for Cython 0.25 compatibility - Various asyncio/future fixes: - \- support raw sockets in pollers - \- allow cancelling async sends -- Fix {meth}`IOLoop.current` in {mod}`zmq.green` + - support raw sockets in pollers + - allow cancelling async sends +- Fix `IOLoop.current()` in {mod}`zmq.green` ## 15.4 @@ -583,12 +583,12 @@ There are no code changes in this release. - Add {meth}`zmq.Socket.subscribe` and {meth}`zmq.Socket.unsubscribe` methods to sockets, so that assignment is no longer needed for subscribing. Verbs should be methods! Assignment is still supported for backward-compatibility. - Accept text (unicode) input to z85 encoding, not just bytes -- {meth}`zmq.Context.socket` forwards keyword arguments to the {class}`Socket` constructor +- {meth}`zmq.Context.socket` forwards keyword arguments to the {class}`~.zmq.Socket` constructor ## 15.2 - FIX: handle multiple events in a single register call in {mod}`zmq.asyncio` -- FIX: unicode/bytes bug in password prompt in {mod}`zmq.ssh` on Python 3 +- FIX: unicode/bytes bug in password prompt in `zmq.ssh` on Python 3 - FIX: workaround gevent monkeypatches in garbage collection thread - update bundled minitornado from tornado-4.3. - improved inspection by setting `binding=True` in cython compile options @@ -604,10 +604,10 @@ There are no code changes in this release. ## 15.0 -PyZMQ 15 adds Future-returning sockets and pollers for both {mod}`asyncio` and {mod}`tornado`. +PyZMQ 15 adds Future-returning sockets and pollers for both {mod}`asyncio` and {mod}`tornado.concurrent`. - add {mod}`asyncio` support via {mod}`zmq.asyncio` -- add {mod}`tornado` future support via {mod}`zmq.eventloop.future` +- add {mod}`tornado.concurrent` future support via {mod}`zmq.eventloop.future` - trigger bundled libzmq if system libzmq is found to be \< 3. System libzmq 2 can be forced by explicitly requesting `--zmq=/prefix/`. @@ -640,7 +640,7 @@ Changes: Bugfixes: -- add missing {attr}`ndim` on memoryviews of Frames +- add missing `ndim` on memoryviews of Frames - allow {func}`copy.copy` and {func}`copy.deepcopy` on Sockets, Contexts ## 14.5.0 @@ -672,7 +672,7 @@ New features: - Experimental support for libzmq-4.1.0 rc (new constants, plus {func}`zmq.has`). - Update bundled libzmq to 4.0.5 - Update bundled libsodium to 1.0.0 -- Fixes for SSH dialogs when using {mod}`zmq.ssh` to create tunnels +- Fixes for SSH dialogs when using {mod}`zmq.ssh.tunnel` to create tunnels - More build/link/load fixes on OS X and Solaris - Get Frame metadata via dict access (libzmq 4) - Contexts and Sockets are context managers (term/close on `__exit__`) @@ -699,7 +699,7 @@ Minor bugfixes to pyzmq 14.3: - PyZMQ no longer calls {meth}`.Socket.close` or {meth}`.Context.term` during process cleanup. Changes to garbage collection in Python 3.4 make this impossible to do sensibly. -- {meth}`ZMQStream.close` closes its socket immediately, rather than scheduling a timeout. +- {meth}`.ZMQStream.close` closes its socket immediately, rather than scheduling a timeout. - Raise the original ImportError when importing zmq fails. Should be more informative than `no module cffi...`. @@ -756,7 +756,7 @@ Bugfix release - Update bundled libzmq to current (4.0.3). - Fix bug in {meth}`.Context.destroy` with no open sockets. - Threadsafety fixes in the garbage collector. -- Python 3 fixes in {mod}`zmq.ssh`. +- Python 3 fixes in {mod}`zmq.ssh.tunnel`. ## 14.0.0 @@ -820,7 +820,7 @@ This means that subclasses of these classes that require extra attributes - The Threadsafe ZMQStream experiment in 2.2.0.1 was deemed inappropriate and not useful, and has been removed. -- The {mod}`zmq.web` experiment has been removed, +- The `zmq.web` experiment has been removed, to be developed as a [standalone project](https://github.com/ellisonbg/zmqweb). ### New Stuff @@ -838,7 +838,7 @@ This means that subclasses of these classes that require extra attributes - {func}`zmq.proxy` - {class}`zmq.devices.Proxy` - Exceptions for common zmq errnos: {class}`zmq.Again`, {class}`zmq.ContextTerminated` - (subclass {class}`ZMQError`, so fully backward-compatible). + (subclass {class}`~.ZMQError`, so fully backward-compatible). - Setting and getting {attr}`.Socket.hwm` sets or gets *both* SNDHWM/RCVHWM for libzmq-3. @@ -867,7 +867,7 @@ and may be removed or changed in incompatible ways in later releases. #### Threadsafe ZMQStream With the IOLoop inherited from tornado, there is exactly one method that is threadsafe: -{meth}`.IOLoop.add_callback`. With this release, we are trying an experimental option +{meth}`~.tornado.ioloop.IOLoop.add_callback`. With this release, we are trying an experimental option to pass all IOLoop calls via this method, so that ZMQStreams can be used from one thread while the IOLoop runs in another. To try out a threadsafe stream: @@ -913,7 +913,7 @@ possible), to allow more permissive use of less-critical code and utilities. ### Name Changes -- The {class}`~.Message` class has been renamed to {class}`~.Frame`, to better match other +- The `Message` class has been renamed to {class}`~.Frame`, to better match other zmq bindings. The old Message name remains for backwards-compatibility. Wherever pyzmq docs say "Message", they should refer to a complete zmq atom of communication (one or more Frames, connected by ZMQ_SNDMORE). Please report any remaining instances of @@ -925,13 +925,13 @@ possible), to allow more permissive use of less-critical code and utilities. ### Other Changes and Removals -- `prefix` removed as an unused keyword argument from {meth}`~.Socket.send_multipart`. +- `prefix` removed as an unused keyword argument from {meth}`~.zmq.Socket.send_multipart`. - ZMQStream {meth}`~.ZMQStream.send` default has been changed to `copy=True`, so it matches - Socket {meth}`~.Socket.send`. + Socket {meth}`~.zmq.Socket.send`. - ZMQStream {meth}`~.ZMQStream.on_err` is deprecated, because it never did anything. - Python 2.5 compatibility has been dropped, and some code has been cleaned up to reflect no-longer-needed hacks. -- Some Cython files in {mod}`zmq.core` have been split, to reduce the amount of +- Some Cython files in `zmq.core` have been split, to reduce the amount of Cython-compiled code. Much of the body of these files were pure Python, and thus did not benefit from the increased compile time. This change also aims to ease maintaining feature parity in other projects, such as @@ -939,47 +939,47 @@ possible), to allow more permissive use of less-critical code and utilities. ### New Stuff -- {class}`~.Context` objects can now set default options when they create a socket. These +- {class}`~.zmq.Context` objects can now set default options when they create a socket. These are set and accessed as attributes to the context. Socket options that do not apply to a socket (e.g. SUBSCRIBE on non-SUB sockets) will simply be ignored. - {meth}`~.ZMQStream.on_recv_stream` has been added, which adds the stream itself as a second argument to the callback, making it easier to use a single callback on multiple streams. -- A {attr}`~Frame.more` boolean attribute has been added to the {class}`~.Frame` (née +- A `Frame.more` boolean attribute has been added to the {class}`~.Frame` (née Message) class, so that frames can be identified as terminal without extra queries of - {attr}`~.Socket.rcvmore`. + `Socket.rcvmore`. ### Experimental New Stuff These features are marked 'experimental', which means that their APIs are not set in stone, and may be removed or changed in incompatible ways in later releases. -- {mod}`zmq.web` added for load-balancing requests in a tornado webapp with zeromq. +- `zmq.web` added for load-balancing requests in a tornado webapp with zeromq. ## 2.1.11 - remove support for LABEL prefixes. A major feature of libzmq-3.0, the LABEL prefix, has been removed from libzmq, prior to the first stable libzmq 3.x release. - - The prefix argument to {meth}`~.Socket.send_multipart` remains, but it continue to behave in + - The prefix argument to {meth}`~.zmq.Socket.send_multipart` remains, but it continue to behave in exactly the same way as it always has on 2.1.x, simply prepending message parts. - - {meth}`~.Socket.recv_multipart` will always return a list, because prefixes are once + - {meth}`~.zmq.Socket.recv_multipart` will always return a list, because prefixes are once again indistinguishable from regular message parts. -- add {meth}`.Socket.poll` method, for simple polling of events on a single socket. +- add {meth}`.zmq.Socket.poll` method, for simple polling of events on a single socket. -- no longer require monkeypatching tornado IOLoop. The {class}`.ioloop.ZMQPoller` class +- no longer require monkeypatching tornado IOLoop. The `ioloop.ZMQPoller` class is a poller implementation that matches tornado's expectations, and pyzmq sockets can be used with any tornado application just by specifying the use of this poller. The pyzmq IOLoop implementation now only trivially differs from tornado's. - It is still recommended to use {func}`.ioloop.install`, which sets *both* the zmq and + It is still recommended to use `ioloop.install()`, which sets *both* the zmq and tornado global IOLoop instances to the same object, but it is no longer necessary. ```{warning} The most important part of this change is that the `IOLoop.READ/WRITE/ERROR` constants now match tornado's, rather than being mapped directly to the zmq - `POLLIN/OUT/ERR`. So applications that used the low-level {meth}`IOLoop.add_handler` + `POLLIN/OUT/ERR`. So applications that used the low-level `IOLoop.add_handler` code with `POLLIN/OUT/ERR` directly (used to work, but was incorrect), rather than using the IOLoop class constants will no longer work. Fixing these to use the IOLoop constants should be insensitive to the actual value of the constants. @@ -1000,18 +1000,18 @@ set in stone, and may be removed or changed in incompatible ways in later releas send_multipart([b"msg", b"parts"], prefix=[b"label", b"prefix"]) ``` - - {meth}`recv_multipart` returns a tuple of `(prefix,msg)` if a label prefix is detected + - {meth}`zmq.Socket.recv_multipart` returns a tuple of `(prefix,msg)` if a label prefix is detected - ZMQStreams and devices also respect the LABEL prefix -- add czmq-style close&term as {meth}`ctx.destroy`, so that {meth}`ctx.term` +- add czmq-style close&term as {meth}`zmq.Context.destroy`, so that {meth}`zmq.Context.term` remains threadsafe and 1:1 with libzmq. -- {meth}`Socket.close` takes optional linger option, for setting linger prior +- {meth}`zmq.Socket.close` takes optional linger option, for setting linger prior to closing. -- add {func}`~zmq.core.version.zmq_version_info` and - {func}`~zmq.core.version.pyzmq_version_info` for getting libzmq and pyzmq versions as +- add {func}`~.zmq_version_info` and + {func}`~.pyzmq_version_info` for getting libzmq and pyzmq versions as tuples of numbers. This helps with the fact that version string comparison breaks down once versions get into double-digits. @@ -1021,8 +1021,8 @@ set in stone, and may be removed or changed in incompatible ways in later releas - added zmq.ssh tools for tunneling socket connections, copied from IPython - Expanded sockopt support to cover changes in libzmq-4.0 dev. -- Fixed an issue that prevented {exc}`KeyboardInterrupts` from being catchable. -- Added attribute-access for set/getsockopt. Setting/Getting attributes of {class}`Sockets` +- Fixed an issue that prevented {exc}`KeyboardInterrupt` from being catchable. +- Added attribute-access for set/getsockopt. Setting/Getting attributes of {class}`~.zmq.Socket`s with the names of socket options is mapped to calls of set/getsockopt. ```python @@ -1032,9 +1032,9 @@ s.linger # -1 ``` -- Terminating a {class}`~Context` closes the sockets it created, matching the behavior in +- Terminating a {class}`~.zmq.Context` closes the sockets it created, matching the behavior in [czmq](http://czmq.zeromq.org/). -- {class}`ThreadDevices` use {meth}`Context.instance` to create sockets, so they can use +- {class}`.ThreadDevice`s use {meth}`.zmq.Context.instance` to create sockets, so they can use inproc connections to sockets in other threads. - fixed units error on {func}`zmq.select`, where the poll timeout was 1000 times longer than expected. @@ -1049,13 +1049,13 @@ s.linger ## 2.1.7 - Added experimental support for libzmq-3.0 API -- Add {func}`zmq.eventloop.ioloop.install` for using pyzmq's IOLoop in a tornado +- Add `zmq.eventloop.ioloop.install` for using pyzmq's IOLoop in a tornado application. ## 2.1.4 - First version with binary distribution support -- Added {meth}`~Context.instance()` method for using a single Context throughout an application +- Added {meth}`zmq.Context.instance()` method for using a single Context throughout an application without passing references around. [cython-build-requires]: https://groups.google.com/g/cython-users/c/ZqKFQmS0JdA/m/1FrK1ApYBAAJ diff --git a/docs/source/conf.py b/docs/source/conf.py index 4e2cdcc26..dcf3c50cb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,16 +10,20 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os import sys +from pathlib import Path -# add repo root to sys.path -here = os.path.dirname(__file__) -sys.path.append(os.path.abspath(os.path.join(here, os.pardir, os.pardir))) +# add repo root to sys.path for buildutils import +here = Path(__file__).parent.absolute() +repo_root = here.parents[1] +sys.path.append(str(repo_root)) # set target libzmq version from buildutils.bundle import bundled_version +# remove repo root from sys.path +sys.path = sys.path[:-1] + target_libzmq = bundled_version # -- General configuration ----------------------------------------------------- @@ -117,7 +121,33 @@ # List of Sphinx warnings that will not be raised suppress_warnings = ['epub.unknown_project_files'] +nitpick_ignore = [ + # napoleon seems to try to resolve everything + # in type descriptions, leave some prose keywords alone + ('py:class', 'optional'), + ('py:class', 'Python object'), + ('py:class', 'native socket'), + ('py:class', 'iterable'), + ('py:class', 'callable'), + # suppress warnings on some old outdated symbols + ('py:class', 'basestring'), + ('py:class', 'unicode'), +] +autodoc_type_aliases = { + # 'Socket': 'zmq.Socket', + # 'Context': 'zmq.Context', + # Cython + 'C.int': 'int', + 'bint': 'bool', + # type aliases + '_MonitorMessage': 'dict', + 'Frame': 'zmq.Frame', + 'Socket': 'zmq.Socket', + 'Context': 'zmq.Context', + '_SocketType': 'zmq.Socket', + '_ContextType': 'zmq.Context', +} # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with diff --git a/docs/source/howto/devices.md b/docs/source/howto/devices.md index 173e65a9f..ffc7e6c3a 100644 --- a/docs/source/howto/devices.md +++ b/docs/source/howto/devices.md @@ -11,7 +11,7 @@ ØMQ has a notion of Devices - simple programs that manage a send-recv pattern for connecting two or more sockets. Being full programs, devices include a `while(True)` loop and thus block execution permanently once invoked. We have provided in the -{mod}`devices` subpackage some facilities for running these devices in the background, as +{mod}`~.zmq.devices` subpackage some facilities for running these devices in the background, as well as a custom three-socket [MonitoredQueue](monitored-queue) device. ## BackgroundDevices @@ -23,7 +23,7 @@ processes. We have provided classes for launching devices in a background thread {class}`.ThreadDevice` and via multiprocessing with {class}`.ProcessDevice`. For threadsafety and running across processes, these methods do not take Socket objects as arguments, but rather socket types, and then the socket creation and configuration happens -via the BackgroundDevice's {meth}`foo_in` proxy methods. For each configuration method +via the BackgroundDevice's `foo_in()` proxy methods. For each configuration method (bind/connect/setsockopt), there are proxy methods for calling those methods on the Socket objects created in the background thread or process, prefixed with 'in\_' or 'out\_', corresponding to the `in_socket` and `out_socket`: @@ -79,4 +79,4 @@ received the message. Or for launching an MQ in the background, there are {class}`.ThreadMonitoredQueue` and {class}`.ProcessMonitoredQueue`, which function just like the base -BackgroundDevice objects, but add {meth}`foo_mon` methods for configuring the monitor socket. +BackgroundDevice objects, but add `foo_mon()` methods for configuring the monitor socket. diff --git a/docs/source/howto/eventloop.md b/docs/source/howto/eventloop.md index 00b702c10..21f93a3ca 100644 --- a/docs/source/howto/eventloop.md +++ b/docs/source/howto/eventloop.md @@ -33,79 +33,32 @@ async def recv(): s.close() ``` -````{note} -In PyZMQ \< 17, an additional step is needed to register the zmq poller prior to starting any async code: - -```python -import zmq.asyncio -zmq.asyncio.install() - -ctx = zmq.asyncio.Context() -``` - -This step is no longer needed in pyzmq 17. - -```` - ## Tornado IOLoop -[Tornado] includes an eventloop for handing poll events on filedescriptors and -native sockets. We have included a small part of Tornado (specifically its -{mod}`.ioloop`), and adapted its {class}`IOStream` class into {class}`.ZMQStream` for -handling poll events on ØMQ sockets. A ZMQStream object works much like a Socket object, -but instead of calling {meth}`~.Socket.recv` directly, you register a callback with -{meth}`~.ZMQStream.on_recv`. Callbacks can also be registered for send events -with {meth}`~.ZMQStream.on_send`. - -(futures)= - -### Futures and coroutines - -```{note} -With recent Python (3.6) and tornado (5), -there's no reason to use {mod}`zmq.eventloop.future` -instead of the strictly-more-compatible {mod}`zmq.asyncio`. -``` - -PyZMQ 15 adds {mod}`zmq.eventloop.future`, containing a Socket subclass -that returns {class}`~.tornado.concurrent.Future` objects for use in {mod}`tornado` coroutines. -To use this API, import {class}`zmq.eventloop.future.Context`. -Sockets created by this Context will return Futures from any would-be blocking method. - -```python -from tornado import gen, ioloop -import zmq -from zmq.eventloop.future import Context +[Tornado] adds some utility on top of asyncio. +You can use {mod}`zmq.asyncio` socket in a tornado application without any special handling. -ctx = Context.instance() +We have adapted tornado's {class}`~.tornado.iostream.IOStream` class into {class}`~.ZMQStream` for +handling message events on ØMQ sockets. A ZMQStream object works much like a Socket object, +but instead of calling {meth}`~.zmq.Socket.recv` directly, you register a callback with +{meth}`~.ZMQStream.on_recv_stream`, which will be called with the result of `~.zmq.Socket.recv_multipart`. +Callbacks can also be registered for send events with {meth}`~.ZMQStream.on_send`. +### {class}`.ZMQStream` -@gen.coroutine -def recv(): - s = ctx.socket(zmq.SUB) - s.connect("tcp://127.0.0.1:5555") - s.subscribe(b"") - while True: - msg = yield s.recv_multipart() - print("received", msg) - s.close() -``` - -### {class}`ZMQStream` - -{class}`ZMQStream` objects let you register callbacks to handle messages as they arrive, +{class}`.ZMQStream` objects let you register callbacks to handle messages as they arrive, for use with the tornado eventloop. -#### {meth}`send` +#### {meth}`.ZMQStream.send` ZMQStream objects do have {meth}`~.ZMQStream.send` and {meth}`~.ZMQStream.send_multipart` -methods, which behaves the same way as {meth}`.Socket.send`, but instead of sending right -away, the {class}`.IOLoop` will wait until socket is able to send (for instance if `HWM` +methods, which behaves the same way as {meth}`.zmq.Socket.send`, but instead of sending right +away, the {class}`~.tornado.ioloop.IOLoop` will wait until socket is able to send (for instance if `HWM` is met, or a `REQ/REP` pattern prohibits sending at a certain point). Messages sent via send will also be passed to the callback registered with {meth}`~.ZMQStream.on_send` after sending. -#### {meth}`on_recv` +#### {meth}`~.ZMQStream.on_recv` {meth}`.ZMQStream.on_recv` is the primary method for using a ZMQStream. It registers a callback to fire with messages as they are received, which will *always* be multipart, @@ -125,7 +78,7 @@ stream.on_recv(echo) ioloop.IOLoop.instance().start() ``` -on_recv can also take a `copy` flag, just like {meth}`.Socket.recv`. If `copy=False`, then +on_recv can also take a `copy` flag, just like {meth}`.zmq.Socket.recv`. If `copy=False`, then callbacks registered with on_recv will receive tracked {class}`.Frame` objects instead of bytes. @@ -137,7 +90,7 @@ socket by setting both callbacks to None. Processing can later be resumed by restoring either callback. ``` -#### {meth}`on_recv_stream` +#### {meth}`~.ZMQStream.on_recv_stream` {meth}`.ZMQStream.on_recv_stream` is just like on_recv above, but the callback will be passed both the message and the stream, rather than just the message. This is meant to make @@ -163,79 +116,15 @@ stream2.on_recv_stream(echo) ioloop.IOLoop.instance().start() ``` -#### {meth}`flush` +#### {meth}`~.ZMQStream.flush` Sometimes with an eventloop, there can be multiple events ready on a single iteration of -the loop. The {meth}`~.ZMQStream.flush` method allows developers to pull messages off of +the loop. The {meth}`.ZMQStream.flush` method allows developers to pull messages off of the queue to enforce some priority over the event loop ordering. flush pulls any pending events off of the queue. You can specify to flush only recv events, only send events, or any events, and you can specify a limit for how many events to flush in order to prevent starvation. -### {func}`install()` - -```{note} -If you are using pyzmq \< 17, there is an additional step -to tell tornado to use the zmq poller instead of its default. -{func}`.ioloop.install` is no longer needed for pyzmq ≥ 17. -``` - -With PyZMQ's ioloop, you can use zmq sockets in any tornado application. You can tell tornado to use zmq's poller by calling the {func}`.ioloop.install` function: - -```python -from zmq.eventloop import ioloop - -ioloop.install() -``` - -You can also do the same thing by requesting the global instance from pyzmq: - -```python -from zmq.eventloop.ioloop import IOLoop - -loop = IOLoop.current() -``` - -This configures tornado's {class}`tornado.ioloop.IOLoop` to use zmq's poller, -and registers the current instance. - -Either `install()` or retrieving the zmq instance must be done before the global * instance is registered, else there will be a conflict. - -It is possible to use PyZMQ sockets with tornado *without* registering as the global instance, -but it is less convenient. First, you must instruct the tornado IOLoop to use the zmq poller: - -```python -from zmq.eventloop.ioloop import ZMQIOLoop - -loop = ZMQIOLoop() -``` - -Then, when you instantiate tornado and ZMQStream objects, you must pass the `io_loop` -argument to ensure that they use this loop, instead of the global instance. - -This is especially useful for writing tests, such as this: - -```python -from tornado.testing import AsyncTestCase -from zmq.eventloop.ioloop import ZMQIOLoop -from zmq.eventloop.zmqstream import ZMQStream - - -class TestZMQBridge(AsyncTestCase): - # Use a ZMQ-compatible I/O loop so that we can use `ZMQStream`. - def get_new_ioloop(self): - return ZMQIOLoop() -``` - -You can also manually install this IOLoop as the global tornado instance, with: - -```python -from zmq.eventloop.ioloop import ZMQIOLoop - -loop = ZMQIOLoop() -loop.install() -``` - (zmq-green)= ## PyZMQ and gevent @@ -276,4 +165,4 @@ zmq.green examples [on GitHub](https://github.com/zeromq/pyzmq/tree/HEAD/example {mod}`zmq.green` began as [gevent_zeromq](https://github.com/tmc/gevent-zeromq), merged into the pyzmq project. -[tornado]: https://tornadoweb.org +[tornado]: https://www.tornadoweb.org diff --git a/docs/source/howto/morethanbindings.md b/docs/source/howto/morethanbindings.md index cc855f49c..afb4b0272 100644 --- a/docs/source/howto/morethanbindings.md +++ b/docs/source/howto/morethanbindings.md @@ -10,22 +10,11 @@ objects for calling into the ØMQ C++ library. ## The Core as Bindings -PyZMQ is currently broken up into four subpackages. First, is the Core. {mod}`zmq.core` +PyZMQ is currently broken up into subpackages. First, is the Backend. `zmq.backend` contains the actual bindings for ZeroMQ, and no extended functionality beyond the very -basic. The core modules are split, such that each basic ZeroMQ object (or function, if no -object is associated) is a separate module, e.g. {mod}`zmq.core.context` contains the -{class}`.Context` object, {mod}`zmq.core.poll` contains a {class}`.Poller` object, as well -as the {func}`.select` function, etc. ZMQ constants are, for convenience, all kept -together in {mod}`zmq.core.constants`. - -There are two reasons for breaking the core into submodules: *recompilation* and -*derivative projects*. The monolithic PyZMQ became quite tedious to have to recompile -everything for a small change to a single object. With separate files, that's no longer -necessary. The second reason has to do with Cython. PyZMQ is written in Cython, a tool for -efficiently writing C-extensions for Python. By separating out our objects into individual -`pyx` files, each with their declarations in a `pxd` header, other projects can write -extensions in Cython and call directly to ZeroMQ at the C-level without the penalty of -going through our Python objects. +basics required. +This is the _compiled_ portion of pyzmq, +either with Cython (for CPython) or CFFI (for PyPy). ## Thread Safety @@ -46,7 +35,7 @@ or [3.2](http://api.zeromq.org/3-2:zmq) ```{versionadded} 2.1.9 ``` -In 0MQ, socket options are set/retrieved with the {meth}`set/getsockopt` methods. With the +In 0MQ, socket options are set/retrieved with the `set/getsockopt()` methods. With the class-based approach in pyzmq, it would be logical to perform these operations with simple attribute access, and this has been added in pyzmq 2.1.9. Simply assign to or request a Socket attribute with the (case-insensitive) name of a sockopt, and it should @@ -124,30 +113,27 @@ with socket.connect(url): ## Core Extensions -We have extended the core functionality in two ways that appear inside the {mod}`core` -bindings, and are not general ØMQ features. +We have extended the core functionality in some ways that appear inside the `zmq.sugar` layer, and are not general ØMQ features. ### Builtin Serialization First, we added common serialization with the builtin {py:mod}`json` and {py:mod}`pickle` -as first-class methods to the {class}`Socket` class. A socket has the methods -{meth}`~.Socket.send_json` and {meth}`~.Socket.send_pyobj`, which correspond to sending an +as first-class methods to the {class}`~.zmq.Socket` class. A socket has the methods +{meth}`~.zmq.Socket.send_json` and {meth}`~.zmq.Socket.send_pyobj`, which correspond to sending an object over the wire after serializing with {mod}`json` and {mod}`pickle` respectively, and any object sent via those methods can be reconstructed with the -{meth}`~.Socket.recv_json` and {meth}`~.Socket.recv_pyobj` methods. Unicode strings are +{meth}`~.zmq.Socket.recv_json` and {meth}`~.zmq.Socket.recv_pyobj` methods. Unicode strings are other objects that are not unambiguously sendable over the wire, so we include -{meth}`~.Socket.send_string` and {meth}`~.Socket.recv_string` that simply send bytes +{meth}`~.zmq.Socket.send_string` and {meth}`~.zmq.Socket.recv_string` that simply send bytes after encoding the message ('utf-8' is the default). ```{seealso} - {ref}`Further information ` on serialization in pyzmq. -- {ref}`Our Unicode discussion ` for more information on the trials and - tribulations of working with Unicode in a C extension while supporting Python 2 and 3. ``` ### MessageTracker -The second extension of basic ØMQ functionality is the {class}`MessageTracker`. The +The second extension of basic ØMQ functionality is the {class}`.MessageTracker`. The MessageTracker is an object used to track when the underlying ZeroMQ is done with a message buffer. One of the main use cases for ØMQ in Python is the ability to perform non-copying sends. Thanks to Python's buffer interface, many objects (including NumPy @@ -158,7 +144,7 @@ worry of corrupting the message. This is what the MessageTracker is for. The MessageTracker is a simple object, but there is a penalty to its use. Since by its very nature, the MessageTracker must involve threadsafe communication (specifically a -builtin {py:class}`~Queue.Queue` object), instantiating a MessageTracker takes a modest +builtin {py:class}`~queue.Queue` object), instantiating a MessageTracker takes a modest amount of time (10s of µs), so in situations instantiating many small messages, this can actually dominate performance. As a result, tracking is optional, via the `track` flag, which is optionally passed, always defaulting to `False`, in each of the three places @@ -166,7 +152,7 @@ where a Frame object (the pyzmq object for wrapping a segment of a message) is instantiated: The {class}`.Frame` constructor, and non-copying sends and receives. A MessageTracker is very simple, and has just one method and one attribute. The property -{attr}`MessageTracker.done` will be `True` when the Frame(s) being tracked are no +{attr}`.MessageTracker.done` will be `True` when the Frame(s) being tracked are no longer in use by ØMQ, and {meth}`.MessageTracker.wait` will block, waiting for the Frame(s) to be released. diff --git a/docs/source/howto/ssh.md b/docs/source/howto/ssh.md index 944313d8b..e3f2d84d7 100644 --- a/docs/source/howto/ssh.md +++ b/docs/source/howto/ssh.md @@ -10,7 +10,7 @@ You may want to connect ØMQ sockets across machines, or untrusted networks. One common way to do this is to tunnel the connection via SSH. [IPython] introduced some tools for tunneling ØMQ connections over ssh in simple cases. These functions have been brought into -pyzmq as {mod}`zmq.ssh` under IPython's BSD license. +pyzmq as {mod}`zmq.ssh.tunnel` under IPython's BSD license. PyZMQ will use the shell ssh command via [pexpect] by default, but it also supports using [paramiko] for tunnels, so it should work on Windows. @@ -47,7 +47,7 @@ ssh.tunnel_connection(sock, "tcp://10.0.1.2:5555", "server") Note that `"server"` can actually be a fully specified `"user@server:port"` ssh url. Since this really just launches a shell command, all your ssh configuration of usernames, -aliases, keys, etc. will be respected. If necessary, {func}`tunnel_connection` does take +aliases, keys, etc. will be respected. If necessary, {func}`.tunnel_connection` does take arguments for specific passwords, private keys (the ssh `-i` option), and non-default choice of whether to use paramiko. @@ -61,7 +61,7 @@ from zmq import ssh ssh.tunnel_connection(sock, "tcp://127.0.0.1:5555", "10.0.1.2") ``` -The {func}`tunnel_connection` function is a simple utility that forwards a random +The {func}`.tunnel_connection` function is a simple utility that forwards a random localhost port to the real destination, and connects a socket to the new local url, rather than the remote one that wouldn't actually work. From 2261b1fd5e78e660103afd9ecde9e144d292d8c9 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Feb 2024 13:26:07 +0100 Subject: [PATCH 4/6] convert rst to md --- docs/source/api/index.md | 29 ++++ docs/source/api/index.rst | 29 ---- docs/source/api/zmq.asyncio.md | 88 ++++++++++ docs/source/api/zmq.asyncio.rst | 82 --------- docs/source/api/zmq.auth.asyncio.md | 21 +++ docs/source/api/zmq.auth.asyncio.rst | 22 --- docs/source/api/zmq.auth.ioloop.md | 10 ++ docs/source/api/zmq.auth.ioloop.rst | 10 -- docs/source/api/{zmq.auth.rst => zmq.auth.md} | 29 ++-- docs/source/api/zmq.auth.thread.md | 27 +++ docs/source/api/zmq.auth.thread.rst | 25 --- docs/source/api/zmq.decorators.md | 20 +++ docs/source/api/zmq.decorators.rst | 18 -- .../api/{zmq.devices.rst => zmq.devices.md} | 93 ++++++---- docs/source/api/zmq.eventloop.future.md | 93 ++++++++++ docs/source/api/zmq.eventloop.future.rst | 87 ---------- docs/source/api/zmq.eventloop.ioloop.md | 14 ++ docs/source/api/zmq.eventloop.ioloop.rst | 13 -- docs/source/api/zmq.eventloop.zmqstream.md | 21 +++ docs/source/api/zmq.eventloop.zmqstream.rst | 19 -- docs/source/api/zmq.green.md | 7 + docs/source/api/zmq.green.rst | 7 - docs/source/api/zmq.log.handlers.md | 33 ++++ docs/source/api/zmq.log.handlers.rst | 32 ---- docs/source/api/{zmq.rst => zmq.md} | 162 ++++++++++++------ .../{zmq.ssh.tunnel.rst => zmq.ssh.tunnel.md} | 26 ++- docs/source/api/zmq.utils.jsonapi.md | 23 +++ docs/source/api/zmq.utils.jsonapi.rst | 19 -- docs/source/api/zmq.utils.monitor.md | 21 +++ docs/source/api/zmq.utils.monitor.rst | 17 -- docs/source/api/zmq.utils.win32.md | 16 ++ docs/source/api/zmq.utils.win32.rst | 17 -- docs/source/api/zmq.utils.z85.md | 23 +++ docs/source/api/zmq.utils.z85.rst | 19 -- docs/source/conf.py | 2 +- docs/source/howto/index.md | 16 +- docs/source/index.md | 6 +- 37 files changed, 656 insertions(+), 540 deletions(-) create mode 100644 docs/source/api/index.md delete mode 100644 docs/source/api/index.rst create mode 100644 docs/source/api/zmq.asyncio.md delete mode 100644 docs/source/api/zmq.asyncio.rst create mode 100644 docs/source/api/zmq.auth.asyncio.md delete mode 100644 docs/source/api/zmq.auth.asyncio.rst create mode 100644 docs/source/api/zmq.auth.ioloop.md delete mode 100644 docs/source/api/zmq.auth.ioloop.rst rename docs/source/api/{zmq.auth.rst => zmq.auth.md} (53%) create mode 100644 docs/source/api/zmq.auth.thread.md delete mode 100644 docs/source/api/zmq.auth.thread.rst create mode 100644 docs/source/api/zmq.decorators.md delete mode 100644 docs/source/api/zmq.decorators.rst rename docs/source/api/{zmq.devices.rst => zmq.devices.md} (52%) create mode 100644 docs/source/api/zmq.eventloop.future.md delete mode 100644 docs/source/api/zmq.eventloop.future.rst create mode 100644 docs/source/api/zmq.eventloop.ioloop.md delete mode 100644 docs/source/api/zmq.eventloop.ioloop.rst create mode 100644 docs/source/api/zmq.eventloop.zmqstream.md delete mode 100644 docs/source/api/zmq.eventloop.zmqstream.rst create mode 100644 docs/source/api/zmq.green.md delete mode 100644 docs/source/api/zmq.green.rst create mode 100644 docs/source/api/zmq.log.handlers.md delete mode 100644 docs/source/api/zmq.log.handlers.rst rename docs/source/api/{zmq.rst => zmq.md} (67%) rename docs/source/api/{zmq.ssh.tunnel.rst => zmq.ssh.tunnel.md} (52%) create mode 100644 docs/source/api/zmq.utils.jsonapi.md delete mode 100644 docs/source/api/zmq.utils.jsonapi.rst create mode 100644 docs/source/api/zmq.utils.monitor.md delete mode 100644 docs/source/api/zmq.utils.monitor.rst create mode 100644 docs/source/api/zmq.utils.win32.md delete mode 100644 docs/source/api/zmq.utils.win32.rst create mode 100644 docs/source/api/zmq.utils.z85.md delete mode 100644 docs/source/api/zmq.utils.z85.rst diff --git a/docs/source/api/index.md b/docs/source/api/index.md new file mode 100644 index 000000000..4d5cb2425 --- /dev/null +++ b/docs/source/api/index.md @@ -0,0 +1,29 @@ +--- +Date: '{{ today }}' +Release: '{{ release }}' +--- + +(api-index)= + +# The PyZMQ API + +```{toctree} +zmq +zmq.devices +zmq.decorators +zmq.green +zmq.eventloop.ioloop +zmq.eventloop.future +zmq.asyncio +zmq.eventloop.zmqstream +zmq.auth +zmq.auth.asyncio +zmq.auth.thread +zmq.auth.ioloop +zmq.log.handlers +zmq.ssh.tunnel +zmq.utils.jsonapi +zmq.utils.monitor +zmq.utils.z85 +zmq.utils.win32 +``` diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst deleted file mode 100644 index cc7ee2ae4..000000000 --- a/docs/source/api/index.rst +++ /dev/null @@ -1,29 +0,0 @@ -.. _api-index: - -################### - The PyZMQ API -################### - -:Release: |release| -:Date: |today| - -.. toctree:: - - zmq - zmq.devices - zmq.decorators - zmq.green - zmq.eventloop.ioloop - zmq.eventloop.future - zmq.asyncio - zmq.eventloop.zmqstream - zmq.auth - zmq.auth.asyncio - zmq.auth.thread - zmq.auth.ioloop - zmq.log.handlers - zmq.ssh.tunnel - zmq.utils.jsonapi - zmq.utils.monitor - zmq.utils.z85 - zmq.utils.win32 diff --git a/docs/source/api/zmq.asyncio.md b/docs/source/api/zmq.asyncio.md new file mode 100644 index 000000000..59f68337f --- /dev/null +++ b/docs/source/api/zmq.asyncio.md @@ -0,0 +1,88 @@ +# asyncio + +## Module: {mod}`zmq.asyncio` + +```{eval-rst} +.. automodule:: zmq.asyncio +``` + +```{currentmodule} zmq.asyncio +``` + +```{versionadded} 15.0 +``` + +As of 15.0, pyzmq now supports {mod}`asyncio`, via {mod}`zmq.asyncio`. +When imported from this module, blocking methods such as +{meth}`Socket.recv_multipart`, {meth}`Socket.poll`, +and {meth}`Poller.poll` return {class}`~.asyncio.Future` s. + +```python +import asyncio +import zmq +import zmq.asyncio + +ctx = zmq.asyncio.Context() + + +async def recv_and_process(): + sock = ctx.socket(zmq.PULL) + sock.bind(url) + msg = await sock.recv_multipart() # waits for msg to be ready + reply = await async_process(msg) + await sock.send_multipart(reply) + + +asyncio.run(recv_and_process()) +``` + +## Classes + +### {class}`Context` + +Context class that creates Future-returning sockets. See {class}`zmq.Context` for more info. + +```{eval-rst} +.. autoclass:: Context + + +``` + +### {class}`Socket` + +Socket subclass that returns {class}`asyncio.Future` s from blocking methods, +for use in coroutines and async applications. + +```{seealso} +{class}`zmq.Socket` for the inherited API. +``` + +```{eval-rst} +.. autoclass:: Socket + + .. automethod:: recv + + .. automethod:: recv_multipart + + .. automethod:: send + + .. automethod:: send_multipart + + .. automethod:: poll + +``` + +### {class}`Poller` + +Poller subclass that returns {class}`asyncio.Future` s from poll, +for use in coroutines and async applications. + +```{seealso} +{class}`zmq.Poller` for the inherited API. +``` + +```{eval-rst} +.. autoclass:: Poller + + .. automethod:: poll +``` diff --git a/docs/source/api/zmq.asyncio.rst b/docs/source/api/zmq.asyncio.rst deleted file mode 100644 index 74a9070a5..000000000 --- a/docs/source/api/zmq.asyncio.rst +++ /dev/null @@ -1,82 +0,0 @@ -asyncio -======= - -Module: :mod:`zmq.asyncio` --------------------------- -.. automodule:: zmq.asyncio - -.. currentmodule:: zmq.asyncio - -.. versionadded:: 15.0 - -As of 15.0, pyzmq now supports :mod:`asyncio`, via :mod:`zmq.asyncio`. -When imported from this module, blocking methods such as -:meth:`Socket.recv_multipart`, :meth:`Socket.poll`, -and :meth:`Poller.poll` return :class:`~.asyncio.Future` s. - -.. sourcecode:: python - - import asyncio - import zmq - import zmq.asyncio - - ctx = zmq.asyncio.Context() - - async def recv_and_process(): - sock = ctx.socket(zmq.PULL) - sock.bind(url) - msg = await sock.recv_multipart() # waits for msg to be ready - reply = await async_process(msg) - await sock.send_multipart(reply) - - asyncio.run(recv_and_process()) - - -Classes -------- - -:class:`Context` -~~~~~~~~~~~~~~~~ - -Context class that creates Future-returning sockets. See :class:`zmq.Context` for more info. - -.. autoclass:: Context - - - -:class:`Socket` -~~~~~~~~~~~~~~~ - -Socket subclass that returns :class:`asyncio.Future` s from blocking methods, -for use in coroutines and async applications. - -.. seealso:: - - :class:`zmq.Socket` for the inherited API. - -.. autoclass:: Socket - - .. automethod:: recv - - .. automethod:: recv_multipart - - .. automethod:: send - - .. automethod:: send_multipart - - .. automethod:: poll - - -:class:`Poller` -~~~~~~~~~~~~~~~ - -Poller subclass that returns :class:`asyncio.Future` s from poll, -for use in coroutines and async applications. - -.. seealso:: - - :class:`zmq.Poller` for the inherited API. - -.. autoclass:: Poller - - .. automethod:: poll diff --git a/docs/source/api/zmq.auth.asyncio.md b/docs/source/api/zmq.auth.asyncio.md new file mode 100644 index 000000000..0768cdd1f --- /dev/null +++ b/docs/source/api/zmq.auth.asyncio.md @@ -0,0 +1,21 @@ +# auth.asyncio + +## Module: {mod}`zmq.auth.asyncio` + +```{eval-rst} +.. automodule:: zmq.auth.asyncio +``` + +```{currentmodule} zmq.auth.asyncio +``` + +## Classes + +### {class}`AsyncioAuthenticator` + +```{eval-rst} +.. autoclass:: AsyncioAuthenticator + :members: + :undoc-members: + :inherited-members: +``` diff --git a/docs/source/api/zmq.auth.asyncio.rst b/docs/source/api/zmq.auth.asyncio.rst deleted file mode 100644 index 6df9b871b..000000000 --- a/docs/source/api/zmq.auth.asyncio.rst +++ /dev/null @@ -1,22 +0,0 @@ -auth.asyncio -============ - -Module: :mod:`zmq.auth.asyncio` -------------------------------- - -.. automodule:: zmq.auth.asyncio - -.. currentmodule:: zmq.auth.asyncio - -Classes -------- - - -:class:`AsyncioAuthenticator` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -.. autoclass:: AsyncioAuthenticator - :members: - :undoc-members: - :inherited-members: diff --git a/docs/source/api/zmq.auth.ioloop.md b/docs/source/api/zmq.auth.ioloop.md new file mode 100644 index 000000000..dc7aed88a --- /dev/null +++ b/docs/source/api/zmq.auth.ioloop.md @@ -0,0 +1,10 @@ +# auth.ioloop + +## Module: :mod}\`zmq.auth.ioloop\` + +```{eval-rst} +.. module:: zmq.auth.ioloop +``` + +This module is deprecated in pyzmq 25. +Use {mod}`zmq.auth.asyncio`. diff --git a/docs/source/api/zmq.auth.ioloop.rst b/docs/source/api/zmq.auth.ioloop.rst deleted file mode 100644 index 5137f65fe..000000000 --- a/docs/source/api/zmq.auth.ioloop.rst +++ /dev/null @@ -1,10 +0,0 @@ -auth.ioloop -=========== - -Module: :mod}`zmq.auth.ioloop` ------------------------------- - -.. module:: zmq.auth.ioloop - -This module is deprecated in pyzmq 25. -Use :mod:`zmq.auth.asyncio`. diff --git a/docs/source/api/zmq.auth.rst b/docs/source/api/zmq.auth.md similarity index 53% rename from docs/source/api/zmq.auth.rst rename to docs/source/api/zmq.auth.md index 73c7166d6..d34887b99 100644 --- a/docs/source/api/zmq.auth.rst +++ b/docs/source/api/zmq.auth.md @@ -1,29 +1,34 @@ -auth -==== +# auth -Module: :mod:`zmq.auth` ------------------------ -.. automodule:: zmq.auth - -.. currentmodule:: zmq.auth +## Module: {mod}`zmq.auth` +```{eval-rst} +.. automodule:: zmq.auth +``` -:class:`Authenticator` ----------------------- +```{currentmodule} zmq.auth +``` +## {class}`Authenticator` +```{eval-rst} .. autoclass:: Authenticator :members: :undoc-members: :inherited-members: +``` -Functions ---------- - +## Functions +```{eval-rst} .. autofunction:: create_certificates +``` +```{eval-rst} .. autofunction:: load_certificate +``` +```{eval-rst} .. autofunction:: load_certificates +``` diff --git a/docs/source/api/zmq.auth.thread.md b/docs/source/api/zmq.auth.thread.md new file mode 100644 index 000000000..bb9de5bc1 --- /dev/null +++ b/docs/source/api/zmq.auth.thread.md @@ -0,0 +1,27 @@ +% AUTO-GENERATED FILE -- DO NOT EDIT! + +# auth.thread + +## Module: {mod}`zmq.auth.thread` + +```{eval-rst} +.. automodule:: zmq.auth.thread +``` + +```{currentmodule} zmq.auth.thread +``` + +## Classes + +### {class}`ThreadAuthenticator` + +```{eval-rst} +.. autoclass:: ThreadAuthenticator + :members: + :undoc-members: + :inherited-members: +``` + +```{eval-rst} +.. autoclass:: AuthenticationThread +``` diff --git a/docs/source/api/zmq.auth.thread.rst b/docs/source/api/zmq.auth.thread.rst deleted file mode 100644 index 013522a38..000000000 --- a/docs/source/api/zmq.auth.thread.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -auth.thread -=========== - -Module: :mod:`zmq.auth.thread` ------------------------------- -.. automodule:: zmq.auth.thread - -.. currentmodule:: zmq.auth.thread - -Classes -------- - - -:class:`ThreadAuthenticator` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -.. autoclass:: ThreadAuthenticator - :members: - :undoc-members: - :inherited-members: - -.. autoclass:: AuthenticationThread diff --git a/docs/source/api/zmq.decorators.md b/docs/source/api/zmq.decorators.md new file mode 100644 index 000000000..9a4006a41 --- /dev/null +++ b/docs/source/api/zmq.decorators.md @@ -0,0 +1,20 @@ +# decorators + +## Module: {mod}`zmq.decorators` + +```{eval-rst} +.. automodule:: zmq.decorators +``` + +```{currentmodule} zmq.decorators +``` + +## Decorators + +```{eval-rst} +.. autofunction:: zmq.decorators.context +``` + +```{eval-rst} +.. autofunction:: zmq.decorators.socket +``` diff --git a/docs/source/api/zmq.decorators.rst b/docs/source/api/zmq.decorators.rst deleted file mode 100644 index 5a7978f31..000000000 --- a/docs/source/api/zmq.decorators.rst +++ /dev/null @@ -1,18 +0,0 @@ -decorators -========== - - -Module: :mod:`zmq.decorators` ------------------------------ - -.. automodule:: zmq.decorators - -.. currentmodule:: zmq.decorators - - -Decorators ----------- - -.. autofunction:: zmq.decorators.context - -.. autofunction:: zmq.decorators.socket diff --git a/docs/source/api/zmq.devices.rst b/docs/source/api/zmq.devices.md similarity index 52% rename from docs/source/api/zmq.devices.rst rename to docs/source/api/zmq.devices.md index c3b082716..40f3a2aca 100644 --- a/docs/source/api/zmq.devices.rst +++ b/docs/source/api/zmq.devices.md @@ -1,106 +1,123 @@ -devices -======= +# devices -Functions ---------- +## Functions +```{eval-rst} .. autofunction:: zmq.device :noindex: +``` +```{eval-rst} .. autofunction:: zmq.proxy :noindex: +``` +```{eval-rst} .. autofunction:: zmq.proxy_steerable :noindex: +``` -Module: :mod:`zmq.devices` --------------------------- -.. automodule:: zmq.devices +## Module: {mod}`zmq.devices` -.. currentmodule:: zmq.devices +```{eval-rst} +.. automodule:: zmq.devices +``` +```{currentmodule} zmq.devices +``` -Base Devices ------------- +## Base Devices -:class:`Device` -*************** +### {class}`Device` +```{eval-rst} .. autoclass:: Device :members: :exclude-members: context_factory, run, run_device +``` -:class:`ThreadDevice` -********************* +### {class}`ThreadDevice` +```{eval-rst} .. autoclass:: ThreadDevice :members: +``` -:class:`ProcessDevice` -********************** +### {class}`ProcessDevice` +```{eval-rst} .. autoclass:: ProcessDevice :members: +``` -Proxy Devices -------------- +## Proxy Devices -:class:`Proxy` -************** +### {class}`Proxy` +```{eval-rst} .. autoclass:: Proxy :members: bind_mon, connect_mon, setsockopt_mon +``` -:class:`ThreadProxy` -******************** +### {class}`ThreadProxy` +```{eval-rst} .. autoclass:: ThreadProxy :members: +``` -:class:`ProcessProxy` -********************* +### {class}`ProcessProxy` +```{eval-rst} .. autoclass:: ProcessProxy :members: +``` -:class:`ProxySteerable` -*********************** +### {class}`ProxySteerable` +```{eval-rst} .. autoclass:: ProxySteerable :members: bind_ctrl, connect_ctrl, setsockopt_ctrl +``` -:class:`ThreadProxySteerable` -***************************** +### {class}`ThreadProxySteerable` +```{eval-rst} .. autoclass:: ThreadProxySteerable :members: +``` -:class:`ProcessProxySteerable` -****************************** +### {class}`ProcessProxySteerable` +```{eval-rst} .. autoclass:: ProcessProxySteerable :members: +``` -MonitoredQueue Devices ----------------------- +## MonitoredQueue Devices +```{eval-rst} .. autofunction:: zmq.devices.monitored_queue +``` -:class:`MonitoredQueue` -*********************** +### {class}`MonitoredQueue` +```{eval-rst} .. autoclass:: MonitoredQueue :members: +``` -:class:`ThreadMonitoredQueue` -***************************** +### {class}`ThreadMonitoredQueue` +```{eval-rst} .. autoclass:: ThreadMonitoredQueue :members: +``` -:class:`ProcessMonitoredQueue` -****************************** +### {class}`ProcessMonitoredQueue` +```{eval-rst} .. autoclass:: ProcessMonitoredQueue :members: +``` diff --git a/docs/source/api/zmq.eventloop.future.md b/docs/source/api/zmq.eventloop.future.md new file mode 100644 index 000000000..bd07870ad --- /dev/null +++ b/docs/source/api/zmq.eventloop.future.md @@ -0,0 +1,93 @@ +% AUTO-GENERATED FILE -- DO NOT EDIT! + +# eventloop.future + +## Module: {mod}`zmq.eventloop.future` + +```{eval-rst} +.. automodule:: zmq.eventloop.future +``` + +```{currentmodule} zmq.eventloop.future +``` + +```{versionadded} 15.0 +``` + +As of pyzmq 15, there is a new Socket subclass that returns Futures for recv methods, +which can be found at {class}`Socket`. +You can create these sockets by instantiating a {class}`Context` +from the same module. +These sockets let you easily use zmq with tornado's coroutines. + +```{seealso} +{mod}`tornado.gen` +``` + +```python +from tornado import gen +from zmq.eventloop.future import Context + +ctx = Context() + + +@gen.coroutine +def recv_and_process(): + sock = ctx.socket(zmq.PULL) + sock.bind(url) + msg = yield sock.recv_multipart() # waits for msg to be ready + reply = yield async_process(msg) + yield sock.send_multipart(reply) +``` + +## Classes + +### {class}`Context` + +Context class that creates Future-returning sockets. See {class}`zmq.Context` for more info. + +```{eval-rst} +.. autoclass:: Context + +``` + +### {class}`Socket` + +Socket subclass that returns {class}`~tornado.concurrent.Future` s from blocking methods, +for use in coroutines and async applications. + +```{seealso} +{class}`zmq.Socket` for the inherited API. +``` + +```{eval-rst} +.. autoclass:: Socket + + .. automethod:: recv + :noindex: + .. automethod:: recv_multipart + :noindex: + .. automethod:: send + :noindex: + .. automethod:: send_multipart + :noindex: + .. automethod:: poll + :noindex: + +``` + +### {class}`Poller` + +Poller subclass that returns {class}`~tornado.concurrent.Future` s from poll, +for use in coroutines and async applications. + +```{seealso} +{class}`zmq.Poller` for the inherited API. +``` + +```{eval-rst} +.. autoclass:: Poller + + .. automethod:: poll + :noindex: +``` diff --git a/docs/source/api/zmq.eventloop.future.rst b/docs/source/api/zmq.eventloop.future.rst deleted file mode 100644 index bcd4cb3c7..000000000 --- a/docs/source/api/zmq.eventloop.future.rst +++ /dev/null @@ -1,87 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -eventloop.future -================ - -Module: :mod:`zmq.eventloop.future` ------------------------------------ -.. automodule:: zmq.eventloop.future - -.. currentmodule:: zmq.eventloop.future - -.. versionadded:: 15.0 - -As of pyzmq 15, there is a new Socket subclass that returns Futures for recv methods, -which can be found at :class:`Socket`. -You can create these sockets by instantiating a :class:`Context` -from the same module. -These sockets let you easily use zmq with tornado's coroutines. - -.. seealso:: - - :mod:`tornado.gen` - -.. sourcecode:: python - - from tornado import gen - from zmq.eventloop.future import Context - - ctx = Context() - - @gen.coroutine - def recv_and_process(): - sock = ctx.socket(zmq.PULL) - sock.bind(url) - msg = yield sock.recv_multipart() # waits for msg to be ready - reply = yield async_process(msg) - yield sock.send_multipart(reply) - -Classes -------- - -:class:`Context` -~~~~~~~~~~~~~~~~ - -Context class that creates Future-returning sockets. See :class:`zmq.Context` for more info. - -.. autoclass:: Context - - -:class:`Socket` -~~~~~~~~~~~~~~~ - -Socket subclass that returns :class:`~tornado.concurrent.Future` s from blocking methods, -for use in coroutines and async applications. - -.. seealso:: - - :class:`zmq.Socket` for the inherited API. - -.. autoclass:: Socket - - .. automethod:: recv - :noindex: - .. automethod:: recv_multipart - :noindex: - .. automethod:: send - :noindex: - .. automethod:: send_multipart - :noindex: - .. automethod:: poll - :noindex: - - -:class:`Poller` -~~~~~~~~~~~~~~~ - -Poller subclass that returns :class:`~tornado.concurrent.Future` s from poll, -for use in coroutines and async applications. - -.. seealso:: - - :class:`zmq.Poller` for the inherited API. - -.. autoclass:: Poller - - .. automethod:: poll - :noindex: diff --git a/docs/source/api/zmq.eventloop.ioloop.md b/docs/source/api/zmq.eventloop.ioloop.md new file mode 100644 index 000000000..02b140b83 --- /dev/null +++ b/docs/source/api/zmq.eventloop.ioloop.md @@ -0,0 +1,14 @@ +# eventloop.ioloop + +## Module: {mod}`zmq.eventloop.ioloop` + +```{currentmodule} zmq.eventloop.ioloop +``` + +```{eval-rst} +.. module:: zmq.eventloop.ioloop + +``` + +This module is deprecated in pyzmq 17. +Use {py:mod}`tornado.ioloop`. diff --git a/docs/source/api/zmq.eventloop.ioloop.rst b/docs/source/api/zmq.eventloop.ioloop.rst deleted file mode 100644 index 4c0498e05..000000000 --- a/docs/source/api/zmq.eventloop.ioloop.rst +++ /dev/null @@ -1,13 +0,0 @@ - -eventloop.ioloop -================ - -Module: :mod:`zmq.eventloop.ioloop` ------------------------------------ -.. currentmodule:: zmq.eventloop.ioloop - -.. module:: zmq.eventloop.ioloop - - -This module is deprecated in pyzmq 17. -Use :py:mod:`tornado.ioloop`. diff --git a/docs/source/api/zmq.eventloop.zmqstream.md b/docs/source/api/zmq.eventloop.zmqstream.md new file mode 100644 index 000000000..ca8951a86 --- /dev/null +++ b/docs/source/api/zmq.eventloop.zmqstream.md @@ -0,0 +1,21 @@ +% AUTO-GENERATED FILE -- DO NOT EDIT! + +# eventloop.zmqstream + +## Module: {mod}`zmq.eventloop.zmqstream` + +```{eval-rst} +.. automodule:: zmq.eventloop.zmqstream +``` + +```{currentmodule} zmq.eventloop.zmqstream +``` + +## {class}`ZMQStream` + +```{eval-rst} +.. autoclass:: ZMQStream + :members: + :undoc-members: + :inherited-members: +``` diff --git a/docs/source/api/zmq.eventloop.zmqstream.rst b/docs/source/api/zmq.eventloop.zmqstream.rst deleted file mode 100644 index 26d8a7b17..000000000 --- a/docs/source/api/zmq.eventloop.zmqstream.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -eventloop.zmqstream -=================== - -Module: :mod:`zmq.eventloop.zmqstream` --------------------------------------- -.. automodule:: zmq.eventloop.zmqstream - -.. currentmodule:: zmq.eventloop.zmqstream - -:class:`ZMQStream` ------------------- - - -.. autoclass:: ZMQStream - :members: - :undoc-members: - :inherited-members: diff --git a/docs/source/api/zmq.green.md b/docs/source/api/zmq.green.md new file mode 100644 index 000000000..2f455676b --- /dev/null +++ b/docs/source/api/zmq.green.md @@ -0,0 +1,7 @@ +# green + +## Module: {mod}`zmq.green` + +```{eval-rst} +.. automodule:: zmq.green +``` diff --git a/docs/source/api/zmq.green.rst b/docs/source/api/zmq.green.rst deleted file mode 100644 index af1779f6f..000000000 --- a/docs/source/api/zmq.green.rst +++ /dev/null @@ -1,7 +0,0 @@ -green -===== - -Module: :mod:`zmq.green` ------------------------- - -.. automodule:: zmq.green diff --git a/docs/source/api/zmq.log.handlers.md b/docs/source/api/zmq.log.handlers.md new file mode 100644 index 000000000..5c20d144c --- /dev/null +++ b/docs/source/api/zmq.log.handlers.md @@ -0,0 +1,33 @@ +% AUTO-GENERATED FILE -- DO NOT EDIT! + +# log.handlers + +## Module: {mod}`zmq.log.handlers` + +```{eval-rst} +.. automodule:: zmq.log.handlers +``` + +```{currentmodule} zmq.log.handlers +``` + +## Classes + +### {class}`PUBHandler` + +```{eval-rst} +.. autoclass:: PUBHandler + :members: + :undoc-members: + :inherited-members: + +``` + +### {class}`TopicLogger` + +```{eval-rst} +.. autoclass:: TopicLogger + :members: + :undoc-members: + :inherited-members: +``` diff --git a/docs/source/api/zmq.log.handlers.rst b/docs/source/api/zmq.log.handlers.rst deleted file mode 100644 index 4da60c4eb..000000000 --- a/docs/source/api/zmq.log.handlers.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -log.handlers -============ - -Module: :mod:`zmq.log.handlers` -------------------------------- -.. automodule:: zmq.log.handlers - -.. currentmodule:: zmq.log.handlers - -Classes -------- - -:class:`PUBHandler` -~~~~~~~~~~~~~~~~~~~ - - -.. autoclass:: PUBHandler - :members: - :undoc-members: - :inherited-members: - - -:class:`TopicLogger` -~~~~~~~~~~~~~~~~~~~~ - - -.. autoclass:: TopicLogger - :members: - :undoc-members: - :inherited-members: diff --git a/docs/source/api/zmq.rst b/docs/source/api/zmq.md similarity index 67% rename from docs/source/api/zmq.rst rename to docs/source/api/zmq.md index 7dfbd51f2..4dc1a3c79 100644 --- a/docs/source/api/zmq.rst +++ b/docs/source/api/zmq.md @@ -1,17 +1,17 @@ -zmq -=== +# zmq +```{eval-rst} .. automodule:: zmq +``` -.. currentmodule:: zmq +```{currentmodule} zmq +``` -Basic Classes -------------- - -:class:`Context` -**************** +## Basic Classes +### {class}`Context` +```{eval-rst} .. autoclass:: Context :members: :inherited-members: @@ -22,11 +22,11 @@ Basic Classes boolean - whether the context has been terminated. If True, you can no longer use this Context. +``` -:class:`Socket` -*************** - +### {class}`Socket` +```{eval-rst} .. autoclass:: Socket :members: :inherited-members: @@ -56,158 +56,214 @@ Basic Classes .. versionadded:: 17 +``` -:class:`Frame` -************** - +### {class}`Frame` +```{eval-rst} .. autoclass:: Frame :members: :inherited-members: +``` -:class:`MessageTracker` -*********************** - +### {class}`MessageTracker` +```{eval-rst} .. autoclass:: MessageTracker :members: :inherited-members: +``` -Polling -------- +## Polling -:class:`Poller` -*************** +### {class}`Poller` +```{eval-rst} .. autoclass:: Poller :members: :inherited-members: +``` +```{eval-rst} .. autofunction:: zmq.select +``` -Constants ---------- +## Constants All libzmq constants are available as top-level attributes -(``zmq.PUSH``, etc.), -as well as via enums (``zmq.SocketType.PUSH``, etc.). - -.. versionchanged:: 23 +(`zmq.PUSH`, etc.), +as well as via enums (`zmq.SocketType.PUSH`, etc.). - constants for unavailable socket types - or draft features will always be defined in pyzmq, - whether the features themselves are available or not. +```{versionchanged} 23 +constants for unavailable socket types +or draft features will always be defined in pyzmq, +whether the features themselves are available or not. +``` -.. versionadded:: 23 - - Each category of zmq constant is now available as an IntEnum. +```{versionadded} 23 +Each category of zmq constant is now available as an IntEnum. +``` +```{eval-rst} .. data:: COPY_THRESHOLD The global default "small message" threshold for copying when `copy=False`. Copying has a thread-coordination cost, so zero-copy only has a benefit for sufficiently large messages. +``` +```{eval-rst} .. autoenum:: SocketType +``` +```{eval-rst} .. autoenum:: SocketOption +``` +```{eval-rst} .. autoenum:: Flag +``` +```{eval-rst} .. autoenum:: PollEvent +``` +```{eval-rst} .. autoenum:: ContextOption +``` +```{eval-rst} .. autoenum:: MessageOption +``` +```{eval-rst} .. autoenum:: Event +``` +```{eval-rst} .. autoenum:: NormMode +``` +```{eval-rst} .. autoenum:: RouterNotify +``` +```{eval-rst} .. autoenum:: ReconnectStop +``` +```{eval-rst} .. autoenum:: SecurityMechanism +``` +```{eval-rst} .. autoenum:: DeviceType +``` +```{eval-rst} .. autoenum:: Errno +``` -Exceptions ----------- +## Exceptions -:class:`ZMQError` -***************** +### {class}`ZMQError` +```{eval-rst} .. autoclass:: ZMQError :members: :inherited-members: +``` -:class:`ZMQVersionError` -************************ +### {class}`ZMQVersionError` +```{eval-rst} .. autoclass:: ZMQVersionError :members: :inherited-members: +``` -:class:`Again` -************** - +### {class}`Again` +```{eval-rst} .. autoclass:: Again +``` -:class:`ContextTerminated` -************************** - +### {class}`ContextTerminated` +```{eval-rst} .. autoclass:: ContextTerminated +``` -:class:`NotDone` -**************** - +### {class}`NotDone` +```{eval-rst} .. autoclass:: NotDone +``` -:class:`ZMQBindError` -********************* - +### {class}`ZMQBindError` +```{eval-rst} .. autoclass:: ZMQBindError +``` -Functions ---------- +## Functions +```{eval-rst} .. autofunction:: zmq.zmq_version +``` +```{eval-rst} .. autofunction:: zmq.pyzmq_version +``` +```{eval-rst} .. autofunction:: zmq.zmq_version_info +``` +```{eval-rst} .. autofunction:: zmq.pyzmq_version_info +``` +```{eval-rst} .. autofunction:: zmq.has +``` +```{eval-rst} .. autofunction:: zmq.device +``` +```{eval-rst} .. autofunction:: zmq.proxy +``` +```{eval-rst} .. autofunction:: zmq.proxy_steerable +``` +```{eval-rst} .. autofunction:: zmq.curve_public +``` +```{eval-rst} .. autofunction:: zmq.curve_keypair +``` +```{eval-rst} .. autofunction:: zmq.get_includes +``` +```{eval-rst} .. autofunction:: zmq.get_library_dirs +``` +```{eval-rst} .. autofunction:: zmq.strerror +``` diff --git a/docs/source/api/zmq.ssh.tunnel.rst b/docs/source/api/zmq.ssh.tunnel.md similarity index 52% rename from docs/source/api/zmq.ssh.tunnel.rst rename to docs/source/api/zmq.ssh.tunnel.md index cd77ce1a6..27fb1b77b 100644 --- a/docs/source/api/zmq.ssh.tunnel.rst +++ b/docs/source/api/zmq.ssh.tunnel.md @@ -1,25 +1,33 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! +% AUTO-GENERATED FILE -- DO NOT EDIT! -ssh.tunnel -========== +# ssh.tunnel -Module: :mod:`zmq.ssh.tunnel` ------------------------------ -.. automodule:: zmq.ssh.tunnel +## Module: {mod}`zmq.ssh.tunnel` -.. currentmodule:: zmq.ssh.tunnel +```{eval-rst} +.. automodule:: zmq.ssh.tunnel +``` -Functions ---------- +```{currentmodule} zmq.ssh.tunnel +``` +## Functions +```{eval-rst} .. autofunction:: zmq.ssh.tunnel.open_tunnel +``` +```{eval-rst} .. autofunction:: zmq.ssh.tunnel.select_random_ports +``` +```{eval-rst} .. autofunction:: zmq.ssh.tunnel.try_passwordless_ssh +``` +```{eval-rst} .. autofunction:: zmq.ssh.tunnel.tunnel_connection +``` diff --git a/docs/source/api/zmq.utils.jsonapi.md b/docs/source/api/zmq.utils.jsonapi.md new file mode 100644 index 000000000..ef402951b --- /dev/null +++ b/docs/source/api/zmq.utils.jsonapi.md @@ -0,0 +1,23 @@ +% AUTO-GENERATED FILE -- DO NOT EDIT! + +# utils.jsonapi + +## Module: {mod}`zmq.utils.jsonapi` + +```{eval-rst} +.. automodule:: zmq.utils.jsonapi +``` + +```{currentmodule} zmq.utils.jsonapi +``` + +## Functions + +```{eval-rst} +.. autofunction:: zmq.utils.jsonapi.dumps + +``` + +```{eval-rst} +.. autofunction:: zmq.utils.jsonapi.loads +``` diff --git a/docs/source/api/zmq.utils.jsonapi.rst b/docs/source/api/zmq.utils.jsonapi.rst deleted file mode 100644 index 1a4e10f11..000000000 --- a/docs/source/api/zmq.utils.jsonapi.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -utils.jsonapi -============= - -Module: :mod:`zmq.utils.jsonapi` --------------------------------- -.. automodule:: zmq.utils.jsonapi - -.. currentmodule:: zmq.utils.jsonapi - -Functions ---------- - - -.. autofunction:: zmq.utils.jsonapi.dumps - - -.. autofunction:: zmq.utils.jsonapi.loads diff --git a/docs/source/api/zmq.utils.monitor.md b/docs/source/api/zmq.utils.monitor.md new file mode 100644 index 000000000..caaa645c4 --- /dev/null +++ b/docs/source/api/zmq.utils.monitor.md @@ -0,0 +1,21 @@ +# utils.monitor + +## Module: {mod}`zmq.utils.monitor` + +```{eval-rst} +.. automodule:: zmq.utils.monitor +``` + +```{currentmodule} zmq.utils.monitor +``` + +## Functions + +```{eval-rst} +.. autofunction:: parse_monitor_message + +``` + +```{eval-rst} +.. autofunction:: recv_monitor_message +``` diff --git a/docs/source/api/zmq.utils.monitor.rst b/docs/source/api/zmq.utils.monitor.rst deleted file mode 100644 index 0eeb60397..000000000 --- a/docs/source/api/zmq.utils.monitor.rst +++ /dev/null @@ -1,17 +0,0 @@ -utils.monitor -============= - -Module: :mod:`zmq.utils.monitor` --------------------------------- -.. automodule:: zmq.utils.monitor - -.. currentmodule:: zmq.utils.monitor - -Functions ---------- - - -.. autofunction:: parse_monitor_message - - -.. autofunction:: recv_monitor_message diff --git a/docs/source/api/zmq.utils.win32.md b/docs/source/api/zmq.utils.win32.md new file mode 100644 index 000000000..9a799808a --- /dev/null +++ b/docs/source/api/zmq.utils.win32.md @@ -0,0 +1,16 @@ +# utils.win32 + +## Module: {mod}`zmq.utils.win32` + +```{eval-rst} +.. automodule:: zmq.utils.win32 +``` + +```{currentmodule} zmq.utils.win32 +``` + +## {class}`allow_interrupt` + +```{eval-rst} +.. autoclass:: allow_interrupt +``` diff --git a/docs/source/api/zmq.utils.win32.rst b/docs/source/api/zmq.utils.win32.rst deleted file mode 100644 index 24d032833..000000000 --- a/docs/source/api/zmq.utils.win32.rst +++ /dev/null @@ -1,17 +0,0 @@ -utils.win32 -=========== - - -Module: :mod:`zmq.utils.win32` ------------------------------- - -.. automodule:: zmq.utils.win32 - -.. currentmodule:: zmq.utils.win32 - - -:class:`allow_interrupt` ------------------------- - - -.. autoclass:: allow_interrupt diff --git a/docs/source/api/zmq.utils.z85.md b/docs/source/api/zmq.utils.z85.md new file mode 100644 index 000000000..a45ec00bf --- /dev/null +++ b/docs/source/api/zmq.utils.z85.md @@ -0,0 +1,23 @@ +% AUTO-GENERATED FILE -- DO NOT EDIT! + +# utils.z85 + +## Module: {mod}`zmq.utils.z85` + +```{eval-rst} +.. automodule:: zmq.utils.z85 +``` + +```{currentmodule} zmq.utils.z85 +``` + +## Functions + +```{eval-rst} +.. autofunction:: zmq.utils.z85.decode + +``` + +```{eval-rst} +.. autofunction:: zmq.utils.z85.encode +``` diff --git a/docs/source/api/zmq.utils.z85.rst b/docs/source/api/zmq.utils.z85.rst deleted file mode 100644 index 82d2368c4..000000000 --- a/docs/source/api/zmq.utils.z85.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -utils.z85 -========= - -Module: :mod:`zmq.utils.z85` ----------------------------- -.. automodule:: zmq.utils.z85 - -.. currentmodule:: zmq.utils.z85 - -Functions ---------- - - -.. autofunction:: zmq.utils.z85.decode - - -.. autofunction:: zmq.utils.z85.encode diff --git a/docs/source/conf.py b/docs/source/conf.py index dcf3c50cb..42aee8df9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,7 +46,7 @@ ] # The suffix of source filenames. -source_suffix = ['.md', '.rst'] +source_suffix = ['.md'] # The encoding of source files. source_encoding = 'utf-8' diff --git a/docs/source/howto/index.md b/docs/source/howto/index.md index dd978d060..baae1d5ef 100644 --- a/docs/source/howto/index.md +++ b/docs/source/howto/index.md @@ -4,12 +4,12 @@ --- maxdepth: 2 --- -build.md -morethanbindings.rst -serialization.rst -devices.rst -eventloop.rst -draft.rst -logging.rst -ssh.rst +build +morethanbindings +serialization +devices +eventloop +draft +logging +ssh ``` diff --git a/docs/source/index.md b/docs/source/index.md index 12241c6b9..4c594f723 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -45,9 +45,9 @@ You can also check out the [examples in the pyzmq repo](https://github.com/zerom --- maxdepth: 2 --- -api/index.rst -changelog.rst -howto/index.rst +api/index +changelog +howto/index ``` # Indices and tables From dd84be0b9aca1a4972a81a535ecc555c54774d35 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Feb 2024 14:31:40 +0100 Subject: [PATCH 5/6] test docs on CI --- .github/workflows/test-docs.yml | 60 +++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 6 ++-- docs/Makefile | 12 +++---- 3 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/test-docs.yml diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml new file mode 100644 index 000000000..46497e8c4 --- /dev/null +++ b/.github/workflows/test-docs.yml @@ -0,0 +1,60 @@ +name: Test docs + +# The tests defined in docs/ are currently influenced by changes to _version.py +# and scopes.py. +on: + pull_request: + paths: + - "docs/**" + - "zmq/**" + - ".github/workflows/test-docs.yml" + push: + paths: + - "docs/**" + - "zmq/**" + - ".github/workflows/test-docs.yml" + branches-ignore: + - "dependabot/**" + - "pre-commit-ci-update-config" + tags: + - "**" + workflow_dispatch: + +env: + LANG: C.UTF-8 + SPHINXOPTS: "-W" + +jobs: + test-docs: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + + - name: Install libzmq + run: | + sudo apt-get -y install libzmq3-dev + + - name: Install pyzmq + run: | + pip install -v . + + - name: Install requirements + run: | + pip install -r docs/requirements.txt + + # readthedocs doesn't halt on warnings, + # so raise any warnings here + - name: build docs + run: | + cd docs + make html + + - name: check links + run: | + cd docs + make linkcheck diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f1ab7b39..217a9324d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,14 @@ on: paths-ignore: - "docs/**" - "tools/**" - - .github/workflows/wheels.yml + - ".github/workflows/*" + - "!.github/workflows/test.yml" pull_request: paths-ignore: - "docs/**" - "tools/**" - - .github/workflows/wheels.yml + - ".github/workflows/*" + - "!.github/workflows/test.yml" concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/docs/Makefile b/docs/Makefile index 6bf400995..fe8b84040 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,16 +2,16 @@ # # You can set these variables from the command line. -SPHINXOPTS = -n -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build -SRCDIR = source +SPHINXOPTS ?= -n +SPHINXBUILD ?= sphinx-build +PAPER ?= +BUILDDIR ?= build +SRCDIR ?= source # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRCDIR) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest From 35d883eef3da10bc63ed2aac84671221b2cdd705 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 29 Feb 2024 15:18:12 +0100 Subject: [PATCH 6/6] disable fuzzy links --- docs/source/conf.py | 2 ++ docs/source/howto/logging.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 42aee8df9..494f466df 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -45,6 +45,8 @@ "substitution", ] +myst_linkify_fuzzy_links = False + # The suffix of source filenames. source_suffix = ['.md'] diff --git a/docs/source/howto/logging.md b/docs/source/howto/logging.md index b6d3a9a35..0f0343d92 100644 --- a/docs/source/howto/logging.md +++ b/docs/source/howto/logging.md @@ -265,7 +265,7 @@ handler.root_topic = "myprogram" ``` Python loggers also have loglevels. The base topic of messages emitted by the PUBHandler -will be of the form: `.`, e.g. 'myprogram.INFO' or +will be of the form: `.`, e.g. `myprogram.INFO` or 'whatever.ERROR'. This way, subscribers can easily subscribe to subsets of the logging messages. Log messages are always two-part, where the first part is the topic tree, and the second part is the actual log message.