isolate 0.26.2__tar.gz → 0.26.4__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 (94) hide show
  1. {isolate-0.26.2/src/isolate.egg-info → isolate-0.26.4}/PKG-INFO +1 -1
  2. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/_isolate_version.py +3 -3
  3. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/_local/_base.py +3 -1
  4. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/agent.py +36 -6
  5. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/server.py +40 -3
  6. {isolate-0.26.2 → isolate-0.26.4/src/isolate.egg-info}/PKG-INFO +1 -1
  7. {isolate-0.26.2 → isolate-0.26.4}/tests/test_server.py +84 -0
  8. {isolate-0.26.2 → isolate-0.26.4}/.github/workflows/claude-code-review.yml +0 -0
  9. {isolate-0.26.2 → isolate-0.26.4}/.github/workflows/claude.yml +0 -0
  10. {isolate-0.26.2 → isolate-0.26.4}/.github/workflows/lint.yml +0 -0
  11. {isolate-0.26.2 → isolate-0.26.4}/.github/workflows/release.yml +0 -0
  12. {isolate-0.26.2 → isolate-0.26.4}/.github/workflows/test.yml +0 -0
  13. {isolate-0.26.2 → isolate-0.26.4}/.gitignore +0 -0
  14. {isolate-0.26.2 → isolate-0.26.4}/.pre-commit-config.yaml +0 -0
  15. {isolate-0.26.2 → isolate-0.26.4}/LICENSE +0 -0
  16. {isolate-0.26.2 → isolate-0.26.4}/README.md +0 -0
  17. {isolate-0.26.2 → isolate-0.26.4}/pyproject.toml +0 -0
  18. {isolate-0.26.2 → isolate-0.26.4}/setup.cfg +0 -0
  19. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/__init__.py +0 -0
  20. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/_version.py +0 -0
  21. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/__init__.py +0 -0
  22. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/_base.py +0 -0
  23. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/common.py +0 -0
  24. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/conda.py +0 -0
  25. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/container.py +0 -0
  26. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/local.py +0 -0
  27. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/pyenv.py +0 -0
  28. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/remote.py +0 -0
  29. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/settings.py +0 -0
  30. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/backends/virtualenv.py +0 -0
  31. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/common/__init__.py +0 -0
  32. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/common/timestamp.py +0 -0
  33. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/__init__.py +0 -0
  34. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/_local/__init__.py +0 -0
  35. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/_local/agent_startup.py +0 -0
  36. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/common.py +0 -0
  37. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/__init__.py +0 -0
  38. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/_base.py +0 -0
  39. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/configuration.py +0 -0
  40. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/__init__.py +0 -0
  41. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/agent.proto +0 -0
  42. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/agent_pb2.py +0 -0
  43. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/agent_pb2.pyi +0 -0
  44. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/agent_pb2_grpc.py +0 -0
  45. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/common.proto +0 -0
  46. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/common_pb2.py +0 -0
  47. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/common_pb2.pyi +0 -0
  48. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/definitions/common_pb2_grpc.py +0 -0
  49. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/grpc/interface.py +0 -0
  50. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/ipc/__init__.py +0 -0
  51. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/ipc/_base.py +0 -0
  52. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/connections/ipc/agent.py +0 -0
  53. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/logger.py +0 -0
  54. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/logs.py +0 -0
  55. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/py.typed +0 -0
  56. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/registry.py +0 -0
  57. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/__init__.py +0 -0
  58. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/definitions/__init__.py +0 -0
  59. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/definitions/server.proto +0 -0
  60. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/definitions/server_pb2.py +0 -0
  61. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/definitions/server_pb2.pyi +0 -0
  62. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/definitions/server_pb2_grpc.py +0 -0
  63. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/health/__init__.py +0 -0
  64. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/health/health.proto +0 -0
  65. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/health/health_pb2.py +0 -0
  66. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/health/health_pb2.pyi +0 -0
  67. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/health/health_pb2_grpc.py +0 -0
  68. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/health_server.py +0 -0
  69. {isolate-0.26.2 → isolate-0.26.4}/src/isolate/server/interface.py +0 -0
  70. {isolate-0.26.2 → isolate-0.26.4}/src/isolate.egg-info/SOURCES.txt +0 -0
  71. {isolate-0.26.2 → isolate-0.26.4}/src/isolate.egg-info/dependency_links.txt +0 -0
  72. {isolate-0.26.2 → isolate-0.26.4}/src/isolate.egg-info/entry_points.txt +0 -0
  73. {isolate-0.26.2 → isolate-0.26.4}/src/isolate.egg-info/requires.txt +0 -0
  74. {isolate-0.26.2 → isolate-0.26.4}/src/isolate.egg-info/top_level.txt +0 -0
  75. {isolate-0.26.2 → isolate-0.26.4}/tests/__init__.py +0 -0
  76. {isolate-0.26.2 → isolate-0.26.4}/tests/conftest.py +0 -0
  77. {isolate-0.26.2 → isolate-0.26.4}/tests/test_agent_backward_compatibility.py +0 -0
  78. {isolate-0.26.2 → isolate-0.26.4}/tests/test_backends.py +0 -0
  79. {isolate-0.26.2 → isolate-0.26.4}/tests/test_concurrency.py +0 -0
  80. {isolate-0.26.2 → isolate-0.26.4}/tests/test_connections.py +0 -0
  81. {isolate-0.26.2 → isolate-0.26.4}/tests/test_connections_grpc_base.py +0 -0
  82. {isolate-0.26.2 → isolate-0.26.4}/tests/test_isolate.py +0 -0
  83. {isolate-0.26.2 → isolate-0.26.4}/tests/test_log.py +0 -0
  84. {isolate-0.26.2 → isolate-0.26.4}/tests/test_log_masking.py +0 -0
  85. {isolate-0.26.2 → isolate-0.26.4}/tests/test_logger.py +0 -0
  86. {isolate-0.26.2 → isolate-0.26.4}/tests/test_serialization.py +0 -0
  87. {isolate-0.26.2 → isolate-0.26.4}/tests/test_shutdown.py +0 -0
  88. {isolate-0.26.2 → isolate-0.26.4}/tools/Dockerfile +0 -0
  89. {isolate-0.26.2 → isolate-0.26.4}/tools/agent_requirements.txt +0 -0
  90. {isolate-0.26.2 → isolate-0.26.4}/tools/isolate_client.py +0 -0
  91. {isolate-0.26.2 → isolate-0.26.4}/tools/protobuf-requirements.txt +0 -0
  92. {isolate-0.26.2 → isolate-0.26.4}/tools/regen_grpc.py +0 -0
  93. {isolate-0.26.2 → isolate-0.26.4}/tools/requirements.txt +0 -0
  94. {isolate-0.26.2 → isolate-0.26.4}/tools/test_agent_requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isolate
3
- Version: 0.26.2
3
+ Version: 0.26.4
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
@@ -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.2'
32
- __version_tuple__ = version_tuple = (0, 26, 2)
31
+ __version__ = version = '0.26.4'
32
+ __version_tuple__ = version_tuple = (0, 26, 4)
33
33
 
34
- __commit_id__ = commit_id = 'g7e1de0bc2'
34
+ __commit_id__ = commit_id = 'gce17302a5'
@@ -188,7 +188,9 @@ class PythonExecutionBase(Generic[ConnectionType]):
188
188
  filtered_env = {
189
189
  k: v
190
190
  for k, v in os.environ.items()
191
- if not k.startswith("NOMAD_") or k.startswith("NOMAD_ALLOC_PORT_")
191
+ if not k.startswith("NOMAD_")
192
+ or k.startswith("NOMAD_ALLOC_PORT_")
193
+ or k.startswith("NOMAD_JOB_ID")
192
194
  }
193
195
 
194
196
  return {
@@ -196,6 +196,40 @@ def _resolve_entrypoint(entrypoint: str) -> Any:
196
196
  return obj
197
197
 
198
198
 
199
+ def _get_callable_kind(message: Any) -> str | None:
200
+ """Return the active callable field across recent proto variants."""
201
+ try:
202
+ return message.WhichOneof("callable")
203
+ except ValueError:
204
+ has_function = _has_field(message, "function")
205
+ has_entrypoint = _has_field(message, "entrypoint")
206
+ if has_function:
207
+ return "function"
208
+ if has_entrypoint:
209
+ return "entrypoint"
210
+ return None
211
+
212
+
213
+ def _has_field(message: Any, field_name: str) -> bool:
214
+ try:
215
+ return message.HasField(field_name)
216
+ except ValueError:
217
+ return False
218
+
219
+
220
+ def _get_optional_bool_field(message: Any, field_name: str) -> Optional[bool]:
221
+ """Read an optional bool field across proto variants.
222
+
223
+ Older generated protobuf modules may expose the field without presence
224
+ tracking. In that case, only ``True`` can be observed distinctly; ``False``
225
+ is treated like "unset" so legacy fallback behavior is preserved.
226
+ """
227
+ try:
228
+ return getattr(message, field_name) if message.HasField(field_name) else None
229
+ except ValueError:
230
+ return True if getattr(message, field_name, False) else None
231
+
232
+
199
233
  class AgentServicer(definitions.AgentServicer):
200
234
  def __init__(self, log_file: TextIO | None = None):
201
235
  super().__init__()
@@ -268,7 +302,7 @@ class AgentServicer(definitions.AgentServicer):
268
302
 
269
303
  # The `callable` oneof enforces at-most-one on the wire; here we
270
304
  # only need to reject the "neither set" case.
271
- callable_kind = request.WhichOneof("callable")
305
+ callable_kind = _get_callable_kind(request)
272
306
  if callable_kind is None:
273
307
  self.abort_with_msg(
274
308
  context,
@@ -329,11 +363,7 @@ class AgentServicer(definitions.AgentServicer):
329
363
  extra_args.append(self._run_cache[cache_key])
330
364
 
331
365
  try:
332
- run_on_main_thread = (
333
- request.run_on_main_thread
334
- if request.HasField("run_on_main_thread")
335
- else None
336
- )
366
+ run_on_main_thread = _get_optional_bool_field(request, "run_on_main_thread")
337
367
  if has_entrypoint:
338
368
  invocation = await self.execute_entrypoint(
339
369
  request.entrypoint,
@@ -69,6 +69,40 @@ class GRPCException(Exception):
69
69
  return f"{self.code.name}: {self.message}"
70
70
 
71
71
 
72
+ def _get_callable_kind(message: Any) -> str | None:
73
+ """Return the active callable field across recent proto variants."""
74
+ try:
75
+ return message.WhichOneof("callable")
76
+ except ValueError:
77
+ has_function = _has_field(message, "function")
78
+ has_entrypoint = _has_field(message, "entrypoint")
79
+ if has_function:
80
+ return "function"
81
+ if has_entrypoint:
82
+ return "entrypoint"
83
+ return None
84
+
85
+
86
+ def _has_field(message: Any, field_name: str) -> bool:
87
+ try:
88
+ return message.HasField(field_name)
89
+ except ValueError:
90
+ return False
91
+
92
+
93
+ def _get_optional_bool_field(message: Any, field_name: str) -> bool | None:
94
+ """Read an optional bool field across proto variants.
95
+
96
+ Older generated protobuf modules may expose the field without presence
97
+ tracking. In that case, only ``True`` can be observed distinctly; ``False``
98
+ is treated like "unset" so legacy fallback behavior is preserved.
99
+ """
100
+ try:
101
+ return getattr(message, field_name) if message.HasField(field_name) else None
102
+ except ValueError:
103
+ return True if getattr(message, field_name, False) else None
104
+
105
+
72
106
  @dataclass
73
107
  class RunnerAgent:
74
108
  stub: definitions.AgentStub
@@ -250,7 +284,7 @@ class IsolateServicer(definitions.IsolateServicer):
250
284
 
251
285
  # The `callable` oneof enforces at-most-one on the wire; here we
252
286
  # only need to reject the "neither set" case.
253
- callable_kind = task.request.WhichOneof("callable")
287
+ callable_kind = _get_callable_kind(task.request)
254
288
  if callable_kind is None:
255
289
  raise GRPCException(
256
290
  "One of 'function' or 'entrypoint' must be set.",
@@ -338,8 +372,11 @@ class IsolateServicer(definitions.IsolateServicer):
338
372
  )
339
373
  if not task.request.HasField("setup_func"):
340
374
  function_call.ClearField("setup_func")
341
- if task.request.HasField("run_on_main_thread"):
342
- function_call.run_on_main_thread = task.request.run_on_main_thread
375
+ run_on_main_thread = _get_optional_bool_field(
376
+ task.request, "run_on_main_thread"
377
+ )
378
+ if run_on_main_thread is not None:
379
+ function_call.run_on_main_thread = run_on_main_thread
343
380
 
344
381
  future = local_pool.submit(
345
382
  _proxy_to_queue,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isolate
3
- Version: 0.26.2
3
+ Version: 0.26.4
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
@@ -139,6 +139,42 @@ def define_environment(kind: str, **kwargs: Any) -> definitions.EnvironmentDefin
139
139
  _NOT_SET = object()
140
140
 
141
141
 
142
+ class LegacyCallableProto:
143
+ """Simulate the pre-oneof / pre-optional-bool generated protobuf API."""
144
+
145
+ def __init__(
146
+ self,
147
+ *,
148
+ function: bool = False,
149
+ entrypoint: bool = False,
150
+ run_on_main_thread: bool = False,
151
+ supports_entrypoint: bool = True,
152
+ ) -> None:
153
+ self.run_on_main_thread = run_on_main_thread
154
+ self._has_field = {
155
+ "function": function,
156
+ "entrypoint": entrypoint,
157
+ }
158
+ self._supports_entrypoint = supports_entrypoint
159
+
160
+ def WhichOneof(self, name: str) -> Optional[str]:
161
+ raise ValueError(f'Protocol message FunctionCall has no "{name}" field.')
162
+
163
+ def HasField(self, name: str) -> bool:
164
+ if name == "run_on_main_thread":
165
+ raise ValueError(
166
+ "Field FunctionCall.run_on_main_thread does not have presence."
167
+ )
168
+ if name == "entrypoint" and not self._supports_entrypoint:
169
+ raise ValueError('Protocol message FunctionCall has no "entrypoint" field.')
170
+ try:
171
+ return self._has_field[name]
172
+ except KeyError as exc:
173
+ raise ValueError(
174
+ f'Protocol message FunctionCall has no "{name}" field.'
175
+ ) from exc
176
+
177
+
142
178
  def run_request(
143
179
  stub: definitions.IsolateStub,
144
180
  request: definitions.BoundFunction,
@@ -417,6 +453,54 @@ def test_agent_import_falls_back_when_common_validator_missing(
417
453
  importlib.reload(agent_module)
418
454
 
419
455
 
456
+ @pytest.mark.parametrize(
457
+ "module_name",
458
+ [
459
+ "isolate.connections.grpc.agent",
460
+ "isolate.server.server",
461
+ ],
462
+ )
463
+ def test_callable_kind_falls_back_when_proto_has_no_oneof(module_name: str) -> None:
464
+ module = importlib.import_module(module_name)
465
+
466
+ assert module._get_callable_kind(LegacyCallableProto(function=True)) == "function"
467
+ assert (
468
+ module._get_callable_kind(
469
+ LegacyCallableProto(function=True, supports_entrypoint=False)
470
+ )
471
+ == "function"
472
+ )
473
+ assert (
474
+ module._get_callable_kind(LegacyCallableProto(entrypoint=True)) == "entrypoint"
475
+ )
476
+ assert module._get_callable_kind(LegacyCallableProto()) is None
477
+
478
+
479
+ @pytest.mark.parametrize(
480
+ "module_name",
481
+ [
482
+ "isolate.connections.grpc.agent",
483
+ "isolate.server.server",
484
+ ],
485
+ )
486
+ def test_optional_bool_field_falls_back_when_proto_has_no_presence(
487
+ module_name: str,
488
+ ) -> None:
489
+ module = importlib.import_module(module_name)
490
+
491
+ assert (
492
+ module._get_optional_bool_field(
493
+ LegacyCallableProto(run_on_main_thread=False),
494
+ "run_on_main_thread",
495
+ )
496
+ is None
497
+ )
498
+ assert module._get_optional_bool_field(
499
+ LegacyCallableProto(run_on_main_thread=True),
500
+ "run_on_main_thread",
501
+ )
502
+
503
+
420
504
  def test_server_entrypoint_module_not_found(
421
505
  stub: definitions.IsolateStub, monkeypatch: Any
422
506
  ) -> None:
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