https://github.com/igorlg/cfn-handler
Modern CloudFormation Custom Resource lifecycle handler for AWS Lambda
https://github.com/igorlg/cfn-handler
aws cfn cloudformation crhelper custom-resource lambda python
Last synced: 2 days ago
JSON representation
Modern CloudFormation Custom Resource lifecycle handler for AWS Lambda
- Host: GitHub
- URL: https://github.com/igorlg/cfn-handler
- Owner: igorlg
- License: apache-2.0
- Created: 2026-05-20T07:15:31.000Z (14 days ago)
- Default Branch: main
- Last Pushed: 2026-05-20T07:56:04.000Z (14 days ago)
- Last Synced: 2026-05-20T11:05:50.379Z (14 days ago)
- Topics: aws, cfn, cloudformation, crhelper, custom-resource, lambda, python
- Language: Python
- Homepage: https://github.com/igorlg/cfn-handler
- Size: 178 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
- Notice: NOTICE
Awesome Lists containing this project
README
# cfn-handler
[](https://github.com/igorlg/cfn-handler/actions/workflows/ci.yml)
[](https://pypi.org/project/cfn-handler/)
[](https://pypi.org/project/cfn-handler/)
[](https://github.com/igorlg/cfn-handler/releases/latest)
[](LICENSE)
A modern, well-engineered Python library for writing AWS CloudFormation
**Custom Resource** lifecycle handlers. Inspired by — but not derived from —
the unmaintained [`aws-cloudformation/custom-resource-helper`][upstream].
[upstream]: https://github.com/aws-cloudformation/custom-resource-helper
## Why cfn-handler?
CloudFormation Custom Resources are AWS Lambda functions invoked during stack
operations. Getting them right is harder than it looks: you must respond to a
presigned URL within the Lambda timeout, handle CREATE/UPDATE/DELETE semantics
correctly, manage long-running operations via polling, and never leave
CloudFormation hanging.
`cfn-handler` does the boilerplate so you can focus on the resource logic.
```python
from cfn_handler import CustomResource
resource = CustomResource()
@resource.create
def on_create(event, context):
# do work, return data dict
return {"Endpoint": "https://my.example.com"}
@resource.update
def on_update(event, context):
return {"Endpoint": "https://my.example.com"}
@resource.delete
def on_delete(event, context):
pass
def handler(event, context):
return resource(event, context)
```
That's the entire happy path. For long-running operations:
```python
@resource.create
def on_create(event, context):
# kick off work; return None to defer
pass
@resource.poll_create
def on_poll_create(event, context):
# check status; return data when done, raise on failure
if check_ready():
return {"Endpoint": "https://my.example.com"}
# else: do nothing, library will reschedule
```
## Installation
```sh
pip install cfn-handler
# or with uv
uv add cfn-handler
```
`cfn-handler` requires Python 3.10+ and has **zero runtime dependencies**.
Polling support uses `boto3` lazily; `boto3` ships preinstalled in the AWS
Lambda Python runtimes, so no extra install is needed there.
### Or use the AWS Lambda Layer
Every release publishes a public Lambda Layer in ~17 commercial regions. To
use it, reference the ARN in your function definition — no `pip install`
during deploy, no vendoring into your function package:
```yaml
# SAM
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.12
Layers:
- arn:aws:lambda:us-east-1::layer:cfn-handler:N
```
Find the right ARN for your region in the [latest release notes][latest-release]
(per-region table) or the JSON manifest:
```sh
curl -fsSL https://github.com/igorlg/cfn-handler/releases/latest/download/layer-arns.json
```
See [`layer/README.md`](layer/README.md) for SAM/CDK snippets, the alternative
deploy-it-yourself path, and the maintainer's account ID.
[latest-release]: https://github.com/igorlg/cfn-handler/releases/latest
## Comparison to crhelper
If you're coming from [`crhelper`][upstream], the model will feel familiar:
| Feature | crhelper | cfn-handler |
|---|---|---|
| Lifecycle decorators | `@helper.create`, `@helper.update`, `@helper.delete` | `@resource.create`, `@resource.update`, `@resource.delete` |
| Polling decorators | `@helper.poll_create` ... | `@resource.poll_create` ... |
| Type hints | None | Full inline (`py.typed`) |
| Python versions | 3.6+ (last release 2020) | 3.10–3.14 |
| Build | `setup.py` | `pyproject.toml` (PEP 621, hatchling) |
| Tests | unittest | pytest + hypothesis |
| Coverage gate | None | 95% line + branch |
| Type checkers | None | mypy strict + pyright strict |
| Releases | Manual | release-please + PyPI Trusted Publishing |
| Maintained | No (since 2020) | Yes |
The public API is intentionally similar — `cfn-handler` is a clean
re-implementation that carries forward the proven semantics and fixes 14
long-standing upstream issues that never merged. See [CHANGELOG.md](CHANGELOG.md)
for the full list.
## Examples
Working SAM-deployable examples live in `examples/`:
- `examples/basic/` — minimal Create/Update/Delete handler.
- `examples/polled/` — long-running operation with polling.
- `examples/with-physical-id/` — explicit physical resource id (replacement on update).
- `examples/failing/` — handler that fails, demonstrating FAILED-response semantics.
## Testing your handlers
`cfn-handler` ships first-class testing helpers under `cfn_handler.testing`.
The dispatch flow runs in-process; no HTTP, no boto3, no moto setup
required for unit tests:
```python
from cfn_handler import CustomResource
from cfn_handler.testing import assert_success, make_event
def test_my_create_handler():
resource = CustomResource()
@resource.create
def on_create(event, context):
return {"Endpoint": "https://x.example"}
replay = resource.replay(make_event())
assert_success(replay, data={"Endpoint": "https://x.example"})
```
Available surface:
- `CustomResource.replay(event, context=None)` — execute the dispatch
in-process, returning a structured `Replay` (status, data, reason,
payload, ...).
- `make_event(...)`, `make_context(...)` — factories with safe defaults.
- `assert_success`, `assert_failed`, `assert_deferred` — assertion helpers
with informative messages on failure.
- pytest fixtures `cfn_create_event`, `cfn_update_event`,
`cfn_delete_event`, `cfn_lambda_context` — auto-discovered via the
`pytest11` entry point; no `pytest_plugins` declaration needed.
For long-running (polled) handlers, the first `replay()` returns
`Replay(status="DEFERRED")` and mutates the event with marker keys.
A second `replay()` with the mutated event resumes through the poll
handler — useful for testing both halves of a polled lifecycle without
provisioning EventBridge rules.
## Project status
v1.0.0 — first stable release. Follows [Semantic Versioning](https://semver.org).
The public API surface is exactly what's exported from `cfn_handler.__all__`;
everything under `cfn_handler._internal` is implementation detail and may
change between minor versions.
## Contributing
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the development workflow,
commit conventions, and lockfile policy. For contributors who use Nix, a
`flake.nix` provides a reproducible dev shell. The CI/release pipeline
itself is documented in [docs/CI.md](docs/CI.md), including the
local-replay tooling and a postmortem of the v1.0.0 release failure.
## License
Apache License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
This project is inspired by, but does not include code from,
[aws-cloudformation/custom-resource-helper][upstream]. Both projects are
licensed under Apache 2.0.