langfun 0.1.2.dev202510280805__py3-none-any.whl → 0.1.2.dev202510300805__py3-none-any.whl

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 langfun might be problematic. Click here for more details.

@@ -26,7 +26,7 @@ import contextlib
26
26
  import functools
27
27
  import threading
28
28
  import time
29
- from typing import Annotated, Any, Callable, Iterator, Sequence, Type
29
+ from typing import Annotated, Any, Iterator
30
30
 
31
31
  from langfun.env import interface
32
32
  from langfun.env.event_handlers import base as event_handler_base
@@ -41,6 +41,11 @@ class BaseSandbox(interface.Sandbox):
41
41
  'The identifier for the sandbox.'
42
42
  ]
43
43
 
44
+ image_id: Annotated[
45
+ str,
46
+ 'The image id for the sandbox.'
47
+ ]
48
+
44
49
  environment: Annotated[
45
50
  pg.Ref[interface.Environment],
46
51
  'The parent environment.'
@@ -95,10 +100,9 @@ class BaseSandbox(interface.Sandbox):
95
100
  self._status = status
96
101
  self._status_start_time = time.time()
97
102
 
98
- def report_maybe_state_error(self, e: BaseException | None) -> None:
103
+ def report_state_error(self, e: interface.SandboxStateError) -> None:
99
104
  """Reports sandbox state errors."""
100
- if (isinstance(e, interface.SandboxStateError)
101
- and e not in self._state_errors):
105
+ if e not in self._state_errors:
102
106
  self._state_errors.append(e)
103
107
 
104
108
  def _setup_features(self) -> None:
@@ -136,7 +140,8 @@ class BaseSandbox(interface.Sandbox):
136
140
  try:
137
141
  feature.teardown()
138
142
  except BaseException as e: # pylint: disable=broad-except
139
- self.report_maybe_state_error(e)
143
+ if isinstance(e, interface.SandboxStateError):
144
+ self.report_state_error(e)
140
145
  errors[feature.name] = e
141
146
  if errors:
142
147
  return interface.FeatureTeardownError(sandbox=self, errors=errors)
@@ -170,7 +175,8 @@ class BaseSandbox(interface.Sandbox):
170
175
  try:
171
176
  feature.teardown_session()
172
177
  except BaseException as e: # pylint: disable=broad-except
173
- self.report_maybe_state_error(e)
178
+ if isinstance(e, interface.SandboxStateError):
179
+ self.report_state_error(e)
174
180
  feature_teardown_errors[name] = e
175
181
 
176
182
  return interface.SessionTeardownError(
@@ -187,10 +193,10 @@ class BaseSandbox(interface.Sandbox):
187
193
  def _on_bound(self) -> None:
188
194
  """Called when the sandbox is bound."""
189
195
  super()._on_bound()
190
-
191
196
  self._features = pg.Dict({
192
197
  name: pg.clone(feature)
193
198
  for name, feature in self.environment.features.items()
199
+ if feature.is_applicable(self.image_id)
194
200
  })
195
201
  self._event_handlers = []
196
202
  self._enable_pre_session_setup = (
@@ -272,28 +278,6 @@ class BaseSandbox(interface.Sandbox):
272
278
  """Returns the features in the sandbox."""
273
279
  return self._features
274
280
 
275
- def _enter_service_call(self) -> bool:
276
- """Enters a service call.
277
-
278
- Returns:
279
- True if the service call is at the top of the call stack.
280
- """
281
- v = getattr(self._tls_state, 'service_call_depth', None)
282
- if v is None:
283
- v = 0
284
- setattr(self._tls_state, 'service_call_depth', v + 1)
285
- return v == 0
286
-
287
- def _exit_service_call(self) -> bool:
288
- """Exits a service call.
289
-
290
- Returns:
291
- True if the service call is at the top of the call stack.
292
- """
293
- v = getattr(self._tls_state, 'service_call_depth')
294
- setattr(self._tls_state, 'service_call_depth', v - 1)
295
- return v == 1
296
-
297
281
  #
298
282
  # Sandbox start/shutdown.
299
283
  #
@@ -364,7 +348,8 @@ class BaseSandbox(interface.Sandbox):
364
348
  '[%s]: Sandbox failed to start in %.2f seconds: %s',
365
349
  self.id, duration, e
366
350
  )
367
- self.report_maybe_state_error(e)
351
+ if isinstance(e, interface.SandboxStateError):
352
+ self.report_state_error(e)
368
353
  self.on_start(duration, e)
369
354
  self.shutdown()
370
355
  raise e
@@ -437,7 +422,8 @@ class BaseSandbox(interface.Sandbox):
437
422
  shutdown_error = None
438
423
  except BaseException as e: # pylint: disable=broad-except
439
424
  shutdown_error = e
440
- self.report_maybe_state_error(e)
425
+ if isinstance(e, interface.SandboxStateError):
426
+ self.report_state_error(e)
441
427
  self._set_status(interface.Sandbox.Status.OFFLINE)
442
428
  pg.logging.error(
443
429
  '[%s]: Sandbox shutdown with error: %s',
@@ -535,7 +521,8 @@ class BaseSandbox(interface.Sandbox):
535
521
  self._set_status(self.Status.IN_SESSION)
536
522
  self.on_session_start(session_id, time.time() - self._session_start_time)
537
523
  except BaseException as e: # pylint: disable=broad-except
538
- self.report_maybe_state_error(e)
524
+ if isinstance(e, interface.SandboxStateError):
525
+ self.report_state_error(e)
539
526
  self.on_session_start(
540
527
  session_id, time.time() - self._session_start_time, e
541
528
  )
@@ -621,7 +608,8 @@ class BaseSandbox(interface.Sandbox):
621
608
  self.id,
622
609
  e
623
610
  )
624
- self.report_maybe_state_error(e)
611
+ if isinstance(e, interface.SandboxStateError):
612
+ self.report_state_error(e)
625
613
  self.shutdown()
626
614
 
627
615
  # End session before setting up the next session.
@@ -672,6 +660,30 @@ class BaseSandbox(interface.Sandbox):
672
660
  and end_session_error.has_non_sandbox_state_error):
673
661
  raise end_session_error # pylint: disable=raising-bad-type
674
662
 
663
+ @contextlib.contextmanager
664
+ def track_activity(
665
+ self,
666
+ name: str,
667
+ feature: interface.Feature | None = None,
668
+ **kwargs: Any
669
+ ) -> Iterator[None]:
670
+ """Tracks an activity for the sandbox."""
671
+ start_time = time.time()
672
+ error = None
673
+ try:
674
+ yield None
675
+ except BaseException as e: # pylint: disable=broad-except
676
+ error = e
677
+ raise
678
+ finally:
679
+ self.on_activity(
680
+ name=name,
681
+ feature=feature,
682
+ duration=time.time() - start_time,
683
+ error=error,
684
+ **kwargs
685
+ )
686
+
675
687
  #
676
688
  # Housekeeping.
677
689
  #
@@ -717,7 +729,7 @@ class BaseSandbox(interface.Sandbox):
717
729
  str(e)
718
730
  )
719
731
  self._housekeep_counter += 1
720
- self.report_maybe_state_error(e)
732
+ self.report_state_error(e)
721
733
  self.on_housekeep(time.time() - housekeep_start, e)
722
734
  self.shutdown()
723
735
  break
@@ -739,7 +751,7 @@ class BaseSandbox(interface.Sandbox):
739
751
  feature.name,
740
752
  e,
741
753
  )
742
- self.report_maybe_state_error(e)
754
+ self.report_state_error(e)
743
755
  self._housekeep_counter += 1
744
756
  self.on_housekeep(time.time() - housekeep_start, e)
745
757
  self.shutdown()
@@ -912,208 +924,3 @@ class BaseSandbox(interface.Sandbox):
912
924
  handler.on_session_end(
913
925
  self.environment, self, session_id, duration, lifetime, error
914
926
  )
915
-
916
-
917
- #
918
- # Sandbox service decorator.
919
- #
920
-
921
-
922
- def sandbox_service(
923
- critical_errors: Sequence[
924
- Type[BaseException] | tuple[Type[BaseException], str]
925
- ] | None = None
926
- ) -> Callable[..., Any]:
927
- """Decorator for Sandbox/Feature methods exposed as sandbox services.
928
-
929
- This decorator will catch errors and map to `SandboxStateError` if the
930
- error matches any of the critical errors. Consequently, the sandbox will be
931
- shutdown automatically when the error is raised.
932
-
933
- Example:
934
-
935
- ```
936
- with env:
937
- with env.sandbox() as sb:
938
- try:
939
- sb.test_feature.do_something_with_non_state_error()
940
- except ValueError:
941
- # sandbox will not be shutdown.
942
- pass
943
-
944
- try:
945
- sb.test_feature.do_something_with_state_error()
946
- except ValueError:
947
- assert sb.state == sb.Status.OFFLINE
948
- ```
949
-
950
- If the decorated method returns a context manager, a wrapper context manager
951
- will be returned, which will end the session when exiting the context.
952
-
953
- Example:
954
-
955
- ```
956
- with env:
957
- with env.test_feature.do_something_with_context_manager() as result:
958
- # sandbox will be alive during the whole context manager cycle.
959
- ```
960
-
961
- For sandbox service methods, an optional `session_id` argument can be passed
962
- to create a new session for the service call, even its signature does not
963
- contain a `session_id` argument.
964
-
965
- Args:
966
- critical_errors: A sequence of exception types or tuples of exception type
967
- and error messages (described in regular expression), when matched, treat
968
- the sandbox as in a bad state, which will trigger a shutdown.
969
-
970
- Returns:
971
- The decorator function.
972
- """
973
- critical_errors = critical_errors or []
974
-
975
- def decorator(func):
976
- signature = pg.typing.get_signature(func)
977
- if 'session_id' in signature.arg_names:
978
- raise ValueError(
979
- '`session_id` should not be used as argument for sandbox '
980
- 'service method. Please use `self.session_id` instead.'
981
- )
982
-
983
- def to_kwargs(*args, **kwargs):
984
- num_non_self_args = len(signature.arg_names) - 1
985
- if len(args) > num_non_self_args:
986
- assert signature.varargs is not None, (signature, args)
987
- kwargs[signature.varargs.name] = tuple(args[num_non_self_args:])
988
- args = args[:num_non_self_args]
989
- for i in range(len(args)):
990
- # The first argument is `self`.
991
- kwargs[signature.arg_names[i + 1]] = args[i]
992
- return kwargs
993
-
994
- @functools.wraps(func)
995
- def method_wrapper(self, *args, **kwargs) -> Any:
996
- """Helper function to safely execute logics in the sandbox."""
997
-
998
- assert isinstance(self, (BaseSandbox, interface.Feature)), self
999
- sandbox = self.sandbox if isinstance(self, interface.Feature) else self
1000
-
1001
- # We count the service call stack depth so we could shutdown the sandbox
1002
- # at the top upon sandbox state error.
1003
- sandbox._enter_service_call() # pylint: disable=protected-access
1004
-
1005
- # When a capability is directly accessed from the environment,
1006
- # we create a new session for the capability call. This
1007
- # prevents the sandbox from being reused for other feature calls.
1008
- if sandbox.status == interface.Sandbox.Status.ACQUIRED:
1009
- new_session = True
1010
- new_session_id = kwargs.get('session_id')
1011
- if new_session_id is None:
1012
- new_session_id = sandbox.environment.new_session_id()
1013
-
1014
- # If it's a feature method called from the environment, start a new
1015
- # session for the feature call.
1016
- sandbox.start_session(new_session_id)
1017
- else:
1018
- new_session = False
1019
-
1020
- kwargs.pop('session_id', None)
1021
- result = None
1022
- error = None
1023
- start_time = time.time()
1024
-
1025
- try:
1026
- # Execute the service function.
1027
- result = func(self, *args, **kwargs)
1028
-
1029
- # If the result is a context manager, wrap it with a context manager
1030
- # to end the session when exiting.
1031
- if isinstance(result, contextlib.AbstractContextManager):
1032
- return _service_context_manager_wrapper(
1033
- service=result,
1034
- sandbox_or_feature=self,
1035
- sandbox=sandbox,
1036
- name=func.__name__,
1037
- kwargs=to_kwargs(*args, **kwargs),
1038
- start_time=start_time,
1039
- new_session=new_session
1040
- )
1041
-
1042
- # Otherwise, return the result and end the session in the finally block.
1043
- return result
1044
- except BaseException as e:
1045
- error = e
1046
- sandbox.report_maybe_state_error(e)
1047
- if pg.match_error(e, critical_errors):
1048
- state_error = interface.SandboxStateError(
1049
- 'Sandbox encountered an unexpected error executing '
1050
- f'`{func.__name__}` (args={args!r}, kwargs={kwargs!r}): {e}',
1051
- sandbox=self
1052
- )
1053
- sandbox.report_maybe_state_error(state_error)
1054
- raise state_error from e
1055
- raise
1056
- finally:
1057
- is_topmost_call = sandbox._exit_service_call() # pylint: disable=protected-access
1058
- if not isinstance(result, contextlib.AbstractContextManager):
1059
- self.on_activity(
1060
- name=func.__name__,
1061
- duration=time.time() - start_time,
1062
- error=error,
1063
- **to_kwargs(*args, **kwargs),
1064
- )
1065
- if new_session:
1066
- assert is_topmost_call
1067
-
1068
- # End the session if it's from a feature method and the result
1069
- # is not a context manager.
1070
- sandbox.end_session()
1071
-
1072
- # Shutdown the sandbox if it is at the top of the service call stack and
1073
- # has state errors.
1074
- if (is_topmost_call
1075
- and sandbox.state_errors
1076
- # Sandbox service method might be called during shutting down, in
1077
- # that case we don't want to shutdown the sandbox again.
1078
- and not sandbox.is_shutting_down):
1079
- sandbox.shutdown()
1080
-
1081
- return method_wrapper
1082
- return decorator
1083
-
1084
-
1085
- @contextlib.contextmanager
1086
- def _service_context_manager_wrapper(
1087
- service: contextlib.AbstractContextManager[Any],
1088
- sandbox_or_feature: BaseSandbox | interface.Feature,
1089
- sandbox: interface.Sandbox,
1090
- name: str,
1091
- kwargs: dict[str, Any],
1092
- new_session: bool,
1093
- start_time: float,
1094
- ) -> Iterator[Any]:
1095
- """Context manager wrapper for ending a sandbox session when exiting."""
1096
- error = None
1097
- sandbox._enter_service_call() # pylint: disable=protected-access
1098
-
1099
- try:
1100
- with service as result:
1101
- yield result
1102
- except BaseException as e:
1103
- error = e
1104
- sandbox.report_maybe_state_error(error)
1105
- raise
1106
- finally:
1107
- sandbox_or_feature.on_activity(
1108
- name=name,
1109
- error=error,
1110
- duration=time.time() - start_time,
1111
- **kwargs,
1112
- )
1113
- is_topmost_call = sandbox._exit_service_call() # pylint: disable=protected-access
1114
-
1115
- if new_session:
1116
- assert is_topmost_call
1117
- sandbox.end_session()
1118
- elif isinstance(error, interface.SandboxStateError):
1119
- sandbox.shutdown()