Typed Python IaC engine: deterministic Atlas-lang config, graph state, parallel reconciliation
Project description
Atlantide
Typed, deterministic Infrastructure-as-Code for Python.
Atlantide is an IaC engine built on three ideas:
- Enforced determinism. Configs are written in plain Python but executed by Atlas-lang — a bounded interpreter with no clock, randomness, environment, or network access. Two runs of the same config produce byte-identical intermediate representation and a stable content hash.
- Graph state with Merkle skip. Resources form a dependency graph. A two-phase
Merkle
input_hashletsapplyskip unchanged nodes with zero provider calls, and independent nodes reconcile in parallel. - Typed resources with per-field mutability. Each field is declared
mutable(),immutable(), orcomputed(). Changing a mutable field is an in-place UPDATE; changing an immutable one is a REPLACE (destroy + create); computed fields never diff.
Install
Requires Python ≥ 3.11. Uses uv.
uv sync
Quickstart
Write a config in Python — valid for your IDE and mypy, run by Atlas-lang:
from atlantide.core import Stack, output
from atlantide.providers.aws import S3Bucket, SqsQueue
for env in ["dev", "prod"]:
with Stack(env, region="eu-north-1", name_prefix="atlantide", tags={"env": env}):
assets = S3Bucket("assets", versioning=(env == "prod"))
jobs = SqsQueue("jobs", fifo=True)
output("assets_arn", assets.arn)
Reading another resource's output (assets.arn) returns a lazy Ref — it wires
the dependency and resolves to the real value at apply time.
Authoring helpers
-
Output combinators —
concat,interpolate,joinbuild a value from an apply-timeRefwithout knowing it yet (the language isn't re-run at apply, so they serialize as data, not closures):S3BucketPolicy("p", bucket=assets.bucket, statements=[ allow("s3:GetObject", on=concat(assets.arn, "/*"), principal="*"), ])
-
Rename without replace — give a renamed resource its old id (or bare old name) so it maps to the existing state node instead of destroy + create:
S3Bucket("assets_v2", lifecycle=Lifecycle(aliases=("assets",)), ...)
-
Per-block region —
with region("us-east-1"):overrides the stack's region for the resources inside (e.g. an ACM cert for CloudFront). -
Components — library-authored L2 groups (e.g.
aws.SecureBucket) bundle several resources behind one parameterized object; children auto-namespace so a component used twice never collides. They can also be published in a git repo and imported by anyone — see Components.
CLI
uv run atlantide plan infra.py # preview changes
uv run atlantide apply infra.py # reconcile (parallel)
uv run atlantide apply infra.py # re-run: all NOOP, zero provider calls
uv run atlantide refresh # detect drift vs live state
uv run atlantide graph infra.py --format mermaid # dependency graph
uv run atlantide destroy # tear down
State defaults to atlantide.db; override with --state. Mutating commands
(apply / deploy / destroy / refresh) take --confirm/-y, --region,
--parallelism/-p, and — where they change state — --on-failure rollback|halt
(default rollback: undo completed nodes on failure). plan and refresh
support --json and --detailed-exitcode (0 = no change, 2 = changes, 1 = error).
Config path, state db, and defaults can be fixed once in atlantide.toml so
commands take no flags inside a project:
config = "infra.py"
state = "atlantide.db"
parallelism = 8
aws_region = "eu-north-1"
aws_profile = "default"
aws_endpoint = "http://localhost:4566" # e.g. LocalStack
secrets_key = ".atlantide.key"
secrets_store = ".atlantide.secrets"
[aws.aliases.prod] # alternate account (multi-account)
profile = "prod-account" # a resource selects it via provider_alias="prod"
endpoint = "http://localhost:4566"
Additional commands:
-
build/verify/deploy— portable.atlasartifacts (content-hashed, provider-version pinned): compile once, promote the same bytes anywhere. -
refresh— read live provider state, report drift;--writesyncs it back. -
secret— manage the local AES-GCM encrypted name→value store; resources reference secrets by name (SecretRef), resolved at apply:uv run atlantide secret set app/signing-key # value prompted (hidden) if omitted uv run atlantide secret get app/signing-key -r # print plaintext (--reveal required) uv run atlantide secret list # names only, no values uv run atlantide secret rm app/signing-key # remove
-
component—add/lock/vendor/verifycomponents published in public git repos; imported asatlantide.components.<alias>. See Components. -
resources/schema— discover resource types and inspect a type's fields.
Components
L2 constructs — several resources behind one parameterized object, like Pulumi's
ComponentResource or a CDK Construct. Config uses components but can't define
them: Atlas-lang bans class, so a component is ordinary Python written by a
library or provider author.
from atlantide.core import Component, child
from atlantide.providers.aws import S3Bucket
class SecureBucket(Component):
def __init__(self, name, *, bucket):
self.bucket = child(S3Bucket, "assets", bucket=bucket)
# ...plus a TLS-only hardening policy wired to self.bucket
A component owns no IR node of its own — its children lower as normal flat
resources, and each child's logical name is namespaced under the instance
({component}-{child}), so instantiating a component twice never collides.
Publishing & consuming
Components can be shared in a public git repo and imported by anyone. There is no
live URL import (Node-style): config is a deterministic sandbox — it may import
only atlantide.* and cannot touch the network — so a fetch at eval time would
break both. Instead you fetch once, pinned to a commit + content hash (the
terraform init model), and import the vendored code locally under the
atlantide.components.* namespace, which the sandbox already permits.
# fetch, pin (commit + content hash) into atlantide.lock, vendor into .atlantis/
uv run atlantide component add https://github.com/acme/secure-bucket --ref v1.2.0 --as acme --subdir src
uv run atlantide component verify # re-hash vendored trees vs the lock (tamper/drift)
uv run atlantide component vendor # rebuild .atlantis/ from the lock alone (e.g. fresh checkout)
uv run atlantide component lock # re-resolve declared refs -> commits
# infra.py — imported and used like any other resource
from atlantide.components.acme import SecureBucket
SecureBucket("assets", bucket="acme-assets")
add records the source under [components.acme] in atlantide.toml and the
resolved pin in atlantide.lock. Commit atlantide.lock; git-ignore .atlantis/
(it's derived — vendor rebuilds it from the lock). A build artifact also records
each component's commit as provenance.
Trust: a published component runs as trusted Python, like a provider — vet what
you add. After that, integrity rests on the pin, and verify fails on any tamper or
drift. See examples/components/ for a runnable
publish-and-consume walkthrough.
How it works
Every plan/apply runs the same pipeline — pure, deterministic stages first,
then the effectful reconcile:
flowchart LR
A[Config .py] --> B[Atlas-lang<br/>+ IR + hash]
B --> C[Graph<br/>+ Merkle]
C --> D[Diff vs<br/>prior state]
D --> E[Plan<br/>+ policy]
E -->|plan| F[Changeset]
E -->|apply| G[Parallel apply<br/>skip unchanged]
Steps
- Atlas-lang — the config is validated (Python subset — no
while,class, dunder,eval, or non-allowlisted imports) and evaluated by a fuel-bounded interpreter with deterministic builtins only. - Registry → IR — evaluated resources,
Refs, andoutput()s lower into an IR graph;Refs become dependency edges. - Canonicalize — IR serializes to canonical JSON (RFC 8785) and gets a stable content hash; two runs are byte-identical.
- Graph — the dependency graph is built and checked for cycles (Tarjan).
- Merkle — each node gets a two-phase
input_hashin topological order. - Diff — desired hashes compare against prior state → per-node action (NOOP / CREATE / UPDATE / REPLACE / DELETE); per-field mutability decides UPDATE vs REPLACE.
- Plan — actions ordered (creates/updates topo, deletes reversed; REPLACE =
destroy-before-create);
prevent_destroyblocks protected deletes. - Policy — plan-time bindings evaluate against the changeset; mandatory
violations block
apply. - Apply — under a whole-state lock, the executor reconciles independent nodes
in parallel, calls providers, skips Merkle-unchanged nodes (zero calls), and
persists incrementally. On failure it rolls back completed nodes as a saga by
default (
--on-failure haltinstead leaves state as-is, resumable). Outputs print.
Providers
local—File,Null. Credential-free; runs in CI.random—Uuid,Password,Id,Timestamp. Values generated once at apply, then pinned in state.aws—S3Bucket,S3BucketPolicy,SqsQueue,IamRole,IamPolicy,LambdaFunction,SnsTopic,SnsSubscription,DynamoDbTable,CloudWatchLogGroup,Vpc,Subnet,SecurityGroup. Plus IAM policy helpers (allow,deny,assume_role,ServicePrincipal) and theSecureBucketL2 component. Any resource may setprovider_alias=to target an alternate account. LocalStack-friendly via--region/aws_endpoint.
Architecture
Layered, with import boundaries enforced by import-linter:
| Layer | Responsibility |
|---|---|
core/ |
Resource, field helpers, Output/Ref, Provider ABC, registry, Stack |
lang/ |
Atlas-lang: subset validator, fuel-bounded interpreter, deterministic builtins |
ir/ |
IR model, lowering (Ref → edges), canonical JSON (RFC 8785), content hash, two-phase Merkle, .atlas artifacts |
graph/ |
Graph build, Tarjan cycle detection, async Kahn scheduler |
state/ |
StateBackend ABC — sqlite (default) + memory; whole-state lock |
reconcile/ |
Diff (Merkle NOOP-skip), planner, parallel executor (saga rollback / resume), refresh |
policy/ |
Modular per-resource policy engine (@policy / enforce; mandatory blocks, advisory warns) |
secrets/ |
SecretsProvider ABC — env + AES-GCM keyfile store; secrets referenced by name, resolved at apply |
engine/ |
Engine — orchestrates compile → plan → apply/destroy/refresh/build/deploy; owns the state lock |
providers/ |
Provider implementations (local, random, aws) |
components/ |
Published components: git fetch, atlantide.lock pinning, vendored-tree hashing, and the import mount |
cli/ |
Typer app; console/render/json/diagram/progress output, project config |
Examples
examples/aws/— a per-environment stack of ~14 resources each, wiring cross-resource and cross-provider dependencies automatically.examples/components/— authoring a publishable component and consuming it from another project (git-pinned, vendored).
Development
uv sync --extra dev
uv run pytest # tests (memory + sqlite backends), 90% coverage floor
uv run mypy # strict type check
uv run ruff check
uv run lint-imports # architecture contracts
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file atlantide-0.1.0.tar.gz.
File metadata
- Download URL: atlantide-0.1.0.tar.gz
- Upload date:
- Size: 305.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b736b31c9cb42a9b5591d11badc98a0e98b84040247662eb40e3cad15908c01b
|
|
| MD5 |
61010f3ef211df022b06c1d57712ed04
|
|
| BLAKE2b-256 |
0dd557a842a2bad06bbdd4f0b20238d388d47f56d3a633ca769891d6042e51ea
|
File details
Details for the file atlantide-0.1.0-py3-none-any.whl.
File metadata
- Download URL: atlantide-0.1.0-py3-none-any.whl
- Upload date:
- Size: 182.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2a498dad19edc514992d0e46d94d0b74c46d8cfcfff33870f63dc0f40cb2956
|
|
| MD5 |
076130016f33293699b384588a6e04d6
|
|
| BLAKE2b-256 |
be64de506ae49a6de453630bf34332ab52110e1a44250c90e47b9e00ffdddaa8
|