Skip to main content

Differences from Python

Atmos Automation Language looks like Python and reads like Python, but it is a smaller language with its own rules. This page lists the differences that most often surprise Python programmers, with the closest equivalent for each. Every example here was run on the language that ships with Atmos.

Quick lookup​

In PythonIn Atmos Automation Language
try / except / raisefail("message") stops the script. Nothing catches it. Use exec.run(..., check=False) to inspect a failing command.
import osload("file.star", "name") for your own code. Atmos names such as exec, fs, json, regex, and env are already available.
classUse dictionaries and functions.
x ** 2x * x, or a loop. There is no ** operator and no pow().
f"{name}""{}".format(name) or "%s" % name.
a < b < ca < b and b < c.
x is Nonex == None.
l.sort()sorted(l), which returns a new list.
l.copy(), d.copy()list(l), dict(d).
del l[0]l.pop(0), d.pop("key").
sum(xs), round(x)Available as Atmos built-ins; see sum and round.
isinstance(x, list)type(x) == "list".
(x for x in xs)[x for x in xs]. There are no generators.
for c in "abc"for c in "abc".codepoints().
global xNot available. Top-level names are assigned once.

Statements​

No try, except, finally, or raise. Errors propagate to the top and stop the script. The fail() function is the only way to raise one. See Errors and tracebacks.

No import. Use load to bring names from another .star file:

load("lib/helpers.star", "deploy")

No class, with, yield, async, del, global, or nonlocal. Each of these is a syntax error, as are decorators (@name) and the walrus operator (:=). There is no assert statement either; the word is an ordinary identifier. Write a check as if not condition: fail("message").

No chained assignment or star-unpacking. Both x = y = 1 and a, *rest = items are syntax errors. Plain tuple unpacking works: a, b = 1, 2, and (c, d), e = (3, 4), 5.

No else clause on loops. A for or while loop followed by else is a syntax error.

Common statements and expressions work as in Python. That includes pass, break, continue, elif, comprehensions, and lambda. A lambda body is a single expression, and a comprehension can contain several for and if clauses.

while and top-level for and if statements are available. Atmos enables them, so a script can compute at the top level without wrapping everything in a function.

Names and scope​

Top-level names are assigned once. A script cannot rebind a global, including with x += 1, a loop variable that is reused, or a swap such as a, b = b, a. Compute changing values inside a function, where local variables can be rebound freely:

x = 1
x = 2 # fails: cannot reassign global x

See Globals are assigned once.

Functions cannot assign to globals. There is no global statement. Assigning inside a function creates a local variable, and count += 1 on a global inside a function fails with local variable count referenced before assignment. Mutate a dictionary or list instead: state["count"] += 1.

Loaded and shared values are frozen. Collections that come from load() are immutable, and collections reachable from steps.parallel tasks become immutable when the tasks start. See Frozen values.

Names are checked before the script runs. A misspelled name is an error even on a line that would never execute. A global must still be assigned before it is read, though functions may refer to names that are defined later in the file.

There is no __name__. A standalone program defines a main function and passes it to cli.command instead of using if __name__ == "__main__".

Numbers and operators​

Division with / always produces a float, and // floors. For example, 7 / 2 is 3.5, 6 / 3 is 2.0, 7 // 2 is 3, and -7 // 2 is -4. The % operator takes the sign of the divisor, as in Python: -7 % 3 is 2. Dividing by zero is an error for both / and //.

There is no ** operator. Use x * x, a loop, or a function. The functions pow and divmod are also unavailable. Atmos supplies sum and round for adding and rounding numbers.

Bitwise operators work. &, |, ^, ~, <<, and >> operate on integers, and integers have arbitrary precision: 1 << 70 is 1180591620717411303424.

Number literals. 0x1f, 0o17, and 0b101 work. Underscores in numbers, as in 1_000, do not.

Booleans are separate from numbers. The expression True + 1 is an error, and True == 1 is False. As dictionary keys, True and 1 are different. The comparison 1 == 1.0 is True, and the keys 1 and 1.0 collide.

Comparison chains are not allowed. Writing 1 < 2 < 3 fails with < does not associate with < (use parens). Write 1 < 2 and 2 < 3 instead.

No is and is not. Compare with == and !=, including for None: x == None.

The operators and, or, and not behave as in Python. 0 or "x" evaluates to "x". The conditional expression a if cond else b works too.

Strings​

Strings are not iterable. A loop such as for c in "abc" fails with string value is not iterable. Use "abc".codepoints() for characters or "abc".elems() for bytes.

Strings hold UTF-8 text and are measured in bytes. The call len("héllo") returns 6, and indexing and slicing count bytes, so "héllo"[1:3] is "é" and "é"[0] is a single byte that is not valid text. Methods such as upper() and lower() handle Unicode correctly. Use len(list(s.codepoints())) to count characters.

No f-strings. Use "{} is {}".format(a, b), "{name}".format(name=n), or "%s is %d" % (a, b). The format method supports {}, {0}, {name}, and the !r conversion, but not format specifications such as {:>5} or lookups such as {a.b}. The % operator supports %s, %d, %i, %r, %x, %X, %o, %e, %f, %g, %c, and %%, without width or precision.

Fewer methods. Strings have 35 methods, listed under String methods. Methods that do not exist include zfill, center, ljust, rjust, swapcase, and expandtabs.

Number parsing does not trim whitespace. The call int(" 7 ") fails, so call .strip() first.

Collections​

Dictionaries keep insertion order. Iterating, keys(), values(), and items() follow the order in which keys were first added, as in modern Python. The methods items(), keys(), and values() return lists, not views. Two dictionaries merge with |: {"a": 1} | {"b": 2}.

Sets are available through set(). There are no set literals, so {1, 2} is a syntax error; write set([1, 2]). Sets keep insertion order, and s.pop() removes the first element. Sets support |, &, -, ^, and the comparisons <, <=, >, >=.

Keys and set members must be hashable. Strings, numbers, booleans, None, and tuples of those are hashable. Lists, dictionaries, and sets are not, and using one as a key fails with unhashable type: list. A missing key in d[key] fails with key "x" not in dict; use d.get("x") for None or d.get("x", default).

Mutating a collection while looping over it is an error. Appending to a list, assigning to one of its elements, or assigning to a dictionary key inside a for loop over that collection fails, even when the key already exists. This fails with messages such as cannot append to list during iteration. Loop over a copy, such as for k in list(d), or build a new collection.

Fewer methods. Lists have seven methods (append, clear, extend, index, insert, pop, remove), so there is no sort(), reverse(), count(), or copy(). Dictionaries have no fromkeys(), and there is no collections module. See Methods.

Sorting is stable. sorted(items, key=...) keeps the original order of elements that compare equal. It returns a new list. Values of different types cannot be ordered, so sorted([1, "a"]) fails with string < int not implemented. The same holds for min, max, and the < operators. Strings compare by their UTF-8 bytes.

Tuples cannot change. t[0] = 1 fails with tuple value does not support item assignment.

Functions​

Keyword-only arguments and *args/**kwargs work. def f(a, b=2, *args, c, **kw) is valid, and so is a bare * before keyword-only parameters. Call-site unpacking with f(*list) and f(**dict) works.

Defaults are evaluated once, when the function is defined, as in Python.

Built-in functions take positional arguments. enumerate(items, start=1) fails, while enumerate(items, 1) works. See Built-in Functions.

The print function writes a line to standard output and accepts only sep. There is no end or file argument. Status messages belong in ui.info() and diagnostics in log.debug(). See Output streams.

Collections print the way the language writes them. Strings inside collections use double quotes, sets print as set([1, 2]), floats always show a decimal point (1.0), and None prints as None.

The type() function returns a string. Test for a type with type(1) == "int". There is no isinstance, callable, iter, next, map, filter, id, open, or input. Comprehensions cover map and filter, and exec.run and fs.read_file cover process and file access.

Recursion works, within a limit. Calls nest to a depth of 10,000. See Recursion.

Standard library​

The Python standard library and Python packages are not part of the runtime, and there is no import. The built-in modules that Atmos supplies cover what automation needs:

PythonIn Atmos Automation Language
subprocessexec.run and atmos.run.
os.environenv, a read-only dictionary of the step's declared inputs.
open(path).read()fs.read_file. File writes are not available.
jsonjson.encode, json.decode, json.indent.
reregex.search, regex.findall, and regex.replace. These use Go (RE2) syntax with no lookaround or backreferences.
argparsecli.command, cli.arg, and cli.flag.
concurrent.futures, threadingsteps.parallel and steps.task.
logginglog and ui.

There is no math, time, datetime, random, pathlib, itertools, functools, collections, or typing equivalent.

Values that come from Atmos​

Some values are not dictionaries. ctx is a struct: read its fields with dot notation, as in ctx.flags, and look them up by name with getattr(ctx, "flags"). Indexing a struct with ctx["flags"] fails. The flags and arguments fields are read-only dictionaries, so ctx.flags["name"] works but assigning to it does not.