A specification and blueprint manager for declarative configuration-as-code tools.
Project description
spectrik
A generic specification and blueprint pattern for declarative configuration-as-code tools.
Overview
spectrik provides a reusable framework for building tools that apply declarative configurations to external systems. It includes:
- Specification — an abstract base class for defining desired-state resources
- SpecOp strategies —
Present,Ensure, andAbsentwrappers that control when specs are applied or removed - Blueprint — a named, ordered collection of spec operations
- Project — a top-level build target that orchestrates blueprints, with support for multiple project types in a single workspace
- Workspace — a lazy-resolving collection of projects built from parsed configuration files
- Lifecycle hooks —
@pre_buildand@post_builddecorators for setup and teardown on project subclasses - HCL loading engine — parse
.hclfiles into blueprints and projects with decorator-based registration
Installation
pip install spectrik
Quick Start
import spectrik
from spectrik.hcl import scan
# Register a custom project type
@spectrik.project("myapp")
class MyProject(spectrik.Project):
api_key: str = ""
# Register specs for HCL block decoding
@spectrik.spec("widget")
class Widget(spectrik.Specification["MyProject"]):
color: str = "red"
def equals(self, ctx):
return current_color() == self.color
def apply(self, ctx):
set_color(self.color)
def remove(self, ctx):
reset_color()
# Load from HCL files and build
ws = scan("./configs")
ws["myapp"].build(dry_run=True)
Core Concepts
Specifications
A Specification[P] defines a single desired-state resource. Subclasses
implement:
apply(ctx)— create or update the resource (required)equals(ctx)— returnTrueif current state matches desired state (optional; defaults toNotImplemented)exists(ctx)— returnTrueif the resource exists (optional; defaults to the result ofequals())remove(ctx)— delete the resource (optional; raisesNotImplementedErrorby default for irreversible resources)
When equals() is not implemented (e.g., for specs managing secrets or
opaque values), the Ensure strategy always applies and logs that
equality is unknown. Present and Absent fall back to exists().
SpecOp Strategies
Strategies wrap a spec with conditional execution logic:
| Strategy | Behavior |
|---|---|
Present |
Apply only if the resource doesn't exist |
Ensure |
Apply if current state doesn't match (or equality is unknown) |
Absent |
Remove if the resource exists |
All strategies support dry-run mode, fire Context events, and handle errors consistently.
Projects and Registration
The base Project class is a Pydantic model that orchestrates blueprints.
Consumer apps subclass it with domain-specific fields and register it
using @spectrik.project():
@spectrik.project("railway")
class RailwayProject(spectrik.Project):
token: str = ""
service_id: str = ""
The block type name in HCL maps to the registered project class. Multiple project types can coexist in a single workspace:
railway "api" {
token = "${env.RAILWAY_TOKEN}"
service_id = "abc123"
use = ["deploy"]
}
project "docs" {
use = ["static-site"]
}
The base Project class is automatically registered as the "project"
block type.
Lifecycle Hooks
Project subclasses can define lifecycle hooks using decorators. Hooks run
during Project.build() — after construction but before (or after) spec
execution.
@spectrik.project("railway")
class RailwayProject(spectrik.Project):
token: str = ""
@spectrik.pre_build
def resolve_secrets(self, ctx: spectrik.Context) -> None:
if not self.token:
raise RuntimeError(f"Project '{self.name}' requires 'token'")
self.token = resolve_secret(self.token)
@spectrik.post_build
def cleanup(self, ctx: spectrik.Context) -> None:
close_api_client()
@pre_build— runs before any specs execute. Raising an exception aborts the build. Use this for credential resolution, connection setup, or field validation that can't happen at construction time.@post_build— always runs after specs complete (even on failure), like afinallyblock. Use this for cleanup.
Multiple hooks of the same type run in MRO order (base class first).
Context and Events
Context carries runtime state through the build pipeline:
ctx = spectrik.Context(target=project, dry_run=True, continue_on_error=False)
target— the project instance being builtdry_run— whenTrue, specs report what they would do without making changescontinue_on_error— whenTrue, the build continues past spec failures
Context provides events for observing spec execution:
ctx.on_spec_start += lambda ctx, op: print(f"Starting {op.spec}")
ctx.on_spec_applied += lambda ctx, op: print(f"Applied {op.spec}")
ctx.on_spec_skipped += lambda ctx, op, reason: print(f"Skipped: {reason}")
ctx.on_spec_failed += lambda ctx, op, err: print(f"Failed: {err}")
ctx.on_spec_finish += lambda ctx, op: print(f"Finished {op.spec}")
ctx.on_spec_removed += lambda ctx, op: print(f"Removed {op.spec}")
Events are for external observation (progress bars, logging, metrics). For project-level setup and teardown, use lifecycle hooks instead.
Workspace
A Workspace is a lazy-resolving Mapping[str, Project] built from
parsed HCL files. Projects are resolved fresh on each access.
ws = scan("./configs")
# Access by name
project = ws["myapp"]
# Query by type or name
railway_projects = ws.select(project_type=RailwayProject)
specific = ws.select(name="api")
subset = ws.select(names=["api", "docs"])
HCL Support
spectrik uses HCL as its configuration language.
import spectrik.hcl as hcl
ws = hcl.scan("./configs", context={
"env": os.environ,
"name": "myapp",
"cwd": os.getcwd,
})
ws["myapp"].build()
Interpolation
String values in HCL files support ${...} variable interpolation. Pass a
context dict when loading, and spectrik resolves references after parsing.
project "app" {
description = "${name}"
home = "${env.HOME}/.config/${name}"
workdir = "${cwd}/data"
}
Dotted references walk the context using attribute or key access, so dicts,
dataclasses, and Pydantic models all work naturally. If a context value is
callable, it is invoked at resolution time — useful for values like
"cwd": os.getcwd.
Escaping
Use $$ to produce a literal $ in the output. This is needed when HCL
values contain template syntax meant for other tools:
| You write in HCL | Output after interpolation |
|---|---|
${name} |
Resolved from context |
$${name} |
Literal ${name} |
$${{ secrets.TOKEN }} |
Literal ${{ secrets.TOKEN }} |
For example, to embed a GitHub Actions workflow that mixes spectrik variables with Actions expressions:
blueprint "deploy" {
present "file" {
path = ".github/workflows/deploy.yaml"
content = <<-EOF
name: Deploy
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Deploying ${app_name}"
env:
TOKEN: $${{ secrets.GITHUB_TOKEN }}
EOF
}
}
In this example, ${app_name} is resolved by spectrik while
$${{ secrets.GITHUB_TOKEN }} produces the literal ${{ secrets.GITHUB_TOKEN }}
that GitHub Actions expects.
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 spectrik-0.5.1.tar.gz.
File metadata
- Download URL: spectrik-0.5.1.tar.gz
- Upload date:
- Size: 66.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c27f961e13a9189b1f41237cadb4ce5e458b29c45d767033c088eb5a54a485a1
|
|
| MD5 |
82930d5eda3ba0c1186ddedb4b64ddc5
|
|
| BLAKE2b-256 |
444d3959ac7c1b78558cc07fe6365c6cdf928cc2f6085d3f435d22e5e38b832c
|
File details
Details for the file spectrik-0.5.1-py3-none-any.whl.
File metadata
- Download URL: spectrik-0.5.1-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8b1bdb1fe2c4ebc5fad024c801adcfe0df48cf3ce95856f5d7d59eac7b212fa
|
|
| MD5 |
aca26dfe2d36a6e5d6d37fae428fff16
|
|
| BLAKE2b-256 |
5da821bdc80f7976b785902c6bd0c35dba25a06bfbf46da37fa0809e51ba769d
|