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)