ps.testing
ps.testing.assert_issue
Section titled “ps.testing.assert_issue”ps.testing.assert_issue(subject: Any, *, code: str, address: str | None = None, fix_contains: str | None = None) -> Issue
Assert that a subject’s checks hold an issue of code, and return that issue.
ps.testing.assert_issue(lambda: ps.tracer.Tracer(name="tracer1", phase="water"), code="tracer.name.invalid") passes: the construction is refused with that code.
Parameters
Section titled “Parameters”subject : object
Where the rows are read: a ps.Model (its m.check() rows), a ps.ModelError or
ps.CompileError (its issues), any object with an issues list, one
ps.Issue, a list of issues, or a callable: called with no argument, the rows of the
ModelError or CompileError it raises, else the rows of what it returns. Any other
exception the callable raises propagates, a field refusal (field.unknown,
field.invalid) among them.
code : str
The issue code to find ("well.control.missing").
address : str, optional
The address the issue must name as well ("schedule/2021-01-01/P1/control").
fix_contains : str, optional
Text the issue’s fix must hold, matched without regard to case.
Returns
Section titled “Returns”Issue The first matching issue.
Raises
Section titled “Raises”AssertionError No issue matches; the message lists the code and address of every issue found.
ps.testing.first_divergence
Section titled “ps.testing.first_divergence”ps.testing.first_divergence(expected: Model, actual: Model) -> str | None
Return where two models first differ, field by field, or None when they are equal.
ps.testing.first_divergence(m, loaded) is None when loaded, the model the deck of m
loads back as, equals it field for field. Where a value came from is not compared (a
provenance, a table’s source, an array’s name), and a summary compares by the
vectors it requests, not how they were grouped. A stated bare EQLDIMS equals no statement
where the compile would derive the bare card itself (EQUIL over one equilibration region):
elsewhere it counts.
Parameters
Section titled “Parameters”expected : Model The model as stated (the one compiled). actual : Model The model to compare with it (the one its deck loads back as).
Returns
Section titled “Returns”str or None
"<dotted path>: expected <value>, imported <value>" for the first field that differs,
walking the models’ fields in order ("model.rock.porosity: expected 0.22, imported 0.2"); None when none does.
ps.testing.records
Section titled “ps.testing.records”ps.testing.records(path_or_text: str | Path, *, includes: Literal['refuse', 'expand'] = 'refuse') -> tuple[KeywordRecord, ...]
Read a deck file, or deck text, as the keyword occurrences it states, in deck order.
A str holding a line break is deck text; any other str or path names a file. Both
are read as the deck importer reads them, and a deck it refuses is refused here with the
same DeckImportError.
Every occurrence is kept, duplicates included, so a keyword the deck
states twice is two records in the order the deck states them; nothing
is keyed by name. Content the deck skips (SKIP/ENDSKIP, after
END) is not read; ps.load reads what a deck means.
Parameters
Section titled “Parameters”path_or_text : str or Path
The deck’s file, or its text.
includes : {“refuse”, “expand”}
"refuse" (the default) refuses a deck that includes another file, so what comes back
is one self-contained file; "expand" reads the included files in place, each record’s
at naming the file it came from. Text has no folder to include from, so an INCLUDE in
text is refused either way, and each record’s at names the text <rendered deck>.
Returns
Section titled “Returns”tuple of KeywordRecord
One per keyword occurrence: its name, its section, its records (the data
records in deck order, each a tuple of int, float, str or None: a n*value repeat
expanded, a defaulted slot None), at (the file and line) and lines (the body of
a keyword kept verbatim, TITLE, UDQ or ACTIONX, which then has no records; empty for
every other keyword).
Raises
Section titled “Raises”DeckImportError
A deck the importer refuses, with its code: deck.include.present for an INCLUDE under
includes="refuse" or in text, naming the INCLUDE line.
ValueError
includes is neither "refuse" nor "expand".
PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt