Skip to main content

Helicopyter

Helicopyter generates Hashicorp Configuration Language (HCL) syntax Terraform from Python. The new Python Syntax for Terraform has minimal dependencies. Support for CDKTF sources moved to helicopyter.cdktf. List cdktf as a dependency if you need it.

1. New deploy using Python Syntax for Terraform

With Python 3.12+, Helicopyter, and OpenTofu (or Terraform), create deploys/example/terraform/main.py:

from helicopyter import provider, resource, terraform

terraform.required_providers(null={'source': 'hashicorp/null', 'version': '~> 3.2'})
provider.null()
resource.null_resource.example(triggers={'message': 'Hello from Python'})

Run from the project root:

python -m helicopyter example

Helicopyter writes deploys/example/terraform/main.tf and autoformats it with OpenTofu. Use --format_with cat to skip autoformatting, or --format_with terraform to format with Terraform.

resource, data, provider, and variable build blocks; attribute access supplies their labels. Use tlocals for a locals block, local and var for references, and Block for other HCL blocks or expressions. Use Terraform attribute names, Python dictionaries for maps, and Block instances for nested blocks.

Each deployment lives at deploys/<codename>/terraform/main.py. Pass multiple codenames, or all, to generate multiple deployments. Additional hand-written .tf files can coexist with main.tf.

Compare the Docker examples using Python Syntax for Terraform and legacy CDKTF syntax.

Cloudflare Workers JAM stacks

jam() provides Cloudflare Workers hosting, with branch-specific preview environments, given a URL like rivertide.biobuddi.es/ or staff@admin.cov.ing/.

from helicopyter.cloudflare import jam

# Public; main deployed to `rivertide.biobuddi.es/`, previews to `rivertide-{branch}.biobuddi.es/`
jam(
    'rivertide.biobuddi.es/',
    account_id='0123456789abcdef0123456789abcdef',
    zone_id='fedcba9876543210fedcba9876543210',
)

# Staff only behind Cloudflare Access; main deployed to `admin.cov.ing/`, previews to `admin-{branch}.cov.ing/`
jam('staff@admin.cov.ing/')  # $CLOUDFLARE_ACCOUNT_ID and $CLOUDFLARE_ZONE_ID read from environment

2. Migrate existing CDKTF to Python Syntax for Terraform

Migrate one deployment at a time. Before editing, synthesize its existing configuration and save it for comparison. Keep its backend, workspace, provider versions, resource labels, and instance keys unchanged so Terraform continues to address the same state objects.

Replace CDKTF imports and the synth(stack: HeliStack) function with module-level block builders. For example, this legacy deployment:

from cdktf_cdktf_provider_null.resource import Resource
from helicopyter.cdktf import HeliStack


def synth(stack: HeliStack):
    stack.provide('null')
    stack.push(Resource, 'example', triggers={'message': 'Hello from Python'})

becomes the null-provider example above.

Use the provider version constraint from your existing configuration instead of adopting the example constraint during migration. The resource address remains null_resource.example.

Legacy CDKTF Python Syntax for Terraform
stack.provide('github', owner='example') provider.github(owner='example')
stack.push(Resource, 'example', ...) resource.<terraform_resource_type>.example(...)
stack.push(TerraformVariable, 'message', ...) variable.message(...)
stack.push(TerraformLocal, 'name', value) tlocals(name=value)
Variable/local token var.message / local.name
Resource attribute token resource.<terraform_resource_type>.example.id
stack.cona from helicopyter import cona

Translate CDKTF-specific properties and nested structures to the provider's Terraform schema. Python strings are quoted; use references such as var.message or Block('length(var.items)') when you need an unquoted Terraform expression. Use Block('output', 'name')(value=...) for outputs. Preserve any count, for_each, lifecycle, import, or moved blocks used by the old configuration. Raw CDKTF configurations may have generated resource IDs; preserve their actual Terraform addresses, not just their Python construct names.

Regenerate with python -m helicopyter <codename> and compare main.tf with the saved configuration. If the old generator produced main.tf.json, move that file out of the Terraform directory so both versions do not declare the same resources. Compare the plan in the existing workspace without upgrading providers, and investigate unexpected changes before applying.

After every deployment and shared helper has stopped importing CDKTF, remove cdktf and generated Python provider packages from your dependencies. Node.js is no longer needed for synthesis, though other project tools may still use it.

3. Continue using legacy CDKTF syntax

Install Node.js for CDKTF's JSII runtime. Declare cdktf and the Python provider packages your configuration imports directly in your project's dependencies. The legacy example above needs helicopyter, cdktf, and cdktf-cdktf-provider-null.

Change from helicopyter import HeliStack to from helicopyter.cdktf import HeliStack. synth(stack: HeliStack), stack.provide, and stack.push continue to work. HeliStack removes CDKTF's App boilerplate and allocates logical IDs in Terraform's resource-prefix style instead of CDK's hash-suffix style. Custom HeliStack subclasses remain supported; the example R2 subclass now lives at stacks.cdktf.BaseStack. The CDKTF CLI and an npm installation of CDKTF are not required by Helicopyter; Node.js itself must be available to JSII.

Legacy deployments can keep CDKTF's import_from and move_from_id methods. The demos extra installs the Python provider packages used by this repository's legacy examples. Repository contributors can use mise install to install the development dependencies and npm tooling; the repository's cdktf optional dependency group feeds requirements.txt through uv pip compile --all-extras. Consumers declare cdktf themselves.

Pure-Python and legacy deployments can coexist. The CLI selects legacy synthesis when a deployment exports synth; otherwise it emits HCL from the registered Python blocks. A command selecting a legacy deployment still needs its CDKTF and Node.js dependencies.

Background

Helicopyter began as a wrapper around CDKTF, inspired by Configerator and Terraformpy. It keeps infrastructure descriptions in Python while leaving planning, state management, and application to OpenTofu (or Terraform).

What Helicopyter does (goals)

  • Name in the resource-prefix Terraform style, instead of the hash-suffix CDK style. This makes reviewing plan output easier, and aligns with "Namespaces are one honking great idea -- let's do more of those!"
  • Simplify main.py files by
    • Removing the App class, which does not correspond to a Terraform concept. Instead
      • A string attribute provides name information
      • Resource-specific scopes are provided by HeliStack.push()
      • The synth method is replaced by a module-level function, and called from a central location instead of distributed boilerplate.
    • Making custom TerraformStack/HeliStack subclasses optional instead of required. Defining a subclass only to instantiate one instance of it is more complicated than instantiating the base class and modifying the instance.
--- documentation/learn_cdktf_docker.py
+++ documentation/learn_helicopyter_cdktf_docker.py
@@ -1,30 +1,18 @@
-"""Upstream CDKTF example to compare with Helicopyter."""
+"""Docker example using Helicopyter with legacy CDKTF syntax."""
 
-from cdktf import App, TerraformStack
 from cdktf_cdktf_provider_docker.container import Container
 from cdktf_cdktf_provider_docker.image import Image
-from cdktf_cdktf_provider_docker.provider import DockerProvider
-from constructs import Construct
+
+from helicopyter.cdktf import HeliStack
 
 
-class MyStack(TerraformStack):
-    def __init__(self, scope: Construct, ns: str) -> None:
-        super().__init__(scope, ns)
+def synth(stack: HeliStack) -> None:
+    stack.provide('docker')
 
-        DockerProvider(self, 'docker')
-
-        docker_image = Image(self, 'nginxImage', name='nginx:latest', keep_locally=False)
-
-        Container(
-            self,
-            'nginxContainer',
-            name='tutorial',
-            image=docker_image.name,
-            ports=[{'internal': 80, 'external': 8000}],
-        )
-
-
-app = App()
-MyStack(app, 'learn-cdktf-docker')
-
-app.synth()
+    stack.push(
+        Container,
+        'nginxContainer',
+        name='tutorial',
+        image=stack.push(Image, 'nginxImage', name='nginx:latest', keep_locally=False).name,
+        ports=[{'internal': 80, 'external': 8000}],
+    )
  • Enable hand-written and auto-generated Hashicorp Configuration Language (HCL) files to co-exist, allowing incremental adoption.
  • Separate object instantiation from synthesis, allowing Python script to import the objects/data and do completely different things with them.
  • Golang Terraform has a pretty good command line interface. The ht[aip] recipes in .biobuddies/justfile try to wrap it very lightly.

What Helicopyter will probably never do (non-goals)

  • Support languages other than Python
  • Use CDKTF's command line interface. Integration with it is untested and not recommended.

What Helicopyter might do in the future

  • Support multiple backend configurations per codename
  • Iterate on supported directory structures. For hysterical raisins, the currently supported directory structure is f'deploys/{cona}/terraform', grouping
    • Primarily by COdeNAme (CONA), which is probably synonymous with application, deployment, and service
    • Secondarily by tool, such as ansible, docker, terraform, python
  • Why do we need a Node.js server? Can we build dataclasses or Pydantic models out of the type annotations already being generated?
  • Provide helper classes or functions for useful but annoyingly verbose patterns such as local-exec provisioner command
  • Backend / state file linter such as: prod must exist, and region/bucket/workspace_key_prefix/key must follow pattern

Release files for helicopyter 2026.38.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for helicopyter 2026.38.1
File Size Uploaded
helicopyter-2026.38.1.tar.gz 99.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for helicopyter 2026.38.1
File Interpreter ABI Platform
helicopyter-2026.38.1-py3-none-any.whl Python 3 none any Details

Total release size: 128.2 kB

Release files / helicopyter-2026.38.1.tar.gz

Download URL helicopyter-2026.38.1.tar.gz
Size 99.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3e48d8904b367eb5b14208157d87dc5edf41ae13161e7a5289d76a2dba085996
BLAKE2b-256 checksum
How to use checksums
a44b4c8ec8ef9c14f12f9eb5fefb96447580c1e679d42a9e18d6518669403c3a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.13

Release files / helicopyter-2026.38.1-py3-none-any.whl

Download URL helicopyter-2026.38.1-py3-none-any.whl
Size 29.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
407faaefd72f1a9bdf8527b4a4c480235f564b04affa5a132f53884f94319a23
BLAKE2b-256 checksum
How to use checksums
e9910b605e5a7326f9c698ba20e20e732b80a43a9abaf4a5d54e9ac464e04bbc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.13
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page