simulo 0.26.0__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.
Files changed (55) hide show
  1. simulo/__init__.py +433 -0
  2. simulo/_client/__init__.py +6 -0
  3. simulo/_client/_entrypoint.py +313 -0
  4. simulo/_client/_mounts.py +25 -0
  5. simulo/_client/_runner.py +186 -0
  6. simulo/_client/_secure_downloads.py +1181 -0
  7. simulo/_client/app.py +1308 -0
  8. simulo/_client/asset.py +331 -0
  9. simulo/_client/asset_api.py +517 -0
  10. simulo/_client/asset_package.py +1103 -0
  11. simulo/_client/asset_pins.py +187 -0
  12. simulo/_client/builtin_aliases.py +107 -0
  13. simulo/_client/bundle.py +254 -0
  14. simulo/_client/cancel_api.py +104 -0
  15. simulo/_client/cli.py +9063 -0
  16. simulo/_client/config.py +186 -0
  17. simulo/_client/credentials.py +210 -0
  18. simulo/_client/discovery.py +214 -0
  19. simulo/_client/export_api.py +212 -0
  20. simulo/_client/export_bundle.py +296 -0
  21. simulo/_client/facades.py +581 -0
  22. simulo/_client/http.py +414 -0
  23. simulo/_client/identity_api.py +117 -0
  24. simulo/_client/install_samples.py +267 -0
  25. simulo/_client/jobs_api.py +224 -0
  26. simulo/_client/learning.py +393 -0
  27. simulo/_client/login.py +319 -0
  28. simulo/_client/mode.py +29 -0
  29. simulo/_client/outputs.py +116 -0
  30. simulo/_client/packaging.py +445 -0
  31. simulo/_client/preflight_api.py +186 -0
  32. simulo/_client/preflight_render.py +200 -0
  33. simulo/_client/registry.py +98 -0
  34. simulo/_client/runtime.py +185 -0
  35. simulo/_client/runtime_display.py +90 -0
  36. simulo/_client/seed_ref.py +76 -0
  37. simulo/_client/stub.py +41 -0
  38. simulo/_client/submit_api.py +1057 -0
  39. simulo/_client/templates/__init__.py +21 -0
  40. simulo/_client/templates/inference/app.py.tmpl +316 -0
  41. simulo/_client/templates/inference/simuloignore.tmpl +30 -0
  42. simulo/_client/templates/scenario/app.py.tmpl +93 -0
  43. simulo/_client/templates/scenario/simuloignore.tmpl +27 -0
  44. simulo/_client/templates/training/app.py.tmpl +235 -0
  45. simulo/_client/templates/training/simuloignore.tmpl +29 -0
  46. simulo/_client/view_fragment.py +21 -0
  47. simulo/_client/view_session_api.py +122 -0
  48. simulo/_client/volume.py +71 -0
  49. simulo/callbacks.py +274 -0
  50. simulo/py.typed +0 -0
  51. simulo-0.26.0.dist-info/METADATA +130 -0
  52. simulo-0.26.0.dist-info/RECORD +55 -0
  53. simulo-0.26.0.dist-info/WHEEL +5 -0
  54. simulo-0.26.0.dist-info/entry_points.txt +2 -0
  55. simulo-0.26.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,393 @@
1
+ """Mode-aware lazy resolution of the learning / authoring surface.
2
+
3
+ The thin client exposes the training & simulation authoring names — ``Task``,
4
+ ``Scene``, ``Robot``, ``LearningEnv``, ``RLTrainer``, components, … — as clean
5
+ top-level ``simulo.*`` attributes via a PEP 562 ``__getattr__`` in
6
+ ``simulo/__init__.py`` that delegates here. The point is to let a user author a
7
+ normal training program with clean names that **submits locally without the heavy
8
+ runtime installed** and is **executed unchanged** by the backend runner.
9
+
10
+ What a name resolves to depends on the thin client's current submit/execution mode:
11
+
12
+ * **discovery** (*submit*, the default) — return a lean, torch-free **stand-in** so
13
+ the user app imports without ``torch`` / ``simulo.core``:
14
+
15
+ - *Subclassable* names (``Task``, ``Scenario``, ``Policy``, ``Player``,
16
+ ``Trainer`` and the
17
+ three component bases) resolve to a concrete class derived from the matching
18
+ ``simulo.interfaces.runtime`` ``@runtime_checkable`` Protocol, so
19
+ ``class CartpoleTask(simulo.Task)`` is definable at module level AND
20
+ ``isinstance(CartpoleTask(), TaskProtocol)`` holds — the task is nominally
21
+ contract-typed without importing any heavy runtime.
22
+ - *Value / config + in-body-only* names (``Scene``, ``Robot``, ``LearningEnv``,
23
+ ``RLTrainer``, …) resolve to an inert :class:`Stub`, so
24
+ ``simulo.Scene(...)`` / ``simulo.LearningEnv(...)`` are harmless if touched
25
+ while a class or job body is *read* (never executed) at submit. The engine
26
+ authoring vocabulary — ``Camera`` and its sibling sensors, ``Simulation``,
27
+ the controllers, the actuators, and the ``*Set`` component containers —
28
+ rides the exact same Stub mechanism: their real definitions live in
29
+ ``simulo.core`` (torch-adjacent modules), so they cannot be eager imports
30
+ the way the RELOCATED authoring value types below are.
31
+ - *Relocated authoring value types* (``Pose``, ``Material``, ``Physics``,
32
+ ``Light`` since Plan C Wave C1; ``Terrain``, ``Entity``, ``Visual`` since
33
+ Wave C2; ``RecordConfig`` and ``World`` since Wave C4 — dependency-free,
34
+ single-definition classes living in
35
+ ``simulo.interfaces.authoring``) resolve to the **real
36
+ class in BOTH modes**: they need no heavy runtime, so submit-time authoring
37
+ gets real construction and precise static typing instead of an inert stub
38
+ (``RecordConfig`` even validates its fields at construction, so a bad
39
+ profile now fails at submit instead of on the worker), and
40
+ ``isinstance`` agrees everywhere because only one definition exists.
41
+ ``World`` names the authoring concept of a prebuilt place you *bring*
42
+ (``World.usd(...)``); ``Terrain`` keeps the ground you *generate*
43
+ (``plane``/``rough``). ``World`` is unrelated to the backend's sim
44
+ clock, ``core.simulation.Simulation`` — a different class entirely.
45
+
46
+ * **execution** (*run*, set only by the ``simulo-backend`` runner on a worker that
47
+ has the heavy runtime) — lazily import the real defining module and return the
48
+ real class. Most names live on the ``simulo.core`` aggregate; a few resolve from
49
+ a per-name source module recorded in :data:`_EXECUTION_ALIAS` (``Scenario`` from
50
+ ``simulo.scenario``, the component bases from their ``simulo.core.<module>``,
51
+ the relocated value types from ``simulo.interfaces.authoring``).
52
+
53
+ Importing the heavy runtime happens **only** in execution mode, and only on first
54
+ access (then cached). At submit ``simulo.core`` / ``simulo.recording`` are never
55
+ imported.
56
+
57
+ Two non-learning names ride the same mechanism:
58
+
59
+ * ``simulo.save_output`` — the execution-time output registration helper.
60
+ Its execution-mode target is the torch-free implementation in the thin
61
+ client's internal outputs module — thin client code, but its call
62
+ reaches the ``simulo-backend`` registry (``simulo.outputs``), so at
63
+ discovery it resolves to an inert :class:`Stub` exactly like the
64
+ value/config names.
65
+ * ``simulo.run`` — the scenario execution loop (``simulo.scenario.run`` on
66
+ the worker). Called from a job body exactly like ``save_output``
67
+ (in-body-only, so a discovery-mode touch is harmless and never executes),
68
+ it resolves at discovery to an inert :class:`Stub` and at execution to the
69
+ real function via :data:`_EXECUTION_ALIAS`. Before this name existed, the
70
+ scaffold and shipped examples spelled it ``from simulo.scenario import
71
+ run`` — a second import into a module the torch-free client cannot even
72
+ see, breaking the one-import rule.
73
+ """
74
+
75
+ from __future__ import annotations
76
+
77
+ import importlib
78
+
79
+ from simulo._client.mode import EXECUTION, current_mode
80
+ from simulo._client.stub import Stub
81
+ from simulo.interfaces.authoring import Entity, Light, Material, Physics, Pose, RecordConfig, Terrain, Visual, World
82
+ from simulo.interfaces.runtime import (
83
+ ObservationComponentProtocol,
84
+ PlayerProtocol,
85
+ PolicyProtocol,
86
+ RewardComponentProtocol,
87
+ ScenarioProtocol,
88
+ TaskProtocol,
89
+ TerminationConditionProtocol,
90
+ TrainerProtocol,
91
+ )
92
+
93
+ #: Every name exposed lazily and mode-aware on ``simulo`` (the PEP 562
94
+ #: surface). Mostly the learning/authoring names; ``save_output`` and
95
+ #: ``run`` are the two non-learning members — execution-time platform helpers
96
+ #: that need exactly the same resolution (discovery → inert Stub, execution →
97
+ #: the real implementation), so they ride the same mechanism rather than a
98
+ #: parallel one.
99
+ LEARNING_NAMES: frozenset[str] = frozenset(
100
+ {
101
+ # subclassable bases
102
+ "Task",
103
+ "Scenario",
104
+ "Policy",
105
+ "Player",
106
+ "Trainer",
107
+ "ObservationComponent",
108
+ "RewardComponent",
109
+ "TerminationCondition",
110
+ # value / config + in-body-only
111
+ "Scene",
112
+ "Robot",
113
+ "LearningEnv",
114
+ "RLTrainer",
115
+ "RLPlayer",
116
+ "RecordConfig",
117
+ "Pose",
118
+ "Terrain",
119
+ "World",
120
+ "Light",
121
+ "Entity",
122
+ "Material",
123
+ "Physics",
124
+ "Visual",
125
+ # engine authoring vocabulary (one-import rule, second half) — value/config classes
126
+ # and one runtime-context class whose real definition lives in
127
+ # ``simulo.core`` (torch-adjacent), so they are Stubs in discovery,
128
+ # never RELOCATED real classes like the authoring types above.
129
+ "Camera",
130
+ "CameraSpawnConfig",
131
+ "SensorOffset",
132
+ "ContactSensor",
133
+ "RayCaster",
134
+ "RayPattern",
135
+ "IMU",
136
+ "ForceTorqueSensor",
137
+ "FrameTransformer",
138
+ "FrameTransformerTarget",
139
+ "Simulation",
140
+ "ObservationSet",
141
+ "RewardSet",
142
+ "TerminationSet",
143
+ "DifferentialIKController",
144
+ "OperationalSpaceController",
145
+ "SurfaceGripperActuator",
146
+ "ParallelGripperActuator",
147
+ "GripperState",
148
+ "GripperCommand",
149
+ "ActuatorGainsConfig",
150
+ "Prop",
151
+ # execution-time output registration (Plan B PR-B8) — in-body-only
152
+ "save_output",
153
+ # scenario execution loop — in-body-only, real definition in
154
+ # simulo.scenario (simulo-backend distribution)
155
+ "run",
156
+ }
157
+ )
158
+
159
+ # --- Discovery-mode subclassable bases --------------------------------------
160
+ #
161
+ # A concrete (non-protocol) subclass of a ``@runtime_checkable`` Protocol is both
162
+ # instantiable AND structurally an instance of that Protocol on Python 3.11 and
163
+ # 3.12. Deriving the user-facing base from the Protocol gives ``class
164
+ # X(simulo.Task)`` nominal contract typing while keeping submit torch-free — the
165
+ # stand-in is the Protocol, never ``simulo.core.Task``.
166
+
167
+
168
+ class _Task(TaskProtocol):
169
+ """Discovery stand-in for :class:`simulo.core.task.Task`."""
170
+
171
+
172
+ class _Scenario(ScenarioProtocol):
173
+ """Discovery stand-in for :class:`simulo.scenario.Scenario`."""
174
+
175
+
176
+ class _Policy(PolicyProtocol):
177
+ """Discovery stand-in for :class:`simulo.core.policy.Policy`."""
178
+
179
+
180
+ class _Player(PlayerProtocol):
181
+ """Discovery stand-in for :class:`simulo.core.player.Player`."""
182
+
183
+
184
+ class _Trainer(TrainerProtocol):
185
+ """Discovery stand-in for :class:`simulo.core.trainer.Trainer`.
186
+
187
+ Joined the subclassable table in Plan C Wave C4: ``Trainer`` is the
188
+ user-extensible training-orchestration base (``RLTrainer`` is the
189
+ instantiated concrete name), so ``class MyTrainer(simulo.Trainer)`` must
190
+ be definable at module level at submit — a bare :class:`Stub` (its
191
+ pre-C4 stand-in) is not subclassable at all.
192
+ """
193
+
194
+
195
+ class _ObservationComponent(ObservationComponentProtocol):
196
+ """Discovery stand-in for ``simulo.core.observations.ObservationComponent``."""
197
+
198
+
199
+ class _RewardComponent(RewardComponentProtocol):
200
+ """Discovery stand-in for ``simulo.core.rewards.RewardComponent``."""
201
+
202
+
203
+ class _TerminationCondition(TerminationConditionProtocol):
204
+ """Discovery stand-in for ``simulo.core.terminations.TerminationCondition``."""
205
+
206
+
207
+ class _RobotDiscoveryStandIn:
208
+ """Discovery-mode ``simulo.Robot`` stand-in that ALSO enforces kind
209
+ safety — the submit-reachable half of the two-layer split: a module-level/entrypoint ``Robot(asset=<world
210
+ handle>)`` fails HERE, at construction, because that code path runs during
211
+ discovery; a task-BODY construction is structurally unreachable at
212
+ discovery (job bodies never execute at submit) and is instead caught by
213
+ the execution-mode guard in ``core/robot.py``. Otherwise inert — returns a
214
+ plain :class:`Stub`, exactly like the bare ``Stub("simulo.Robot")`` this
215
+ replaces.
216
+ """
217
+
218
+ def __call__(self, *args: object, **kwargs: object) -> Stub:
219
+ asset = kwargs.get("asset", args[0] if args else None)
220
+ require_kind = getattr(asset, "require_kind", None)
221
+ if callable(require_kind):
222
+ require_kind("robot")
223
+ return Stub("simulo.Robot()")
224
+
225
+ def __getattr__(self, item: str) -> Stub:
226
+ if item.startswith("__") and item.endswith("__"):
227
+ raise AttributeError(item)
228
+ return Stub(f"simulo.Robot.{item}")
229
+
230
+
231
+ # Discovery-mode resolution table. Subclassable names map to their Protocol-derived
232
+ # base (a stable singleton class — stable identity for ``isinstance`` / ``issubclass``);
233
+ # value/config names map to an inert :class:`Stub` (``Robot`` maps to the
234
+ # kind-checking stand-in above instead of a bare Stub — see its docstring).
235
+ # RELOCATED authoring value types (``Pose``/``Material``/``Physics``/``Light``
236
+ # from Wave C1; ``Terrain``/``Entity``/``Visual`` from Wave C2;
237
+ # ``RecordConfig``/``World`` from Wave C4)
238
+ # map to their REAL, dependency-free class — the same object execution mode
239
+ # resolves, so submit-time authoring constructs real values (see the module
240
+ # docstring).
241
+ _DISCOVERY_STANDINS: dict[str, object] = {
242
+ "Task": _Task,
243
+ "Scenario": _Scenario,
244
+ "Policy": _Policy,
245
+ "Player": _Player,
246
+ "Trainer": _Trainer,
247
+ "ObservationComponent": _ObservationComponent,
248
+ "RewardComponent": _RewardComponent,
249
+ "TerminationCondition": _TerminationCondition,
250
+ "Scene": Stub("simulo.Scene"),
251
+ "Robot": _RobotDiscoveryStandIn(),
252
+ "LearningEnv": Stub("simulo.LearningEnv"),
253
+ "RLTrainer": Stub("simulo.RLTrainer"),
254
+ "RLPlayer": Stub("simulo.RLPlayer"),
255
+ "RecordConfig": RecordConfig,
256
+ "Pose": Pose,
257
+ "Terrain": Terrain,
258
+ "World": World,
259
+ "Light": Light,
260
+ "Entity": Entity,
261
+ "Material": Material,
262
+ "Physics": Physics,
263
+ "Visual": Visual,
264
+ # Engine authoring vocabulary (one-import rule, second half): plain inert Stubs, same
265
+ # posture as Scene/LearningEnv/RLTrainer/RLPlayer above — none of these
266
+ # are subclassed, all are constructed and then only touched (attached to
267
+ # a scene/robot, read for config), which a Stub tolerates harmlessly.
268
+ "Camera": Stub("simulo.Camera"),
269
+ "CameraSpawnConfig": Stub("simulo.CameraSpawnConfig"),
270
+ "SensorOffset": Stub("simulo.SensorOffset"),
271
+ "ContactSensor": Stub("simulo.ContactSensor"),
272
+ "RayCaster": Stub("simulo.RayCaster"),
273
+ "RayPattern": Stub("simulo.RayPattern"),
274
+ "IMU": Stub("simulo.IMU"),
275
+ "ForceTorqueSensor": Stub("simulo.ForceTorqueSensor"),
276
+ "FrameTransformer": Stub("simulo.FrameTransformer"),
277
+ "FrameTransformerTarget": Stub("simulo.FrameTransformerTarget"),
278
+ "Simulation": Stub("simulo.Simulation"),
279
+ "ObservationSet": Stub("simulo.ObservationSet"),
280
+ "RewardSet": Stub("simulo.RewardSet"),
281
+ "TerminationSet": Stub("simulo.TerminationSet"),
282
+ "DifferentialIKController": Stub("simulo.DifferentialIKController"),
283
+ "OperationalSpaceController": Stub("simulo.OperationalSpaceController"),
284
+ "SurfaceGripperActuator": Stub("simulo.SurfaceGripperActuator"),
285
+ "ParallelGripperActuator": Stub("simulo.ParallelGripperActuator"),
286
+ "GripperState": Stub("simulo.GripperState"),
287
+ "GripperCommand": Stub("simulo.GripperCommand"),
288
+ "ActuatorGainsConfig": Stub("simulo.ActuatorGainsConfig"),
289
+ "Prop": Stub("simulo.Prop"),
290
+ # ``simulo.save_output`` is in-body-only: job bodies never execute at
291
+ # submit, so a discovery-mode call (necessarily module-level) is inert and
292
+ # registers nothing — the same posture as touching ``simulo.Scene(...)``.
293
+ "save_output": Stub("simulo.save_output"),
294
+ # ``simulo.run`` (the scenario execution loop) is in-body-only too: a
295
+ # scenario job's ``@app.job`` body calls ``simulo.run(MyScenario, ...)``
296
+ # and job bodies never execute at submit, so the discovery-mode call is
297
+ # inert — same posture as ``save_output`` above.
298
+ "run": Stub("simulo.run"),
299
+ }
300
+
301
+ # Execution-mode resolution targets: thin-client name -> (source module, attribute).
302
+ # Most learning names are re-exported from the ``simulo.core`` package aggregate,
303
+ # so they default to ``("simulo.core", name)``. Names whose defining module is NOT
304
+ # the ``simulo.core`` aggregate carry an explicit per-name source module here: the
305
+ # component bases and ``Scenario`` live in their own modules, and ``RecordConfig``
306
+ # belongs to the recording subpackage (``simulo.recording``) — it is deliberately
307
+ # not re-exported from ``simulo.core``.
308
+ _EXECUTION_ALIAS: dict[str, tuple[str, str]] = {
309
+ "Scenario": ("simulo.scenario", "Scenario"),
310
+ # ``run`` is the scenario execution loop that takes a ``Scenario``
311
+ # subclass and drives its lifecycle — it lives alongside ``Scenario``
312
+ # itself in ``simulo.scenario`` (the simulo-backend distribution), not on
313
+ # the ``simulo.core`` aggregate.
314
+ "run": ("simulo.scenario", "run"),
315
+ "ObservationComponent": ("simulo.core.observations", "ObservationComponent"),
316
+ "RewardComponent": ("simulo.core.rewards", "RewardComponent"),
317
+ "TerminationCondition": ("simulo.core.terminations", "TerminationCondition"),
318
+ # RELOCATED authoring value types (Plan C — Waves C1, C2, then C4's
319
+ # ``RecordConfig`` — formerly resolved from the recording subpackage —
320
+ # and C4's ``World``, the spatial-vocabulary split): the
321
+ # single definition lives in ``simulo.interfaces.authoring`` —
322
+ # ``simulo.core`` deliberately does NOT re-export it ("nuke as you go", no
323
+ # shims), so the default ``getattr(simulo.core, name)`` resolution would
324
+ # fail on the worker.
325
+ "RecordConfig": ("simulo.interfaces.authoring", "RecordConfig"),
326
+ "Pose": ("simulo.interfaces.authoring", "Pose"),
327
+ "Material": ("simulo.interfaces.authoring", "Material"),
328
+ "Physics": ("simulo.interfaces.authoring", "Physics"),
329
+ "Light": ("simulo.interfaces.authoring", "Light"),
330
+ "Terrain": ("simulo.interfaces.authoring", "Terrain"),
331
+ "World": ("simulo.interfaces.authoring", "World"),
332
+ "Entity": ("simulo.interfaces.authoring", "Entity"),
333
+ "Visual": ("simulo.interfaces.authoring", "Visual"),
334
+ # Engine authoring vocabulary (one-import rule, second half): unlike the RELOCATED
335
+ # authoring types above, these stay defined in ``simulo.core`` — but
336
+ # ``simulo.core.__init__`` only re-exports a curated subset onto the
337
+ # aggregate (measured: ``Simulation``, ``SurfaceGripperActuator``,
338
+ # ``ParallelGripperActuator``, ``GripperState``, ``GripperCommand``,
339
+ # ``Prop`` and ``DifferentialIKController`` ARE on the aggregate, so they
340
+ # fall through to the default ``("simulo.core", name)`` resolution
341
+ # below and need no entry here). Everything else here is bound only on
342
+ # its OWN submodule, never on the aggregate, so the default resolution
343
+ # would raise ``AttributeError`` on the worker without an explicit alias.
344
+ "Camera": ("simulo.core.sensor", "Camera"),
345
+ "CameraSpawnConfig": ("simulo.core.sensor", "CameraSpawnConfig"),
346
+ "SensorOffset": ("simulo.core.sensor", "SensorOffset"),
347
+ "ContactSensor": ("simulo.core.sensor", "ContactSensor"),
348
+ "RayCaster": ("simulo.core.sensor", "RayCaster"),
349
+ "RayPattern": ("simulo.core.sensor", "RayPattern"),
350
+ "IMU": ("simulo.core.sensor", "IMU"),
351
+ "ForceTorqueSensor": ("simulo.core.sensor", "ForceTorqueSensor"),
352
+ "FrameTransformer": ("simulo.core.sensor", "FrameTransformer"),
353
+ "FrameTransformerTarget": ("simulo.core.sensor", "FrameTransformerTarget"),
354
+ "ObservationSet": ("simulo.core.observations", "ObservationSet"),
355
+ "RewardSet": ("simulo.core.rewards", "RewardSet"),
356
+ "TerminationSet": ("simulo.core.terminations", "TerminationSet"),
357
+ "OperationalSpaceController": ("simulo.core.controller", "OperationalSpaceController"),
358
+ "ActuatorGainsConfig": ("simulo.core.robot", "ActuatorGainsConfig"),
359
+ # The real ``save_output`` lives in the thin client itself (it is
360
+ # torch-free); only its CALL reaches the ``simulo.outputs`` registry that
361
+ # ships with ``simulo-backend`` — see ``_client/outputs.py``.
362
+ "save_output": ("simulo._client.outputs", "save_output"),
363
+ }
364
+
365
+ # Cache resolved execution-mode classes for stable identity across repeated access.
366
+ _EXECUTION_CACHE: dict[str, object] = {}
367
+
368
+
369
+ def _resolve_execution(name: str) -> object:
370
+ """Lazily import the name's real defining module and return the real class."""
371
+ module_name, attribute = _EXECUTION_ALIAS.get(name, ("simulo.core", name))
372
+ module = importlib.import_module(module_name)
373
+ return getattr(module, attribute)
374
+
375
+
376
+ def resolve(name: str) -> object:
377
+ """Resolve a learning name under the current mode.
378
+
379
+ Execution → the real class from its defining module (``simulo.core`` by
380
+ default, or the per-name source in :data:`_EXECUTION_ALIAS`; imported lazily,
381
+ then cached). Discovery → the lean torch-free stand-in (a stable singleton) —
382
+ which for the RELOCATED authoring value types is the real class itself.
383
+ Raises ``AttributeError`` for any name outside :data:`LEARNING_NAMES`.
384
+ """
385
+ if name not in LEARNING_NAMES:
386
+ raise AttributeError(name)
387
+ if current_mode() == EXECUTION:
388
+ cached = _EXECUTION_CACHE.get(name)
389
+ if cached is None:
390
+ cached = _resolve_execution(name)
391
+ _EXECUTION_CACHE[name] = cached
392
+ return cached
393
+ return _DISCOVERY_STANDINS[name]