app.engine.dsl_execution_context module

ExecutionContext: precomputes everything a strategy AST will need so the interpreter walk is pure CPU and never makes a database call.

The precompute strategy is deliberately lazy at the AST level: we walk the tree once, collect the set of field paths it actually reads, and only fetch (or mock) those. A strategy that ignores user.avg_time pays no cost for it. A malicious AST that tries to reference an unknown path is rejected by the validator long before we get here.

mock_state is the back door used by the /simulate endpoint: keys are dotted-path strings matching FIELD_RESOLVERS entries (or data.* prefixes). When present, the precompute uses the mock value verbatim and never calls the analytics service. This is what lets a designer iterate on logic against synthetic inputs while still hitting real production analytics methods when mock_state is left unset.

class app.engine.dsl_execution_context.ExecutionContext(externalGameId: 'str', externalTaskId: 'str', externalUserId: 'str', data: 'Mapping[str, Any]', resolved_fields: 'Mapping[str, Any]' = <factory>)[source]

Bases: object

Parameters:
  • externalGameId (str)

  • externalTaskId (str)

  • externalUserId (str)

  • data (Mapping[str, Any])

  • resolved_fields (Mapping[str, Any])

async classmethod build_for_ast(ast, *, externalGameId, externalTaskId, externalUserId, data, analytics_service, mock_state=None, parent_result=None, analytics_cache=None)[source]

Precompute every field referenced by ast and return a frozen context the interpreter can walk synchronously.

The static paths are computed without any I/O. Analytics paths each trigger at most one awaited call to the analytics service, and only when the AST actually references them. mock_state short-circuits both, useful for the simulate endpoint and for tests that don’t want a real DB.

analytics_cache is an optional caller-owned dict memoising analytics-field values within a single scoring call. DSL_EXTEND builds two contexts (pre + post) for the same user and request window; passing the same dict to both means each analytics method (a DB round-trip) runs once instead of twice. Only analytics-kind fields are cached: static fields are pure CPU, and data.* fields legitimately differ between phases because pre-rules may mutate data. Pass None (the default) to opt out - DSL_FULL builds a single context and gains nothing.

Parameters:
  • ast (Dict[str, Any])

  • externalGameId (str)

  • externalTaskId (str)

  • externalUserId (str)

  • data (Dict[str, Any] | None)

  • analytics_service (Any)

  • mock_state (Dict[str, Any] | None)

  • parent_result (Dict[str, Any] | None)

  • analytics_cache (Dict[str, Any] | None)

Return type:

ExecutionContext