isolate 0.26.5__tar.gz → 0.26.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/test.yml +2 -2
  2. {isolate-0.26.5/src/isolate.egg-info → isolate-0.26.7}/PKG-INFO +1 -1
  3. {isolate-0.26.5 → isolate-0.26.7}/pyproject.toml +5 -0
  4. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/_isolate_version.py +3 -3
  5. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/common.py +6 -4
  6. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/common.py +32 -11
  7. {isolate-0.26.5 → isolate-0.26.7/src/isolate.egg-info}/PKG-INFO +1 -1
  8. {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/SOURCES.txt +1 -0
  9. {isolate-0.26.5 → isolate-0.26.7}/tests/test_backends.py +1 -0
  10. isolate-0.26.7/tests/test_logged_io_pipe.py +132 -0
  11. {isolate-0.26.5 → isolate-0.26.7}/tests/test_serialization.py +36 -0
  12. {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/claude-code-review.yml +0 -0
  13. {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/claude.yml +0 -0
  14. {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/lint.yml +0 -0
  15. {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/release.yml +0 -0
  16. {isolate-0.26.5 → isolate-0.26.7}/.gitignore +0 -0
  17. {isolate-0.26.5 → isolate-0.26.7}/.pre-commit-config.yaml +0 -0
  18. {isolate-0.26.5 → isolate-0.26.7}/LICENSE +0 -0
  19. {isolate-0.26.5 → isolate-0.26.7}/README.md +0 -0
  20. {isolate-0.26.5 → isolate-0.26.7}/setup.cfg +0 -0
  21. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/__init__.py +0 -0
  22. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/_version.py +0 -0
  23. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/__init__.py +0 -0
  24. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/_base.py +0 -0
  25. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/conda.py +0 -0
  26. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/container.py +0 -0
  27. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/local.py +0 -0
  28. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/pyenv.py +0 -0
  29. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/remote.py +0 -0
  30. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/settings.py +0 -0
  31. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/virtualenv.py +0 -0
  32. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/common/__init__.py +0 -0
  33. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/common/timestamp.py +0 -0
  34. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/__init__.py +0 -0
  35. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/_local/__init__.py +0 -0
  36. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/_local/_base.py +0 -0
  37. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/_local/agent_startup.py +0 -0
  38. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/__init__.py +0 -0
  39. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/_base.py +0 -0
  40. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/agent.py +0 -0
  41. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/configuration.py +0 -0
  42. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/__init__.py +0 -0
  43. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent.proto +0 -0
  44. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2.py +0 -0
  45. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2.pyi +0 -0
  46. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2_grpc.py +0 -0
  47. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common.proto +0 -0
  48. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2.py +0 -0
  49. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2.pyi +0 -0
  50. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2_grpc.py +0 -0
  51. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/interface.py +0 -0
  52. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/ipc/__init__.py +0 -0
  53. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/ipc/_base.py +0 -0
  54. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/ipc/agent.py +0 -0
  55. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/logger.py +0 -0
  56. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/logs.py +0 -0
  57. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/py.typed +0 -0
  58. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/registry.py +0 -0
  59. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/__init__.py +0 -0
  60. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/__init__.py +0 -0
  61. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server.proto +0 -0
  62. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server_pb2.py +0 -0
  63. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server_pb2.pyi +0 -0
  64. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server_pb2_grpc.py +0 -0
  65. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/__init__.py +0 -0
  66. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health.proto +0 -0
  67. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health_pb2.py +0 -0
  68. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health_pb2.pyi +0 -0
  69. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health_pb2_grpc.py +0 -0
  70. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health_server.py +0 -0
  71. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/interface.py +0 -0
  72. {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/server.py +0 -0
  73. {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/dependency_links.txt +0 -0
  74. {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/entry_points.txt +0 -0
  75. {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/requires.txt +0 -0
  76. {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/top_level.txt +0 -0
  77. {isolate-0.26.5 → isolate-0.26.7}/tests/__init__.py +0 -0
  78. {isolate-0.26.5 → isolate-0.26.7}/tests/conftest.py +0 -0
  79. {isolate-0.26.5 → isolate-0.26.7}/tests/test_agent_backward_compatibility.py +0 -0
  80. {isolate-0.26.5 → isolate-0.26.7}/tests/test_concurrency.py +0 -0
  81. {isolate-0.26.5 → isolate-0.26.7}/tests/test_connections.py +0 -0
  82. {isolate-0.26.5 → isolate-0.26.7}/tests/test_connections_grpc_base.py +0 -0
  83. {isolate-0.26.5 → isolate-0.26.7}/tests/test_isolate.py +0 -0
  84. {isolate-0.26.5 → isolate-0.26.7}/tests/test_log.py +0 -0
  85. {isolate-0.26.5 → isolate-0.26.7}/tests/test_log_masking.py +0 -0
  86. {isolate-0.26.5 → isolate-0.26.7}/tests/test_logger.py +0 -0
  87. {isolate-0.26.5 → isolate-0.26.7}/tests/test_server.py +0 -0
  88. {isolate-0.26.5 → isolate-0.26.7}/tests/test_shutdown.py +0 -0
  89. {isolate-0.26.5 → isolate-0.26.7}/tools/Dockerfile +0 -0
  90. {isolate-0.26.5 → isolate-0.26.7}/tools/agent_requirements.txt +0 -0
  91. {isolate-0.26.5 → isolate-0.26.7}/tools/isolate_client.py +0 -0
  92. {isolate-0.26.5 → isolate-0.26.7}/tools/protobuf-requirements.txt +0 -0
  93. {isolate-0.26.5 → isolate-0.26.7}/tools/regen_grpc.py +0 -0
  94. {isolate-0.26.5 → isolate-0.26.7}/tools/requirements.txt +0 -0
  95. {isolate-0.26.5 → isolate-0.26.7}/tools/test_agent_requirements.txt +0 -0
@@ -22,12 +22,12 @@ jobs:
22
22
 
23
23
  matrix:
24
24
  os: [ubuntu-latest]
25
- python: ["3.8", "3.9", "3.10", "3.11"]
25
+ python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
26
26
  include:
27
27
  - os: macos-15-intel
28
28
  python: "3.8"
29
29
  - os: macos-15-intel
30
- python: "3.11"
30
+ python: "3.14"
31
31
  steps:
32
32
  - uses: actions/checkout@v3
33
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isolate
3
- Version: 0.26.5
3
+ Version: 0.26.7
4
4
  Summary: Managed isolated environments for Python
5
5
  Author-email: Features & Labels <hello@fal.ai>
6
6
  Project-URL: Issues, https://github.com/fal-ai/isolate/issues
@@ -76,3 +76,8 @@ keep-runtime-typing = true
76
76
 
77
77
  [tool.ruff.lint]
78
78
  select = ["E", "F", "W", "PLC", "PLE", "PLW", "I", "UP"]
79
+
80
+ [tool.pytest.ini_options]
81
+ addopts = "-ra --durations=50"
82
+ asyncio_mode = "auto"
83
+ testpaths = "tests"
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.26.5'
32
- __version_tuple__ = version_tuple = (0, 26, 5)
31
+ __version__ = version = '0.26.7'
32
+ __version_tuple__ = version_tuple = (0, 26, 7)
33
33
 
34
- __commit_id__ = commit_id = 'g597c919ae'
34
+ __commit_id__ = commit_id = 'g9b3604b86'
@@ -166,13 +166,15 @@ def _close_fd(fd: int) -> None:
166
166
 
167
167
 
168
168
  def _unblocked_pipe() -> tuple[int, int]:
169
- """Create a pair of unblocked pipes. This is actually
170
- the same as os.pipe2(os.O_NONBLOCK), but that is not
171
- available in MacOS so we have to do it manually."""
169
+ """Create a pipe whose read end is non-blocking, so the reader thread in
170
+ `_io_observer` can `select()` + `read()` without risk of hanging on a
171
+ partial-line edge case. The write end stays blocking so the worker's
172
+ stdout/stderr applies backpressure (waits for the reader to drain the
173
+ kernel pipe buffer) instead of raising BlockingIOError when the buffer
174
+ fills up — which happens reliably with chunky tracebacks on Python 3.14."""
172
175
 
173
176
  read_fd, write_fd = os.pipe()
174
177
  os.set_blocking(read_fd, False)
175
- os.set_blocking(write_fd, False)
176
178
  return read_fd, write_fd
177
179
 
178
180
 
@@ -4,6 +4,7 @@ import importlib
4
4
  import os
5
5
  from contextlib import contextmanager
6
6
  from dataclasses import dataclass
7
+ from types import TracebackType
7
8
  from typing import TYPE_CHECKING, Any, Iterator, cast
8
9
 
9
10
  from tblib import Traceback, TracebackParseError
@@ -28,6 +29,14 @@ class SerializationError(Exception):
28
29
  message: str
29
30
 
30
31
 
32
+ @dataclass
33
+ class ExceptionDeserializationError(SerializationError):
34
+ """Raised when a remote exception cannot be deserialized locally (e.g. the
35
+ module that defines its type isn't importable here)."""
36
+
37
+ original_traceback: TracebackType | None
38
+
39
+
31
40
  # NOTE: tblib's install() will search for BaseException subclasses,
32
41
  # so we have to call it after the SerializationError is defined.
33
42
  tblib_install()
@@ -77,8 +86,19 @@ def load_serialized_object(
77
86
  importlib.import_module(serialization_method)
78
87
  )
79
88
 
80
- with _step("deserializing the given object"):
81
- result = serialization_backend.loads(raw_object)
89
+ try:
90
+ with _step("deserializing the given object"):
91
+ result = serialization_backend.loads(raw_object)
92
+ except SerializationError as exc:
93
+ if was_it_raised:
94
+ # We were trying to reconstruct a remote exception but its type
95
+ # isn't importable here, so loads() failed. Surface the genuine
96
+ # local cause along with the remote traceback.
97
+ raise ExceptionDeserializationError(
98
+ exc.message,
99
+ original_traceback=_prepare_traceback(stringized_traceback),
100
+ ) from exc.__cause__
101
+ raise
82
102
 
83
103
  if was_it_raised:
84
104
  raise prepare_exc(result, stringized_traceback=stringized_traceback)
@@ -111,18 +131,19 @@ def validate_entrypoint(entrypoint: str) -> None:
111
131
  raise ValueError(f"Invalid entrypoint {entrypoint!r}: expected 'module:attr'.")
112
132
 
113
133
 
134
+ def _prepare_traceback(stringized_traceback: str | None) -> TracebackType | None:
135
+ if stringized_traceback:
136
+ try:
137
+ return Traceback.from_string(stringized_traceback).as_traceback()
138
+ except TracebackParseError:
139
+ pass
140
+ return None
141
+
142
+
114
143
  def prepare_exc(
115
144
  exc: BaseException,
116
145
  *,
117
146
  stringized_traceback: str | None = None,
118
147
  ) -> BaseException:
119
- if stringized_traceback:
120
- try:
121
- traceback = Traceback.from_string(stringized_traceback).as_traceback()
122
- except TracebackParseError:
123
- traceback = None
124
- else:
125
- traceback = None
126
-
127
- exc.__traceback__ = traceback
148
+ exc.__traceback__ = _prepare_traceback(stringized_traceback)
128
149
  return exc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isolate
3
- Version: 0.26.5
3
+ Version: 0.26.7
4
4
  Summary: Managed isolated environments for Python
5
5
  Author-email: Features & Labels <hello@fal.ai>
6
6
  Project-URL: Issues, https://github.com/fal-ai/isolate/issues
@@ -79,6 +79,7 @@ tests/test_connections_grpc_base.py
79
79
  tests/test_isolate.py
80
80
  tests/test_log.py
81
81
  tests/test_log_masking.py
82
+ tests/test_logged_io_pipe.py
82
83
  tests/test_logger.py
83
84
  tests/test_serialization.py
84
85
  tests/test_server.py
@@ -555,6 +555,7 @@ class TestConda(GenericEnvironmentTests):
555
555
  "packages": [],
556
556
  },
557
557
  "black": {
558
+ "python_version": "3.11",
558
559
  "packages": ["black=22.12.0"],
559
560
  },
560
561
  "old-python": {
@@ -0,0 +1,132 @@
1
+ """Regression tests for the pipe wiring used by `logged_io`.
2
+
3
+ Background: `_unblocked_pipe()` is consumed by `logged_io()`, where the
4
+ *read* end is drained from a reader thread (`_io_observer`) using
5
+ `select()` + `os.read()`. Only the read end needs to be non-blocking
6
+ (so the post-`select` read can never hang on a partial-line edge case).
7
+
8
+ Historically both ends were marked non-blocking. That makes the *write*
9
+ end raise `BlockingIOError` (errno EAGAIN) the moment the kernel pipe
10
+ buffer (~64 KB on Linux) fills up before the reader thread drains it —
11
+ which is exactly what happens when a worker emits a chunky traceback.
12
+ With a blocking write end, writers apply backpressure instead.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import subprocess
19
+ import sys
20
+ import threading
21
+
22
+ import pytest
23
+ from isolate.backends.common import _unblocked_pipe, logged_io
24
+
25
+
26
+ def test_unblocked_pipe_write_end_is_blocking():
27
+ """The write end must be blocking so that worker stdout/stderr applies
28
+ backpressure instead of raising BlockingIOError when the pipe buffer fills."""
29
+ read_fd, write_fd = _unblocked_pipe()
30
+ try:
31
+ assert not os.get_blocking(read_fd), "read end must be non-blocking"
32
+ assert os.get_blocking(write_fd), (
33
+ "write end must be blocking so the worker gets backpressure "
34
+ "instead of BlockingIOError when the pipe buffer fills up"
35
+ )
36
+ finally:
37
+ os.close(read_fd)
38
+ os.close(write_fd)
39
+
40
+
41
+ def test_writing_more_than_pipe_buffer_does_not_raise_blocking_io_error():
42
+ """Direct reproducer: write more than the kernel pipe buffer (64 KB on
43
+ Linux) through a Python BufferedWriter on top of the pipe's write end,
44
+ while a reader thread drains it slowly. With a non-blocking write end
45
+ this raises `BlockingIOError`; with a blocking write end the writer
46
+ transparently waits for the reader to make room.
47
+ """
48
+ read_fd, write_fd = _unblocked_pipe()
49
+ drained = bytearray()
50
+ drain_done = threading.Event()
51
+
52
+ def drain():
53
+ # Slow reader: read in small chunks with a `select` loop, mimicking
54
+ # `_io_observer`'s pattern but slower so the pipe buffer fills up.
55
+ import select as _select
56
+
57
+ while not drain_done.is_set():
58
+ ready, _, _ = _select.select([read_fd], [], [], 0.05)
59
+ if read_fd in ready:
60
+ try:
61
+ chunk = os.read(read_fd, 4096)
62
+ except BlockingIOError:
63
+ continue
64
+ if not chunk:
65
+ return
66
+ drained.extend(chunk)
67
+
68
+ reader = threading.Thread(target=drain)
69
+ reader.start()
70
+ try:
71
+ # 512 KB is well past the 64 KB pipe buffer on Linux.
72
+ payload = b"x" * (512 * 1024)
73
+ with open(write_fd, "wb", closefd=False) as stream:
74
+ stream.write(payload)
75
+ stream.flush()
76
+ # Give the reader a moment to catch up.
77
+ deadline_loops = 50
78
+ while len(drained) < len(payload) and deadline_loops > 0:
79
+ deadline_loops -= 1
80
+ drain_done.wait(0.05)
81
+ finally:
82
+ drain_done.set()
83
+ os.close(write_fd)
84
+ reader.join(timeout=2)
85
+ os.close(read_fd)
86
+
87
+ assert len(drained) == len(
88
+ payload
89
+ ), f"reader only drained {len(drained)} of {len(payload)} bytes"
90
+
91
+
92
+ @pytest.mark.skipif(sys.platform == "win32", reason="pipes / pass_fds are POSIX-y")
93
+ def test_logged_io_subprocess_emitting_large_output_does_not_crash():
94
+ """End-to-end reproducer mirroring how `logged_io` is used in
95
+ `isolate.connections._local._base`: a child process writes a chunky
96
+ payload (~256 KB) to stdout. With a non-blocking write end the child's
97
+ Python runtime raises `BlockingIOError` when flushing stdout; with
98
+ backpressure the child completes cleanly and the parent receives
99
+ every line."""
100
+
101
+ captured: list[str] = []
102
+ lock = threading.Lock()
103
+
104
+ def record(line: str) -> None:
105
+ with lock:
106
+ captured.append(line)
107
+
108
+ n_lines = 4000
109
+ line_template = "trace-{idx:08d}-" + ("y" * 50)
110
+ child_script = (
111
+ "import sys\n"
112
+ f"for i in range({n_lines}):\n"
113
+ f" sys.stdout.write({line_template!r}.format(idx=i) + '\\n')\n"
114
+ "sys.stdout.flush()\n"
115
+ )
116
+
117
+ with logged_io(record) as (stdout, stderr, log_fd):
118
+ proc = subprocess.run(
119
+ [sys.executable, "-u", "-c", child_script],
120
+ stdout=stdout,
121
+ stderr=stderr,
122
+ pass_fds=(log_fd,),
123
+ text=True,
124
+ check=False,
125
+ )
126
+
127
+ assert proc.returncode == 0, (
128
+ f"child exited with {proc.returncode}; with a non-blocking write end "
129
+ "Python's stdout buffer raises BlockingIOError on large flushes"
130
+ )
131
+ # All `n_lines` should have been captured by the observer thread.
132
+ assert len(captured) == n_lines, f"expected {n_lines} lines, got {len(captured)}"
@@ -1,7 +1,11 @@
1
+ import importlib
2
+ import sys
3
+ import traceback
1
4
  from functools import partial
2
5
 
3
6
  import pytest
4
7
  from isolate.connections.common import (
8
+ ExceptionDeserializationError,
5
9
  SerializationError,
6
10
  load_serialized_object,
7
11
  serialize_object,
@@ -37,6 +41,38 @@ def test_deserialize_raised_exception():
37
41
  assert exc_info.value.args == ("some error",)
38
42
 
39
43
 
44
+ def test_deserialize_raised_exception_with_unimportable_type_preserves_traceback(
45
+ tmp_path,
46
+ monkeypatch,
47
+ ):
48
+ module_name = "remote_only_exc_for_isolate_test"
49
+ module_path = tmp_path / f"{module_name}.py"
50
+ module_path.write_text("class RemoteOnlyError(Exception):\n pass\n")
51
+ monkeypatch.syspath_prepend(str(tmp_path))
52
+ remote_module = importlib.import_module(module_name)
53
+
54
+ try:
55
+ raise remote_module.RemoteOnlyError("remote boom")
56
+ except remote_module.RemoteOnlyError as exc:
57
+ serialized = serialize_object("pickle", exc)
58
+ stringized_traceback = traceback.format_exc()
59
+
60
+ sys.modules.pop(module_name, None)
61
+ sys.path.remove(str(tmp_path))
62
+
63
+ with pytest.raises(ExceptionDeserializationError) as exc_info:
64
+ load_serialized_object(
65
+ "pickle",
66
+ serialized,
67
+ was_it_raised=True,
68
+ stringized_traceback=stringized_traceback,
69
+ )
70
+
71
+ assert exc_info.value.message == "Error while deserializing the given object"
72
+ assert exc_info.value.original_traceback is not None
73
+ assert isinstance(exc_info.value.__cause__, ModuleNotFoundError)
74
+
75
+
40
76
  def error_while_serializing():
41
77
  anon = lambda: 2 + 2 # anonymous functions are not # noqa: E731
42
78
  # serializable by pickle
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes