Python runtime for the Fluent (FTL) specification: bidirectional parsing, CLDR-backed locale-aware formatting, and fail-fast boot validation with structured audit evidence.
Project description
FTLLexEngine
Python runtime for the Fluent (FTL) specification. Locale-aware numbers, dates, and currency — bidirectional, thread-safe, strict-mode validated, Decimal-precise — in .ftl files, not your code.
Why FTLLexEngine?
- Locale-aware numbers, dates, and currency --
NUMBER(),DATETIME(),CURRENCY()format values per Unicode CLDR for 200+ locales. One function call, correct output everywhere - Bidirectional -- Format data for display and parse user input back to exact Python types.
"12.450,00 EUR"→(Decimal('12450.00'), 'EUR') - Thread-safe -- No global state. 100 concurrent requests, zero locale conflicts
- Strict by default -- Errors raise exceptions, not silent
{$amount}fallbacks. Passstrict=Falsefor soft error recovery - Boot validation --
LocalizationBootConfigvalidates all resources and message schemas before your application accepts traffic. Fail before the first request, not during it - Introspectable -- Query what variables a message needs before you call it
- Declarative grammar -- Plurals, gender, and cases in
.ftlfiles. Code stays clean - Decimal precision --
Decimalthroughout. No float math, no rounding surprises
Meet Alice and Bob.
Alice exports specialty coffee. Her invoices ship to buyers in Tokyo, Hamburg, and New York. Three languages, three currency formats, zero tolerance for rounding errors. "1 bag" in English, "1 Sack" in German, "1袋" in Japanese -- and Polish has four plural forms, Arabic has six. She moved grammar rules to .ftl files and never looked back.
Bob runs supply operations at Mars Colony 1. Personnel from Germany, Japan, and Colombia order provisions in their own locale. A German engineer types "12.450,00 EUR". A Japanese technician enters "¥1,245,000". Bob's system needs exact Decimal values from both. One parsing error on a cargo manifest means delayed shipments for 200 colonists.
FTLLexEngine keeps their systems coherent. Built on the Fluent specification that powers Firefox. 200+ locales via Unicode CLDR. Thread-safe by default.
Quick Start
from ftllexengine import FluentBundle
bundle = FluentBundle("en_US", use_isolating=False)
bundle.add_resource("""
coffee-order = { $bags ->
[one] 1 bag of { $origin } coffee
*[other] { $bags } bags of { $origin } coffee
}
""")
result, errors = bundle.format_pattern("coffee-order", {"bags": 500, "origin": "Ethiopian"})
# "500 bags of Ethiopian coffee"
use_isolating=Falseremoves Unicode bidi isolation markers from output, making strings suitable for direct comparison and logging. The defaultuse_isolating=Truewraps each placeable in U+2068/U+2069 markers for correct bidirectional text rendering in UI contexts.
Parse user input back to Python types:
from ftllexengine.parsing import parse_currency
# German buyer enters a bid price
result, errors = parse_currency("12.450,00 EUR", "de_DE", default_currency="EUR")
if not errors:
amount, currency = result # (Decimal('12450.00'), 'EUR')
Table of Contents
- Installation
- Multi-Locale Formatting — Alice Ships to Every Port
- Bidirectional Parsing — Bob Parses Every Input
- Thread-Safe Concurrency — 100 Threads, Zero Race Conditions
- Streaming Resource Loading — Large Files Without Peak Memory
- Async Applications — Non-Blocking Formatting
- Message Introspection — Pre-Flight Checks
- Production Boot Validation — Systems That Accept Traffic Safely
- Currency Data — Operations Across Borders
- Architecture at a Glance
- When to Use FTLLexEngine
- Documentation
- Contributing
- License
Installation
uv add ftllexengine[babel]
Or with pip:
pip install ftllexengine[babel]
Requirements: Python >= 3.13 | Babel >= 2.18
Parser-only installation (no Babel dependency)
uv add ftllexengine
Or: pip install ftllexengine
Works without Babel:
- FTL syntax parsing (
parse_ftl(),serialize_ftl()) - AST manipulation and transformation
- Validation and introspection
Requires Babel:
FluentBundle(locale-aware formatting)FluentLocalization(multi-locale fallback)- Bidirectional parsing (numbers, dates, currency)
Multi-Locale Formatting — Alice Ships to Every Port
Alice's invoices go to Tokyo, Hamburg, and New York. Same data, different languages, different number formats. She maintains one .ftl file per locale. Translators edit the files. Her trading platform ships features.
English (New York buyer):
from decimal import Decimal
from ftllexengine import FluentBundle
bundle = FluentBundle("en_US", use_isolating=False)
bundle.add_resource("""
shipment-line = { $bags ->
[0] No bags shipped
[one] 1 bag of { $origin } coffee
*[other] { $bags } bags of { $origin } coffee
}
invoice-total = Total: { CURRENCY($amount, currency: "USD") }
""")
result, _ = bundle.format_pattern("shipment-line", {"bags": 500, "origin": "Colombian"})
# "500 bags of Colombian coffee"
result, _ = bundle.format_pattern("invoice-total", {"amount": Decimal("187500.00")})
# "Total: $187,500.00"
German (Hamburg buyer):
bundle_de = FluentBundle("de_DE", use_isolating=False)
bundle_de.add_resource("""
shipment-line = { $bags ->
[0] Keine Saecke versandt
[one] 1 Sack { $origin } Kaffee
*[other] { $bags } Saecke { $origin } Kaffee
}
invoice-total = Gesamt: { CURRENCY($amount, currency: "EUR") }
""")
result, _ = bundle_de.format_pattern("shipment-line", {"bags": 500, "origin": "kolumbianischer"})
# "500 Saecke kolumbianischer Kaffee"
result, _ = bundle_de.format_pattern("invoice-total", {"amount": Decimal("187500.00")})
# "Gesamt: 187.500,00\u00a0€" (CLDR: non-breaking space before symbol)
Japanese (Tokyo buyer):
bundle_ja = FluentBundle("ja_JP", use_isolating=False)
bundle_ja.add_resource("""
shipment-line = { $bags ->
[0] 出荷なし
*[other] { $origin }コーヒー { $bags }袋
}
invoice-total = 合計:{ CURRENCY($amount, currency: "JPY") }
""")
result, _ = bundle_ja.format_pattern("shipment-line", {"bags": 500, "origin": "コロンビア"})
# "コロンビアコーヒー 500袋"
result, _ = bundle_ja.format_pattern("invoice-total", {"amount": Decimal("28125000")})
# "合計:¥28,125,000"
Bob uses the same pattern at Mars Colony 1. Spanish for the Colombian agronomists? Add one .ftl file. Zero code changes.
In production, translators maintain separate
.ftlfiles per locale. Your code loads them withPath("invoice_de.ftl").read_text().
Bidirectional Parsing — Bob Parses Every Input
Most libraries only format outbound data. That's a one-way trip.
Bob's colonists type orders and quantities in their local format. A German engineer enters "12.450,00 EUR". A Colombian agronomist enters "45.000.000 COP". A Japanese technician files a delivery date as "2026年3月15日". FTLLexEngine parses them all to exact Python types.
from ftllexengine.parsing import (
parse_currency,
parse_date,
parse_decimal,
parse_fluent_number,
)
# German engineer enters a bid in EUR
bid_result, errors = parse_currency("12.450,00 EUR", "de_DE", default_currency="EUR")
if not errors:
bid_amount, bid_currency = bid_result # (Decimal('12450.00'), 'EUR')
# Colombian agronomist enters an ask in COP
ask_result, errors = parse_currency("45.000.000 COP", "es_CO", default_currency="COP")
if not errors:
ask_amount, ask_currency = ask_result # (Decimal('45000000'), 'COP')
# Japanese technician enters a delivery date
contract_date, errors = parse_date("2026年3月15日", "ja_JP")
# datetime.date(2026, 3, 15)
# German engineer enters a localized amount for use in a Fluent message
fnum, errors = parse_fluent_number("12.450,00", "de_DE")
if not errors:
# FluentNumber(value=Decimal('12450.00'), formatted='12.450,00', precision=2)
# Pass fnum directly as a $variable — it carries its formatting metadata
pass
flowchart TB
A["German Engineer<br>12.450,00 EUR"] --> PA["parse_currency()<br>de_DE"]
B["Colombian Agronomist<br>45.000.000 COP"] --> PB["parse_currency()<br>es_CO"]
C["Japanese Technician<br>2026年3月15日"] --> PC["parse_date()<br>ja_JP"]
PA --> RA["Decimal('12450.00')<br>EUR"]
PB --> RB["Decimal('45000000')<br>COP"]
PC --> RC["date(2026, 3, 15)"]
RA & RB & RC --> SYS[("Inventory System<br>Exact Python types")]
style PA fill:#f9f,stroke:#333,stroke-width:2px
style PB fill:#f9f,stroke:#333,stroke-width:2px
style PC fill:#f9f,stroke:#333,stroke-width:2px
When parsing fails, you get structured errors -- not exceptions:
price, errors = parse_decimal("twelve thousand", "en_US")
# price = None
# errors = (FrozenFluentError(...),)
if errors:
err = errors[0]
print(err) # "Failed to parse decimal 'twelve thousand' for locale 'en_US': ..."
Decimal Precision
Alice calculates contract values. Float math fails: 0.1 + 0.2 = 0.30000000000000004.
FTLLexEngine uses Decimal throughout:
from decimal import Decimal
from ftllexengine.parsing import parse_currency
price_result, errors = parse_currency("$4.25", "en_US", default_currency="USD")
if not errors:
price_per_lb, currency = price_result # (Decimal('4.25'), 'USD')
bags = 500
lbs_per_bag = Decimal("132") # Standard 60kg bag
total_lbs = bags * lbs_per_bag
contract_value = total_lbs * price_per_lb
# Decimal('280500.00') -- exact, every time
No Silent Failures
[!NOTE] A missing variable returns a fallback string like
"Contract: 500 bags at {!CURRENCY}/lb". In financial systems or mission-critical operations, displaying this to a user is unacceptable.
FluentBundle defaults to strict=True. On any formatting error, FTLLexEngine raises immediately -- no bad data reaches the user.
from decimal import Decimal
from ftllexengine import CacheConfig, FluentBundle, FormattingIntegrityError
# strict=True is the DEFAULT: raises FormattingIntegrityError on ANY formatting error
bundle = FluentBundle("en_US", cache=CacheConfig(), use_isolating=False)
bundle.add_resource('confirm = Contract: { $bags } bags at { CURRENCY($price, currency: "USD") }/lb')
# Works normally when all variables are provided
result, _ = bundle.format_pattern("confirm", {"bags": 500, "price": Decimal("4.25")})
# "Contract: 500 bags at $4.25/lb"
# Missing variable raises immediately (default strict=True behavior)
try:
bundle.format_pattern("confirm", {"bags": 500}) # forgot $price
except FormattingIntegrityError as e:
print(f"HALT: {e.message_id} failed")
# e.fallback_value = "Contract: 500 bags at {!CURRENCY}/lb"
# e.fluent_errors = (FrozenFluentError(...),)
# For soft error recovery, opt in with strict=False
soft_bundle = FluentBundle("en_US", strict=False, use_isolating=False)
soft_result, soft_errors = soft_bundle.format_pattern("missing-message", {})
# soft_result = "{missing-message}" (fallback: key wrapped in braces)
# soft_errors = (FrozenFluentError(...),)
Thread-Safe Concurrency — 100 Threads, Zero Race Conditions
Alice's trading desk gets busy. Bids from Frankfurt, asks from Bogota, confirmations to Tokyo -- concurrent requests, each in a different locale. Bob's colony runs the same pattern: 200 settlers, simultaneous orders, mixed locales.
The problem: Python's locale module uses global state. Thread A sets German, Thread B reads it, chaos ensues.
The solution: FTLLexEngine bundles are isolated. No global state. No locks you manage. No race conditions.
from concurrent.futures import ThreadPoolExecutor
from decimal import Decimal
from ftllexengine import FluentBundle
# Create locale-specific bundles (typically done once at startup)
de_bundle = FluentBundle("de_DE", use_isolating=False)
es_bundle = FluentBundle("es_CO", use_isolating=False)
ja_bundle = FluentBundle("ja_JP", use_isolating=False)
ftl_source = 'confirm = { CURRENCY($amount, currency: "USD") } per { $unit }'
de_bundle.add_resource(ftl_source)
es_bundle.add_resource(ftl_source)
ja_bundle.add_resource(ftl_source)
def format_confirmation(bundle, amount, unit):
result, _ = bundle.format_pattern("confirm", {"amount": amount, "unit": unit})
return result
with ThreadPoolExecutor(max_workers=100) as executor:
futures = [
executor.submit(format_confirmation, de_bundle, Decimal("4.25"), "lb"),
executor.submit(format_confirmation, es_bundle, Decimal("4.25"), "lb"),
executor.submit(format_confirmation, ja_bundle, Decimal("4.25"), "lb"),
]
confirmations = [f.result() for f in futures]
# ["4,25\u00a0$ per lb", "US$4,25 per lb", "$4.25 per lb"]
# (CLDR locale-specific symbols; de_DE uses non-breaking space before $)
FluentBundle and FluentLocalization are thread-safe by design:
- Multiple threads can format messages simultaneously (read lock)
- Adding resources or functions acquires exclusive access (write lock)
- You don't manage any of this -- it just works
Streaming Resource Loading — Large Files Without Peak Memory
Bob's colony manifest system loads .ftl files that grow as new message templates accumulate. Loading the entire file into a string before parsing wastes memory on large resources — and for pipelines reading from network streams, full-string loading isn't possible.
add_resource_stream and parse_stream_ftl accept any line iterator. Memory stays proportional to the largest single FTL entry, not the full file:
from ftllexengine import FluentBundle, parse_stream_ftl
# Load directly from a file object — no full-file read
bundle = FluentBundle("en_US")
with open("colony_messages.ftl", encoding="utf-8") as f:
junk = bundle.add_resource_stream(f, source_path="colony_messages.ftl")
# Or iterate entries without a bundle (parser-only install works too)
with open("colony_messages.ftl", encoding="utf-8") as f:
for entry in parse_stream_ftl(f):
print(type(entry).__name__, getattr(entry.id, "name", ""))
Same guarantees as add_resource:
- Strict mode: raises
SyntaxIntegrityErroron junk entries (defaultstrict=True) - Thread-safe: entries collected outside the lock, committed atomically
- Soft mode:
add_resource_streamreturnstuple[Junk, ...]whenstrict=False
FluentLocalization.add_resource_stream works identically for multi-locale setups:
from ftllexengine import FluentLocalization
from ftllexengine.localization import PathResourceLoader
l10n = FluentLocalization(["de_DE", "en_US"], ["messages"], PathResourceLoader("locales/"))
with open("extra_de.ftl", encoding="utf-8") as f:
l10n.add_resource_stream("de_DE", f, source_path="extra_de.ftl")
Async Applications — Non-Blocking Formatting
Alice's trading platform runs on asyncio. FluentBundle is thread-safe, but calling it from an async handler blocks the event loop for the duration of each format call. AsyncFluentBundle eliminates this: every mutation and formatting operation runs in a thread pool via asyncio.to_thread(), leaving the event loop free.
import asyncio
from ftllexengine import AsyncFluentBundle
async def handle_request(name: str, bags: int) -> str:
async with AsyncFluentBundle("en_US") as bundle:
await bundle.add_resource("""
coffee-order = { $bags ->
[one] 1 bag for { $name }
*[other] { $bags } bags for { $name }
}
""")
result, _ = await bundle.format_pattern(
"coffee-order", {"name": name, "bags": bags}
)
return result
# Shared bundle across requests (create once, reuse):
_bundle = AsyncFluentBundle("en_US")
async def startup() -> None:
with open("messages.ftl", encoding="utf-8") as f:
await _bundle.add_resource_stream(f, source_path="messages.ftl")
Same strict-mode guarantees as FluentBundle:
strict=True(default): raisesFormattingIntegrityErroron any resolution error- Fast read operations (
has_message,get_message,introspect_message) remain synchronous — O(1) dict lookups too short to block the event loop
Message Introspection — Pre-Flight Checks
Bob's systems generate cargo manifests. Before calling format_pattern(), they verify: what variables does this message require? Are all of them available?
Alice's compliance team uses the same introspection to catch missing variables at build time, not during live operations.
from ftllexengine import FluentBundle
bundle = FluentBundle("en_US", use_isolating=False)
bundle.add_resource("""
contract = { $buyer } purchases { $bags ->
[one] 1 bag
*[other] { $bags } bags
} of { $grade } coffee from { $seller } at { CURRENCY($price, currency: "USD") }/lb.
Shipment: { $port } by { DATETIME($ship_date) }.
""")
info = bundle.introspect_message("contract")
info.get_variable_names()
# frozenset({'buyer', 'bags', 'grade', 'seller', 'price', 'port', 'ship_date'})
info.get_function_names()
# frozenset({'CURRENCY', 'DATETIME'})
info.has_selectors
# True (uses plural selection for bags)
info.requires_variable("price")
# True
Use cases:
- Verify all required data before generating manifests or confirmations
- Auto-generate input fields from message templates
- Catch missing variables at build time, not during live operations
Production Boot Validation — Systems That Accept Traffic Safely
Alice's trading platform and Bob's colony manifest system can't discover a bad .ftl file mid-operation. They validate everything at startup.
LocalizationBootConfig is the production boot sequence: load all resources, run require_clean() to assert every locale loaded without errors, and validate all message schemas before the first request arrives. If anything is wrong, it raises before traffic starts -- not during it.
from ftllexengine import LocalizationBootConfig
# Load .ftl files from disk, validate schemas, raise before accepting traffic
cfg = LocalizationBootConfig.from_path(
locales=("en_US", "de_DE", "ja_JP"),
resource_ids=("invoice.ftl", "shipment.ftl"),
base_path="locales/{locale}",
message_schemas={
"invoice-total": {"amount"},
"shipment-line": {"bags", "origin"},
},
# Enforce that critical messages exist in at least one locale
required_messages=frozenset({"invoice-total", "shipment-line"}),
)
# Primary API: returns structured evidence for audit trails
l10n, summary, schema_results = cfg.boot()
# raises IntegrityCheckFailedError if any resource fails, required message is absent, or schema mismatches
print(f"Loaded {summary.total_attempted} resources, {summary.errors} errors")
# "Loaded 6 resources, 0 errors"
# schema_results: tuple[MessageVariableValidationResult, ...] -- one per message_schemas entry
When only the localization object is needed:
l10n = cfg.boot_simple() # raises on failure, discards audit evidence
# l10n is now safe to use for the lifetime of the application
Use cases:
- Regulated systems that must prove clean boot before accepting requests
- Container health checks: boot validation as the readiness probe
- CI pipelines: fail the build if any
.ftlfile has junk or schema drift
Currency Data — Operations Across Borders
Alice sources beans from Colombia, Ethiopia, and Brazil. She sells to importers in Japan, Germany, and the US. Each country uses different currencies with different decimal places.
from ftllexengine.introspection.iso import get_territory_currencies, get_currency
# New buyer in Japan -- what currency?
currencies = get_territory_currencies("JP")
# ("JPY",)
# How many decimal places for yen?
jpy = get_currency("JPY")
jpy.decimal_digits
# 0 -- no decimal places for yen
# Compare to Colombian peso
cop = get_currency("COP")
cop.decimal_digits
# 2
# Multi-currency territories
panama_currencies = get_territory_currencies("PA")
# ("PAB", "USD") -- Panama uses both Balboa and US Dollar
Alice's invoices format correctly: JPY 28,125,000 in Tokyo, $187,500.00 in New York.
Architecture at a Glance
| Component | What It Does | Requires Babel? |
|---|---|---|
Syntax — ftllexengine.syntax |
FTL parser, AST, serializer, visitor pattern | No |
Runtime — ftllexengine.runtime |
FluentBundle, message resolution, thread-safe formatting, built-in functions (NUMBER, CURRENCY, DATETIME) |
Yes |
Localization — ftllexengine.localization |
FluentLocalization multi-locale fallback chains; LocalizationBootConfig strict-mode production boot |
Yes |
Parsing — ftllexengine.parsing |
Bidirectional parsing: numbers, dates, currency back to Python types | Yes |
Introspection — ftllexengine.introspection |
Message variable/function extraction, ISO 3166/4217 territory and currency data | Partial |
Validation — ftllexengine.validation |
Cycle detection, reference validation, semantic checks | No |
Diagnostics — ftllexengine.diagnostics |
Structured error types, error codes, formatting | No |
Integrity — ftllexengine.integrity |
BLAKE2b checksums, strict mode, immutable exceptions | No |
When to Use FTLLexEngine
Use It When:
| Scenario | Why FTLLexEngine |
|---|---|
| Regulated / audited deployments | Boot validation raises before traffic starts. Immutable structured errors for audit trails. |
| Locale-aware numbers, dates, currency | CLDR-backed NUMBER(), DATETIME(), CURRENCY() for 200+ locales. Correct by spec, not by approximation. |
| Parsing user input | Errors as data, not exceptions. Show helpful feedback. |
| Financial calculations | Decimal precision throughout. Strict mode on every bundle. |
| Concurrent systems | Thread-safe. No global locale state. |
| Complex plurals | Polish has 4 forms. Arabic has 6. Handle them declaratively. |
| Multi-locale apps | 200+ locales. CLDR-compliant. |
| Multi-currency operations | ISO 4217 data. Territory-to-currency mapping. Correct decimal places. |
| AI integrations | Introspect messages before formatting. |
| Content/code separation | Translators edit .ftl files. Developers ship code. |
Use Something Simpler When:
| Scenario | Why Skip It |
|---|---|
| Single locale, no user input | f"{value:,.2f}" is enough |
| No grammar logic | No plurals, no conditionals |
| Zero dependencies required | You need pure stdlib |
Documentation
| Resource | Description |
|---|---|
| Quick Reference | Copy-paste patterns for common tasks |
| API Reference | Complete class and function documentation |
| Parsing Guide | Bidirectional parsing deep-dive |
| Data Integrity | Strict mode, checksums, immutable errors |
| Terminology | Fluent and FTLLexEngine concepts |
| Examples | Working code you can run |
Contributing
Contributions welcome. See CONTRIBUTING.md for setup and guidelines.
License
MIT License - See LICENSE.
Implements the Fluent Specification (Apache 2.0).
Legal: PATENTS.md | NOTICE
Project details
Release history Release notifications | RSS feed
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 ftllexengine-0.162.0.tar.gz.
File metadata
- Download URL: ftllexengine-0.162.0.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
216aa5ad7593a7783d7dd04a29472666e2470829e65632afe6e5b1185c173a83
|
|
| MD5 |
9952dab6afb15a667a917b89aff6754a
|
|
| BLAKE2b-256 |
8596862472c7a4715d741200e7e6742cc4bc32c1cdb846b88e051af0861a5fa6
|
Provenance
The following attestation bundles were made for ftllexengine-0.162.0.tar.gz:
Publisher:
publish.yml on resoltico/FTLLexEngine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ftllexengine-0.162.0.tar.gz -
Subject digest:
216aa5ad7593a7783d7dd04a29472666e2470829e65632afe6e5b1185c173a83 - Sigstore transparency entry: 1170545986
- Sigstore integration time:
-
Permalink:
resoltico/FTLLexEngine@36ced1fc28bacb77c2664fbbe9e7b1f685f99fad -
Branch / Tag:
refs/tags/v0.162.0 - Owner: https://github.com/resoltico
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@36ced1fc28bacb77c2664fbbe9e7b1f685f99fad -
Trigger Event:
release
-
Statement type:
File details
Details for the file ftllexengine-0.162.0-py3-none-any.whl.
File metadata
- Download URL: ftllexengine-0.162.0-py3-none-any.whl
- Upload date:
- Size: 363.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43d6831f029c6457183038f74f726d0dfd0a9fe3ded4ceff2533781e9e47689d
|
|
| MD5 |
4a215e5c44b76bfad67e6fda330e7c1a
|
|
| BLAKE2b-256 |
6e9d9806a0bd17bf5d18bec3e40719106dcc9ca59e4d8751527cb5c4eb3a7514
|
Provenance
The following attestation bundles were made for ftllexengine-0.162.0-py3-none-any.whl:
Publisher:
publish.yml on resoltico/FTLLexEngine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ftllexengine-0.162.0-py3-none-any.whl -
Subject digest:
43d6831f029c6457183038f74f726d0dfd0a9fe3ded4ceff2533781e9e47689d - Sigstore transparency entry: 1170546075
- Sigstore integration time:
-
Permalink:
resoltico/FTLLexEngine@36ced1fc28bacb77c2664fbbe9e7b1f685f99fad -
Branch / Tag:
refs/tags/v0.162.0 - Owner: https://github.com/resoltico
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@36ced1fc28bacb77c2664fbbe9e7b1f685f99fad -
Trigger Event:
release
-
Statement type: