Policy, Locale, Pivot, and Render APIs¶
Version scope
This API reference describes WolfXL 2.1.0. Every symbol on this page is
Added in 2.1.0.
These eight areas share one design rule: nothing executes, refreshes, or evaluates unless the caller explicitly opts in, and every refusal is typed.
Two invariants hold across the whole page:
- Active content never executes without an explicit caller-supplied evaluator callback. WolfXL never supplies one.
- Default DataModel evaluation is disabled.
ModelEvaluationPolicy()is default-deny and refuses before a measure is parsed or inspected.
Every example below runs offline against the installed wheel. None of them needs Microsoft Excel or network access.
Lifecycle and deprecation policy¶
What is public: the names this page imports from wolfxl, wolfxl.package,
wolfxl.security, wolfxl.external, and wolfxl.datamodel.engine. Anything
with a leading underscore, plus wolfxl._rust, is internal and may change in
any release.
Within a major series, this surface only grows. A minor release may add a symbol, an enum member, a report field, or an optional keyword argument; it never removes one, renames one, or reorders positional parameters. Treat every typed enumeration as open: match the members you handle and route the rest to a default branch instead of assuming the set is final.
Two behavioral guarantees are part of the contract, not implementation detail. A default that refuses to execute, refresh, or evaluate is never flipped on by a minor release, and a redacted field never starts carrying package payloads, external targets, or credential material.
Structured payloads that a caller parses carry their own schema_version,
which increments when an existing field changes meaning. Assert the versions
you support and ignore unrecognized fields.
A deprecation is marked here as Deprecated in <version>, recorded in the
release notes, and keeps working for the rest of
the major series. Removal happens only in a major release. See
Versioning and Upgrades for the
pinning and upgrade flow.
Active content: inspect, authorize, execute¶
Added in 2.1.0
Import from wolfxl.security.active_content (also re-exported from
wolfxl.security).
Inspection is metadata-only. ActiveContentInventory is built from a
PackageInventory and reports typed channels through ActiveContentFinding.
ActiveContentFinding.part_name is str | None: it is None for an external
relationship originating at the package root and otherwise names the source
part. Findings also carry via_external_relationship, and deliberately omit
external targets, relationship ids, payloads, connection strings, and provider
values.
ActiveContentKind members: VBA, XLM_MACROSHEET, OLE_EMBEDDING,
ACTIVEX, ADD_IN, CUSTOM_UI, POWER_QUERY_DATA_MASHUP, DATA_MODEL,
EXTERNAL_WORKBOOK_LINK, QUERY_TABLE, CONNECTION,
LINKED_RICH_VALUE_PROVIDER, EXTERNAL_RELATIONSHIP.
A customXml/ item is reported as a POWER_QUERY_DATA_MASHUP candidate,
because DataMashup is encoded inside custom XML and cannot be distinguished
without reading that payload. The conservative classification keeps the
redaction boundary from becoming an under-detection boundary.
from wolfxl.package import PackageInventory
from wolfxl.security.active_content import (
ActiveContentExecutionUnavailable,
ActiveContentKind,
ActiveContentPolicy,
evaluate_active_content,
inventory_active_content,
)
parts = {
"[Content_Types].xml": (
b'<Types xmlns="http://schemas.openxmlformats.org/package/2006/'
b'content-types"><Default Extension="xml" ContentType="application/xml"/>'
b"</Types>"
),
"xl/vbaProject.bin": b"\x00\x01",
"customXml/item1.xml": b"<x/>",
}
inventory = inventory_active_content(PackageInventory(parts))
print(inventory.has_active_content) # True
print([kind.value for kind in inventory.kinds])
# ['power_query_data_mashup', 'vba']
print(inventory.has_kind(ActiveContentKind.VBA)) # True
# The default policy refuses before the evaluator argument is even observed.
try:
evaluate_active_content(inventory)
except ActiveContentExecutionUnavailable as refusal:
print(refusal.diagnostic.code.value) # disabled_by_policy
print(refusal.diagnostic.channel_count) # 2
Authorization defaults and bounds¶
ActiveContentPolicy defaults: allow_evaluator=False,
max_recursion_depth=8, max_wall_clock_seconds=30.0,
max_memory_bytes=67108864 (64 MiB), cancellation=None. Hard ceilings
rejected with ValueError at construction: depth above 64, wall clock above
300.0 seconds, memory above 536870912 bytes (512 MiB).
Bounds are handed to the evaluator through
ActiveContentEvaluationRequest (max_recursion_depth,
max_wall_clock_seconds, max_memory_bytes, deadline_monotonic, and
cancelled()). A synchronous Python callback cannot be preempted or
memory-capped by this library, so a caller needing hard isolation must
implement it inside the evaluator, for example in a constrained worker
process.
policy = ActiveContentPolicy(
allow_evaluator=True,
max_recursion_depth=4,
max_wall_clock_seconds=1.0,
max_memory_bytes=1 << 20,
)
outcome = evaluate_active_content(
inventory,
lambda request: request.max_recursion_depth,
policy=policy,
)
print(outcome.status.value) # completed
print(outcome.diagnostic.code.value) # completed
failed = evaluate_active_content(
inventory,
lambda request: (_ for _ in ()).throw(RuntimeError("boom")),
policy=policy,
)
print(failed.status.value) # failed
print(failed.diagnostic.code.value) # evaluator_failed
The evaluator's return value is discarded on purpose: it may carry untrusted
package data, credentials, or provider output, so it must not cross this
security boundary. ActiveContentEvaluationOutcome exposes only status,
diagnostic, and elapsed_seconds.
ActiveContentDiagnosticCode: COMPLETED, DISABLED_BY_POLICY,
EVALUATOR_REQUIRED, CANCELLED, WALL_CLOCK_LIMIT_EXCEEDED,
EVALUATOR_FAILED.
ActiveContentExecutionStatus: COMPLETED, CANCELLED, LIMIT_EXCEEDED,
FAILED.
Raise-versus-return boundary:
| Situation | Result |
|---|---|
allow_evaluator=False |
raises ActiveContentExecutionUnavailable, code disabled_by_policy |
policy allows, evaluator=None |
raises ActiveContentExecutionUnavailable, code evaluator_required |
| cancellation already signalled | raises ActiveContentExecutionUnavailable, code cancelled |
evaluator not callable |
raises TypeError |
| evaluator raised | returns outcome, status failed |
| evaluator overran the wall clock | returns outcome, status limit_exceeded |
| cancellation signalled during the call | returns outcome, status cancelled |
ActiveContentPolicy is unrelated to wolfxl.external.RefreshPolicy: a
refresh allowlist resolves external workbook channels, whereas this policy
decides whether a supplied evaluator may run at all.
OOXML package admission¶
Added in 2.1.0
Import from wolfxl.package. PackageInventory accepts a mapping of part
name to bytes, a bytes payload, a path, or an open zipfile.ZipFile, and
produces an immutable, deterministic, payload-redacted inventory.
from wolfxl.package import PackageError, PackageInventory, allocate_custom_xml_slot
inventory = PackageInventory(parts)
print(inventory.part_names)
# ('[Content_Types].xml', 'customXml/item1.xml', 'xl/vbaProject.bin')
print([(part.name, part.content_type, part.size) for part in inventory.parts][2])
# ('xl/vbaProject.bin', None, 2)
slot = allocate_custom_xml_slot(inventory)
print(slot.parts)
# ('customXml/item2.xml', 'customXml/itemProps2.xml', 'customXml/_rels/item2.xml.rels')
try:
PackageInventory({"../evil.xml": b""})
except PackageError as error:
print(error) # unsafe OOXML package part path
PackagePart exposes name, content_type, size, and sha256;
never the payload. PackageRelationship sets target_part only for internal
relationships, because an external target string can carry credentials or
other sensitive locator material; is_external reports the mode.
PackageInventory also groups custom_xml_parts, embedded_parts, and
external_parts.
allocate_custom_xml_slot returns the first triple whose item,
item-properties, and relationships paths are all free. reserved_parts lets a
caller hold back paths not yet written, so a planned DataMashup triple stays
unavailable to a concurrent generic allocation.
Admission limits¶
Defaults, each overridable by environment variable:
| Limit | Default | Environment variable |
|---|---|---|
| Member count | 200000 | WOLFXL_MAX_ZIP_ENTRIES |
| Single part size | 536870912 (512 MiB) | WOLFXL_MAX_ZIP_ENTRY_BYTES |
| Total uncompressed size | 4294967296 (4 GiB) | WOLFXL_MAX_ZIP_TOTAL_BYTES |
| Per-part compression ratio | 1000 | WOLFXL_MAX_ZIP_COMPRESSION_RATIO |
Redacted error strings¶
Every admission failure raises PackageError, a ValueError subclass. The
messages are fixed and value-redacted, so a rejection never echoes package
content:
invalid OOXML packageOOXML package exceeds member limitOOXML package exceeds total size limitOOXML package part exceeds size limit: <part name>OOXML package part exceeds compression ratio limitOOXML package contains duplicate part namesOOXML package contains case-colliding part namesOOXML package contains duplicate normalized part namesunable to read OOXML package partOOXML package part size changed while readingunsafe OOXML package part pathinvalid content types,invalid content type default,invalid content type override,duplicate content type default,duplicate content type overrideinvalid relationships,invalid relationship part path,relationship source part is missing,relationship target part is missingunsafe relationship target,unsafe external relationship target
Part names are compared after percent-escape decoding, NFC normalization, and
case folding, so duplicate, case-colliding, and normalization-colliding names
are rejected rather than silently merged. Absolute, backslashed, traversing,
and control-character paths are rejected. An external relationship target with
a dangerous URI scheme is rejected as unsafe external relationship target.
Native reader admission tolerates a missing internal sharedStrings or
externalLink target only for the exact standard relationship type URI and
only when the relationship is owned by the workbook part. This narrow
compatibility rule admits stale optional relationships emitted by real
spreadsheet tools; every other missing internal target remains rejected.
Not supported: writing, repairing, or decrypting a package through this API. It is read-only inspection.
Delimited export: formula-injection policy¶
Added in 2.1.0
Import ConversionOptions, FormulaInjectionPolicy,
FormulaInjectionAction, FormulaInjectionReportEntry,
FormulaInjectionRejectedError, and the convert* entry points from
wolfxl.
FormulaInjectionPolicy.ESCAPE is the default. The policy applies to CSV, TSV,
and TXT exports; any other policy paired with another destination format raises
ValueError (formula_injection_policy is supported only for CSV, TSV, and TXT).
| Policy | Rendered bytes | Report entry | Diagnostic |
|---|---|---|---|
ESCAPE (default) |
candidate gets a leading ' |
action escaped |
none |
WARN |
candidate retained verbatim | action warned |
feature_rewritten warning, feature formula_injection |
REJECT |
no artifact published | none | raises FormulaInjectionRejectedError |
ALLOW |
candidate retained verbatim | action allowed |
none |
A candidate is rendered text whose first character after leading ASCII spaces
is a formula marker, or any C0 control in that lead-in (importers can discard
controls before evaluating). A complete signed numeric literal such as -12.5
stays numeric text and is not a candidate.
Delimited exports render U+0000 as the literal text \x00 after policy
evaluation so the output remains readable by supported Python 3.10 CSV tooling.
import io
from wolfxl import (
ConversionOptions,
FormulaInjectionPolicy,
FormulaInjectionRejectedError,
Workbook,
convert_to_bytes,
)
workbook = Workbook()
sheet = workbook.active
sheet["A1"] = "=1+1"
sheet["A2"] = "safe"
buffer = io.BytesIO()
workbook.save(buffer)
source = buffer.getvalue()
escaped = convert_to_bytes(source, options=ConversionOptions(destination_format="csv"))
print(escaped.data) # b"'=1+1\r\nsafe\r\n"
print([entry.to_dict() for entry in escaped.report.formula_injection_entries])
# [{'policy': 'escape', 'action': 'escaped', 'candidates': 1}]
warned = convert_to_bytes(
source,
options=ConversionOptions(
destination_format="csv",
formula_injection_policy=FormulaInjectionPolicy.WARN,
),
)
print(warned.data) # b'=1+1\r\nsafe\r\n'
try:
convert_to_bytes(
source,
options=ConversionOptions(
destination_format="csv",
formula_injection_policy=FormulaInjectionPolicy.REJECT,
),
)
except FormulaInjectionRejectedError as rejection:
print(rejection.to_dict())
# {'policy': 'reject', 'action': 'rejected', 'candidates': 1}
allowed = convert_to_bytes(
source,
options=ConversionOptions(
destination_format="tsv",
formula_injection_policy=FormulaInjectionPolicy.ALLOW,
),
)
print([entry.to_dict() for entry in allowed.report.formula_injection_entries])
# [{'policy': 'allow', 'action': 'allowed', 'candidates': 1}]
Evidence is aggregate and value-redacted: FormulaInjectionReportEntry and
FormulaInjectionRejectedError both expose only policy, action, and a
candidates count, through attributes and to_dict(). The candidate cell text
never appears. A report entry is emitted only when at least one candidate was
seen; REJECT publishes no artifact at all, so it emits no entry.
ConversionOptions also gates cell_range, values, encoding, and bom to
CSV and TSV, rejects utf-16 and utf-32 without an explicit byte order, and
allows bom=True only for UTF encodings.
Multi-area references¶
Added in 2.1.0
Import from wolfxl.formula.reference.
ReferenceArea is one normalized rectangular A1 area; MultiAreaReference is
an ordered tuple of one or more areas. parse_reference_expression parses the
parenthesized Excel reference grammar: a comma inside a grouping parenthesis is
the union operator, whitespace between references is the intersection operator,
and a comma that separates function arguments stays a separator.
from wolfxl.formula.reference import (
MultiAreaReference,
NullIntersectionError,
parse_reference_expression,
)
union = parse_reference_expression("=(A1:B3,D1:D5)")
print(len(union.areas), union.render()) # 2 A1:B3,D1:D5
intersection = parse_reference_expression("=(A1:C5 B2:D9)")
print(intersection.render()) # B2:C5
left = MultiAreaReference.area("Sheet1!A1:B2")
right = MultiAreaReference.area("Sheet1!B2:C3")
print(left.union(right).render()) # Sheet1!A1:B2,Sheet1!B2:C3
print(left.intersection(right).render()) # Sheet1!B2
try:
MultiAreaReference.area("A1:B2").intersection(MultiAreaReference.area("D1:E2"))
except NullIntersectionError as error:
print(NullIntersectionError.error, error) # #NULL! references do not overlap
Calculation semantics:
- Union preserves source order and keeps repeated and overlapping areas.
Excel functions consume each supplied area independently, including
duplicates, so
SUM((A1:A3,A1:A3))double-counts by design. - Intersection pairs every left area with every right area and keeps each non-empty overlap, so an intersection may itself be multi-area.
- Two areas on different sheets never intersect.
- An empty intersection raises
NullIntersectionError, whose class attributeerroris the Excel spelling#NULL!. MultiAreaReferencevalues are frozen and reusable; a reference with zero areas raisesReferenceError.
ReferenceError is the ValueError base for a malformed expression:
an invalid cell or range, a missing closing parenthesis, a non-reference
operand, or a trailing token. NullIntersectionError subclasses it.
Not supported: 3-D references spanning a sheet range, whole-row or whole-column shorthand, and structured table references.
Locale-aware formula input¶
Added in 2.1.0
Import from wolfxl.formula.locale. normalize_formula converts localized
input into invariant OOXML/English syntax before it is assigned to a workbook.
It has no side effects and never touches a workbook.
LocaleProfile defaults are the invariant profile:
decimal_separator=".", function_argument_separator=",",
array_column_separator=",", array_row_separator=";", and empty
localized_booleans, localized_errors, and function_aliases mappings.
LocaleProfile.invariant() returns it.
from wolfxl.formula.locale import (
FormulaNormalizationError,
InvalidLocaleProfile,
LocaleProfile,
normalize_formula,
)
en_us = LocaleProfile.invariant()
print(normalize_formula("=SUM(A1:A3,1.5)", en_us)) # =SUM(A1:A3,1.5)
de_de = LocaleProfile(
decimal_separator=",",
function_argument_separator=";",
array_column_separator="\\",
array_row_separator=";",
localized_booleans={"WAHR": "TRUE", "FALSCH": "FALSE"},
localized_errors={"#WERT!": "#VALUE!"},
function_aliases={"SUMME": "SUM", "WENN": "IF"},
)
print(normalize_formula("=SUMME(A1:A3;1,5)", de_de)) # =SUM(A1:A3,1.5)
print(normalize_formula("=WENN(WAHR;{1\\2;3\\4};#WERT!)", de_de))
# =IF(TRUE,{1,2;3,4},#VALUE!)
OOXML normalization rules:
- The decimal separator becomes
.only between digits; elsewhere a,decimal separator is valid solely as a union operator inside a grouping parenthesis. - The function argument separator becomes
,. - The array column separator becomes
,and the array row separator becomes;, resolved per delimiter context, so a profile that reuses;for function arguments and array rows stays unambiguous. - Boolean and error aliases are matched case-insensitively and replaced with
TRUE,FALSE, or the invariant Excel error code. - A function alias is applied only to an identifier immediately followed by
(. Sheet-qualified identifiers before!, quoted strings, quoted sheet names, and bracketed structured references are preserved byte-for-byte. Unqualified identifiers matching configured boolean or error aliases are normalized even when a caller intended them as defined names; localized defined names are outside this API's contract. - The normalized result is re-tokenized before it is returned, so a profile cannot emit syntax the tokenizer rejects.
Validation and errors:
InvalidLocaleProfile(aFormulaNormalizationError, itself aValueError) is raised at profile construction for a decimal separator other than.or,, a grammar separator outside,;\|, a grammar separator equal to the decimal separator, identical array column and row separators, a boolean target other thanTRUE/FALSE, or an error target outside#NULL!,#DIV/0!,#VALUE!,#REF!,#NAME?,#NUM!,#N/A,#GETTING_DATA.FormulaNormalizationErroris raised for input not starting with=, a separator used in the wrong context, a mismatched or unclosed delimiter, an unterminated string, an unterminated bracketed reference, and any residual tokenizer failure.
Not supported: localized number formats, localized defined names, and localized cell reference styles such as R1C1.
Native pivot filters¶
Added in 2.1.0
Import from wolfxl.pivot.filters (also re-exported from wolfxl.pivot).
Every specification is a frozen dataclass; the public collection is an
immutable tuple.
PivotFilterSpec is the union of LabelFilter, ValueFilter, TopNFilter,
and DateFilter. Each specification names exactly one field, and the
specifications are applied in the order given, each narrowing the record set
the previous one selected.
from datetime import date
from wolfxl.pivot.filters import (
DateFilter,
DatePeriod,
DatePeriodKind,
DateRange,
LabelFilter,
LabelOperator,
NumericOperator,
TopDirection,
TopMode,
TopNFilter,
ValueFilter,
)
label = LabelFilter(field="region", operator=LabelOperator.BEGINS_WITH, value="no")
print(label.to_rust_dict())
# {'kind': 'label', 'field': 'region', 'operator': 'begins_with',
# 'value': 'no', 'case_sensitive': False}
value = ValueFilter(
field="region",
operator=NumericOperator.BETWEEN,
value=10.0,
upper_value=20.0,
measure="amount",
)
top = TopNFilter(field="region", value=3, mode=TopMode.ITEMS, direction=TopDirection.TOP)
window = DateFilter(
field="closed",
selection=DateRange(start=date(2026, 1, 1), end=date(2026, 3, 31)),
)
quarter = DateFilter(
field="closed",
selection=DatePeriod(kind=DatePeriodKind.QUARTER, year=2026, quarter=1),
)
# Public specs are immutable.
try:
label.value = "other"
except Exception as error:
print(type(error).__name__) # FrozenInstanceError
Wire the specifications into a table with
PivotTable(..., filters=(label, top)). PivotTable.filters returns the
immutable tuple; assigning it validates that every element is a
PivotFilterSpec and raises RuntimeError once the layout has been
materialized. Passing a non-spec element raises TypeError. A filter naming a
field absent from the cache raises ValueError
(PivotTable filter does not match a cache field), and a measure name that
matches no data field, or more than one, raises ValueError.
Operators and defaults¶
LabelOperator: EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS,
BEGINS_WITH, NOT_BEGINS_WITH, ENDS_WITH, NOT_ENDS_WITH, BLANK,
NOT_BLANK. BLANK and NOT_BLANK take no operand and reject one with
ValueError; every other operator requires a string value and raises
TypeError without it. case_sensitive defaults to False and folding is
locale-independent.
NumericOperator: EQUALS, NOT_EQUALS, GREATER_THAN,
GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, BETWEEN,
NOT_BETWEEN. measure defaults to 0, the first data field, and accepts a
non-negative index or a data-field name. upper_value is required for the two
range operators, rejected for the others, and must not be below value.
include_lower and include_upper both default to True. Non-finite numbers
raise ValueError.
TopNFilter defaults to direction=TopDirection.TOP and
mode=TopMode.ITEMS with measure=0. ITEMS selects value ranked items
and requires an integral value; PERCENT selects
ceil(value / 100 * item_count) ranked items and requires 0 <= value <= 100;
SUM selects ranked items through the first cumulative aggregate that reaches
value. value must be non-negative. Ties at the cutoff are retained.
DateRange requires plain datetime.date endpoints with start <= end and
defaults both include_start and include_end to True. DatePeriod is
matched without consulting the wall clock: DatePeriodKind.YEAR takes only
year (1 through 9999), QUARTER requires quarter in 1 through 4, MONTH
requires month in 1 through 12, and DAY requires month and day forming
a valid calendar date. Any other combination raises ValueError.
Not supported: filtering a cache field backed by an external hierarchy, and native filter recompute in a build without the pivot recompute extension. In both cases the table raises rather than silently returning unfiltered records.
Loaded-workbook render behavior¶
Added in 2.1.0
Import from wolfxl.render. RenderService routes every request through the
public Workbook.render_* methods, so direct and service callers share one
validation boundary.
import io
from wolfxl import Workbook, load_workbook
from wolfxl.render import RenderOptions, RenderService
workbook = Workbook()
sheet = workbook.active
sheet["A1"] = "hello"
sheet.freeze_panes = "B2"
sheet.print_title_rows = "1:1"
sheet.row_dimensions[2].hidden = True
buffer = io.BytesIO()
workbook.save(buffer)
loaded = load_workbook(io.BytesIO(buffer.getvalue()))
service = RenderService(workbook=loaded)
report = service.render(RenderOptions(format="png", sheet="Sheet"))
print(type(report.result).__name__) # bytes
print(report.diagnostics.selector) # sheet
print(report.diagnostics.rendered_sheet_count) # 1
print(report.diagnostics.sheet_titles) # ('Sheet',)
print(report.diagnostics.unsupported_features) # ()
paginated = service.render(
RenderOptions(format="pdf", sheet="Sheet", paginated=True, pages=[1])
)
print(paginated.diagnostics.page_count) # 1
Supported loaded render behavior:
- Loaded conditional formatting is applied to the rendered cells:
cellIs,expression,containsTextand its negations,containsBlanksandnotContainsBlanks,containsErrorsandnotContainsErrors,duplicateValuesanduniqueValues,top10by rank or percent,aboveAverage, two- and three-stopcolorScale,dataBar, andiconSet. - Conditional
expressionrules and formula-backedcellIs, color-scale, data-bar, and icon-set thresholds use the native calculation engine over the loaded worksheet snapshot. This includes relative and absolute A1 anchoring, rectangular ranges, and the engine's registered function library. - x14 extension metadata for
dataBarandiconSetrules is read, includingx14:cfIconcustom-icon references. A custom icon resolves only when it names a built-in icon set and a valid icon index within it. - Hidden rows and hidden columns collapse to zero height and zero width, so they contribute no pixels and shift their neighbors.
- Freeze and split panes contribute pane offsets to the worksheet canvas layout, and the canvas extent grows so a pane pointing past the last populated cell still lays out the intervening default rows and columns.
- A right-to-left sheet view mirrors the worksheet canvas horizontally: cell rectangles, strokes, images, glyph-run origins, and chart anchors are all reflected across the canvas width.
- Paginated output repeats print-title rows and columns on each page without duplicating them on the page where they naturally appear. Titles that do not start at row 1 or column A begin repeating only after their natural page. Paginated routes ignore freeze and split viewports.
Loud unsupported boundaries. These raise instead of silently omitting content:
- A loaded conditional-format rule the renderer cannot represent raises
ValueErrorwith the messageunsupported loaded conditional format: <code>. Stable codes includeconditional_format_time_period,conditional_format_custom_icon_payload,conditional_format_data_bar_border,conditional_format_data_bar_color_role,conditional_format_unknown_rule, andconditional_format_<rule type>for any other rule type. - A
colorScalewith a stop count outside two or three, adataBaroriconSetover non-numeric cells, a rule referencing a missing differential style, a non-solid or RGB-less differential fill, and an invalid targetsqrefall raiseRuntimeErrorcarrying the nativeunsupported:orinvalid input:detail. - In a right-to-left sheet, rotated cell text raises: mirroring cell glyph
runs never allows rotation. Loaded drawing-shape text keeps its verified
rotation, but a loaded right-to-left shape using line rotation, form-control
rotation, a grouped child path gradient, a grouped gradient combined with
text, a grouped child gradient with non-uniform scaling or non-cardinal
rotation, or a flip the renderer cannot represent raises
ValueErrornaming the shape and the unsupported detail. - A manual page break inside a print-title band, a print-title rows value that is not a whole-row range, and a print-title columns value that is not a whole-column range are all refused.
- A loaded workbook in any format other than
xlsxraisesNotImplementedError. - A wheel built without the
renderCargo feature raisesRuntimeErrornaming the required build flag.
RenderDiagnostics.unsupported_features is reserved for sheet-level visual
content that the renderer intentionally omits, deduplicated and sorted across
every rendered sheet. Font codes are font_family_fallback,
font_substitution_applied, font_family_missing, font_style_fallback,
font_file_unreadable, and font_system_discovery. A chart render reports no
sheet-level codes. A build that cannot inspect a worksheet reports a
backend_unavailable diagnostic rather than an empty tuple that would read as
"nothing was omitted".
Bounded DataModel evaluation¶
Added in 2.1.0
Import from wolfxl.datamodel.engine. Evaluation is offline, in-memory, and
disabled by default.
ModelEvaluationPolicy() is default-deny: enabled=False with every bound at
zero. A disabled policy carrying a non-zero bound raises ValueError, and an
enabled policy missing any positive bound raises ValueError
(enabled model evaluation requires positive finite bounds).
ModelEvaluationPolicy.bounded is the only convenience constructor that
enables evaluation, and it requires max_recursion_depth, timeout_seconds,
and max_memory_bytes together, with an optional CancellationToken. The
policy is unrelated to refresh or network permissions.
from wolfxl.datamodel.engine import (
ColumnReference,
DataModel,
Measure,
ModelEvaluationPolicy,
ModelEvaluationUnavailableError,
Table,
)
sales = Table.from_rows(
"Sales",
("region", "amount"),
[{"region": "north", "amount": 10.0}, {"region": "south", "amount": 5.0}],
)
model = DataModel(
tables=(sales,),
measures=(
Measure(name="Total", expression="SUM('Sales'[amount])"),
Measure(name="North", expression="CALCULATE([Total], 'Sales'[region] = \"north\")"),
),
)
print(model.measure_dependencies("North")) # ('Total',)
# Default policy refuses before parsing or inspecting the measure.
try:
model.evaluate_measure("Total")
except ModelEvaluationUnavailableError as refusal:
print(refusal) # embedded model evaluation is disabled
policy = ModelEvaluationPolicy.bounded(
max_recursion_depth=8,
timeout_seconds=1.0,
max_memory_bytes=1 << 20,
)
print(model.evaluate_measure("Total", policy)) # 15.0
print(model.evaluate_measure("North", policy)) # 10.0
print(model.cube_value("Total", {ColumnReference("Sales", "region"): "south"}, policy))
# 5.0
DataModel.evaluate_measure(name, policy=ModelEvaluationPolicy()) is the
public measure evaluator; the equivalent Rust entry point is
DataModel::evaluate_measure in wolfxl-model, whose internal eval_measure
recursion is not part of the public surface.
DataModel.cube_value(measure, filters, policy) performs a deterministic
CUBE-style lookup with column-qualified equality filters.
DataModel.measure_dependencies(name) resolves transitive references in sorted
name order without evaluating anything. It uses the same fixed internal analysis
limits as public DAX parsing: a recursion depth of 128, five seconds, and 1 MiB
of tracked memory. These limits are independent of ModelEvaluationPolicy and
return ModelEvaluationLimitError rather than exhausting the host.
Model metadata is immutable: Table, Relationship, Measure,
ColumnReference, and DataModel are frozen. A Relationship propagates a
filter one way, from from_column to to_column.
The DAX subset is intentionally small: numbers, measure references,
column references, SUM, arithmetic, and CALCULATE with a single equality
filter. tokenize_dax and parse_dax expose that grammar without evaluating
it; both use the fixed internal analysis limits above.
Redacted error types, all subclasses of ModelEvaluationError (a
RuntimeError). None of their messages contain a formula, a row value, a
package byte, or a credential:
| Error | Meaning |
|---|---|
ModelEvaluationUnavailableError |
evaluation refused before parsing or inspection |
ModelEvaluationLimitError |
a finite recursion, time, or memory bound was reached |
ModelEvaluationCycleError |
a measure dependency cycle was detected |
ModelEvaluationCancelledError |
a caller-owned cancellation token stopped evaluation |
ModelExpressionError |
the restricted DAX subset cannot evaluate the expression |
ModelMetadataError |
model metadata is inconsistent |
Not supported: reading an embedded Power Pivot model out of a workbook
package, refreshing a model, non-equality CALCULATE filters, and aggregate
functions beyond SUM.