isolate 0.13.8__tar.gz → 0.13.10__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 (86) hide show
  1. {isolate-0.13.8 → isolate-0.13.10}/PKG-INFO +1 -1
  2. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/_isolate_version.py +2 -2
  3. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/logger.py +14 -0
  4. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/server.py +113 -3
  5. {isolate-0.13.8 → isolate-0.13.10}/src/isolate.egg-info/PKG-INFO +1 -1
  6. {isolate-0.13.8 → isolate-0.13.10}/src/isolate.egg-info/SOURCES.txt +1 -0
  7. isolate-0.13.10/tests/test_logger.py +22 -0
  8. {isolate-0.13.8 → isolate-0.13.10}/.github/workflows/release.yml +0 -0
  9. {isolate-0.13.8 → isolate-0.13.10}/.github/workflows/test.yml +0 -0
  10. {isolate-0.13.8 → isolate-0.13.10}/.gitignore +0 -0
  11. {isolate-0.13.8 → isolate-0.13.10}/.pre-commit-config.yaml +0 -0
  12. {isolate-0.13.8 → isolate-0.13.10}/LICENSE +0 -0
  13. {isolate-0.13.8 → isolate-0.13.10}/README.md +0 -0
  14. {isolate-0.13.8 → isolate-0.13.10}/pyproject.toml +0 -0
  15. {isolate-0.13.8 → isolate-0.13.10}/setup.cfg +0 -0
  16. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/__init__.py +0 -0
  17. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/_version.py +0 -0
  18. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/__init__.py +0 -0
  19. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/_base.py +0 -0
  20. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/common.py +0 -0
  21. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/conda.py +0 -0
  22. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/container.py +0 -0
  23. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/local.py +0 -0
  24. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/pyenv.py +0 -0
  25. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/remote.py +0 -0
  26. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/settings.py +0 -0
  27. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/backends/virtualenv.py +0 -0
  28. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/common/__init__.py +0 -0
  29. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/common/timestamp.py +0 -0
  30. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/__init__.py +0 -0
  31. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/_local/__init__.py +0 -0
  32. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/_local/_base.py +0 -0
  33. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/_local/agent_startup.py +0 -0
  34. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/common.py +0 -0
  35. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/__init__.py +0 -0
  36. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/_base.py +0 -0
  37. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/agent.py +0 -0
  38. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/configuration.py +0 -0
  39. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/__init__.py +0 -0
  40. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/agent.proto +0 -0
  41. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/agent_pb2.py +0 -0
  42. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/agent_pb2.pyi +0 -0
  43. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/agent_pb2_grpc.py +0 -0
  44. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/common.proto +0 -0
  45. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/common_pb2.py +0 -0
  46. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/common_pb2.pyi +0 -0
  47. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/definitions/common_pb2_grpc.py +0 -0
  48. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/grpc/interface.py +0 -0
  49. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/ipc/__init__.py +0 -0
  50. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/ipc/_base.py +0 -0
  51. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/connections/ipc/agent.py +0 -0
  52. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/logs.py +0 -0
  53. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/py.typed +0 -0
  54. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/registry.py +0 -0
  55. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/__init__.py +0 -0
  56. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/definitions/__init__.py +0 -0
  57. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/definitions/server.proto +0 -0
  58. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/definitions/server_pb2.py +0 -0
  59. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/definitions/server_pb2.pyi +0 -0
  60. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/definitions/server_pb2_grpc.py +0 -0
  61. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/health/__init__.py +0 -0
  62. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/health/health.proto +0 -0
  63. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/health/health_pb2.py +0 -0
  64. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/health/health_pb2.pyi +0 -0
  65. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/health/health_pb2_grpc.py +0 -0
  66. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/health_server.py +0 -0
  67. {isolate-0.13.8 → isolate-0.13.10}/src/isolate/server/interface.py +0 -0
  68. {isolate-0.13.8 → isolate-0.13.10}/src/isolate.egg-info/dependency_links.txt +0 -0
  69. {isolate-0.13.8 → isolate-0.13.10}/src/isolate.egg-info/entry_points.txt +0 -0
  70. {isolate-0.13.8 → isolate-0.13.10}/src/isolate.egg-info/requires.txt +0 -0
  71. {isolate-0.13.8 → isolate-0.13.10}/src/isolate.egg-info/top_level.txt +0 -0
  72. {isolate-0.13.8 → isolate-0.13.10}/tests/__init__.py +0 -0
  73. {isolate-0.13.8 → isolate-0.13.10}/tests/conftest.py +0 -0
  74. {isolate-0.13.8 → isolate-0.13.10}/tests/test_backends.py +0 -0
  75. {isolate-0.13.8 → isolate-0.13.10}/tests/test_concurrency.py +0 -0
  76. {isolate-0.13.8 → isolate-0.13.10}/tests/test_connections.py +0 -0
  77. {isolate-0.13.8 → isolate-0.13.10}/tests/test_isolate.py +0 -0
  78. {isolate-0.13.8 → isolate-0.13.10}/tests/test_log.py +0 -0
  79. {isolate-0.13.8 → isolate-0.13.10}/tests/test_serialization.py +0 -0
  80. {isolate-0.13.8 → isolate-0.13.10}/tests/test_server.py +0 -0
  81. {isolate-0.13.8 → isolate-0.13.10}/tools/Dockerfile +0 -0
  82. {isolate-0.13.8 → isolate-0.13.10}/tools/agent_requirements.txt +0 -0
  83. {isolate-0.13.8 → isolate-0.13.10}/tools/protobuf-requirements.txt +0 -0
  84. {isolate-0.13.8 → isolate-0.13.10}/tools/regen_grpc.py +0 -0
  85. {isolate-0.13.8 → isolate-0.13.10}/tools/requirements.txt +0 -0
  86. {isolate-0.13.8 → isolate-0.13.10}/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.8
3
+ Version: 0.13.10
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.8'
16
- __version_tuple__ = version_tuple = (0, 13, 8)
15
+ __version__ = version = '0.13.10'
16
+ __version_tuple__ = version_tuple = (0, 13, 10)
@@ -1,15 +1,29 @@
1
1
  import json
2
+ import os
2
3
 
3
4
 
4
5
  # NOTE: we probably should've created a proper `logging.getLogger` here,
5
6
  # but it handling `source` would be not trivial, so we are better off
6
7
  # just keeping it simple for now.
7
8
  class IsolateLogger:
9
+ def __init__(self):
10
+ self.log_labels = {}
11
+ raw = os.getenv("ISOLATE_LOG_LABELS")
12
+ if raw:
13
+ labels = json.loads(raw)
14
+ for key, value in labels.items():
15
+ if value.startswith("$"):
16
+ expanded = os.getenv(value[1:])
17
+ else:
18
+ expanded = value
19
+ self.log_labels[key] = expanded
20
+
8
21
  def log(self, level, message, source):
9
22
  record = {
10
23
  "isolate_source": source.name,
11
24
  "level": level.name,
12
25
  "message": message,
26
+ **self.log_labels,
13
27
  }
14
28
  print(json.dumps(record))
15
29
 
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
3
4
  import os
4
5
  import threading
5
6
  import time
6
7
  import traceback
7
8
  import uuid
9
+ from argparse import ArgumentParser
8
10
  from collections import defaultdict
9
11
  from concurrent import futures
10
12
  from concurrent.futures import ThreadPoolExecutor
@@ -16,6 +18,7 @@ from typing import Any, Callable, Iterator, cast
16
18
 
17
19
  import grpc
18
20
  from grpc import ServicerContext, StatusCode
21
+ from grpc.experimental import wrap_server_method_handler
19
22
 
20
23
  from isolate.backends import (
21
24
  EnvironmentCreationError,
@@ -465,13 +468,120 @@ class LogHandler:
465
468
  self.messages.put_nowait(grpc_result)
466
469
 
467
470
 
468
- def main() -> None:
471
+ @dataclass
472
+ class ServerBoundInterceptor(grpc.ServerInterceptor):
473
+ _server: grpc.Server | None = None
474
+ _servicer: IsolateServicer | None = None
475
+
476
+ def register_server(self, server: grpc.Server) -> None:
477
+ if self._server is not None:
478
+ raise RuntimeError("A server is already bound to this interceptor.")
479
+
480
+ self._server = server
481
+
482
+ @property
483
+ def server(self) -> grpc.Server:
484
+ if self._server is None:
485
+ raise RuntimeError("No server was bound to this interceptor.")
486
+
487
+ return self._server
488
+
489
+ def register_servicer(self, servicer: IsolateServicer) -> None:
490
+ if self._servicer is not None:
491
+ raise RuntimeError("A servicer is already bound to this interceptor.")
492
+
493
+ self._servicer = servicer
494
+
495
+ @property
496
+ def servicer(self) -> IsolateServicer:
497
+ if self._servicer is None:
498
+ raise RuntimeError("No servicer was bound to this interceptor.")
499
+
500
+ return self._servicer
501
+
502
+
503
+ @dataclass
504
+ class SingleTaskInterceptor(ServerBoundInterceptor):
505
+ """Sets server to terminate after the first Submit/Run task."""
506
+
507
+ _done: bool = False
508
+
509
+ def intercept_service(self, continuation, handler_call_details):
510
+ handler = continuation(handler_call_details)
511
+
512
+ is_submit = handler_call_details.method == "/Isolate/Submit"
513
+ is_run = handler_call_details.method == "/Isolate/Run"
514
+ is_new_task = is_submit or is_run
515
+
516
+ if is_new_task and self._done:
517
+ raise grpc.RpcError(
518
+ grpc.StatusCode.UNAVAILABLE,
519
+ "Server has already served one Run/Submit task.",
520
+ )
521
+ elif is_new_task:
522
+ self._done = True
523
+ else:
524
+ # Let other requests like List/Cancel/etc pass through
525
+ return continuation(handler_call_details)
526
+
527
+ def wrapper(method_impl):
528
+ @functools.wraps(method_impl)
529
+ def _wrapper(request, context):
530
+ def _stop():
531
+ if is_submit:
532
+ # Wait for the task to finish
533
+ while self.server.servicer.background_tasks:
534
+ time.sleep(0.1)
535
+ self.server.stop(grace=0.1)
536
+
537
+ context.add_callback(_stop)
538
+ return method_impl(request, context)
539
+
540
+ return _wrapper
541
+
542
+ return wrap_server_method_handler(wrapper, handler)
543
+
544
+
545
+ def main(argv: list[str] | None = None) -> None:
546
+ parser = ArgumentParser()
547
+ parser.add_argument("--host", default="0.0.0.0")
548
+ parser.add_argument("--port", type=int, default=50001)
549
+ parser.add_argument(
550
+ "--single-use",
551
+ action="store_true",
552
+ help="Terminate the server after the first Run or Submit task is completed.",
553
+ )
554
+ parser.add_argument(
555
+ "--num-workers",
556
+ type=int,
557
+ default=MAX_THREADS,
558
+ help="Number of worker threads to use for the gRPC server.",
559
+ )
560
+
561
+ options = parser.parse_args(argv)
562
+ if options.num_workers is None:
563
+ options.num_workers = 1 if options.single_use else os.cpu_count()
564
+
565
+ interceptors: list[ServerBoundInterceptor] = []
566
+ if options.single_use:
567
+ interceptors.append(SingleTaskInterceptor())
568
+
469
569
  server = grpc.server(
470
- futures.ThreadPoolExecutor(max_workers=MAX_THREADS),
570
+ futures.ThreadPoolExecutor(max_workers=options.num_workers),
471
571
  options=get_default_options(),
572
+ interceptors=interceptors,
472
573
  )
574
+
575
+ for interceptor in interceptors:
576
+ interceptor.register_server(server)
577
+
473
578
  with BridgeManager() as bridge_manager:
474
- definitions.register_isolate(IsolateServicer(bridge_manager), server)
579
+ servicer = IsolateServicer(bridge_manager)
580
+
581
+ for interceptor in interceptors:
582
+ interceptor.register_servicer(servicer)
583
+
584
+ definitions.register_isolate(servicer, server)
475
585
  health.register_health(HealthServicer(), server)
476
586
 
477
587
  server.add_insecure_port("[::]:50001")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: isolate
3
- Version: 0.13.8
3
+ Version: 0.13.10
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
@@ -73,6 +73,7 @@ tests/test_concurrency.py
73
73
  tests/test_connections.py
74
74
  tests/test_isolate.py
75
75
  tests/test_log.py
76
+ tests/test_logger.py
76
77
  tests/test_serialization.py
77
78
  tests/test_server.py
78
79
  tools/Dockerfile
@@ -0,0 +1,22 @@
1
+ import json
2
+
3
+ from isolate.logger import IsolateLogger
4
+
5
+
6
+ def test_logger(monkeypatch):
7
+ labels = {
8
+ "foo": "$MYENVVAR1",
9
+ "bar": "$MYENVVAR2",
10
+ "baz": "baz",
11
+ "qux": "qux",
12
+ }
13
+ monkeypatch.setenv("MYENVVAR1", "myenvvar1")
14
+ monkeypatch.setenv("MYENVVAR2", "myenvvar2")
15
+ monkeypatch.setenv("ISOLATE_LOG_LABELS", json.dumps(labels))
16
+ logger = IsolateLogger()
17
+ assert logger.log_labels == {
18
+ "foo": "myenvvar1",
19
+ "bar": "myenvvar2",
20
+ "baz": "baz",
21
+ "qux": "qux",
22
+ }
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