Python Pulumi Wrapper - Dataclass-based configuration framework for Pulumi infrastructure as code
Project description
pypuwa
Python Pulumi Wrapper — Dataclass-based configuration framework for Pulumi infrastructure as code.
Define your infrastructure config in Python dataclasses. Get interpolation, secret management, environment variable generation, and a deploy CLI for free.
Install
pip install pypuwa
Quick Start
from dataclasses import dataclass, field
from typing import FrozenSet
from pypuwa import (
BaseStackConfig,
ProviderConfig,
BaseDatabaseConfig,
BaseAppRunnerConfig,
Secret,
secret,
create_config,
)
# 1. Define your service configs
@dataclass(kw_only=True)
class MyDatabaseConfig(BaseDatabaseConfig):
INSTANCE_ID: str = "{stack}-my-api-db"
NAME: str = "my_api"
USERNAME: str = "api_user"
@dataclass(kw_only=True)
class MyAppConfig(BaseAppRunnerConfig):
SERVICE_NAME: str = "{stack}-my-api"
CPU: str = "2 vCPU"
MEMORY: str = "4 GB"
# Application env vars
DEBUG: str = "False"
DATABASE_NAME: str = "${services.database.NAME}"
API_SECRET: Secret = secret()
_NON_ENV_FIELDS: FrozenSet[str] = frozenset(["CPU", "MEMORY"])
# 2. Compose into a stack config
@dataclass(kw_only=True)
class MyServicesConfig:
database: MyDatabaseConfig = field(default_factory=MyDatabaseConfig)
app: MyAppConfig = field(default_factory=MyAppConfig)
@dataclass(kw_only=True)
class MyStackConfig(BaseStackConfig):
services: MyServicesConfig = field(default_factory=MyServicesConfig)
# 3. Define environments
staging_config = MyStackConfig(
providers=ProviderConfig(aws={"region": "us-east-1"}),
)
production_config = MyStackConfig(
providers=ProviderConfig(aws={"region": "us-west-2"}),
services=MyServicesConfig(
database=MyDatabaseConfig(MULTI_AZ=True, ALLOCATED_STORAGE=100),
app=MyAppConfig(CPU="4 vCPU", MEMORY="8 GB"),
),
)
# 4. Create the config resolver (auto-detects environment from Pulumi stack)
config = create_config(
environments={"staging": staging_config, "production": production_config}
)
CLI
pypuwa includes a deploy orchestrator that syncs your Python config to Pulumi YAML, manages secrets interactively, and runs pulumi up.
# Full deploy: sync config → set new secrets → pulumi up
pypuwa deploy staging
# Preview only (sync + show changes, no deploy)
pypuwa preview production
# Sync config to Pulumi YAML without deploying
pypuwa sync staging
# With options
pypuwa deploy production --backup # Backup existing YAML before sync
pypuwa deploy staging --dry-run # Sync config, skip deploy
pypuwa deploy staging --runner "uv run pulumi" # Custom Pulumi runner
pypuwa deploy staging --project-name my-infra # Custom config prefix
What pypuwa deploy does
- Sync — Generates
Pulumi.<env>.yamlfrom your Python config, preserving existing encrypted secrets - New secrets — Detects new
secret()fields and prompts you to set values (arrow-key selection, all pre-selected) - Existing secrets — Offers to update existing secret values (arrow-key selection)
- Deploy — Runs
pulumi upon the selected stack
Interactive secret management
New secrets are detected automatically and presented with arrow-key navigation:
? Select secrets to set now (space to toggle, enter to confirm):
❯ ◉ services.api.database.PASSWORD
◉ services.api.app_runner.DJANGO_SECRET_KEY
◉ services.redis.AUTH_TOKEN
All new secrets are pre-selected — just hit enter to set them all, or space to deselect ones you want to skip. Values are entered with hidden input.
Features
Interpolation
SERVICE_NAME: str = "{stack}-my-service" # -> "staging-my-service"
DB_NAME: str = "${services.database.NAME}" # -> resolved from config tree
Secrets
# Auto-inferred from field name
PASSWORD: Secret = secret() # -> loaded from Pulumi encrypted config
# Explicit key
API_KEY: Secret = secret("EXTERNAL_API_KEY")
# Cross-service reference
REDIS_PASS: Secret = secret("${services.redis.AUTH_TOKEN}")
Environment Variables
@dataclass(kw_only=True)
class MyConfig(BaseAppRunnerConfig):
DEBUG: str = "True"
DATABASE_URL: str = "postgres://..."
CPU: str = "2 vCPU" # Infrastructure, not an env var
_NON_ENV_FIELDS: FrozenSet[str] = frozenset(["CPU", "MEMORY"])
config = MyConfig(SERVICE_NAME="api")
env_vars = config.env_dict()
# {"SERVICE_NAME": "api", "DEBUG": "True", "DATABASE_URL": "postgres://..."}
# CPU and MEMORY are excluded
Configuration Manager
from pypuwa import ConfigurationManager
manager = ConfigurationManager(
environments={"staging": staging_config, "production": production_config}
)
# Generate resolved config
stack = manager.generate_stack_config("my-stack", "staging")
# Generate Pulumi YAML
yaml_config = manager.to_pulumi_yaml("my-stack", "production")
# Validate
issues = manager.validate("staging")
Project Structure
A project using pypuwa looks like this:
my-infra/
├── __main__.py # Pulumi entry point
├── Pulumi.yaml # Pulumi project definition
├── Pulumi.staging.yaml # Generated (secrets encrypted here)
├── Pulumi.production.yaml # Generated
├── config/
│ ├── base/
│ │ ├── stack.py # MyStackConfig(BaseStackConfig)
│ │ └── services/
│ │ ├── api.py # Database + AppRunner configs
│ │ ├── worker.py # Worker config
│ │ └── redis.py # Cache config
│ └── environments/
│ ├── staging.py # staging_config = MyStackConfig(...)
│ └── production.py # production_config = MyStackConfig(...)
├── components/ # Your cloud-specific Pulumi wrappers
│ ├── database.py
│ ├── container_app.py
│ └── redis.py
└── services/ # Deployment orchestration
├── api/entrypoint.py
└── worker/entrypoint.py
See examples/basic-project for a complete working example.
Requirements
- Python >= 3.11
- Pulumi >= 3.0
License
MIT
Project details
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 pypuwa-0.1.3.tar.gz.
File metadata
- Download URL: pypuwa-0.1.3.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
144460cd0ca561bc59d9a2c44f0fa1cc25ff793a963fef9a90c0eec64ac4471a
|
|
| MD5 |
2f8ccba9a8777c1335e98bda92dcf830
|
|
| BLAKE2b-256 |
3764ff67942e6cf159f499098b5b2133a29bb3d3d1a3a10c6154136ce7b03fac
|
Provenance
The following attestation bundles were made for pypuwa-0.1.3.tar.gz:
Publisher:
publish.yml on cafadev/pypuwa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pypuwa-0.1.3.tar.gz -
Subject digest:
144460cd0ca561bc59d9a2c44f0fa1cc25ff793a963fef9a90c0eec64ac4471a - Sigstore transparency entry: 1598385501
- Sigstore integration time:
-
Permalink:
cafadev/pypuwa@bc5ae32684c0fa0e9102ee675a969927ffffdab3 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/cafadev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc5ae32684c0fa0e9102ee675a969927ffffdab3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pypuwa-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pypuwa-0.1.3-py3-none-any.whl
- Upload date:
- Size: 21.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e503a4ba31da29d897f506993e672af7b5ae0f55b2c40d10c567ed6f1fd4fefd
|
|
| MD5 |
e655e95fee5803d20f22caf9ec65c04b
|
|
| BLAKE2b-256 |
54caef8f600ffddb991e8faaa36fcd7b11f76cc5e10ebd8b8942caa4d16bbb5c
|
Provenance
The following attestation bundles were made for pypuwa-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on cafadev/pypuwa
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pypuwa-0.1.3-py3-none-any.whl -
Subject digest:
e503a4ba31da29d897f506993e672af7b5ae0f55b2c40d10c567ed6f1fd4fefd - Sigstore transparency entry: 1598385588
- Sigstore integration time:
-
Permalink:
cafadev/pypuwa@bc5ae32684c0fa0e9102ee675a969927ffffdab3 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/cafadev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bc5ae32684c0fa0e9102ee675a969927ffffdab3 -
Trigger Event:
release
-
Statement type: