isolate 0.13.4__tar.gz → 0.13.6__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.

Potentially problematic release.


This version of isolate might be problematic. Click here for more details.

Files changed (85) hide show
  1. {isolate-0.13.4 → isolate-0.13.6}/PKG-INFO +1 -1
  2. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/_isolate_version.py +2 -2
  3. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/_base.py +0 -2
  4. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/agent.py +13 -1
  5. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/definitions/server.proto +25 -1
  6. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/definitions/server_pb2.py +14 -4
  7. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/definitions/server_pb2.pyi +71 -2
  8. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/definitions/server_pb2_grpc.py +88 -0
  9. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/server.py +103 -43
  10. {isolate-0.13.4 → isolate-0.13.6}/src/isolate.egg-info/PKG-INFO +1 -1
  11. {isolate-0.13.4 → isolate-0.13.6}/tests/test_server.py +29 -7
  12. {isolate-0.13.4 → isolate-0.13.6}/.github/workflows/release.yml +0 -0
  13. {isolate-0.13.4 → isolate-0.13.6}/.github/workflows/test.yml +0 -0
  14. {isolate-0.13.4 → isolate-0.13.6}/.gitignore +0 -0
  15. {isolate-0.13.4 → isolate-0.13.6}/.pre-commit-config.yaml +0 -0
  16. {isolate-0.13.4 → isolate-0.13.6}/LICENSE +0 -0
  17. {isolate-0.13.4 → isolate-0.13.6}/README.md +0 -0
  18. {isolate-0.13.4 → isolate-0.13.6}/pyproject.toml +0 -0
  19. {isolate-0.13.4 → isolate-0.13.6}/setup.cfg +0 -0
  20. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/__init__.py +0 -0
  21. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/_version.py +0 -0
  22. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/__init__.py +0 -0
  23. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/_base.py +0 -0
  24. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/common.py +0 -0
  25. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/conda.py +0 -0
  26. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/container.py +0 -0
  27. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/local.py +0 -0
  28. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/pyenv.py +0 -0
  29. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/remote.py +0 -0
  30. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/settings.py +0 -0
  31. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/backends/virtualenv.py +0 -0
  32. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/common/__init__.py +0 -0
  33. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/common/timestamp.py +0 -0
  34. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/__init__.py +0 -0
  35. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/_local/__init__.py +0 -0
  36. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/_local/_base.py +0 -0
  37. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/_local/agent_startup.py +0 -0
  38. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/common.py +0 -0
  39. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/__init__.py +0 -0
  40. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/configuration.py +0 -0
  41. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/__init__.py +0 -0
  42. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/agent.proto +0 -0
  43. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/agent_pb2.py +0 -0
  44. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/agent_pb2.pyi +0 -0
  45. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/agent_pb2_grpc.py +0 -0
  46. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/common.proto +0 -0
  47. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/common_pb2.py +0 -0
  48. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/common_pb2.pyi +0 -0
  49. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/definitions/common_pb2_grpc.py +0 -0
  50. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/grpc/interface.py +0 -0
  51. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/ipc/__init__.py +0 -0
  52. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/ipc/_base.py +0 -0
  53. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/connections/ipc/agent.py +0 -0
  54. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/logger.py +0 -0
  55. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/logs.py +0 -0
  56. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/py.typed +0 -0
  57. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/registry.py +0 -0
  58. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/__init__.py +0 -0
  59. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/definitions/__init__.py +0 -0
  60. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/health/__init__.py +0 -0
  61. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/health/health.proto +0 -0
  62. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/health/health_pb2.py +0 -0
  63. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/health/health_pb2.pyi +0 -0
  64. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/health/health_pb2_grpc.py +0 -0
  65. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/health_server.py +0 -0
  66. {isolate-0.13.4 → isolate-0.13.6}/src/isolate/server/interface.py +0 -0
  67. {isolate-0.13.4 → isolate-0.13.6}/src/isolate.egg-info/SOURCES.txt +0 -0
  68. {isolate-0.13.4 → isolate-0.13.6}/src/isolate.egg-info/dependency_links.txt +0 -0
  69. {isolate-0.13.4 → isolate-0.13.6}/src/isolate.egg-info/entry_points.txt +0 -0
  70. {isolate-0.13.4 → isolate-0.13.6}/src/isolate.egg-info/requires.txt +0 -0
  71. {isolate-0.13.4 → isolate-0.13.6}/src/isolate.egg-info/top_level.txt +0 -0
  72. {isolate-0.13.4 → isolate-0.13.6}/tests/__init__.py +0 -0
  73. {isolate-0.13.4 → isolate-0.13.6}/tests/conftest.py +0 -0
  74. {isolate-0.13.4 → isolate-0.13.6}/tests/test_backends.py +0 -0
  75. {isolate-0.13.4 → isolate-0.13.6}/tests/test_concurrency.py +0 -0
  76. {isolate-0.13.4 → isolate-0.13.6}/tests/test_connections.py +0 -0
  77. {isolate-0.13.4 → isolate-0.13.6}/tests/test_isolate.py +0 -0
  78. {isolate-0.13.4 → isolate-0.13.6}/tests/test_log.py +0 -0
  79. {isolate-0.13.4 → isolate-0.13.6}/tests/test_serialization.py +0 -0
  80. {isolate-0.13.4 → isolate-0.13.6}/tools/Dockerfile +0 -0
  81. {isolate-0.13.4 → isolate-0.13.6}/tools/agent_requirements.txt +0 -0
  82. {isolate-0.13.4 → isolate-0.13.6}/tools/protobuf-requirements.txt +0 -0
  83. {isolate-0.13.4 → isolate-0.13.6}/tools/regen_grpc.py +0 -0
  84. {isolate-0.13.4 → isolate-0.13.6}/tools/requirements.txt +0 -0
  85. {isolate-0.13.4 → isolate-0.13.6}/tools/test_agent_requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: isolate
3
- Version: 0.13.4
3
+ Version: 0.13.6
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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.13.4'
16
- __version_tuple__ = version_tuple = (0, 13, 4)
15
+ __version__ = version = '0.13.6'
16
+ __version_tuple__ = version_tuple = (0, 13, 6)
@@ -16,7 +16,6 @@ from isolate.connections.common import serialize_object
16
16
  from isolate.connections.grpc import agent, definitions
17
17
  from isolate.connections.grpc.configuration import get_default_options
18
18
  from isolate.connections.grpc.interface import from_grpc
19
- from isolate.logger import logger
20
19
  from isolate.logs import LogLevel, LogSource
21
20
 
22
21
 
@@ -149,5 +148,4 @@ class LocalPythonGRPC(PythonExecutionBase[str], GRPCExecutionBase):
149
148
  ]
150
149
 
151
150
  def handle_agent_log(self, line: str, level: LogLevel, source: LogSource) -> None:
152
- logger.log(level, line, source)
153
151
  self.log(line, level=level, source=source)
@@ -1,4 +1,12 @@
1
1
  # agent-requires: isolate[server]
2
+ """
3
+ This file contains the implementation of the gRPC agent. The agent is a
4
+ separate process that is responsible for running the user code in a
5
+ sandboxed environment.
6
+
7
+ This file is referenced by the latest version of the `isolate` package
8
+ but then runs it in the context of the frozen agent built environment.
9
+ """
2
10
 
3
11
  from __future__ import annotations
4
12
 
@@ -17,7 +25,11 @@ from typing import (
17
25
  import grpc
18
26
  from grpc import ServicerContext, StatusCode
19
27
 
20
- from isolate import __version__ as agent_version
28
+ try:
29
+ from isolate import __version__ as agent_version
30
+ except ImportError:
31
+ agent_version = "UNKNOWN"
32
+
21
33
  from isolate.backends.common import sha256_digest_of
22
34
  from isolate.connections.common import SerializationError, serialize_object
23
35
  from isolate.connections.grpc import definitions
@@ -10,6 +10,12 @@ service Isolate {
10
10
 
11
11
  // Submit a function to be run without waiting for results.
12
12
  rpc Submit (SubmitRequest) returns (SubmitResponse) {}
13
+
14
+ // List running tasks
15
+ rpc List (ListRequest) returns (ListResponse) {}
16
+
17
+ // Cancel a running task
18
+ rpc Cancel (CancelRequest) returns (CancelResponse) {}
13
19
  }
14
20
 
15
21
  message BoundFunction {
@@ -33,5 +39,23 @@ message SubmitRequest {
33
39
  }
34
40
 
35
41
  message SubmitResponse {
36
- // Reserved for future use.
42
+ string task_id = 1;
43
+ }
44
+
45
+ message ListRequest {
46
+ }
47
+
48
+ message TaskInfo {
49
+ string task_id = 1;
50
+ }
51
+
52
+ message ListResponse {
53
+ repeated TaskInfo tasks = 1;
54
+ }
55
+
56
+ message CancelRequest {
57
+ string task_id = 1;
58
+ }
59
+
60
+ message CancelResponse {
37
61
  }
@@ -16,7 +16,7 @@ from isolate.connections.grpc.definitions import common_pb2 as common__pb2
16
16
  from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
17
17
 
18
18
 
19
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cserver.proto\x1a\x0c\x63ommon.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x9d\x01\n\rBoundFunction\x12,\n\x0c\x65nvironments\x18\x01 \x03(\x0b\x32\x16.EnvironmentDefinition\x12#\n\x08\x66unction\x18\x02 \x01(\x0b\x32\x11.SerializedObject\x12*\n\nsetup_func\x18\x03 \x01(\x0b\x32\x11.SerializedObjectH\x00\x88\x01\x01\x42\r\n\x0b_setup_func\"d\n\x15\x45nvironmentDefinition\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12.\n\rconfiguration\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\r\n\x05\x66orce\x18\x03 \x01(\x08\"1\n\rSubmitRequest\x12 \n\x08\x66unction\x18\x01 \x01(\x0b\x32\x0e.BoundFunction\"\x10\n\x0eSubmitResponse2d\n\x07Isolate\x12,\n\x03Run\x12\x0e.BoundFunction\x1a\x11.PartialRunResult\"\x00\x30\x01\x12+\n\x06Submit\x12\x0e.SubmitRequest\x1a\x0f.SubmitResponse\"\x00\x62\x06proto3')
19
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cserver.proto\x1a\x0c\x63ommon.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x9d\x01\n\rBoundFunction\x12,\n\x0c\x65nvironments\x18\x01 \x03(\x0b\x32\x16.EnvironmentDefinition\x12#\n\x08\x66unction\x18\x02 \x01(\x0b\x32\x11.SerializedObject\x12*\n\nsetup_func\x18\x03 \x01(\x0b\x32\x11.SerializedObjectH\x00\x88\x01\x01\x42\r\n\x0b_setup_func\"d\n\x15\x45nvironmentDefinition\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12.\n\rconfiguration\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\r\n\x05\x66orce\x18\x03 \x01(\x08\"1\n\rSubmitRequest\x12 \n\x08\x66unction\x18\x01 \x01(\x0b\x32\x0e.BoundFunction\"!\n\x0eSubmitResponse\x12\x0f\n\x07task_id\x18\x01 \x01(\t\"\r\n\x0bListRequest\"\x1b\n\x08TaskInfo\x12\x0f\n\x07task_id\x18\x01 \x01(\t\"(\n\x0cListResponse\x12\x18\n\x05tasks\x18\x01 \x03(\x0b\x32\t.TaskInfo\" \n\rCancelRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\"\x10\n\x0e\x43\x61ncelResponse2\xb8\x01\n\x07Isolate\x12,\n\x03Run\x12\x0e.BoundFunction\x1a\x11.PartialRunResult\"\x00\x30\x01\x12+\n\x06Submit\x12\x0e.SubmitRequest\x1a\x0f.SubmitResponse\"\x00\x12%\n\x04List\x12\x0c.ListRequest\x1a\r.ListResponse\"\x00\x12+\n\x06\x43\x61ncel\x12\x0e.CancelRequest\x1a\x0f.CancelResponse\"\x00\x62\x06proto3')
20
20
 
21
21
  _globals = globals()
22
22
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -30,7 +30,17 @@ if not _descriptor._USE_C_DESCRIPTORS:
30
30
  _globals['_SUBMITREQUEST']._serialized_start=322
31
31
  _globals['_SUBMITREQUEST']._serialized_end=371
32
32
  _globals['_SUBMITRESPONSE']._serialized_start=373
33
- _globals['_SUBMITRESPONSE']._serialized_end=389
34
- _globals['_ISOLATE']._serialized_start=391
35
- _globals['_ISOLATE']._serialized_end=491
33
+ _globals['_SUBMITRESPONSE']._serialized_end=406
34
+ _globals['_LISTREQUEST']._serialized_start=408
35
+ _globals['_LISTREQUEST']._serialized_end=421
36
+ _globals['_TASKINFO']._serialized_start=423
37
+ _globals['_TASKINFO']._serialized_end=450
38
+ _globals['_LISTRESPONSE']._serialized_start=452
39
+ _globals['_LISTRESPONSE']._serialized_end=492
40
+ _globals['_CANCELREQUEST']._serialized_start=494
41
+ _globals['_CANCELREQUEST']._serialized_end=526
42
+ _globals['_CANCELRESPONSE']._serialized_start=528
43
+ _globals['_CANCELRESPONSE']._serialized_end=544
44
+ _globals['_ISOLATE']._serialized_start=547
45
+ _globals['_ISOLATE']._serialized_end=731
36
46
  # @@protoc_insertion_point(module_scope)
@@ -88,12 +88,81 @@ global___SubmitRequest = SubmitRequest
88
88
 
89
89
  @typing.final
90
90
  class SubmitResponse(google.protobuf.message.Message):
91
- """Reserved for future use."""
92
-
93
91
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
94
92
 
93
+ TASK_ID_FIELD_NUMBER: builtins.int
94
+ task_id: builtins.str
95
95
  def __init__(
96
96
  self,
97
+ *,
98
+ task_id: builtins.str = ...,
97
99
  ) -> None: ...
100
+ def ClearField(self, field_name: typing.Literal["task_id", b"task_id"]) -> None: ...
98
101
 
99
102
  global___SubmitResponse = SubmitResponse
103
+
104
+ @typing.final
105
+ class ListRequest(google.protobuf.message.Message):
106
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
107
+
108
+ def __init__(
109
+ self,
110
+ ) -> None: ...
111
+
112
+ global___ListRequest = ListRequest
113
+
114
+ @typing.final
115
+ class TaskInfo(google.protobuf.message.Message):
116
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
117
+
118
+ TASK_ID_FIELD_NUMBER: builtins.int
119
+ task_id: builtins.str
120
+ def __init__(
121
+ self,
122
+ *,
123
+ task_id: builtins.str = ...,
124
+ ) -> None: ...
125
+ def ClearField(self, field_name: typing.Literal["task_id", b"task_id"]) -> None: ...
126
+
127
+ global___TaskInfo = TaskInfo
128
+
129
+ @typing.final
130
+ class ListResponse(google.protobuf.message.Message):
131
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
132
+
133
+ TASKS_FIELD_NUMBER: builtins.int
134
+ @property
135
+ def tasks(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TaskInfo]: ...
136
+ def __init__(
137
+ self,
138
+ *,
139
+ tasks: collections.abc.Iterable[global___TaskInfo] | None = ...,
140
+ ) -> None: ...
141
+ def ClearField(self, field_name: typing.Literal["tasks", b"tasks"]) -> None: ...
142
+
143
+ global___ListResponse = ListResponse
144
+
145
+ @typing.final
146
+ class CancelRequest(google.protobuf.message.Message):
147
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
148
+
149
+ TASK_ID_FIELD_NUMBER: builtins.int
150
+ task_id: builtins.str
151
+ def __init__(
152
+ self,
153
+ *,
154
+ task_id: builtins.str = ...,
155
+ ) -> None: ...
156
+ def ClearField(self, field_name: typing.Literal["task_id", b"task_id"]) -> None: ...
157
+
158
+ global___CancelRequest = CancelRequest
159
+
160
+ @typing.final
161
+ class CancelResponse(google.protobuf.message.Message):
162
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
163
+
164
+ def __init__(
165
+ self,
166
+ ) -> None: ...
167
+
168
+ global___CancelResponse = CancelResponse
@@ -50,6 +50,16 @@ class IsolateStub(object):
50
50
  request_serializer=server__pb2.SubmitRequest.SerializeToString,
51
51
  response_deserializer=server__pb2.SubmitResponse.FromString,
52
52
  _registered_method=True)
53
+ self.List = channel.unary_unary(
54
+ '/Isolate/List',
55
+ request_serializer=server__pb2.ListRequest.SerializeToString,
56
+ response_deserializer=server__pb2.ListResponse.FromString,
57
+ _registered_method=True)
58
+ self.Cancel = channel.unary_unary(
59
+ '/Isolate/Cancel',
60
+ request_serializer=server__pb2.CancelRequest.SerializeToString,
61
+ response_deserializer=server__pb2.CancelResponse.FromString,
62
+ _registered_method=True)
53
63
 
54
64
 
55
65
  class IsolateServicer(object):
@@ -70,6 +80,20 @@ class IsolateServicer(object):
70
80
  context.set_details('Method not implemented!')
71
81
  raise NotImplementedError('Method not implemented!')
72
82
 
83
+ def List(self, request, context):
84
+ """List running tasks
85
+ """
86
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
87
+ context.set_details('Method not implemented!')
88
+ raise NotImplementedError('Method not implemented!')
89
+
90
+ def Cancel(self, request, context):
91
+ """Cancel a running task
92
+ """
93
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
94
+ context.set_details('Method not implemented!')
95
+ raise NotImplementedError('Method not implemented!')
96
+
73
97
 
74
98
  def add_IsolateServicer_to_server(servicer, server):
75
99
  rpc_method_handlers = {
@@ -83,6 +107,16 @@ def add_IsolateServicer_to_server(servicer, server):
83
107
  request_deserializer=server__pb2.SubmitRequest.FromString,
84
108
  response_serializer=server__pb2.SubmitResponse.SerializeToString,
85
109
  ),
110
+ 'List': grpc.unary_unary_rpc_method_handler(
111
+ servicer.List,
112
+ request_deserializer=server__pb2.ListRequest.FromString,
113
+ response_serializer=server__pb2.ListResponse.SerializeToString,
114
+ ),
115
+ 'Cancel': grpc.unary_unary_rpc_method_handler(
116
+ servicer.Cancel,
117
+ request_deserializer=server__pb2.CancelRequest.FromString,
118
+ response_serializer=server__pb2.CancelResponse.SerializeToString,
119
+ ),
86
120
  }
87
121
  generic_handler = grpc.method_handlers_generic_handler(
88
122
  'Isolate', rpc_method_handlers)
@@ -147,3 +181,57 @@ class Isolate(object):
147
181
  timeout,
148
182
  metadata,
149
183
  _registered_method=True)
184
+
185
+ @staticmethod
186
+ def List(request,
187
+ target,
188
+ options=(),
189
+ channel_credentials=None,
190
+ call_credentials=None,
191
+ insecure=False,
192
+ compression=None,
193
+ wait_for_ready=None,
194
+ timeout=None,
195
+ metadata=None):
196
+ return grpc.experimental.unary_unary(
197
+ request,
198
+ target,
199
+ '/Isolate/List',
200
+ server__pb2.ListRequest.SerializeToString,
201
+ server__pb2.ListResponse.FromString,
202
+ options,
203
+ channel_credentials,
204
+ insecure,
205
+ call_credentials,
206
+ compression,
207
+ wait_for_ready,
208
+ timeout,
209
+ metadata,
210
+ _registered_method=True)
211
+
212
+ @staticmethod
213
+ def Cancel(request,
214
+ target,
215
+ options=(),
216
+ channel_credentials=None,
217
+ call_credentials=None,
218
+ insecure=False,
219
+ compression=None,
220
+ wait_for_ready=None,
221
+ timeout=None,
222
+ metadata=None):
223
+ return grpc.experimental.unary_unary(
224
+ request,
225
+ target,
226
+ '/Isolate/Cancel',
227
+ server__pb2.CancelRequest.SerializeToString,
228
+ server__pb2.CancelResponse.FromString,
229
+ options,
230
+ channel_credentials,
231
+ insecure,
232
+ call_credentials,
233
+ compression,
234
+ wait_for_ready,
235
+ timeout,
236
+ metadata,
237
+ _registered_method=True)
@@ -4,12 +4,12 @@ import os
4
4
  import threading
5
5
  import time
6
6
  import traceback
7
+ import uuid
7
8
  from collections import defaultdict
8
9
  from concurrent import futures
9
10
  from concurrent.futures import ThreadPoolExecutor
10
11
  from contextlib import ExitStack, contextmanager
11
12
  from dataclasses import dataclass, field, replace
12
- from functools import partial
13
13
  from queue import Empty as QueueEmpty
14
14
  from queue import Queue
15
15
  from typing import Any, Callable, Iterator, cast
@@ -26,6 +26,7 @@ from isolate.backends.local import LocalPythonEnvironment
26
26
  from isolate.backends.virtualenv import VirtualPythonEnvironment
27
27
  from isolate.connections.grpc import AgentError, LocalPythonGRPC
28
28
  from isolate.connections.grpc.configuration import get_default_options
29
+ from isolate.logger import logger
29
30
  from isolate.logs import Log, LogLevel, LogSource
30
31
  from isolate.server import definitions, health
31
32
  from isolate.server.health_server import HealthServicer
@@ -114,11 +115,11 @@ class BridgeManager:
114
115
  self,
115
116
  connection: LocalPythonGRPC,
116
117
  queue: Queue,
117
- ) -> Iterator[tuple[definitions.AgentStub, Queue]]:
118
+ ) -> Iterator[RunnerAgent]:
118
119
  agent = self._allocate_new_agent(connection, queue)
119
120
 
120
121
  try:
121
- yield agent.stub, agent.message_queue
122
+ yield agent
122
123
  finally:
123
124
  self._cache_agent(connection, agent)
124
125
 
@@ -165,19 +166,34 @@ class BridgeManager:
165
166
  agent.terminate()
166
167
 
167
168
 
169
+ @dataclass
170
+ class RunTask:
171
+ request: definitions.BoundFunction
172
+ future: futures.Future | None = None
173
+ agent: RunnerAgent | None = None
174
+
175
+ def cancel(self):
176
+ while True:
177
+ self.future.cancel()
178
+ if self.agent:
179
+ self.agent.terminate()
180
+ try:
181
+ self.future.exception(timeout=0.1)
182
+ return
183
+ except futures.TimeoutError:
184
+ pass
185
+
186
+
168
187
  @dataclass
169
188
  class IsolateServicer(definitions.IsolateServicer):
170
189
  bridge_manager: BridgeManager
171
190
  default_settings: IsolateSettings = field(default_factory=IsolateSettings)
172
- background_tasks: set[futures.Future] = field(default_factory=set)
191
+ background_tasks: dict[str, RunTask] = field(default_factory=dict)
173
192
 
174
- def _run_function(
175
- self,
176
- request: definitions.BoundFunction,
177
- ) -> Iterator[definitions.PartialRunResult]:
193
+ def _run_task(self, task: RunTask) -> Iterator[definitions.PartialRunResult]:
178
194
  messages: Queue[definitions.PartialRunResult] = Queue()
179
195
  environments = []
180
- for env in request.environments:
196
+ for env in task.request.environments:
181
197
  try:
182
198
  environments.append((env.force, from_grpc(env)))
183
199
  except ValueError:
@@ -191,10 +207,11 @@ class IsolateServicer(definitions.IsolateServicer):
191
207
  StatusCode.INVALID_ARGUMENT,
192
208
  )
193
209
 
210
+ log_handler = LogHandler(messages)
194
211
  run_settings = replace(
195
212
  self.default_settings,
196
- log_hook=partial(_add_log_to_queue, messages),
197
- serialization_method=request.function.method,
213
+ log_hook=log_handler.handle,
214
+ serialization_method=task.request.function.method,
198
215
  )
199
216
 
200
217
  for _, environment in environments:
@@ -243,28 +260,28 @@ class IsolateServicer(definitions.IsolateServicer):
243
260
  extra_inheritance_paths=inheritance_paths,
244
261
  )
245
262
 
246
- with self.bridge_manager.establish(connection, queue=messages) as (
247
- bridge,
248
- queue,
249
- ):
263
+ with self.bridge_manager.establish(connection, queue=messages) as agent:
264
+ task.agent = agent
250
265
  function_call = definitions.FunctionCall(
251
- function=request.function,
252
- setup_func=request.setup_func,
266
+ function=task.request.function,
267
+ setup_func=task.request.setup_func,
253
268
  )
254
- if not request.HasField("setup_func"):
269
+ if not task.request.HasField("setup_func"):
255
270
  function_call.ClearField("setup_func")
256
271
 
257
272
  future = local_pool.submit(
258
273
  _proxy_to_queue,
259
- queue=queue,
260
- bridge=bridge,
274
+ queue=agent.message_queue,
275
+ bridge=agent.stub,
261
276
  input=function_call,
262
277
  )
263
278
 
264
279
  # Unlike above; we are not interested in the result value of future
265
280
  # here, since it will be already transferred to other side without
266
281
  # us even seeing (through the queue).
267
- yield from self.watch_queue_until_completed(queue, future.done)
282
+ yield from self.watch_queue_until_completed(
283
+ agent.message_queue, future.done
284
+ )
268
285
 
269
286
  # But we still have to check whether there were any errors raised
270
287
  # during the execution, and handle them accordingly.
@@ -292,14 +309,8 @@ class IsolateServicer(definitions.IsolateServicer):
292
309
  StatusCode.UNKNOWN,
293
310
  )
294
311
 
295
- def _run_function_in_background(
296
- self,
297
- bound_function: definitions.BoundFunction,
298
- ) -> None:
299
- try:
300
- for _ in self._run_function(bound_function):
301
- pass
302
- except GRPCException:
312
+ def _run_task_in_background(self, task: RunTask) -> None:
313
+ for _ in self._run_task(task):
303
314
  pass
304
315
 
305
316
  def Submit(
@@ -307,13 +318,24 @@ class IsolateServicer(definitions.IsolateServicer):
307
318
  request: definitions.SubmitRequest,
308
319
  context: ServicerContext,
309
320
  ) -> definitions.SubmitResponse:
310
- run_future = RUNNER_THREAD_POOL.submit(
311
- self._run_function_in_background,
312
- request.function,
321
+ task = RunTask(request=request.function)
322
+ task.future = RUNNER_THREAD_POOL.submit(
323
+ self._run_task_in_background,
324
+ task,
313
325
  )
314
- self.background_tasks.add(run_future)
326
+ task_id = str(uuid.uuid4())
327
+
328
+ print(f"Submitted a task {task_id}")
329
+
330
+ self.background_tasks[task_id] = task
315
331
 
316
- return definitions.SubmitResponse()
332
+ def _callback(_):
333
+ print(f"Task {task_id} finished")
334
+ self.background_tasks.pop(task_id, None)
335
+
336
+ task.future.add_done_callback(_callback)
337
+
338
+ return definitions.SubmitResponse(task_id=task_id)
317
339
 
318
340
  def Run(
319
341
  self,
@@ -321,7 +343,7 @@ class IsolateServicer(definitions.IsolateServicer):
321
343
  context: ServicerContext,
322
344
  ) -> Iterator[definitions.PartialRunResult]:
323
345
  try:
324
- yield from self._run_function(request)
346
+ yield from self._run_task(RunTask(request=request))
325
347
  except GRPCException as exc:
326
348
  return self.abort_with_msg(
327
349
  exc.message,
@@ -329,6 +351,32 @@ class IsolateServicer(definitions.IsolateServicer):
329
351
  code=exc.code,
330
352
  )
331
353
 
354
+ def List(
355
+ self,
356
+ request: definitions.ListRequest,
357
+ context: ServicerContext,
358
+ ) -> definitions.ListResponse:
359
+ return definitions.ListResponse(
360
+ tasks=[
361
+ definitions.TaskInfo(task_id=task_id)
362
+ for task_id in self.background_tasks.keys()
363
+ ]
364
+ )
365
+
366
+ def Cancel(
367
+ self,
368
+ request: definitions.CancelRequest,
369
+ context: ServicerContext,
370
+ ) -> definitions.CancelResponse:
371
+ task_id = request.task_id
372
+
373
+ print(f"Canceling task {task_id}")
374
+ task = self.background_tasks.get(task_id)
375
+ if task is not None:
376
+ task.cancel()
377
+
378
+ return definitions.CancelResponse()
379
+
332
380
  def watch_queue_until_completed(
333
381
  self, queue: Queue, is_completed: Callable[[], bool]
334
382
  ) -> Iterator[definitions.PartialRunResult]:
@@ -380,6 +428,10 @@ class IsolateServicer(definitions.IsolateServicer):
380
428
  context.set_details(message)
381
429
  return None
382
430
 
431
+ def cancel_tasks(self):
432
+ for task in self.background_tasks.values():
433
+ task.cancel()
434
+
383
435
 
384
436
  def _proxy_to_queue(
385
437
  queue: Queue,
@@ -390,14 +442,22 @@ def _proxy_to_queue(
390
442
  queue.put_nowait(message)
391
443
 
392
444
 
393
- def _add_log_to_queue(messages: Queue, log: Log) -> None:
394
- grpc_log = cast(definitions.Log, to_grpc(log))
395
- grpc_result = definitions.PartialRunResult(
396
- is_complete=False,
397
- logs=[grpc_log],
398
- result=None,
399
- )
400
- messages.put_nowait(grpc_result)
445
+ @dataclass
446
+ class LogHandler:
447
+ messages: Queue
448
+
449
+ def handle(self, log: Log) -> None:
450
+ logger.log(log.level, log.message, source=log.source)
451
+ self._add_log_to_queue(log)
452
+
453
+ def _add_log_to_queue(self, log: Log) -> None:
454
+ grpc_log = cast(definitions.Log, to_grpc(log))
455
+ grpc_result = definitions.PartialRunResult(
456
+ is_complete=False,
457
+ logs=[grpc_log],
458
+ result=None,
459
+ )
460
+ self.messages.put_nowait(grpc_result)
401
461
 
402
462
 
403
463
  def main() -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: isolate
3
- Version: 0.13.4
3
+ Version: 0.13.6
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
@@ -65,11 +65,7 @@ def make_server(tmp_path):
65
65
  yield Stubs(isolate_stub=isolate_stub, health_stub=health_stub)
66
66
  finally:
67
67
  server.stop(None)
68
- for task in servicer.background_tasks:
69
- if task.done():
70
- task.result()
71
- else:
72
- print("leaking background task", task)
68
+ servicer.cancel_tasks()
73
69
 
74
70
 
75
71
  @pytest.fixture
@@ -217,7 +213,7 @@ def test_server_builder_error(stub: definitions.IsolateStub, monkeypatch: Any) -
217
213
  assert "Failure during 'pip install': Command" in exc.value.details()
218
214
 
219
215
  raw_logs = [log.message for log in build_logs]
220
- assert "ERROR: Invalid requirement: '$$$$'" in raw_logs
216
+ assert any("ERROR: Invalid requirement: '$$$$'" in raw_log for raw_log in raw_logs)
221
217
 
222
218
 
223
219
  def test_user_logs_immediate(stub: definitions.IsolateStub, monkeypatch: Any) -> None:
@@ -402,7 +398,7 @@ def test_agent_show_logs_from_agent_requirements(
402
398
  assert "Failure during 'pip install': Command" in exc.value.details()
403
399
 
404
400
  raw_logs = [log.message for log in build_logs]
405
- assert "ERROR: Invalid requirement: '$$$$'" in raw_logs
401
+ assert any("ERROR: Invalid requirement: '$$$$'" in raw_log for raw_log in raw_logs)
406
402
 
407
403
 
408
404
  def test_bridge_connection_reuse(
@@ -697,3 +693,29 @@ def test_server_submit(
697
693
  stub.Submit(request)
698
694
  time.sleep(5)
699
695
  assert "completed" in file.read_text()
696
+ assert not list(stub.List(definitions.ListRequest()).tasks)
697
+
698
+
699
+ def myserver():
700
+ import time
701
+
702
+ while True:
703
+ print("running")
704
+ time.sleep(1)
705
+
706
+
707
+ def test_server_submit_server(
708
+ stub: definitions.IsolateStub,
709
+ monkeypatch: Any,
710
+ ) -> None:
711
+ inherit_from_local(monkeypatch)
712
+
713
+ request = definitions.SubmitRequest(function=prepare_request(myserver))
714
+ task_id = stub.Submit(request).task_id
715
+
716
+ tasks = [task.task_id for task in stub.List(definitions.ListRequest()).tasks]
717
+ assert task_id in tasks
718
+
719
+ stub.Cancel(definitions.CancelRequest(task_id=task_id))
720
+
721
+ assert not list(stub.List(definitions.ListRequest()).tasks)
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