langfun 0.1.2.dev202510310805__py3-none-any.whl → 0.1.2.dev202511020804__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.

@@ -0,0 +1,228 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import unittest
15
+
16
+ from langfun.env import test_utils
17
+
18
+ TestingEnvironment = test_utils.TestingEnvironment
19
+ TestingNonSandboxBasedFeature = test_utils.TestingNonSandboxBasedFeature
20
+ TestingEventHandler = test_utils.TestingEventHandler
21
+
22
+
23
+ class NonSandboxBasedFeatureTests(unittest.TestCase):
24
+
25
+ def test_basics(self):
26
+ feature = TestingNonSandboxBasedFeature()
27
+ event_handler = TestingEventHandler(
28
+ log_session_setup=True,
29
+ log_feature_setup=True,
30
+ log_sandbox_status=True
31
+ )
32
+ env = TestingEnvironment(
33
+ image_ids=[],
34
+ features={'test_feature': feature},
35
+ event_handler=event_handler,
36
+ )
37
+ self.assertFalse(env.is_online)
38
+ self.assertEqual(len(list(env.non_sandbox_based_features())), 1)
39
+ with env:
40
+ self.assertTrue(env.is_online)
41
+ with env.test_feature('session1') as feature:
42
+ self.assertIsNone(feature.sandbox)
43
+ self.assertEqual(feature.session_id, 'session1')
44
+
45
+ self.assertEqual(
46
+ event_handler.logs,
47
+ [
48
+ '[testing-env/test_feature] feature setup',
49
+ '[testing-env] environment started',
50
+ '[testing-env/test_feature@session1] feature setup session',
51
+ '[testing-env/test_feature@session1] feature teardown session',
52
+ '[testing-env/test_feature] feature teardown',
53
+ '[testing-env] environment shutdown'
54
+ ]
55
+ )
56
+
57
+ def test_feature_setup_error(self):
58
+ event_handler = TestingEventHandler(
59
+ log_session_setup=True,
60
+ log_feature_setup=True,
61
+ log_sandbox_status=True
62
+ )
63
+ env = TestingEnvironment(
64
+ image_ids=[],
65
+ features={
66
+ 'test_feature': TestingNonSandboxBasedFeature(
67
+ simulate_setup_error=ValueError
68
+ )
69
+ },
70
+ event_handler=event_handler,
71
+ )
72
+ with self.assertRaises(ValueError):
73
+ with env:
74
+ pass
75
+ self.assertEqual(
76
+ event_handler.logs,
77
+ [
78
+ '[testing-env/test_feature] feature setup with ValueError',
79
+ '[testing-env] environment started with ValueError',
80
+ '[testing-env/test_feature] feature teardown',
81
+ '[testing-env] environment shutdown'
82
+ ]
83
+ )
84
+
85
+ def test_feature_teardown_error(self):
86
+ event_handler = TestingEventHandler(
87
+ log_session_setup=True,
88
+ log_feature_setup=True,
89
+ log_sandbox_status=True
90
+ )
91
+ env = TestingEnvironment(
92
+ image_ids=[],
93
+ features={
94
+ 'test_feature': TestingNonSandboxBasedFeature(
95
+ simulate_teardown_error=ValueError
96
+ )
97
+ },
98
+ event_handler=event_handler,
99
+ )
100
+ with env:
101
+ pass
102
+ self.assertEqual(
103
+ event_handler.logs,
104
+ [
105
+ '[testing-env/test_feature] feature setup',
106
+ '[testing-env] environment started',
107
+ '[testing-env/test_feature] feature teardown with ValueError',
108
+ '[testing-env] environment shutdown'
109
+ ]
110
+ )
111
+
112
+ def test_feature_setup_session_error(self):
113
+ event_handler = TestingEventHandler(
114
+ log_session_setup=True,
115
+ log_feature_setup=True,
116
+ log_sandbox_status=True
117
+ )
118
+ env = TestingEnvironment(
119
+ image_ids=[],
120
+ features={
121
+ 'test_feature': TestingNonSandboxBasedFeature(
122
+ simulate_setup_session_error=ValueError
123
+ )
124
+ },
125
+ event_handler=event_handler,
126
+ )
127
+ with env:
128
+ with self.assertRaises(ValueError):
129
+ with env.test_feature('session1'):
130
+ pass
131
+ self.assertEqual(
132
+ event_handler.logs,
133
+ [
134
+ # pylint: disable=line-too-long
135
+ '[testing-env/test_feature] feature setup',
136
+ '[testing-env] environment started',
137
+ '[testing-env/test_feature@session1] feature setup session with ValueError',
138
+ '[testing-env/test_feature@session1] feature teardown session',
139
+ '[testing-env/test_feature] feature teardown',
140
+ '[testing-env] environment shutdown',
141
+ # pylint: enable=line-too-long
142
+ ]
143
+ )
144
+
145
+ def test_feature_teardown_session_error(self):
146
+ event_handler = TestingEventHandler(
147
+ log_session_setup=True,
148
+ log_feature_setup=True,
149
+ log_sandbox_status=True
150
+ )
151
+ env = TestingEnvironment(
152
+ image_ids=[],
153
+ features={
154
+ 'test_feature': TestingNonSandboxBasedFeature(
155
+ simulate_teardown_session_error=ValueError
156
+ )
157
+ },
158
+ event_handler=event_handler,
159
+ )
160
+ with env:
161
+ with env.test_feature('session1'):
162
+ pass
163
+ self.assertEqual(
164
+ event_handler.logs,
165
+ [
166
+ # pylint: disable=line-too-long
167
+ '[testing-env/test_feature] feature setup',
168
+ '[testing-env] environment started',
169
+ '[testing-env/test_feature@session1] feature setup session',
170
+ '[testing-env/test_feature@session1] feature teardown session with ValueError',
171
+ '[testing-env/test_feature] feature teardown',
172
+ '[testing-env] environment shutdown',
173
+ # pylint: enable=line-too-long
174
+ ]
175
+ )
176
+
177
+ def test_feature_housekeeping(self):
178
+ event_handler = TestingEventHandler(
179
+ log_sandbox_status=False,
180
+ log_feature_setup=False,
181
+ log_housekeep=True
182
+ )
183
+ env = TestingEnvironment(
184
+ image_ids=[],
185
+ features={
186
+ 'test_feature': TestingNonSandboxBasedFeature(
187
+ housekeep_interval=0.1
188
+ )
189
+ },
190
+ event_handler=event_handler,
191
+ housekeep_interval=0.2
192
+ )
193
+ with env:
194
+ env.wait_for_housekeeping()
195
+ self.assertIn(
196
+ '[testing-env/test_feature] feature housekeeping 0',
197
+ event_handler.logs
198
+ )
199
+
200
+ def test_feature_housekeeping_error(self):
201
+ event_handler = TestingEventHandler(
202
+ log_sandbox_status=False,
203
+ log_feature_setup=False,
204
+ log_housekeep=True
205
+ )
206
+ env = TestingEnvironment(
207
+ image_ids=[],
208
+ features={
209
+ 'test_feature': TestingNonSandboxBasedFeature(
210
+ simulate_housekeep_error=ValueError,
211
+ housekeep_interval=0.1
212
+ )
213
+ },
214
+ event_handler=event_handler,
215
+ housekeep_interval=0.2
216
+ )
217
+ with env:
218
+ env.wait_for_housekeeping()
219
+ self.assertFalse(env.is_online)
220
+ self.assertIn(
221
+ '[testing-env/test_feature] feature housekeeping 0 with ValueError',
222
+ event_handler.logs
223
+ )
224
+
225
+
226
+ if __name__ == '__main__':
227
+ unittest.main()
228
+
@@ -29,7 +29,6 @@ import time
29
29
  from typing import Annotated, Any, Iterator
30
30
 
31
31
  from langfun.env import interface
32
- from langfun.env.event_handlers import base as event_handler_base
33
32
  import pyglove as pg
34
33
 
35
34
 
@@ -198,7 +197,7 @@ class BaseSandbox(interface.Sandbox):
198
197
  for name, feature in self.environment.features.items()
199
198
  if feature.is_applicable(self.image_id)
200
199
  })
201
- self._event_handlers = []
200
+ self._event_handler = self.environment.event_handler
202
201
  self._enable_pre_session_setup = (
203
202
  self.reusable and self.proactive_session_setup
204
203
  )
@@ -247,20 +246,6 @@ class BaseSandbox(interface.Sandbox):
247
246
  """Returns the housekeeping counter."""
248
247
  return self._housekeep_counter
249
248
 
250
- def add_event_handler(
251
- self,
252
- event_handler: event_handler_base.EventHandler | None
253
- ) -> None:
254
- """Sets the event handler for the sandbox."""
255
- self._event_handlers.append(event_handler)
256
-
257
- def remove_event_handler(
258
- self,
259
- event_handler: event_handler_base.EventHandler | None
260
- ) -> None:
261
- """Removes the event handler for the sandbox."""
262
- self._event_handlers.remove(event_handler)
263
-
264
249
  @property
265
250
  def state_errors(self) -> list[interface.SandboxStateError]:
266
251
  """Returns all errors encountered during sandbox lifecycle."""
@@ -648,7 +633,6 @@ class BaseSandbox(interface.Sandbox):
648
633
  shutdown_sandbox = True
649
634
 
650
635
  self._session_start_time = None
651
- self._session_event_handler = None
652
636
 
653
637
  if shutdown_sandbox:
654
638
  self.shutdown()
@@ -664,7 +648,6 @@ class BaseSandbox(interface.Sandbox):
664
648
  def track_activity(
665
649
  self,
666
650
  name: str,
667
- feature: interface.Feature | None = None,
668
651
  **kwargs: Any
669
652
  ) -> Iterator[None]:
670
653
  """Tracks an activity for the sandbox."""
@@ -678,7 +661,6 @@ class BaseSandbox(interface.Sandbox):
678
661
  finally:
679
662
  self.on_activity(
680
663
  name=name,
681
- feature=feature,
682
664
  duration=time.time() - start_time,
683
665
  error=error,
684
666
  **kwargs
@@ -771,8 +753,7 @@ class BaseSandbox(interface.Sandbox):
771
753
  error: BaseException | None = None
772
754
  ) -> None:
773
755
  """Called when the sandbox is started."""
774
- for handler in self._event_handlers:
775
- handler.on_sandbox_start(self.environment, self, duration, error)
756
+ self._event_handler.on_sandbox_start(self, duration, error)
776
757
 
777
758
  def on_status_change(
778
759
  self,
@@ -781,14 +762,9 @@ class BaseSandbox(interface.Sandbox):
781
762
  ) -> None:
782
763
  """Called when the sandbox status changes."""
783
764
  status_duration = time.time() - self._status_start_time
784
- for handler in self._event_handlers:
785
- handler.on_sandbox_status_change(
786
- self.environment,
787
- self,
788
- old_status,
789
- new_status,
790
- status_duration
791
- )
765
+ self._event_handler.on_sandbox_status_change(
766
+ self, old_status, new_status, status_duration
767
+ )
792
768
 
793
769
  def on_shutdown(
794
770
  self,
@@ -796,14 +772,13 @@ class BaseSandbox(interface.Sandbox):
796
772
  error: BaseException | None = None
797
773
  ) -> None:
798
774
  """Called when the sandbox is shutdown."""
799
- if self._start_time is None:
800
- lifetime = 0.0
801
- else:
802
- lifetime = time.time() - self._start_time
803
- for handler in self._event_handlers:
804
- handler.on_sandbox_shutdown(
805
- self.environment, self, duration, lifetime, error
806
- )
775
+ self._event_handler.on_sandbox_shutdown(
776
+ sandbox=self,
777
+ duration=duration,
778
+ lifetime=(0.0 if self._start_time is None
779
+ else (time.time() - self._start_time)),
780
+ error=error
781
+ )
807
782
 
808
783
  def on_housekeep(
809
784
  self,
@@ -812,72 +787,13 @@ class BaseSandbox(interface.Sandbox):
812
787
  **kwargs
813
788
  ) -> None:
814
789
  """Called when the sandbox finishes a round of housekeeping."""
815
- counter = self._housekeep_counter
816
- for handler in self._event_handlers:
817
- handler.on_sandbox_housekeep(
818
- self.environment, self, counter, duration, error, **kwargs
819
- )
820
-
821
- def on_feature_setup(
822
- self,
823
- feature: interface.Feature,
824
- duration: float,
825
- error: BaseException | None = None
826
- ) -> None:
827
- """Called when a feature is setup."""
828
- for handler in self._event_handlers:
829
- handler.on_feature_setup(
830
- self.environment, self, feature, duration, error
831
- )
832
-
833
- def on_feature_teardown(
834
- self,
835
- feature: interface.Feature,
836
- duration: float,
837
- error: BaseException | None = None
838
- ) -> None:
839
- """Called when a feature is teardown."""
840
- for handler in self._event_handlers:
841
- handler.on_feature_teardown(
842
- self.environment, self, feature, duration, error
843
- )
844
-
845
- def on_feature_setup_session(
846
- self,
847
- feature: interface.Feature,
848
- duration: float,
849
- error: BaseException | None = None
850
- ) -> None:
851
- """Called when a feature is setup for a user session."""
852
- for handler in self._event_handlers:
853
- handler.on_feature_setup_session(
854
- self.environment, self, feature, self.session_id, duration, error
855
- )
856
-
857
- def on_feature_teardown_session(
858
- self,
859
- feature: interface.Feature,
860
- duration: float,
861
- error: BaseException | None = None
862
- ) -> None:
863
- """Called when a feature is teardown for a user session."""
864
- for handler in self._event_handlers:
865
- handler.on_feature_teardown_session(
866
- self.environment, self, feature, self.session_id, duration, error
867
- )
868
-
869
- def on_feature_housekeep(
870
- self,
871
- feature: interface.Feature,
872
- counter: int,
873
- duration: float,
874
- error: BaseException | None = None
875
- ) -> None:
876
- """Called when a feature is housekeeping."""
877
- for handler in self._event_handlers:
878
- handler.on_feature_housekeep(
879
- self.environment, self, feature, counter, duration, error
880
- )
790
+ self._event_handler.on_sandbox_housekeep(
791
+ sandbox=self,
792
+ counter=self._housekeep_counter,
793
+ duration=duration,
794
+ error=error,
795
+ **kwargs
796
+ )
881
797
 
882
798
  def on_session_start(
883
799
  self,
@@ -886,31 +802,29 @@ class BaseSandbox(interface.Sandbox):
886
802
  error: BaseException | None = None
887
803
  ) -> None:
888
804
  """Called when the user session starts."""
889
- for handler in self._event_handlers:
890
- handler.on_session_start(
891
- self.environment, self, session_id, duration, error
892
- )
805
+ self._event_handler.on_sandbox_session_start(
806
+ sandbox=self,
807
+ session_id=session_id,
808
+ duration=duration,
809
+ error=error
810
+ )
893
811
 
894
812
  def on_activity(
895
813
  self,
896
814
  name: str,
897
815
  duration: float,
898
- feature: interface.Feature | None = None,
899
816
  error: BaseException | None = None,
900
817
  **kwargs
901
818
  ) -> None:
902
819
  """Called when a sandbox activity is performed."""
903
- for handler in self._event_handlers:
904
- handler.on_sandbox_activity(
905
- name=name,
906
- environment=self.environment,
907
- sandbox=self,
908
- feature=feature,
909
- session_id=self.session_id,
910
- duration=duration,
911
- error=error,
912
- **kwargs
913
- )
820
+ self._event_handler.on_sandbox_activity(
821
+ name=name,
822
+ sandbox=self,
823
+ session_id=self.session_id,
824
+ duration=duration,
825
+ error=error,
826
+ **kwargs
827
+ )
914
828
 
915
829
  def on_session_end(
916
830
  self,
@@ -919,8 +833,10 @@ class BaseSandbox(interface.Sandbox):
919
833
  error: BaseException | None = None
920
834
  ) -> None:
921
835
  """Called when the user session ends."""
922
- lifetime = time.time() - self._session_start_time
923
- for handler in self._event_handlers:
924
- handler.on_session_end(
925
- self.environment, self, session_id, duration, lifetime, error
926
- )
836
+ self._event_handler.on_sandbox_session_end(
837
+ sandbox=self,
838
+ session_id=session_id,
839
+ duration=duration,
840
+ lifetime=time.time() - self._session_start_time,
841
+ error=error
842
+ )