@@ -36,25 +36,17 @@ class InstanceInfo:
3636 exercise_version : int
3737
3838
39+ _cached_info : ty .Optional [InstanceInfo ] = None
40+
41+
3942def _sign_request (instance_id : int , key : bytes , payload : ty .Dict [str , ty .Any ]) -> str :
4043 signer = TimedSerializer (key , salt = "from-container-to-web" )
4144 payload = dict (payload )
4245 payload ["instance_id" ] = instance_id
4346 return signer .dumps (payload ) # type: ignore[no-any-return]
4447
4548
46- def get_instance_info (timeout_s : float = 10.0 ) -> InstanceInfo :
47- """Fetch trusted details about the current instance from the webapp.
48-
49- All fields originate from the webserver's database; the request is signed
50- with the instance-specific key at ``/etc/key``, which is not accessible to
51- the student user. Use this to branch test behavior on user role without
52- trusting anything the student can tamper with.
53-
54- Raises:
55- InstanceInfoError: if credentials are missing, the webapp is
56- unreachable, or the response cannot be parsed.
57- """
49+ def _fetch_instance_info (timeout_s : float ) -> InstanceInfo :
5850 try :
5951 key = KEY_PATH .read_bytes ()
6052 instance_id = int (INSTANCE_ID_PATH .read_text ().strip ())
@@ -83,3 +75,30 @@ def get_instance_info(timeout_s: float = 10.0) -> InstanceInfo:
8375 return InstanceInfo (** data )
8476 except TypeError as e :
8577 raise InstanceInfoError (f"Response missing or extra fields: { e } " ) from e
78+
79+
80+ def get_instance_info (timeout_s : float = 10.0 , * , refresh : bool = False ) -> InstanceInfo :
81+ """Fetch trusted details about the current instance from the webapp.
82+
83+ All fields originate from the webserver's database; the request is signed
84+ with the instance-specific key at ``/etc/key``, which is not accessible to
85+ the student user. Use this to branch test behavior on user role without
86+ trusting anything the student can tamper with.
87+
88+ The result is cached process-wide on first success (instance metadata does
89+ not change during a test run, and the endpoint is rate-limited). Pass
90+ ``refresh=True`` to bypass the cache and force a new fetch. Failed
91+ requests are not cached, so a transient outage does not poison subsequent
92+ calls.
93+
94+ Raises:
95+ InstanceInfoError: if credentials are missing, the webapp is
96+ unreachable, or the response cannot be parsed.
97+ """
98+ global _cached_info
99+ if _cached_info is not None and not refresh :
100+ return _cached_info
101+
102+ info = _fetch_instance_info (timeout_s )
103+ _cached_info = info
104+ return info
0 commit comments