“An inscribed or painted symbol considered to have magical power.”
Sigils is a Python library for text and meta-text interpolation. It provides context-based interpolation, function execution, nested and recursive interpolation, ambient execution context, built-in transformation tools, and a command-line interface.
The maintained PyPI distribution is named gway-sigils. The Python package and command-line interface remain named sigils, so existing imports and runtime usage are unchanged.
Any Python object can be provided as explicit context, including nested dictionaries, lists, and functions. Sigils supports Python 3.11 and newer and uses the standard-library tomllib module for TOML support.
Optional features can be installed individually or together:
pip install gway-sigils[dotenv]
pip install gway-sigils[yaml]
pip install gway-sigils[markdown]
pip install gway-sigils[astronomy]
pip install gway-sigils[all]
Installation
Install Sigils using pip:
pip install gway-sigils
Syntax
[...] is the canonical sigil syntax. Normal sigils are lazy: wrapping text in Sigil parses and preserves them, and they are resolved only when solve() or the % operator is used.
from sigils import Sigil
template = Sigil("Hello, [user.name]!")
context = {"user": {"name": "Alice"}}
print(template.solve(context)) # Hello, Alice!
print(template % context) # Hello, Alice!
A leading % changes when a sigil is resolved. %[...] is eager and is resolved as soon as the text is placed inside a Sigil envelope.
In Python, eager sigils use the current execution context in this order:
caller locals
caller globals
the active Context
built-in Sigils tools
from sigils import Sigil
name = "Alice"
template = Sigil("Hello, %[name]!")
print(template.template) # Hello, Alice!
Normal and eager sigils can coexist. This allows early-bound values from the execution environment and late-bound values supplied by a later caller:
from sigils import Sigil
project = "gway"
template = Sigil("Project: %[project], user: [user]")
print(template.template)
# Project: gway, user: [user]
print(template.solve({"user": "Alice"}))
# Project: gway, user: Alice
If an eager sigil cannot be resolved from the current execution context, it is preserved intact so that a later explicit solve can still resolve it:
from sigils import Sigil
template = Sigil("%[available_later]")
print(template.template) # %[available_later]
print(template.solve({"available_later": "ready"}))
# ready
Context
Context contributes ambient values to eager sigils without requiring those values to be passed to every Sigil constructor.
from sigils import Context, Sigil
with Context({"greeting": "Hello, world!"}):
template = Sigil("%[greeting]")
print(template.template) # Hello, world!
Caller locals and globals take precedence over values from Context. Explicit solve(context) resolution uses the context passed to solve; it does not implicitly merge the ambient Context.
Nested values, lists, and tools
Dotted sigils traverse nested dictionaries, lists, and object attributes:
from sigils import Sigil
context = {
"users": [
{"name": "Alice"},
{"name": "Bob"},
]
}
print(Sigil("[users.1.name]") % context) # Bob
Dictionary keys are case-sensitive and exact. For example, [name] and [Name] may refer to two different values in the same context.
Built-in tools can be chained as part of an expression:
context = {"name": "alice"}
print(Sigil("[name.upper]") % context) # ALICE
Functions in the context may also be called. Arguments are separated from the function name by spaces:
context = {
"name": "Alice",
"greet": lambda name: f"Hello, {name}!",
}
print(Sigil("[greet name]") % context)
# Hello, Alice!
Prefixing an argument with % treats that argument as a literal value instead of resolving it as another sigil:
print(Sigil("[greet %name]") % context)
# Hello, name!
Multi-argument tools receive all declared arguments after each argument is resolved. For example, structured-data tools can parse a value and select a field in one expression:
context = {
"payload": '{"name": "Alice"}',
"field": "name",
}
print(Sigil("[json payload field]") % context)
# Alice
The built-in sigil tool emits canonical lazy syntax, so applying it to name produces [name] rather than an eager token.
Recursive interpolation
Values may themselves contain sigils. Explicit resolution recursively resolves lazy and eager sigils up to the configured maximum depth.
During constructor-time eager resolution, only eager sigils are consumed. If an eager value expands to a lazy token, that lazy token remains available for a later explicit solve.
from sigils import Sigil
early = "[late]"
template = Sigil("%[early]")
print(template.template) # [late]
print(template.solve({"late": "resolved later"}))
# resolved later
Command-Line Usage
The CLI resolves normal [...] sigils against values supplied on the command line or loaded from a JSON/TOML context file:
sigils "Hello, [user.name]!" -c context.json
sigils "Hello, [name]!" -v name=Alice
sigils -e name.upper -v name=alice
Use --max-depth to control recursive interpolation and --list-sep to choose the separator used when a dictionary is rendered as its keys:
sigils "[a]" -d 0 -v 'a=[b]' -v b=resolved
sigils "[mapping]" -c context.json --list-sep ","
Files can be rendered to standard output, written elsewhere, or overwritten:
sigils -f template.conf -c context.toml
sigils -f template.conf -w generated.conf -c context.toml
sigils -f template.conf -r -c context.toml
When -f points to a directory, files whose names contain sigils are resolved recursively. Both the generated filename and its file contents use the supplied context. A resolved filename must remain a basename in the same directory; absolute paths and parent-relative paths are rejected. Existing destinations are also rejected unless --overwrite is explicitly supplied. Symlinks are removed before an explicit overwrite so rendering never follows a pre-existing symlink outside the selected directory.
--value entries are merged only into mapping contexts. JSON list or scalar contexts remain usable when no --value merge is requested.
The CLI deliberately contains only interpolation operations. Historical benchmark, test-runner, make, and package-release switches are development concerns and are no longer exposed as public CLI flags.
Protected values
Secret marks a value as sensitive without changing sigil syntax. It is a redaction and taint-propagation primitive, not encryption.
from sigils import Secret, Sigil
password = Secret("swordfish")
print(password) # [REDACTED]
print(repr(password)) # Secret('[REDACTED]')
print(password.reveal()) # swordfish -- explicit trusted access
template = Sigil("password=[password]")
context = {"password": password}
print(template.solve(context))
# password=swordfish
print(template.results(context))
# {'password': '[REDACTED]'}
Template rendering is an intentional output operation, so it can consume the real wrapped value. Resolver introspection through results() recursively redacts protected values instead. Dotted traversal, built-in tools, callable arguments, and recursive interpolation preserve the protection marker.
Eager protected values are captured out-of-band instead of storing their raw text in the template:
password = Secret("swordfish")
template = Sigil("password=%[password]")
print(template.template) # password=[REDACTED]
print(template.solve()) # password=swordfish
Use reveal() only at an explicit trusted boundary. Secret does not stop code that deliberately unwraps or renders the value; its purpose is to prevent accidental disclosure through ordinary representations and introspection.
Considerations
Function Execution: If the value of a sigil is callable, it may be executed and its return value used in the string. Only provide contexts and tools that are safe to execute.
Recursion Depth: Sigils resolves recursively up to 6 levels by default. Pass max_depth to Sigil or --max-depth to the CLI to choose a different limit.
Thread Safety: Context uses thread-local state. Mutable objects stored inside a context still require normal application-level synchronization.
Eager Python Context: %[...] reads the Python caller’s locals and globals. Use lazy [...] when a template should depend only on an explicit context supplied later.
Environment Tool: the env built-in exposes only explicitly allowlisted names. Common non-secret process values such as PATH, HOME, USER, SHELL, LANG, PWD, TERM, and TZ are allowed by default. Embedding applications can expose additional names with the comma-separated SIGILS_ENV_ALLOWLIST environment variable. Unapproved names return an empty string, and requesting the complete environment returns only approved entries.
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 gway_sigils-0.4.2.tar.gz.
File metadata
- Download URL: gway_sigils-0.4.2.tar.gz
- Upload date:
- Size: 22.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb4b954bdc8d898a49ff57d94fc7365f1c9d70e678733877dd9a78671344ec1a
|
|
| MD5 |
fcc171299284f2fec68900110aaba796
|
|
| BLAKE2b-256 |
73f34e98b5c63e9d4573b4eafa7912232f20e9e1c97e9d3ffe90e7c900c43db3
|
Provenance
The following attestation bundles were made for gway_sigils-0.4.2.tar.gz:
Publisher:
release.yml on arthexis/sigils
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gway_sigils-0.4.2.tar.gz -
Subject digest:
fb4b954bdc8d898a49ff57d94fc7365f1c9d70e678733877dd9a78671344ec1a - Sigstore transparency entry: 2752746582
- Sigstore integration time:
-
Permalink:
arthexis/sigils@54c3f7f637d7ccf8607de715757bc3199d53c251 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/arthexis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@54c3f7f637d7ccf8607de715757bc3199d53c251 -
Trigger Event:
workflow_run
-
Statement type:
File details
Details for the file gway_sigils-0.4.2-py3-none-any.whl.
File metadata
- Download URL: gway_sigils-0.4.2-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f28fe5c7761ea36a1372ca96333369e04f20bafbc567f474a44d232a0e679076
|
|
| MD5 |
b0f131d1ae384745dcd102f68b2edcda
|
|
| BLAKE2b-256 |
e225eae615e7cec2ceeccf3a6add4157a1169aa07f5a0ae26b0be07855445ef9
|
Provenance
The following attestation bundles were made for gway_sigils-0.4.2-py3-none-any.whl:
Publisher:
release.yml on arthexis/sigils
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gway_sigils-0.4.2-py3-none-any.whl -
Subject digest:
f28fe5c7761ea36a1372ca96333369e04f20bafbc567f474a44d232a0e679076 - Sigstore transparency entry: 2752746583
- Sigstore integration time:
-
Permalink:
arthexis/sigils@54c3f7f637d7ccf8607de715757bc3199d53c251 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/arthexis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@54c3f7f637d7ccf8607de715757bc3199d53c251 -
Trigger Event:
workflow_run
-
Statement type: