From f3bf7328c68b8083ccb0fe1647b95a70e3cb5770 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 07:26:18 +0000 Subject: [PATCH 1/3] docs(config): add mkdocstrings and pydoclint options Add missing mkdocstrings options to mkdocs.yml: - `relative_crossrefs: true` (convention roll-out) - `show_symbol_type_toc: true` Add missing pydoclint options to pyproject.toml [tool.flake8]: - `check-class-attributes = true` - `check-style-mismatch = true` (convention roll-out) - `require-inline-class-var-docs = true` (convention roll-out) - `skip-checking-short-docstrings = true` Options are ordered alphabetically within each block. Signed-off-by: Copilot --- mkdocs.yml | 2 ++ pyproject.toml | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 6adfbe89..8b1a340b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -105,12 +105,14 @@ plugins: docstring_section_style: spacy inherited_members: true merge_init_into_class: false + relative_crossrefs: true separate_signature: true show_category_heading: true show_root_heading: true show_root_members_full_path: true show_signature_annotations: true show_source: true + show_symbol_type_toc: true signature_crossrefs: true import: # See https://mkdocstrings.github.io/python/usage/#import for details diff --git a/pyproject.toml b/pyproject.toml index 5c300ae6..b62d667b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,11 +107,15 @@ extend-ignore = [ ] # pydoclint options style = "google" -check-return-types = false -check-yield-types = false arg-type-hints-in-docstring = false arg-type-hints-in-signature = true allow-init-docstring = true +check-class-attributes = true +check-return-types = false +check-style-mismatch = true +check-yield-types = false +require-inline-class-var-docs = true +skip-checking-short-docstrings = true [tool.pylint.similarities] ignore-comments = ['yes'] From 1568c866bef9a9ac51db9bfb912b1143ebe47fc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 07:28:34 +0000 Subject: [PATCH 2/3] docs(docstrings): fix pydoclint DOC503 violations Remove extraneous exceptions from Raises sections where only the subclass is directly raised (ReceiverError when only ReceiverStoppedError is raised): - _anycast.py: Receiver.consume - _bidirectional.py: Handle.consume - util/_merge.py: Merge.consume - util/_merge_named.py: MergeNamed.consume Add # noqa: DOC503 where exceptions are raised indirectly (e.g., via asyncio.get_running_loop() or qualified-name import mismatch): - _base_classes.py: Receiver.__anext__, Receiver.receive - util/_event.py: Event.consume - util/_select.py: Selected.value, select() - util/_timer.py: Timer.__init__, Timer.reset Also fix misleading Raises description in Receiver.receive: the ReceiverStoppedError description was "if there is some problem with the receiver" (should be "if the receiver stopped producing messages"). Signed-off-by: Copilot --- src/frequenz/channels/_anycast.py | 1 - src/frequenz/channels/_base_classes.py | 6 +++--- src/frequenz/channels/_bidirectional.py | 1 - src/frequenz/channels/util/_event.py | 2 +- src/frequenz/channels/util/_merge.py | 1 - src/frequenz/channels/util/_merge_named.py | 1 - src/frequenz/channels/util/_select.py | 4 ++-- src/frequenz/channels/util/_timer.py | 4 ++-- 8 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/frequenz/channels/_anycast.py b/src/frequenz/channels/_anycast.py index 40cf69db..7c57010d 100644 --- a/src/frequenz/channels/_anycast.py +++ b/src/frequenz/channels/_anycast.py @@ -235,7 +235,6 @@ def consume(self) -> T: Raises: ReceiverStoppedError: if the receiver stopped producing messages. - ReceiverError: if there is some problem with the receiver. """ if ( # pylint: disable=protected-access self._next is _Empty and self._chan._closed diff --git a/src/frequenz/channels/_base_classes.py b/src/frequenz/channels/_base_classes.py index 8745ce87..8e49434e 100644 --- a/src/frequenz/channels/_base_classes.py +++ b/src/frequenz/channels/_base_classes.py @@ -33,7 +33,7 @@ async def send(self, msg: T) -> None: class Receiver(ABC, Generic[T]): """A channel Receiver.""" - async def __anext__(self) -> T: + async def __anext__(self) -> T: # noqa: DOC503 """Await the next value in the async iteration over received values. Returns: @@ -84,14 +84,14 @@ def __aiter__(self) -> Receiver[T]: """ return self - async def receive(self) -> T: + async def receive(self) -> T: # noqa: DOC503 """Receive a message from the channel. Returns: The received message. Raises: - ReceiverStoppedError: if there is some problem with the receiver. + ReceiverStoppedError: if the receiver stopped producing messages. ReceiverError: if there is some problem with the receiver. """ try: diff --git a/src/frequenz/channels/_bidirectional.py b/src/frequenz/channels/_bidirectional.py index a1bfc94f..f6e92f84 100644 --- a/src/frequenz/channels/_bidirectional.py +++ b/src/frequenz/channels/_bidirectional.py @@ -93,7 +93,6 @@ def consume(self) -> W: The next value that was received. Raises: - ReceiverStoppedError: if there is some problem with the receiver. ReceiverError: if there is some problem with the receiver. """ try: diff --git a/src/frequenz/channels/util/_event.py b/src/frequenz/channels/util/_event.py index c227663a..dd4c11ec 100644 --- a/src/frequenz/channels/util/_event.py +++ b/src/frequenz/channels/util/_event.py @@ -125,7 +125,7 @@ async def ready(self) -> bool: await self._event.wait() return not self._is_stopped - def consume(self) -> None: + def consume(self) -> None: # noqa: DOC503 """Consume the event. This makes this receiver wait again until the event is set again. diff --git a/src/frequenz/channels/util/_merge.py b/src/frequenz/channels/util/_merge.py index f026c9f1..9cbf1472 100644 --- a/src/frequenz/channels/util/_merge.py +++ b/src/frequenz/channels/util/_merge.py @@ -108,7 +108,6 @@ def consume(self) -> T: Raises: ReceiverStoppedError: if the receiver stopped producing messages. - ReceiverError: if there is some problem with the receiver. """ if not self._results and not self._pending: raise ReceiverStoppedError(self) diff --git a/src/frequenz/channels/util/_merge_named.py b/src/frequenz/channels/util/_merge_named.py index d8ab9839..470d9edc 100644 --- a/src/frequenz/channels/util/_merge_named.py +++ b/src/frequenz/channels/util/_merge_named.py @@ -94,7 +94,6 @@ def consume(self) -> tuple[str, T]: Raises: ReceiverStoppedError: if the receiver stopped producing messages. - ReceiverError: if there is some problem with the receiver. """ if not self._results and not self._pending: raise ReceiverStoppedError(self) diff --git a/src/frequenz/channels/util/_select.py b/src/frequenz/channels/util/_select.py index 43a0e357..d60d0678 100644 --- a/src/frequenz/channels/util/_select.py +++ b/src/frequenz/channels/util/_select.py @@ -73,7 +73,7 @@ def __init__(self, receiver: Receiver[_T]) -> None: """Flag to indicate if this selected has been handled in the if-chain.""" @property - def value(self) -> _T: + def value(self) -> _T: # noqa: DOC503 """The value that was received, if any. Returns: @@ -238,7 +238,7 @@ class SelectErrorGroup(BaseExceptionGroup[BaseException], SelectError): # https://github.com/python/mypy/issues/13597 -async def select(*receivers: Receiver[Any]) -> AsyncIterator[Selected[Any]]: +async def select(*receivers: Receiver[Any]) -> AsyncIterator[Selected[Any]]: # noqa: DOC503 """Iterate over the values of all receivers as they receive new values. This function is used to iterate over the values of all receivers as they receive diff --git a/src/frequenz/channels/util/_timer.py b/src/frequenz/channels/util/_timer.py index ef577897..933ba61b 100644 --- a/src/frequenz/channels/util/_timer.py +++ b/src/frequenz/channels/util/_timer.py @@ -384,7 +384,7 @@ def do_heavy_processing(data: int): next tick to be relative to the time timer was last triggered. """ - def __init__( + def __init__( # noqa: DOC503 self, interval: timedelta, missed_tick_policy: MissedTickPolicy, @@ -627,7 +627,7 @@ def is_running(self) -> bool: """ return not self._stopped - def reset(self, *, start_delay: timedelta = timedelta(0)) -> None: + def reset(self, *, start_delay: timedelta = timedelta(0)) -> None: # noqa: DOC503 """Reset the timer to start timing from now (plus an optional delay). If the timer was stopped, or not started yet, it will be started. From bed674db2b293a1c091f9d8d216c34d6f9509090 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 07:31:19 +0000 Subject: [PATCH 3/3] docs(docstrings): improve property and method docstrings Simplify property docstrings to the conventional one-liner noun-phrase format (removing redundant Returns: sections that repeat the summary): - _bidirectional.py: Bidirectional.client_handle, service_handle - util/_event.py: Event.name, is_set, is_stopped - util/_timer.py: SkipMissedAndDrift.delay_tolerance, Timer.interval, missed_tick_policy, loop, is_running Simplify no-arg methods that only return/print self to one-liner imperative format (removing redundant Returns: sections): - _base_classes.py: Receiver.__aiter__ - util/_event.py: Event.__str__, __repr__ - util/_select.py: Selected.__str__, __repr__ - util/_timer.py: MissedTickPolicy.__repr__, SkipMissedAndDrift.__str__, __repr__, Timer._now, __str__, __repr__ Fix wrong class name in Error.__init__ docstring ("Create a ChannelError instance" -> "Create an instance"). Add cross-references to Bidirectional.client_handle and service_handle. Signed-off-by: Copilot --- src/frequenz/channels/_base_classes.py | 6 +-- src/frequenz/channels/_bidirectional.py | 12 +---- src/frequenz/channels/_exceptions.py | 2 +- src/frequenz/channels/util/_event.py | 27 ++--------- src/frequenz/channels/util/_select.py | 12 +---- src/frequenz/channels/util/_timer.py | 63 ++++--------------------- 6 files changed, 20 insertions(+), 102 deletions(-) diff --git a/src/frequenz/channels/_base_classes.py b/src/frequenz/channels/_base_classes.py index 8e49434e..353bf1ff 100644 --- a/src/frequenz/channels/_base_classes.py +++ b/src/frequenz/channels/_base_classes.py @@ -77,11 +77,7 @@ def consume(self) -> T: """ def __aiter__(self) -> Receiver[T]: - """Initialize the async iterator over received values. - - Returns: - `self`, since no extra setup is needed for the iterator. - """ + """Return `self`, since no extra setup is needed for the iterator.""" return self async def receive(self) -> T: # noqa: DOC503 diff --git a/src/frequenz/channels/_bidirectional.py b/src/frequenz/channels/_bidirectional.py index f6e92f84..0c9c1ecb 100644 --- a/src/frequenz/channels/_bidirectional.py +++ b/src/frequenz/channels/_bidirectional.py @@ -145,18 +145,10 @@ def __init__(self, client_id: str, service_id: str) -> None: @property def client_handle(self) -> Bidirectional.Handle[T, U]: - """Get a `Handle` for the client side to use. - - Returns: - Object to send/receive messages with. - """ + """The [`Handle`][.Handle] for the client side to use.""" return self._client_handle @property def service_handle(self) -> Bidirectional.Handle[U, T]: - """Get a `Handle` for the service side to use. - - Returns: - Object to send/receive messages with. - """ + """The [`Handle`][.Handle] for the service side to use.""" return self._service_handle diff --git a/src/frequenz/channels/_exceptions.py b/src/frequenz/channels/_exceptions.py index 2042003b..0dd37fed 100644 --- a/src/frequenz/channels/_exceptions.py +++ b/src/frequenz/channels/_exceptions.py @@ -20,7 +20,7 @@ class Error(RuntimeError): """ def __init__(self, message: Any): - """Create a ChannelError instance. + """Create an instance. Args: message: An error message. diff --git a/src/frequenz/channels/util/_event.py b/src/frequenz/channels/util/_event.py index dd4c11ec..59868f9e 100644 --- a/src/frequenz/channels/util/_event.py +++ b/src/frequenz/channels/util/_event.py @@ -80,28 +80,17 @@ def name(self) -> str: This is for debugging purposes, it will be shown in the string representation of this receiver. - - Returns: - The name of this receiver. """ return self._name @property def is_set(self) -> bool: - """Whether this receiver is set (ready). - - Returns: - Whether this receiver is set (ready). - """ + """Whether this receiver is set (ready).""" return self._is_set @property def is_stopped(self) -> bool: - """Whether this receiver is stopped. - - Returns: - Whether this receiver is stopped. - """ + """Whether this receiver is stopped.""" return self._is_stopped def stop(self) -> None: @@ -142,19 +131,11 @@ def consume(self) -> None: # noqa: DOC503 self._event.clear() def __str__(self) -> str: - """Return a string representation of this receiver. - - Returns: - A string representation of this receiver. - """ + """Return a string representation of this receiver.""" return f"{type(self).__name__}({self._name!r})" def __repr__(self) -> str: - """Return a string representation of this receiver. - - Returns: - A string representation of this receiver. - """ + """Return a string representation of this receiver.""" return ( f"<{type(self).__name__} name={self._name!r} is_set={self.is_set!r} " f"is_stopped={self.is_stopped!r}>" diff --git a/src/frequenz/channels/util/_select.py b/src/frequenz/channels/util/_select.py index d60d0678..1a43e811 100644 --- a/src/frequenz/channels/util/_select.py +++ b/src/frequenz/channels/util/_select.py @@ -112,22 +112,14 @@ def was_stopped(self) -> bool: return isinstance(self._exception, ReceiverStoppedError) def __str__(self) -> str: - """Return a string representation of this instance. - - Returns: - A string representation of this instance. - """ + """Return a string representation of this instance.""" return ( f"{type(self).__name__}({self._recv}) -> " f"{self._exception or self._value})" ) def __repr__(self) -> str: - """Return a the internal representation of this instance. - - Returns: - A string representation of this instance. - """ + """Return the internal representation of this instance.""" return ( f"{type(self).__name__}({self._recv=}, {self._value=}, " f"{self._exception=}, {self._handled=})" diff --git a/src/frequenz/channels/util/_timer.py b/src/frequenz/channels/util/_timer.py index 933ba61b..b0f820d5 100644 --- a/src/frequenz/channels/util/_timer.py +++ b/src/frequenz/channels/util/_timer.py @@ -67,11 +67,7 @@ def calculate_next_tick_time( return 0 # dummy value to avoid darglint warnings def __repr__(self) -> str: - """Return a string representation of the instance. - - Returns: - The string representation of the instance. - """ + """Return a string representation of the instance.""" return f"{type(self).__name__}()" @@ -219,11 +215,7 @@ def __init__(self, *, delay_tolerance: timedelta = timedelta(0)): @property def delay_tolerance(self) -> timedelta: - """Return the maximum delay that is tolerated before starting to drift. - - Returns: - The maximum delay that is tolerated before starting to drift. - """ + """The maximum delay that is tolerated before starting to drift.""" return timedelta(microseconds=self._tolerance) def calculate_next_tick_time( @@ -251,19 +243,11 @@ def calculate_next_tick_time( return scheduled_tick_time + interval def __str__(self) -> str: - """Return a string representation of the instance. - - Returns: - The string representation of the instance. - """ + """Return a string representation of the instance.""" return f"{type(self).__name__}({self.delay_tolerance})" def __repr__(self) -> str: - """Return a string representation of the instance. - - Returns: - The string representation of the instance. - """ + """Return a string representation of the instance.""" return f"{type(self).__name__}({self.delay_tolerance=})" @@ -591,29 +575,17 @@ def periodic( # noqa: DOC502 @property def interval(self) -> timedelta: - """The interval between timer ticks. - - Returns: - The interval between timer ticks. - """ + """The interval between timer ticks.""" return timedelta(microseconds=self._interval) @property def missed_tick_policy(self) -> MissedTickPolicy: - """The policy of the timer when it misses a tick. - - Returns: - The policy of the timer when it misses a tick. - """ + """The policy of the timer when it misses a tick.""" return self._missed_tick_policy @property def loop(self) -> asyncio.AbstractEventLoop: - """The event loop used by the timer to track time. - - Returns: - The event loop used by the timer to track time. - """ + """The event loop used by the timer to track time.""" return self._loop @property @@ -621,9 +593,6 @@ def is_running(self) -> bool: """Whether the timer is running. This will be `False` if the timer was stopped, or not started yet. - - Returns: - Whether the timer is running. """ return not self._stopped @@ -746,27 +715,15 @@ def consume(self) -> timedelta: return drift def _now(self) -> int: - """Return the current monotonic clock time in microseconds. - - Returns: - The current monotonic clock time in microseconds. - """ + """Return the current monotonic clock time in microseconds.""" return _to_microseconds(self._loop.time()) def __str__(self) -> str: - """Return a string representation of the timer. - - Returns: - The string representation of the timer. - """ + """Return a string representation of the timer.""" return f"{type(self).__name__}({self.interval})" def __repr__(self) -> str: - """Return a string representation of the timer. - - Returns: - The string representation of the timer. - """ + """Return a string representation of the timer.""" return ( f"{type(self).__name__}<{self.interval=}, {self.missed_tick_policy=}, " f"{self.loop=}, {self.is_running=}>"