langfun 0.1.2.dev202511010804__py3-none-any.whl → 0.1.2.dev202511030805__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.
- langfun/core/eval/v2/runners_test.py +3 -0
- langfun/env/__init__.py +1 -1
- langfun/env/base_environment.py +62 -5
- langfun/env/base_environment_test.py +5 -5
- langfun/env/base_feature.py +38 -20
- langfun/env/base_feature_test.py +228 -0
- langfun/env/base_sandbox.py +27 -33
- langfun/env/base_sandbox_test.py +258 -251
- langfun/env/event_handlers/chain.py +58 -80
- langfun/env/event_handlers/chain_test.py +83 -111
- langfun/env/event_handlers/event_logger.py +77 -80
- langfun/env/event_handlers/event_logger_test.py +18 -18
- langfun/env/event_handlers/metric_writer.py +176 -140
- langfun/env/interface.py +493 -199
- langfun/env/interface_test.py +30 -1
- langfun/env/test_utils.py +110 -78
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511030805.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511030805.dist-info}/RECORD +21 -20
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511030805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511030805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511030805.dist-info}/top_level.txt +0 -0
langfun/env/interface.py
CHANGED
|
@@ -169,7 +169,176 @@ class SessionTeardownError(SandboxError):
|
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
class Environment(pg.Object):
|
|
172
|
-
"""Base class for an environment.
|
|
172
|
+
"""Base class for an environment.
|
|
173
|
+
|
|
174
|
+
An **Environment** is the central component for managing sandboxes and
|
|
175
|
+
**Features**. It acts as an abstraction layer, hiding the implementation
|
|
176
|
+
details of the underlying container/sandboxing system
|
|
177
|
+
(e.g., Docker, virtual machines).
|
|
178
|
+
|
|
179
|
+
The core goal is to enable the development of features that are **agnostic**
|
|
180
|
+
to how or where they are executed (sandboxed or not).
|
|
181
|
+
|
|
182
|
+
-----------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
## Core Concepts
|
|
185
|
+
|
|
186
|
+
1. **Sandbox-Based Features:** Features that require isolated execution
|
|
187
|
+
contexts (sandboxes). They become available as properties of the sandbox
|
|
188
|
+
object (e.g., `sandbox.feature1`). Applicability is determined by an image
|
|
189
|
+
ID regex.
|
|
190
|
+
2. **Non-Sandbox-Based Features:** Features that run directly within the host
|
|
191
|
+
process or manage their own execution context outside of the Environment's
|
|
192
|
+
managed sandboxes. They are accessed directly via the Environment (e.g.,
|
|
193
|
+
`env.feature3()`).
|
|
194
|
+
|
|
195
|
+
How to Use:
|
|
196
|
+
|
|
197
|
+
The primary usage patterns are creating a **sandbox session** or directly
|
|
198
|
+
accessing a specific **Feature**, which transparently handles sandbox creation
|
|
199
|
+
if needed.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
env = MyEnvironment(
|
|
203
|
+
image_ids=['image1', 'image2'],
|
|
204
|
+
features={
|
|
205
|
+
'feature1': Feature1(applicable_images=['image1.*']),
|
|
206
|
+
'feature2': Feature2(applicable_images=['image2.*']),
|
|
207
|
+
'feature3': Feature3(is_sandbox_based=False)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
# Context manager for the Environment lifetime.
|
|
211
|
+
with env:
|
|
212
|
+
|
|
213
|
+
# 1. Access a specific sandbox directly.
|
|
214
|
+
# Upon exiting the context, the sandbox will be shutdown or returned to
|
|
215
|
+
# the pool for reuse.
|
|
216
|
+
with env.sandbox(image_id='image1') as sandbox:
|
|
217
|
+
# Execute a shell command inside the sandbox.
|
|
218
|
+
sandbox.shell('echo "hello world"')
|
|
219
|
+
|
|
220
|
+
# Access a sandbox-based feature (feature1 is applicable to image1).
|
|
221
|
+
sandbox.feature1.feature_method()
|
|
222
|
+
|
|
223
|
+
# Attempts to access inapplicable features will raise AttributeError:
|
|
224
|
+
# sandbox.feature2 # Not applicable to image1.
|
|
225
|
+
# sandbox.feature3 # Not sandbox-based.
|
|
226
|
+
|
|
227
|
+
# 2. Access a sandbox-based feature and let the Environment manage the
|
|
228
|
+
# sandbox. A suitable sandbox (e.g., one built from an image matching
|
|
229
|
+
# 'image1.*') will be provisioned, and the feature instance will be yielded.
|
|
230
|
+
with env.feature1() as feature1:
|
|
231
|
+
feature1.feature_method()
|
|
232
|
+
|
|
233
|
+
# 3. Access a non-sandbox-based feature.
|
|
234
|
+
with env.feature3() as feature3:
|
|
235
|
+
feature3.feature_method()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
-----------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
## Multi-tenancy and Pooling
|
|
241
|
+
|
|
242
|
+
The Environment supports multi-tenancy (working with multiple image types) and
|
|
243
|
+
pooling (reusing sandboxes) to amortize setup costs across different user
|
|
244
|
+
requests. Pooling is configured via the pool_size parameter.
|
|
245
|
+
|
|
246
|
+
| pool_size Value | Behavior |
|
|
247
|
+
|---------------------------|------------------------------------------------|
|
|
248
|
+
| 0 or (0, 0) | No pooling. Sandboxes are created and shut down|
|
|
249
|
+
| | on demand (useful for local development). |
|
|
250
|
+
|---------------------------|------------------------------------------------|
|
|
251
|
+
| (MIN, MAX) tuple | Global Pool: Applies the same minimum and |
|
|
252
|
+
| | maximum pool size to sandboxes created from all|
|
|
253
|
+
| | specified images. |
|
|
254
|
+
|---------------------------|------------------------------------------------|
|
|
255
|
+
| {image_regex: (MIN, MAX)} | Per-Image Pool: Allows customizing pool |
|
|
256
|
+
| | settings based on image ID regular expressions.|
|
|
257
|
+
| | (e.g., 'image1.*': (64, 256), '.*': (0, 256)). |
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
**Example 1: No Pooling (pool_size=0)**
|
|
261
|
+
Sandboxes are created and shutdown immediately upon session end.
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
env = MyEnvironment(image_ids=['image1', 'image2'], pool_size=0)
|
|
265
|
+
|
|
266
|
+
# Sandbox created and shutdown on demand.
|
|
267
|
+
with env.sandbox(image_id='image1') as sandbox1:
|
|
268
|
+
...
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Exaxmple 2: Global Pooling (pool_size=(0, 256))**
|
|
272
|
+
Up to 256 sandboxes will be created and pooled across both images as needed.
|
|
273
|
+
None are created initially.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
env = MyEnvironment(
|
|
277
|
+
image_ids=['image1', 'image2'],
|
|
278
|
+
pool_size=(0, 256)
|
|
279
|
+
)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Example 3: Per-Image Custom Pooling**:
|
|
283
|
+
For images matching 'image1.*': 64 sandboxes are pre-created (MIN=64) and
|
|
284
|
+
pooled, up to a MAX of 256.
|
|
285
|
+
For all other images ('.*'): Sandboxes are created and pooled on demand
|
|
286
|
+
(MIN=0), up to a MAX of 256.
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
env = MyEnvironment(
|
|
290
|
+
image_ids=['image1', 'image2'],
|
|
291
|
+
pool_size={
|
|
292
|
+
'image1.*': (64, 256),
|
|
293
|
+
'.*': (0, 256),
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Handling Sandbox Failures
|
|
299
|
+
|
|
300
|
+
Sandboxes often run in distributed, ephemeral environments and must be treated
|
|
301
|
+
as fault-tolerant. Langfun provides a protocol for handling unexpected sandbox
|
|
302
|
+
state issues.
|
|
303
|
+
|
|
304
|
+
### Communicating Errors
|
|
305
|
+
If a feature encounters an unexpected state in its sandbox (e.g., a process
|
|
306
|
+
died), it should raise `lf.env.SandboxStateError`.
|
|
307
|
+
|
|
308
|
+
The sandbox will be automatically shut down when its context manager exits.
|
|
309
|
+
The Environment handles replacement and ensures future requests are routed to
|
|
310
|
+
healthy sandboxes.
|
|
311
|
+
|
|
312
|
+
For example:
|
|
313
|
+
```
|
|
314
|
+
with env:
|
|
315
|
+
with env.sandbox() as sb:
|
|
316
|
+
# If SandboxStateError is raised within this block, the sandbox
|
|
317
|
+
# will be forcefully shut down upon block exit.
|
|
318
|
+
sb.shell('echo hi')
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Robust User Code
|
|
322
|
+
A simple strategy for robust user code is to wrap critical operations in a
|
|
323
|
+
retry loop:
|
|
324
|
+
```
|
|
325
|
+
while True:
|
|
326
|
+
try:
|
|
327
|
+
result = do_something_that_involves_sandbox()
|
|
328
|
+
break # Success!
|
|
329
|
+
except lf.env.SandboxStateError:
|
|
330
|
+
# The sandbox failed; a new, healthy one will be provisioned on the next
|
|
331
|
+
# iteration.
|
|
332
|
+
# Wait briefly to avoid resource thrashing.
|
|
333
|
+
time.sleep(1)
|
|
334
|
+
except lf.env.EnvironmentOutageError:
|
|
335
|
+
# If the Environment is down for too long
|
|
336
|
+
# (past BaseEnvironment.outage_grace_period)
|
|
337
|
+
# and cannot provision a healthy replacement, this error is raised.
|
|
338
|
+
# The retry loop should be broken or an outer failure reported.
|
|
339
|
+
raise
|
|
340
|
+
```
|
|
341
|
+
"""
|
|
173
342
|
|
|
174
343
|
# Disable symbolic comparison and hashing for environment objects.
|
|
175
344
|
use_symbolic_comparison = False
|
|
@@ -238,6 +407,12 @@ class Environment(pg.Object):
|
|
|
238
407
|
f'No image ID found for feature {feature.name} in {self.image_ids}.'
|
|
239
408
|
)
|
|
240
409
|
|
|
410
|
+
def non_sandbox_based_features(self) -> Iterator['Feature']:
|
|
411
|
+
"""Returns non-sandbox-based features."""
|
|
412
|
+
for feature in self.features.values():
|
|
413
|
+
if not feature.is_sandbox_based:
|
|
414
|
+
yield feature
|
|
415
|
+
|
|
241
416
|
@property
|
|
242
417
|
@abc.abstractmethod
|
|
243
418
|
def event_handler(self) -> 'EventHandler':
|
|
@@ -324,11 +499,13 @@ class Environment(pg.Object):
|
|
|
324
499
|
|
|
325
500
|
def sandbox(
|
|
326
501
|
self,
|
|
327
|
-
image_id: str | None = None,
|
|
328
502
|
session_id: str | None = None,
|
|
503
|
+
image_id: str | None = None,
|
|
329
504
|
) -> ContextManager['Sandbox']:
|
|
330
505
|
"""Gets a sandbox from the environment and starts a new user session."""
|
|
331
|
-
return self.acquire(image_id=image_id).new_session(
|
|
506
|
+
return self.acquire(image_id=image_id).new_session(
|
|
507
|
+
session_id or self.new_session_id()
|
|
508
|
+
)
|
|
332
509
|
|
|
333
510
|
def __getattr__(self, name: str) -> Any:
|
|
334
511
|
"""Gets a feature session from a free sandbox from the environment.
|
|
@@ -356,13 +533,14 @@ class Environment(pg.Object):
|
|
|
356
533
|
|
|
357
534
|
|
|
358
535
|
@contextlib.contextmanager
|
|
359
|
-
def
|
|
536
|
+
def _sandbox_session_for_feature(
|
|
360
537
|
environment: Environment,
|
|
361
538
|
feature: 'Feature',
|
|
362
539
|
image_id: str | None = None,
|
|
363
540
|
session_id: str | None = None,
|
|
364
541
|
) -> Iterator['Feature']:
|
|
365
542
|
"""Returns a context manager for a session for a feature."""
|
|
543
|
+
assert feature.is_sandbox_based
|
|
366
544
|
if image_id is None:
|
|
367
545
|
image_id = environment.image_id_for(feature)
|
|
368
546
|
elif not feature.is_applicable(image_id):
|
|
@@ -370,14 +548,25 @@ def _session_for_feature(
|
|
|
370
548
|
f'Feature {feature.name!r} is not applicable to image {image_id!r}.'
|
|
371
549
|
)
|
|
372
550
|
sandbox = environment.acquire(image_id=image_id)
|
|
373
|
-
with sandbox.new_session(
|
|
551
|
+
with sandbox.new_session(
|
|
552
|
+
session_id=session_id or environment.new_session_id(feature.name)
|
|
553
|
+
):
|
|
374
554
|
yield sandbox.features[feature.name]
|
|
375
555
|
|
|
376
556
|
|
|
377
557
|
def _feature_session_creator(environment: Environment, feature: 'Feature'):
|
|
378
558
|
"""Returns a callable that returns a context manager for a feature session."""
|
|
379
|
-
def fn(
|
|
380
|
-
|
|
559
|
+
def fn(session_id: str | None = None, image_id: str | None = None):
|
|
560
|
+
if feature.is_sandbox_based:
|
|
561
|
+
return _sandbox_session_for_feature(
|
|
562
|
+
environment, feature, image_id, session_id
|
|
563
|
+
)
|
|
564
|
+
assert image_id is None, (
|
|
565
|
+
'Non-sandbox based feature does not support image ID.'
|
|
566
|
+
)
|
|
567
|
+
return feature.new_session(
|
|
568
|
+
session_id or environment.new_session_id(feature.name)
|
|
569
|
+
)
|
|
381
570
|
return fn
|
|
382
571
|
|
|
383
572
|
|
|
@@ -386,7 +575,12 @@ pg.typing.register_converter(str, Environment.Id, Environment.Id)
|
|
|
386
575
|
|
|
387
576
|
|
|
388
577
|
class Sandbox(pg.Object):
|
|
389
|
-
"""Interface for sandboxes.
|
|
578
|
+
"""Interface for sandboxes.
|
|
579
|
+
|
|
580
|
+
A sandbox is a container that runs a single image with a set of features.
|
|
581
|
+
It will be brought up by the environment, setup the features, fullfill user
|
|
582
|
+
requests, and then tear down features and finally the sandbox itself.
|
|
583
|
+
"""
|
|
390
584
|
|
|
391
585
|
# Disable symbolic comparison and hashing for sandbox objects.
|
|
392
586
|
use_symbolic_comparison = False
|
|
@@ -695,14 +889,12 @@ class Sandbox(pg.Object):
|
|
|
695
889
|
def track_activity(
|
|
696
890
|
self,
|
|
697
891
|
name: str,
|
|
698
|
-
feature: Optional['Feature'] = None,
|
|
699
892
|
**kwargs: Any
|
|
700
893
|
) -> ContextManager[None]:
|
|
701
894
|
"""Context manager that tracks a sandbox activity.
|
|
702
895
|
|
|
703
896
|
Args:
|
|
704
897
|
name: The name of the activity.
|
|
705
|
-
feature: The feature that the activity is associated with.
|
|
706
898
|
**kwargs: Additional keyword arguments to pass to the activity handler.
|
|
707
899
|
|
|
708
900
|
Returns:
|
|
@@ -717,12 +909,7 @@ class Sandbox(pg.Object):
|
|
|
717
909
|
#
|
|
718
910
|
|
|
719
911
|
@contextlib.contextmanager
|
|
720
|
-
def new_session(
|
|
721
|
-
self,
|
|
722
|
-
session_id: str | None = None,
|
|
723
|
-
*,
|
|
724
|
-
feature_hint: str | None = None,
|
|
725
|
-
) -> Iterator['Sandbox']:
|
|
912
|
+
def new_session(self, session_id: str) -> Iterator['Sandbox']:
|
|
726
913
|
"""Context manager for obtaining a sandbox for a user session.
|
|
727
914
|
|
|
728
915
|
State transitions:
|
|
@@ -730,11 +917,7 @@ class Sandbox(pg.Object):
|
|
|
730
917
|
ACQUIRED -> IN_SESSINO -> OFFLINE: When session setup or teardown fails.
|
|
731
918
|
|
|
732
919
|
Args:
|
|
733
|
-
session_id: The identifier for the user session.
|
|
734
|
-
ID will be generated.
|
|
735
|
-
feature_hint: A hint of which feature is the main user intent when
|
|
736
|
-
starting the session. This is used for generating the session id if
|
|
737
|
-
`session_id` is not provided.
|
|
920
|
+
session_id: The identifier for the user session.
|
|
738
921
|
|
|
739
922
|
Yields:
|
|
740
923
|
The sandbox for the user session.
|
|
@@ -744,8 +927,6 @@ class Sandbox(pg.Object):
|
|
|
744
927
|
BaseException: If session setup or teardown failed with user-defined
|
|
745
928
|
errors.
|
|
746
929
|
"""
|
|
747
|
-
if session_id is None:
|
|
748
|
-
session_id = self.environment.new_session_id(feature_hint)
|
|
749
930
|
self.start_session(session_id)
|
|
750
931
|
try:
|
|
751
932
|
yield self
|
|
@@ -779,7 +960,61 @@ class Sandbox(pg.Object):
|
|
|
779
960
|
|
|
780
961
|
|
|
781
962
|
class Feature(pg.Object):
|
|
782
|
-
"""Interface for
|
|
963
|
+
"""Interface for features that run in a Langfun environment.
|
|
964
|
+
|
|
965
|
+
There are two type of features: sandbox-based and non-sandbox-based.
|
|
966
|
+
Sandbox-based features run in a sandbox, which is emulated in a separate
|
|
967
|
+
process. Non-sandbox-based features do not run in a sandbox.
|
|
968
|
+
|
|
969
|
+
Features can be directly accessed through the environment, for example:
|
|
970
|
+
|
|
971
|
+
```python
|
|
972
|
+
env = MyEnvironment(
|
|
973
|
+
feature={
|
|
974
|
+
'feature1': SandboxBasedFeature(),
|
|
975
|
+
'feature2': NonSandboxBasedFeature(),
|
|
976
|
+
}
|
|
977
|
+
)
|
|
978
|
+
# Start the environment.
|
|
979
|
+
with env:
|
|
980
|
+
# Access feature1, which involves acquiring a sandbox and return the feature
|
|
981
|
+
# associated with the sandbox.
|
|
982
|
+
with env.feature1() as f1:
|
|
983
|
+
f1.feature_method()
|
|
984
|
+
|
|
985
|
+
# Access feature2, which does not involve acquiring a sandbox.
|
|
986
|
+
with env.feature2() as f2:
|
|
987
|
+
f2.feature_method()
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
Sandbox-based features can also be accessed through the sandbox, for example:
|
|
991
|
+
|
|
992
|
+
```python
|
|
993
|
+
with env.sandbox('session1') as sb:
|
|
994
|
+
# Access feature1 within the sandbox.
|
|
995
|
+
sb.feature1.feature_method()
|
|
996
|
+
|
|
997
|
+
# Attribute error.
|
|
998
|
+
sb.feature2
|
|
999
|
+
```
|
|
1000
|
+
"""
|
|
1001
|
+
|
|
1002
|
+
@dataclasses.dataclass
|
|
1003
|
+
class Id:
|
|
1004
|
+
container_id: Environment.Id | Sandbox.Id
|
|
1005
|
+
feature_name: str
|
|
1006
|
+
|
|
1007
|
+
def __str__(self) -> str:
|
|
1008
|
+
return f'{self.container_id}/{self.feature_name}'
|
|
1009
|
+
|
|
1010
|
+
def working_dir(self, root_dir: str | None) -> str | None:
|
|
1011
|
+
"""Returns the working directory for the feature."""
|
|
1012
|
+
if root_dir is None:
|
|
1013
|
+
return None
|
|
1014
|
+
return os.path.join(
|
|
1015
|
+
self.container_id.working_dir(root_dir),
|
|
1016
|
+
_make_path_compatible(self.feature_name)
|
|
1017
|
+
)
|
|
783
1018
|
|
|
784
1019
|
# Disable symbolic comparison and hashing for sandbox objects.
|
|
785
1020
|
allow_symbolic_comparison = False
|
|
@@ -789,6 +1024,13 @@ class Feature(pg.Object):
|
|
|
789
1024
|
def name(self) -> str:
|
|
790
1025
|
"""Name of the feature, which will be used as key to access the feature."""
|
|
791
1026
|
|
|
1027
|
+
@functools.cached_property
|
|
1028
|
+
def id(self) -> Id:
|
|
1029
|
+
"""Returns the identifier of the feature."""
|
|
1030
|
+
if self.is_sandbox_based:
|
|
1031
|
+
return Feature.Id(self.sandbox.id, self.name)
|
|
1032
|
+
return Feature.Id(self.environment.id, self.name)
|
|
1033
|
+
|
|
792
1034
|
@property
|
|
793
1035
|
@abc.abstractmethod
|
|
794
1036
|
def environment(self) -> Environment:
|
|
@@ -796,14 +1038,17 @@ class Feature(pg.Object):
|
|
|
796
1038
|
|
|
797
1039
|
@property
|
|
798
1040
|
@abc.abstractmethod
|
|
799
|
-
def
|
|
1041
|
+
def is_sandbox_based(self) -> bool:
|
|
1042
|
+
"""Returns True if the feature is sandbox-based."""
|
|
1043
|
+
|
|
1044
|
+
@property
|
|
1045
|
+
@abc.abstractmethod
|
|
1046
|
+
def sandbox(self) -> Sandbox | None:
|
|
800
1047
|
"""Returns the sandbox that the feature is running in.
|
|
801
1048
|
|
|
802
1049
|
Returns:
|
|
803
|
-
The sandbox that the feature is running in.
|
|
804
|
-
|
|
805
|
-
Raises:
|
|
806
|
-
AssertError: If the feature is not set up with a sandbox yet.
|
|
1050
|
+
The sandbox that the feature is running in. None if the feature is not
|
|
1051
|
+
sandbox-based or not yet bound with a sandbox.
|
|
807
1052
|
"""
|
|
808
1053
|
|
|
809
1054
|
@abc.abstractmethod
|
|
@@ -811,8 +1056,17 @@ class Feature(pg.Object):
|
|
|
811
1056
|
"""Returns True if the feature is applicable to the given image."""
|
|
812
1057
|
|
|
813
1058
|
@abc.abstractmethod
|
|
814
|
-
def setup(self, sandbox: Sandbox) -> None:
|
|
815
|
-
"""Sets up the feature
|
|
1059
|
+
def setup(self, sandbox: Sandbox | None = None) -> None:
|
|
1060
|
+
"""Sets up the feature.
|
|
1061
|
+
|
|
1062
|
+
For sandbox-based features, the setup will be called when a sandbox is
|
|
1063
|
+
started for the first time.
|
|
1064
|
+
|
|
1065
|
+
For non-sandbox-based features, the setup will be called when the
|
|
1066
|
+
environment starts.
|
|
1067
|
+
|
|
1068
|
+
When a feature's `setup` is called, its `teardown` is guaranteed to be
|
|
1069
|
+
called.
|
|
816
1070
|
|
|
817
1071
|
State transitions:
|
|
818
1072
|
SETTING_UP -> READY: When setup succeeds.
|
|
@@ -915,17 +1169,54 @@ class Feature(pg.Object):
|
|
|
915
1169
|
will not be housekeeping.
|
|
916
1170
|
"""
|
|
917
1171
|
|
|
1172
|
+
@abc.abstractmethod
|
|
1173
|
+
def track_activity(
|
|
1174
|
+
self,
|
|
1175
|
+
name: str,
|
|
1176
|
+
**kwargs: Any
|
|
1177
|
+
) -> ContextManager[None]:
|
|
1178
|
+
"""Context manager that tracks a feature activity.
|
|
1179
|
+
|
|
1180
|
+
Args:
|
|
1181
|
+
name: The name of the activity.
|
|
1182
|
+
**kwargs: Additional keyword arguments to pass to the activity handler.
|
|
1183
|
+
|
|
1184
|
+
Returns:
|
|
1185
|
+
A context manager that tracks the activity, including duration and error.
|
|
1186
|
+
"""
|
|
1187
|
+
|
|
918
1188
|
@property
|
|
919
1189
|
def session_id(self) -> str | None:
|
|
920
1190
|
"""Returns the current user session identifier."""
|
|
921
|
-
|
|
922
|
-
|
|
1191
|
+
if self.is_sandbox_based:
|
|
1192
|
+
return self.sandbox.session_id
|
|
1193
|
+
return self._non_sandbox_based_session_id
|
|
923
1194
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1195
|
+
@contextlib.contextmanager
|
|
1196
|
+
def new_session(self, session_id: str) -> Iterator['Feature']:
|
|
1197
|
+
"""Context manager for obtaining a non-sandbox-based feature session."""
|
|
1198
|
+
assert not self.is_sandbox_based, (
|
|
1199
|
+
'Applicable only to non-sandbox-based features. '
|
|
1200
|
+
'For sandbox-based features, use `Sandbox.new_session` instead.'
|
|
928
1201
|
)
|
|
1202
|
+
try:
|
|
1203
|
+
self._non_sandbox_based_session_id = session_id
|
|
1204
|
+
self.setup_session()
|
|
1205
|
+
yield self
|
|
1206
|
+
finally:
|
|
1207
|
+
try:
|
|
1208
|
+
# Since the session is ended, we don't want to raise any errors during
|
|
1209
|
+
# session teardown to the user. So we catch all exceptions here.
|
|
1210
|
+
# However the event handler will still be notified and log the error.
|
|
1211
|
+
self.teardown_session()
|
|
1212
|
+
except BaseException: # pylint: disable=broad-except
|
|
1213
|
+
pass
|
|
1214
|
+
self._non_sandbox_based_session_id = None
|
|
1215
|
+
|
|
1216
|
+
def _on_bound(self) -> None:
|
|
1217
|
+
"""Called when the feature is bound to a sandbox."""
|
|
1218
|
+
super()._on_bound()
|
|
1219
|
+
self._non_sandbox_based_session_id = None
|
|
929
1220
|
|
|
930
1221
|
|
|
931
1222
|
def _make_path_compatible(id_str: str) -> str:
|
|
@@ -982,8 +1273,8 @@ def treat_as_sandbox_state_error(
|
|
|
982
1273
|
return decorator
|
|
983
1274
|
|
|
984
1275
|
|
|
985
|
-
def
|
|
986
|
-
"""Decorator for Sandbox/Feature methods to log sandbox activity."""
|
|
1276
|
+
def log_activity(name: str | None = None):
|
|
1277
|
+
"""Decorator for Sandbox/Feature methods to log sandbox/feature activity."""
|
|
987
1278
|
|
|
988
1279
|
def decorator(func):
|
|
989
1280
|
signature = pg.typing.get_signature(func)
|
|
@@ -1013,167 +1304,89 @@ def log_sandbox_activity(name: str | None = None):
|
|
|
1013
1304
|
|
|
1014
1305
|
|
|
1015
1306
|
#
|
|
1016
|
-
# Interface for
|
|
1307
|
+
# Interface for event handlers.
|
|
1017
1308
|
#
|
|
1018
1309
|
|
|
1019
1310
|
|
|
1020
|
-
class
|
|
1021
|
-
"""Base class for
|
|
1311
|
+
class _EnvironmentEventHandler:
|
|
1312
|
+
"""Base class for event handlers of an environment."""
|
|
1022
1313
|
|
|
1023
|
-
def
|
|
1024
|
-
|
|
1025
|
-
environment: Environment,
|
|
1026
|
-
sandbox: Sandbox,
|
|
1027
|
-
session_id: str,
|
|
1028
|
-
duration: float,
|
|
1029
|
-
error: BaseException | None
|
|
1030
|
-
) -> None:
|
|
1031
|
-
"""Called when a sandbox session starts.
|
|
1314
|
+
def on_environment_starting(self, environment: Environment) -> None:
|
|
1315
|
+
"""Called when the environment is getting started.
|
|
1032
1316
|
|
|
1033
1317
|
Args:
|
|
1034
1318
|
environment: The environment.
|
|
1035
|
-
sandbox: The sandbox.
|
|
1036
|
-
session_id: The session ID.
|
|
1037
|
-
duration: The time spent on starting the session.
|
|
1038
|
-
error: The error that caused the session to start. If None, the session
|
|
1039
|
-
started normally.
|
|
1040
1319
|
"""
|
|
1041
1320
|
|
|
1042
|
-
def
|
|
1321
|
+
def on_environment_start(
|
|
1043
1322
|
self,
|
|
1044
1323
|
environment: Environment,
|
|
1045
|
-
sandbox: Sandbox,
|
|
1046
|
-
session_id: str,
|
|
1047
1324
|
duration: float,
|
|
1048
|
-
lifetime: float,
|
|
1049
1325
|
error: BaseException | None
|
|
1050
1326
|
) -> None:
|
|
1051
|
-
"""Called when
|
|
1327
|
+
"""Called when the environment is started.
|
|
1052
1328
|
|
|
1053
1329
|
Args:
|
|
1054
1330
|
environment: The environment.
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
lifetime: The session lifetime in seconds.
|
|
1059
|
-
error: The error that caused the session to end. If None, the session
|
|
1060
|
-
ended normally.
|
|
1331
|
+
duration: The environment start duration in seconds.
|
|
1332
|
+
error: The error that failed the environment start. If None, the
|
|
1333
|
+
environment started normally.
|
|
1061
1334
|
"""
|
|
1062
1335
|
|
|
1063
|
-
|
|
1064
|
-
class _FeatureEventHandler:
|
|
1065
|
-
"""Base class for feature event handlers."""
|
|
1066
|
-
|
|
1067
|
-
def on_feature_setup(
|
|
1336
|
+
def on_environment_housekeep(
|
|
1068
1337
|
self,
|
|
1069
1338
|
environment: Environment,
|
|
1070
|
-
|
|
1071
|
-
feature: Feature,
|
|
1339
|
+
counter: int,
|
|
1072
1340
|
duration: float,
|
|
1073
|
-
error: BaseException | None
|
|
1341
|
+
error: BaseException | None,
|
|
1342
|
+
**kwargs
|
|
1074
1343
|
) -> None:
|
|
1075
|
-
"""Called when a
|
|
1344
|
+
"""Called when the environment finishes a round of housekeeping.
|
|
1076
1345
|
|
|
1077
1346
|
Args:
|
|
1078
1347
|
environment: The environment.
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1348
|
+
counter: Zero-based counter of the housekeeping round.
|
|
1349
|
+
duration: The environment start duration in seconds.
|
|
1350
|
+
error: The error that failed the housekeeping. If None, the
|
|
1351
|
+
housekeeping succeeded.
|
|
1352
|
+
**kwargs: Environment-specific properties computed during housekeeping.
|
|
1084
1353
|
"""
|
|
1085
1354
|
|
|
1086
|
-
def
|
|
1355
|
+
def on_environment_shutting_down(
|
|
1087
1356
|
self,
|
|
1088
1357
|
environment: Environment,
|
|
1089
|
-
|
|
1090
|
-
feature: Feature,
|
|
1091
|
-
duration: float,
|
|
1092
|
-
error: BaseException | None
|
|
1358
|
+
offline_duration: float,
|
|
1093
1359
|
) -> None:
|
|
1094
|
-
"""Called when
|
|
1360
|
+
"""Called when the environment is shutting down.
|
|
1095
1361
|
|
|
1096
1362
|
Args:
|
|
1097
1363
|
environment: The environment.
|
|
1098
|
-
|
|
1099
|
-
feature: The feature.
|
|
1100
|
-
duration: The feature teardown duration in seconds.
|
|
1101
|
-
error: The error happened during the feature teardown. If None,
|
|
1102
|
-
the feature teardown performed normally.
|
|
1364
|
+
offline_duration: The environment offline duration in seconds.
|
|
1103
1365
|
"""
|
|
1104
1366
|
|
|
1105
|
-
def
|
|
1367
|
+
def on_environment_shutdown(
|
|
1106
1368
|
self,
|
|
1107
1369
|
environment: Environment,
|
|
1108
|
-
sandbox: Sandbox,
|
|
1109
|
-
feature: Feature,
|
|
1110
|
-
session_id: str,
|
|
1111
1370
|
duration: float,
|
|
1371
|
+
lifetime: float,
|
|
1112
1372
|
error: BaseException | None
|
|
1113
1373
|
) -> None:
|
|
1114
|
-
"""Called when
|
|
1115
|
-
|
|
1116
|
-
Args:
|
|
1117
|
-
environment: The environment.
|
|
1118
|
-
sandbox: The sandbox.
|
|
1119
|
-
feature: The feature.
|
|
1120
|
-
session_id: The session ID.
|
|
1121
|
-
duration: The feature teardown session duration in seconds.
|
|
1122
|
-
error: The error happened during the feature teardown session. If
|
|
1123
|
-
None, the feature teardown session performed normally.
|
|
1124
|
-
"""
|
|
1125
|
-
|
|
1126
|
-
def on_feature_setup_session(
|
|
1127
|
-
self,
|
|
1128
|
-
environment: Environment,
|
|
1129
|
-
sandbox: Sandbox,
|
|
1130
|
-
feature: Feature,
|
|
1131
|
-
session_id: str | None,
|
|
1132
|
-
duration: float,
|
|
1133
|
-
error: BaseException | None,
|
|
1134
|
-
) -> None:
|
|
1135
|
-
"""Called when a feature is setup with a session.
|
|
1136
|
-
|
|
1137
|
-
Args:
|
|
1138
|
-
environment: The environment.
|
|
1139
|
-
sandbox: The sandbox.
|
|
1140
|
-
feature: The feature.
|
|
1141
|
-
session_id: The session ID.
|
|
1142
|
-
duration: The feature setup session duration in seconds.
|
|
1143
|
-
error: The error happened during the feature setup session. If
|
|
1144
|
-
None, the feature setup session performed normally.
|
|
1145
|
-
"""
|
|
1146
|
-
|
|
1147
|
-
def on_feature_housekeep(
|
|
1148
|
-
self,
|
|
1149
|
-
environment: Environment,
|
|
1150
|
-
sandbox: Sandbox,
|
|
1151
|
-
feature: Feature,
|
|
1152
|
-
counter: int,
|
|
1153
|
-
duration: float,
|
|
1154
|
-
error: BaseException | None,
|
|
1155
|
-
**kwargs,
|
|
1156
|
-
) -> None:
|
|
1157
|
-
"""Called when a sandbox feature is housekeeping.
|
|
1374
|
+
"""Called when the environment is shutdown.
|
|
1158
1375
|
|
|
1159
1376
|
Args:
|
|
1160
1377
|
environment: The environment.
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
error: The error happened during the feature housekeeping. If None, the
|
|
1166
|
-
feature housekeeping normally.
|
|
1167
|
-
**kwargs: Feature-specific properties computed during housekeeping.
|
|
1378
|
+
duration: The environment shutdown duration in seconds.
|
|
1379
|
+
lifetime: The environment lifetime in seconds.
|
|
1380
|
+
error: The error that caused the environment to shutdown. If None, the
|
|
1381
|
+
environment shutdown normally.
|
|
1168
1382
|
"""
|
|
1169
1383
|
|
|
1170
1384
|
|
|
1171
|
-
class _SandboxEventHandler
|
|
1385
|
+
class _SandboxEventHandler:
|
|
1172
1386
|
"""Base class for sandbox event handlers."""
|
|
1173
1387
|
|
|
1174
1388
|
def on_sandbox_start(
|
|
1175
1389
|
self,
|
|
1176
|
-
environment: Environment,
|
|
1177
1390
|
sandbox: Sandbox,
|
|
1178
1391
|
duration: float,
|
|
1179
1392
|
error: BaseException | None
|
|
@@ -1181,7 +1394,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1181
1394
|
"""Called when a sandbox is started.
|
|
1182
1395
|
|
|
1183
1396
|
Args:
|
|
1184
|
-
environment: The environment.
|
|
1185
1397
|
sandbox: The sandbox.
|
|
1186
1398
|
duration: The time spent on starting the sandbox.
|
|
1187
1399
|
error: The error that caused the sandbox to start. If None, the sandbox
|
|
@@ -1190,7 +1402,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1190
1402
|
|
|
1191
1403
|
def on_sandbox_status_change(
|
|
1192
1404
|
self,
|
|
1193
|
-
environment: Environment,
|
|
1194
1405
|
sandbox: Sandbox,
|
|
1195
1406
|
old_status: 'Sandbox.Status',
|
|
1196
1407
|
new_status: 'Sandbox.Status',
|
|
@@ -1199,7 +1410,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1199
1410
|
"""Called when a sandbox status changes.
|
|
1200
1411
|
|
|
1201
1412
|
Args:
|
|
1202
|
-
environment: The environment.
|
|
1203
1413
|
sandbox: The sandbox.
|
|
1204
1414
|
old_status: The old sandbox status.
|
|
1205
1415
|
new_status: The new sandbox status.
|
|
@@ -1208,7 +1418,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1208
1418
|
|
|
1209
1419
|
def on_sandbox_shutdown(
|
|
1210
1420
|
self,
|
|
1211
|
-
environment: Environment,
|
|
1212
1421
|
sandbox: Sandbox,
|
|
1213
1422
|
duration: float,
|
|
1214
1423
|
lifetime: float,
|
|
@@ -1217,7 +1426,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1217
1426
|
"""Called when a sandbox is shutdown.
|
|
1218
1427
|
|
|
1219
1428
|
Args:
|
|
1220
|
-
environment: The environment.
|
|
1221
1429
|
sandbox: The sandbox.
|
|
1222
1430
|
duration: The time spent on shutting down the sandbox.
|
|
1223
1431
|
lifetime: The sandbox lifetime in seconds.
|
|
@@ -1225,12 +1433,46 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1225
1433
|
sandbox shutdown normally.
|
|
1226
1434
|
"""
|
|
1227
1435
|
|
|
1436
|
+
def on_sandbox_session_start(
|
|
1437
|
+
self,
|
|
1438
|
+
sandbox: Sandbox,
|
|
1439
|
+
session_id: str,
|
|
1440
|
+
duration: float,
|
|
1441
|
+
error: BaseException | None
|
|
1442
|
+
) -> None:
|
|
1443
|
+
"""Called when a sandbox session starts.
|
|
1444
|
+
|
|
1445
|
+
Args:
|
|
1446
|
+
sandbox: The sandbox.
|
|
1447
|
+
session_id: The session ID.
|
|
1448
|
+
duration: The time spent on starting the session.
|
|
1449
|
+
error: The error that caused the session to start. If None, the session
|
|
1450
|
+
started normally.
|
|
1451
|
+
"""
|
|
1452
|
+
|
|
1453
|
+
def on_sandbox_session_end(
|
|
1454
|
+
self,
|
|
1455
|
+
sandbox: Sandbox,
|
|
1456
|
+
session_id: str,
|
|
1457
|
+
duration: float,
|
|
1458
|
+
lifetime: float,
|
|
1459
|
+
error: BaseException | None
|
|
1460
|
+
) -> None:
|
|
1461
|
+
"""Called when a sandbox session ends.
|
|
1462
|
+
|
|
1463
|
+
Args:
|
|
1464
|
+
sandbox: The sandbox.
|
|
1465
|
+
session_id: The session ID.
|
|
1466
|
+
duration: The time spent on ending the session.
|
|
1467
|
+
lifetime: The session lifetime in seconds.
|
|
1468
|
+
error: The error that caused the session to end. If None, the session
|
|
1469
|
+
ended normally.
|
|
1470
|
+
"""
|
|
1471
|
+
|
|
1228
1472
|
def on_sandbox_activity(
|
|
1229
1473
|
self,
|
|
1230
1474
|
name: str,
|
|
1231
|
-
environment: Environment,
|
|
1232
1475
|
sandbox: Sandbox,
|
|
1233
|
-
feature: Feature | None,
|
|
1234
1476
|
session_id: str | None,
|
|
1235
1477
|
duration: float,
|
|
1236
1478
|
error: BaseException | None,
|
|
@@ -1240,9 +1482,7 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1240
1482
|
|
|
1241
1483
|
Args:
|
|
1242
1484
|
name: The name of the sandbox activity.
|
|
1243
|
-
environment: The environment.
|
|
1244
1485
|
sandbox: The sandbox.
|
|
1245
|
-
feature: The feature that is associated with the sandbox activity.
|
|
1246
1486
|
session_id: The session ID.
|
|
1247
1487
|
duration: The sandbox activity duration in seconds.
|
|
1248
1488
|
error: The error that caused the sandbox activity to perform. If None,
|
|
@@ -1252,7 +1492,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1252
1492
|
|
|
1253
1493
|
def on_sandbox_housekeep(
|
|
1254
1494
|
self,
|
|
1255
|
-
environment: Environment,
|
|
1256
1495
|
sandbox: Sandbox,
|
|
1257
1496
|
counter: int,
|
|
1258
1497
|
duration: float,
|
|
@@ -1262,7 +1501,6 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1262
1501
|
"""Called when a sandbox finishes a round of housekeeping.
|
|
1263
1502
|
|
|
1264
1503
|
Args:
|
|
1265
|
-
environment: The environment.
|
|
1266
1504
|
sandbox: The sandbox.
|
|
1267
1505
|
counter: Zero-based counter of the housekeeping round.
|
|
1268
1506
|
duration: The sandbox housekeeping duration in seconds.
|
|
@@ -1272,75 +1510,131 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
|
|
|
1272
1510
|
"""
|
|
1273
1511
|
|
|
1274
1512
|
|
|
1275
|
-
class
|
|
1276
|
-
"""Base class for event handlers
|
|
1513
|
+
class _FeatureEventHandler:
|
|
1514
|
+
"""Base class for feature event handlers."""
|
|
1277
1515
|
|
|
1278
|
-
def
|
|
1279
|
-
|
|
1516
|
+
def on_feature_setup(
|
|
1517
|
+
self,
|
|
1518
|
+
feature: Feature,
|
|
1519
|
+
duration: float,
|
|
1520
|
+
error: BaseException | None
|
|
1521
|
+
) -> None:
|
|
1522
|
+
"""Called when a sandbox feature is setup.
|
|
1523
|
+
|
|
1524
|
+
Applicable to both sandbox-based and non-sandbox-based features.
|
|
1280
1525
|
|
|
1281
1526
|
Args:
|
|
1282
|
-
|
|
1527
|
+
feature: The feature.
|
|
1528
|
+
duration: The feature setup duration in seconds.
|
|
1529
|
+
error: The error happened during the feature setup. If None,
|
|
1530
|
+
the feature setup performed normally.
|
|
1283
1531
|
"""
|
|
1284
1532
|
|
|
1285
|
-
def
|
|
1533
|
+
def on_feature_teardown(
|
|
1286
1534
|
self,
|
|
1287
|
-
|
|
1535
|
+
feature: Feature,
|
|
1288
1536
|
duration: float,
|
|
1289
1537
|
error: BaseException | None
|
|
1290
1538
|
) -> None:
|
|
1291
|
-
"""Called when
|
|
1539
|
+
"""Called when a sandbox feature is teardown.
|
|
1540
|
+
|
|
1541
|
+
Applicable to both sandbox-based and non-sandbox-based features.
|
|
1292
1542
|
|
|
1293
1543
|
Args:
|
|
1294
|
-
|
|
1295
|
-
duration: The
|
|
1296
|
-
error: The error
|
|
1297
|
-
|
|
1544
|
+
feature: The feature.
|
|
1545
|
+
duration: The feature teardown duration in seconds.
|
|
1546
|
+
error: The error happened during the feature teardown. If None,
|
|
1547
|
+
the feature teardown performed normally.
|
|
1298
1548
|
"""
|
|
1299
1549
|
|
|
1300
|
-
def
|
|
1550
|
+
def on_feature_teardown_session(
|
|
1301
1551
|
self,
|
|
1302
|
-
|
|
1303
|
-
|
|
1552
|
+
feature: Feature,
|
|
1553
|
+
session_id: str,
|
|
1554
|
+
duration: float,
|
|
1555
|
+
error: BaseException | None
|
|
1556
|
+
) -> None:
|
|
1557
|
+
"""Called when a feature is teardown with a session.
|
|
1558
|
+
|
|
1559
|
+
Applicable to both sandbox-based and non-sandbox-based features.
|
|
1560
|
+
|
|
1561
|
+
Args:
|
|
1562
|
+
feature: The feature.
|
|
1563
|
+
session_id: The session ID.
|
|
1564
|
+
duration: The feature teardown session duration in seconds.
|
|
1565
|
+
error: The error happened during the feature teardown session. If
|
|
1566
|
+
None, the feature teardown session performed normally.
|
|
1567
|
+
"""
|
|
1568
|
+
|
|
1569
|
+
def on_feature_setup_session(
|
|
1570
|
+
self,
|
|
1571
|
+
feature: Feature,
|
|
1572
|
+
session_id: str | None,
|
|
1304
1573
|
duration: float,
|
|
1305
1574
|
error: BaseException | None,
|
|
1306
|
-
**kwargs
|
|
1307
1575
|
) -> None:
|
|
1308
|
-
"""Called when
|
|
1576
|
+
"""Called when a feature is setup with a session.
|
|
1577
|
+
|
|
1578
|
+
Applicable to both sandbox-based and non-sandbox-based features.
|
|
1309
1579
|
|
|
1310
1580
|
Args:
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
duration: The
|
|
1314
|
-
error: The error
|
|
1315
|
-
|
|
1316
|
-
**kwargs: Environment-specific properties computed during housekeeping.
|
|
1581
|
+
feature: The feature.
|
|
1582
|
+
session_id: The session ID.
|
|
1583
|
+
duration: The feature setup session duration in seconds.
|
|
1584
|
+
error: The error happened during the feature setup session. If
|
|
1585
|
+
None, the feature setup session performed normally.
|
|
1317
1586
|
"""
|
|
1318
1587
|
|
|
1319
|
-
def
|
|
1588
|
+
def on_feature_activity(
|
|
1320
1589
|
self,
|
|
1321
|
-
|
|
1322
|
-
|
|
1590
|
+
name: str,
|
|
1591
|
+
feature: Feature,
|
|
1592
|
+
session_id: str | None,
|
|
1593
|
+
duration: float,
|
|
1594
|
+
error: BaseException | None,
|
|
1595
|
+
**kwargs
|
|
1323
1596
|
) -> None:
|
|
1324
|
-
"""Called when
|
|
1597
|
+
"""Called when a feature activity is performed.
|
|
1598
|
+
|
|
1599
|
+
Applicable to both sandbox-based and non-sandbox-based features.
|
|
1325
1600
|
|
|
1326
1601
|
Args:
|
|
1327
|
-
|
|
1328
|
-
|
|
1602
|
+
name: The name of the feature activity.
|
|
1603
|
+
feature: The feature.
|
|
1604
|
+
session_id: The session ID. Session ID could be None if a feature
|
|
1605
|
+
activity is performed when setting up a session
|
|
1606
|
+
(e.g. BaseEnvironment.proactive_session_setup is on)
|
|
1607
|
+
duration: The feature activity duration in seconds.
|
|
1608
|
+
error: The error happened during the feature activity. If None,
|
|
1609
|
+
the feature activity performed normally.
|
|
1610
|
+
**kwargs: The keyword arguments of the feature activity.
|
|
1329
1611
|
"""
|
|
1330
1612
|
|
|
1331
|
-
def
|
|
1613
|
+
def on_feature_housekeep(
|
|
1332
1614
|
self,
|
|
1333
|
-
|
|
1615
|
+
feature: Feature,
|
|
1616
|
+
counter: int,
|
|
1334
1617
|
duration: float,
|
|
1335
|
-
|
|
1336
|
-
|
|
1618
|
+
error: BaseException | None,
|
|
1619
|
+
**kwargs,
|
|
1337
1620
|
) -> None:
|
|
1338
|
-
"""Called when
|
|
1621
|
+
"""Called when a sandbox feature is housekeeping.
|
|
1622
|
+
|
|
1623
|
+
Applicable to both sandbox-based and non-sandbox-based features.
|
|
1339
1624
|
|
|
1340
1625
|
Args:
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
error: The error
|
|
1345
|
-
|
|
1626
|
+
feature: The feature.
|
|
1627
|
+
counter: Zero-based counter of the housekeeping round.
|
|
1628
|
+
duration: The feature housekeeping duration in seconds.
|
|
1629
|
+
error: The error happened during the feature housekeeping. If None, the
|
|
1630
|
+
feature housekeeping normally.
|
|
1631
|
+
**kwargs: Feature-specific properties computed during housekeeping.
|
|
1346
1632
|
"""
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
class EventHandler(
|
|
1636
|
+
_EnvironmentEventHandler,
|
|
1637
|
+
_SandboxEventHandler,
|
|
1638
|
+
_FeatureEventHandler,
|
|
1639
|
+
):
|
|
1640
|
+
"""Base class for langfun/env handlers."""
|