Skip to content

[refactor] Use builtin TimeoutError instead of our own#3419

Open
pieleric wants to merge 1 commit intodelmic:masterfrom
pieleric:refactor-use-builtin-timeouterror-instead-of-our-own
Open

[refactor] Use builtin TimeoutError instead of our own#3419
pieleric wants to merge 1 commit intodelmic:masterfrom
pieleric:refactor-use-builtin-timeouterror-instead-of-our-own

Conversation

@pieleric
Copy link
Member

@pieleric pieleric commented Mar 24, 2026

Python 2 didn't have a TimeoutError, so we introduced our own. Now there is a builtin version, let's use that one, which has the nice advantage of reducing the chances of mixing up the 2 different exceptions.

Note there is yet another one: concurrent.futures.TimeoutError. On Python 3.12, it's now the same as the builtin one, but on older Pythons, it wasn't. So for now, we keep them separated.

Copilot AI review requested due to automatic review settings March 24, 2026 15:58
@pieleric pieleric requested review from tepals and tmoerkerken March 24, 2026 16:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the codebase to stop using Odemis’ custom TimeoutError (originally needed for Python 2 compatibility) and instead rely on Python’s builtin TimeoutError, reducing ambiguity between multiple timeout exception types.

Changes:

  • Removes the custom TimeoutError definition from odemis.util.
  • Updates imports/usages across drivers, acquisition code, plugins, scripts, and tests to use the builtin TimeoutError.
  • Cleans up now-unneeded TimeoutError imports from odemis.util.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/odemis/util/test/util_test.py Stops importing TimeoutError from odemis.util; tests now use builtin TimeoutError.
src/odemis/util/init.py Removes the custom TimeoutError class from the util package.
src/odemis/driver/tmcm.py Drops TimeoutError import from odemis.util to rely on builtin.
src/odemis/driver/test/hamamatsurx_test.py Updates assertions to expect builtin TimeoutError.
src/odemis/driver/tescan.py Removes TimeoutError import from odemis.util.
src/odemis/driver/pigcs.py Drops TimeoutError import from odemis.util.
src/odemis/driver/picoquant.py Removes TimeoutError import from odemis.util.
src/odemis/driver/hamamatsurx.py Raises builtin TimeoutError instead of util.TimeoutError.
src/odemis/driver/avantes.py Removes TimeoutError import from odemis.util.
src/odemis/acq/path.py Removes TimeoutError import from odemis.util.
src/odemis/acq/fastem.py Removes TimeoutError import from odemis.util.
src/odemis/acq/align/find_overlay.py Removes TimeoutError import from odemis.util.
scripts/demo_overlay.py Removes TimeoutError import from odemis.util.
plugins/tileacq.py Removes TimeoutError import from odemis.util.
Comments suppressed due to low confidence (1)

src/odemis/util/init.py:767

  • Removing odemis.util.TimeoutError entirely is a breaking API change for any downstream code that does from odemis.util import TimeoutError or references util.TimeoutError. Consider keeping a backwards-compatible alias (e.g., TimeoutError = builtins.TimeoutError) for at least a deprecation cycle while still switching all internal uses to the builtin.

Also note the builtin TimeoutError subclasses OSError/IOError, which can change behavior for callers that catch OSError broadly.



# TODO: only works on Unix, needs a fallback on windows (at least, don't complain)
# from http://stackoverflow.com/questions/2281850/timeout-function-if-it-takes-too-long-to-finish
# see http://code.activestate.com/recipes/577853-timeout-decorator-with-multiprocessing/

@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

This change set removes the custom TimeoutError exception class from odemis.util and updates the codebase to use Python's built-in TimeoutError instead. The custom exception definition is deleted from src/odemis/util/__init__.py, and all modules that previously imported this custom exception are updated to remove the import statement. One module (src/odemis/driver/hamamatsurx.py) explicitly replaces util.TimeoutError with the built-in TimeoutError in its exception raising logic, and corresponding test expectations are updated accordingly.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main refactoring change: replacing a custom TimeoutError implementation with Python's builtin TimeoutError across the codebase.
Description check ✅ Passed The description clearly explains the rationale for the change, provides historical context about Python 2 compatibility, and mentions considerations around concurrent.futures.TimeoutError.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/odemis/driver/hamamatsurx.py (1)

1383-1393: ⚠️ Potential issue | 🟡 Minor

Preserve the original queue.Empty as the cause.

The TimeoutError raised here triggers Ruff B904 by raising a new exception in the except block without chaining to the original. Capture the exception and chain it with from err to maintain timeout diagnostics and satisfy the linter.

♻️ Proposed fix
-                except queue.Empty:
+                except queue.Empty as err:
                     if latest_response:
                         # log the last error code received before timeout
                         logging.error("Latest response before timeout was '%s'",
                                       latest_response)
                     # TODO: try to close/reopen the connection. However, not re-send
                     # the command as we don't know whether it was received, and
                     # whether it's safe to send twice the same command. So still
                     # report a timeout, but hopefully the next command works again.
-                    raise TimeoutError("No answer received after %s s for command %s."
-                                            % (timeout, to_str_escape(command)))
+                    raise TimeoutError(
+                        "No answer received after %s s for command %s."
+                        % (timeout, to_str_escape(command))
+                    ) from err
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/odemis/driver/hamamatsurx.py` around lines 1383 - 1393, The except block
handling queue.Empty should preserve the original exception as the cause: change
the handler to catch the queue.Empty as a variable (e.g., "except queue.Empty as
err") and then re-raise the TimeoutError with chaining using "from err"; keep
the existing logging of latest_response and the TimeoutError message using
timeout and to_str_escape(command) but raise it as "raise TimeoutError(...) from
err" so the original queue.Empty is retained for diagnostics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugins/tileacq.py`:
- Line 47: The timeout raised by stage.moveAbs(...).result(t) is a
concurrent.futures.TimeoutError on Python 3.10 and is not being caught by the
current handler that expects the builtin TimeoutError; update the imports to
include the concurrent.futures TimeoutError (e.g., add "from concurrent.futures
import TimeoutError" alongside the existing imports such as the "from
odemis.util import img" line) or modify the exception handler around the
stage.moveAbs(...).result(t) call to catch both builtins.TimeoutError and
concurrent.futures.TimeoutError so the timeout is handled consistently (match
the pattern used in src/odemis/acq/stitching/_tiledacq.py).

---

Outside diff comments:
In `@src/odemis/driver/hamamatsurx.py`:
- Around line 1383-1393: The except block handling queue.Empty should preserve
the original exception as the cause: change the handler to catch the queue.Empty
as a variable (e.g., "except queue.Empty as err") and then re-raise the
TimeoutError with chaining using "from err"; keep the existing logging of
latest_response and the TimeoutError message using timeout and
to_str_escape(command) but raise it as "raise TimeoutError(...) from err" so the
original queue.Empty is retained for diagnostics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 691c2ed2-b22c-44d4-bcc7-075b5fc4aa5d

📥 Commits

Reviewing files that changed from the base of the PR and between 841305f and 8a40554.

📒 Files selected for processing (14)
  • plugins/tileacq.py
  • scripts/demo_overlay.py
  • src/odemis/acq/align/find_overlay.py
  • src/odemis/acq/fastem.py
  • src/odemis/acq/path.py
  • src/odemis/driver/avantes.py
  • src/odemis/driver/hamamatsurx.py
  • src/odemis/driver/picoquant.py
  • src/odemis/driver/pigcs.py
  • src/odemis/driver/tescan.py
  • src/odemis/driver/test/hamamatsurx_test.py
  • src/odemis/driver/tmcm.py
  • src/odemis/util/__init__.py
  • src/odemis/util/test/util_test.py
💤 Files with no reviewable changes (2)
  • src/odemis/util/test/util_test.py
  • src/odemis/util/init.py

Python 2 didn't have a TimeoutError, so we introduced our own.
Now there is a builtin version, let's use that one, which has the nice
advantage of reducing the chances of mixing up the 2 different
exceptions.

Note there is again another one: concurrent.futures.TimeoutError.
On Python 3.12, it's now the same as the builtin one, but on older
Pythons, it wasn't. So for now, we keep them separated.
@pieleric pieleric force-pushed the refactor-use-builtin-timeouterror-instead-of-our-own branch from 8a40554 to 4a9ccb1 Compare March 26, 2026 08:18
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/odemis/driver/hamamatsurx.py (1)

1392-1393: Add exception chaining with from None to suppress misleading context.

When raising a new exception inside an except block, Python best practices recommend explicit exception chaining. Since queue.Empty is just the detection mechanism (not the actual cause of the timeout), use from None to suppress the misleading chain.

♻️ Proposed fix
-                    raise TimeoutError("No answer received after %s s for command %s."
-                                            % (timeout, to_str_escape(command)))
+                    raise TimeoutError("No answer received after %s s for command %s."
+                                            % (timeout, to_str_escape(command))) from None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/odemis/driver/hamamatsurx.py` around lines 1392 - 1393, The TimeoutError
raised when no response is received should suppress chaining from the caught
queue.Empty; modify the raise in the exception handler that currently does raise
TimeoutError("No answer received after %s s for command %s." % (timeout,
to_str_escape(command))) so it reads the same but uses "from None" (i.e., raise
TimeoutError(...) from None) to avoid misleading exception context coming from
queue.Empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/odemis/driver/hamamatsurx.py`:
- Around line 1392-1393: The TimeoutError raised when no response is received
should suppress chaining from the caught queue.Empty; modify the raise in the
exception handler that currently does raise TimeoutError("No answer received
after %s s for command %s." % (timeout, to_str_escape(command))) so it reads the
same but uses "from None" (i.e., raise TimeoutError(...) from None) to avoid
misleading exception context coming from queue.Empty.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 186221bc-4636-494d-969b-43db1fa84cc4

📥 Commits

Reviewing files that changed from the base of the PR and between 8a40554 and 4a9ccb1.

📒 Files selected for processing (13)
  • scripts/demo_overlay.py
  • src/odemis/acq/align/find_overlay.py
  • src/odemis/acq/fastem.py
  • src/odemis/acq/path.py
  • src/odemis/driver/avantes.py
  • src/odemis/driver/hamamatsurx.py
  • src/odemis/driver/picoquant.py
  • src/odemis/driver/pigcs.py
  • src/odemis/driver/tescan.py
  • src/odemis/driver/test/hamamatsurx_test.py
  • src/odemis/driver/tmcm.py
  • src/odemis/util/__init__.py
  • src/odemis/util/test/util_test.py
💤 Files with no reviewable changes (2)
  • src/odemis/util/test/util_test.py
  • src/odemis/util/init.py
✅ Files skipped from review due to trivial changes (4)
  • src/odemis/driver/avantes.py
  • src/odemis/acq/align/find_overlay.py
  • src/odemis/driver/tmcm.py
  • src/odemis/driver/tescan.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/odemis/driver/test/hamamatsurx_test.py
  • scripts/demo_overlay.py
  • src/odemis/driver/pigcs.py
  • src/odemis/acq/fastem.py
  • src/odemis/acq/path.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants