Skip to main content

Build a process/service runtime stamp from a format template and pluggable value sources

Project description

runstamp

PyPI Python Tests License

Build a process/service runtime stamp from a str.format template and a handful of pluggable value sources.

Installation

pip install runstamp

Requires Python 3.11+.

Why runstamp?

Almost every long-running service wants a single string identifying the running instance — something to stamp into logs, metrics labels, and tracing spans:

service:acme/billing/api, build:1.42.7, host:node-07, user:svc-billing, run:2026-04-22T09:14:01+00:00

The pieces come from different places: some from the surrounding application, some from environment variables, some from a build-info module, some from socket.gethostname(). There is no stdlib way to assemble them — every project ends up hand-rolling the string with hidden indirections to each value.

runstamp describes that string as a format template plus one Source per placeholder. Sources are plain Python objects, the resolver is async — just a function, a dataclass, and a Protocol.

Quick Start

# my_app/build_info.py — committed as a stub, overwritten by CI before packaging.
# Recommended CI step (e.g. in your build job):
#     echo "build_id = \"$CI_BUILD_VERSION\"" > my_app/build_info.py
# Locally the stub is used as-is, so `build` resolves to "dev" via the source's default.
build_id = "dev"
import asyncio
import socket
from datetime import datetime, timezone

from runstamp import (
    build_runstamp,
    RunstampConfig,
    KwargsSource,
    ImportSource,
    CallableSource,
)

config = RunstampConfig(
    template="service:{project}, build:{build}, host:{host}, run:{started_at}",
    sources={
        "project":    KwargsSource("ctx", attr="project_name"),
        "build":      ImportSource("my_app.build_info", attr="build_id", default="dev"),
        "host":       CallableSource(socket.gethostname),
        "started_at": CallableSource(
            lambda: datetime.now(timezone.utc).isoformat()
        ),
    },
)

app_ctx = type("Ctx", (), {"project_name": "billing"})()

stamp = asyncio.run(build_runstamp(config, ctx=app_ctx))
# In CI (after build_info.py was rewritten with the release version):
# 'service:billing, build:1.42.7, host:node-07, run:2026-04-22T09:14:01+00:00'
# Locally (build_info.py contains build_id = "dev"):
# 'service:billing, build:dev, host:my-laptop, run:2026-04-22T09:14:01+00:00'

Overview

Core: build_runstamp | RunstampConfig | Source

Built-in sources: ConstantSource | EnvVarSource | KwargsSource | ImportSource | CallableSource

Defaults: default_sources | DEFAULT_TEMPLATE


Core

build_runstamp

async def build_runstamp(config: RunstampConfig, /, **context: Any) -> str

Renders config.template by calling resolve(**context) on each source. Every {name} in the template must have a matching entry in config.sources; otherwise KeyError is raised before any source runs. Extra unused sources are ignored — convenient when one source mapping is shared across several templates.

RunstampConfig

Frozen dataclass pairing a template with its source mapping.

from runstamp import RunstampConfig, EnvVarSource

config = RunstampConfig(
    template="{region}-{stage}",
    sources={
        "region": EnvVarSource("AWS_REGION"),
        "stage":  EnvVarSource("STAGE", default="dev"),
    },
)

Source

Protocol describing the single method every source implements:

class Source(Protocol):
    async def resolve(self, **context: Any) -> Any: ...

Write your own source whenever a built-in doesn't fit — for example, a source that fetches from a config server:

from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class ConfigServerSource:
    key: str

    async def resolve(self, **context):
        return await my_config_client.get(self.key)

resolve is async so I/O-bound sources are natural; synchronous work is fine too — just async def and return.

Built-in sources

Every built-in source accepts an optional default=... keyword. When set, any expected failure (missing env var, missing attribute, failed import, callable raising) returns the default instead of propagating. When not set, the failure raises.

ConstantSource

ConstantSource("prod")

Always returns the given value.

EnvVarSource

EnvVarSource("SHARD_ID")
EnvVarSource("REGION", default="us-east-1")

Reads an environment variable. Raises KeyError if missing and default was not supplied.

KwargsSource

KwargsSource("ctx")                           # -> context["ctx"]
KwargsSource("ctx", attr="project")           # -> context["ctx"].project
KwargsSource("ctx", attr="project.name")      # -> context["ctx"].project.name
KwargsSource("ctx", attr="missing", default=None)

Pulls a key from the context kwargs passed to build_runstamp, with optional dotted attribute access.

ImportSource

ImportSource("socket")                                  # -> the module
ImportSource("my_app.build_info", attr="build_id")      # -> attribute value
ImportSource("socket", attr="gethostname", call=True)   # -> gethostname()
ImportSource("missing.mod", attr="x", default=None)     # -> None on ImportError/AttributeError

Imports a module and optionally drills into a dotted attribute path. Set call=True to call the final attribute; if it returns a coroutine it is awaited.

CallableSource

CallableSource(socket.gethostname)
CallableSource(lambda: datetime.now(timezone.utc).isoformat())
CallableSource(fetch_current_tenant)  # async function also fine

Calls any zero-argument callable. Coroutines are awaited. Pass default=... to swallow a specific failure.

Defaults

default_sources

from runstamp import default_sources, RunstampConfig, DEFAULT_TEMPLATE

config = RunstampConfig(DEFAULT_TEMPLATE, default_sources())

Returns a ready-made mapping:

Placeholder Source
company KwargsSource("context", attr="company")
group KwargsSource("context", attr="project_group")
project KwargsSource("context", attr="project_name")
build ImportSource("build_info", attr="build_id", default=None)
environment EnvVarSource("ENVIRONMENT", default=None)
shard_id EnvVarSource("SHARD_ID", default=None)
started_at CallableSource(lambda: datetime.now(UTC).astimezone().isoformat())
host CallableSource(socket.gethostname)
user CallableSource(getpass.getuser)

Mix and match — override just the keys you need:

sources = {**default_sources(), "project": KwargsSource("app", attr="name")}

DEFAULT_TEMPLATE

service:{company}/{group}/{project}, built:{build}, host:{host}, user:{user}, run:{started_at}

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

runstamp-1.0.0.tar.gz (11.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runstamp-1.0.0-py3-none-any.whl (8.6 kB view details)

Uploaded Python 3

File details

Details for the file runstamp-1.0.0.tar.gz.

File metadata

  • Download URL: runstamp-1.0.0.tar.gz
  • Upload date:
  • Size: 11.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for runstamp-1.0.0.tar.gz
Algorithm Hash digest
SHA256 deec6ccada10744d21898b8993f7558c4b45e84928f534878e145dc8b20a4bf9
MD5 8eab0213beb987ac0cc3e6fe12fedb60
BLAKE2b-256 c08d8ed8459e8a296a6f3068bd3802648d71b0a2823cee15143504bb1eb2c2cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for runstamp-1.0.0.tar.gz:

Publisher: publish-pypi.yml on miriada-io/runstamp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file runstamp-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: runstamp-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 8.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for runstamp-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 113c33f2169fd766b97087d001cb706b6c4baebf26accb0ade2afe86a8309993
MD5 dbd95ac8f3e329fd17f47224602b629d
BLAKE2b-256 81347c9c79704f75a6ae1893bfb6b18e61737b63fe2fdd4b71d339f638d53c73

See more details on using hashes here.

Provenance

The following attestation bundles were made for runstamp-1.0.0-py3-none-any.whl:

Publisher: publish-pypi.yml on miriada-io/runstamp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page