Skip to content

Documentation

Python environment: imports, data types, and restrictions

The runtime uses CPython 3.13. A scenario is a restricted data-processing program. It receives no server filesystem, direct network, environment variables, or package installer.

Entry point and ordinary code

Declare one def run(ctx): function with one argument. Top-level code may contain permitted imports, helper function definitions, a docstring, and literal constants. Place service calls and execution inside run; a top-level items = ctx.param(...) is not permitted.

from decimal import Decimal, ROUND_HALF_UP
import json

CURRENCY = 'RUB'

def run(ctx):
    amount = Decimal('12.345').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
    record = {'currency': CURRENCY, 'price': float(amount)}
    ctx.emit(record, 'A-1')
    ctx.log('Record prepared', {'bytes': len(json.dumps(record))})

Regular functions, conditions, loops, comprehensions, generators, and permitted exceptions can be used. Async code, classes, decorators, global/nonlocal declarations, and private attributes are restricted. Scenario names and argument names must not start with _. Type annotations do not load packages or grant capabilities.

Permitted modules

Each module exposes selected safe names, not unrestricted module access. Wildcard imports, relative imports, and arbitrary submodules are blocked. Use import json, from decimal import Decimal, or from urllib.parse import urljoin.

Module Exposed names
json loads, dumps, JSONDecodeError
re compile, search, match, fullmatch, findall, finditer, split, sub, subn, escape; I/IGNORECASE, M/MULTILINE, S/DOTALL, X/VERBOSE, A/ASCII, U/UNICODE, NOFLAG, error
math Public mathematical functions and constants
decimal Decimal, Context, getcontext, localcontext, InvalidOperation, DivisionByZero; ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_DOWN, ROUND_UP, ROUND_CEILING, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_05UP
datetime datetime, date, time, timedelta, timezone, MINYEAR, MAXYEAR
statistics mean, fmean, geometric_mean, harmonic_mean, median, median_low, median_high, median_grouped, mode, multimode, quantiles, pstdev, pvariance, stdev, variance, StatisticsError
collections Counter, defaultdict, deque, OrderedDict
itertools accumulate, chain, combinations, combinations_with_replacement, compress, count, cycle, dropwhile, filterfalse, groupby, islice, pairwise, permutations, product, repeat, starmap, takewhile, tee, zip_longest, batched
functools reduce, partial, cmp_to_key
html escape, unescape
base64 b64encode, b64decode, urlsafe_b64encode, urlsafe_b64decode, b32encode, b32decode, b16encode, b16decode
csv reader, writer, DictReader, DictWriter, QUOTE_ALL, QUOTE_MINIMAL, QUOTE_NONNUMERIC, QUOTE_NONE, Error
io StringIO, BytesIO (in-memory buffers only)
hashlib sha256, sha384, sha512, sha3_256, sha3_512, blake2b, blake2s
unicodedata normalize, category, decimal, digit, numeric, name, lookup, combining, east_asian_width
urllib.parse urljoin, urlparse, urlsplit, urlunparse, urlunsplit, parse_qs, parse_qsl, urlencode, quote, quote_plus, unquote, unquote_plus

urllib.parse only manipulates URLs and cannot send requests. datetime.time is a time value type, not the sleep module. Bound infinite itertools iterators with islice. Regular expressions use Python re; limit large input and expensive expressions.

Your own dict, list, set, bytearray, Counter, defaultdict, OrderedDict, and deque support ordinary data mutation. Mutable indexed containers support item assignment and operations such as counts[key] += 1, preserving Python's evaluation order. These permissions apply to the exact standard types, not arbitrary subclasses or SDK objects. Mutating a data dictionary does not grant permission to modify context internals.

Built-ins

Available data helpers are abs, all, any, bin, bool, bytes, bytearray, callable, chr, complex, dict, divmod, enumerate, filter, float, format, frozenset, hex, int, isinstance, issubclass, iter, len, list, map, max, min, next, oct, ord, pow, range, repr, reversed, round, set, slice, sorted, str, sum, tuple, zip, plus safe exception classes. A permitted type is not necessarily JSON-compatible.

print() becomes a log event. Prefer ctx.log() for structured context and severity. print(file=...) is blocked. Use concatenation, f-strings, or the permitted format built-in; string format/format_map methods are restricted.

Output serialization

emit() accepts a dictionary with string keys; the optional record key must be a string. Values must serialize to JSON without NaN or infinity. Convert Decimal to the chosen number/string representation explicitly, datetime with .isoformat(), and bytes with .decode('utf-8') only when they contain text. Never emit a whole SDK object.

def run(ctx):
    ctx.emit({'collected_at': ctx.now().isoformat(), 'price': 1250.5}, 'A-1')

Prohibited actions and isolation

Imports of os, sys, socket, subprocess, pathlib, importlib, ctypes, pickle, marshal, HTTP clients, and third-party packages are unavailable. Dynamic execution, file access, reflection, private internals, and frame access are blocked. Do not modify SDK objects; ordinary mutation of your own dictionaries, lists, sets, and bytearrays is supported.

Language policy is backed by process and container boundaries. Address space and CPU are limited; new files, sockets, child processes, and privilege escalation are prohibited. SDK hostcalls use the platform's authorization and limits. Execution must fail if mandatory isolation cannot be enabled.

The CPU budget and reported processor time apply to user execution after interpreter preparation. Wall time additionally bounds startup and time spent waiting for external services. The operating system enforces CPU limits in whole seconds, so the stopping point may be rounded up.

Validation errors identify a line, rule, and explanation. Fix the source, validate again, and run a trial. ValueError/TypeError generally indicate invalid data or arguments; RuntimeError also represents a refused platform call. CPU, memory, and wall-time exhaustion fail the run and do not prove no records were previously submitted. Stable keys remain necessary for retry.

In-memory CSV and stable fingerprints

StringIO/BytesIO do not grant filesystem access. Use them to process small permitted payloads in memory. Encoding helpers do not encrypt content. A hash can create a stable fingerprint, but never hash random data to generate a new key each run.

from io import StringIO
import csv
import hashlib

def run(ctx):
    text = ctx.param('csv_text', 'sku;price\nA-1;1250\n')
    for row in csv.DictReader(StringIO(text), delimiter=';'):
        sku = row.get('sku', '').strip()
        if not sku:
            continue
        key = hashlib.sha256(sku.encode('utf-8')).hexdigest()
        ctx.emit({'sku': sku, 'price': ctx.text().money(row.get('price'))}, key)