Project¶
opm.Project provides a high-level API into opm. It loads an
opm.toml and does what the commands do, taking the same settings from the
config: transform, chunk, index and coverage. ODDs compile on
demand into the user cache, as they do for the CLI.
from pathlib import Path
from opm import Project, collect_xpath_errors
from opm.indexing import write_jsonl
from opm.typst_compile import compile_pdf
project = Project.load('edition/opm.toml')
# One document in several formats. The ODD for each mode comes from
# [transform.<mode>] odd, then [transform] odd, then the packaged ODD.
html = project.transform('edition/data/letter.xml')
docx = project.transform('edition/data/letter.xml', mode='docx') # bytes
toc = project.transform('edition/data/letter.xml', parameters={'mode': 'toc'})
# Typst output compiled to PDF: running typst is up to you.
typst = project.transform('edition/data/letter.xml', mode='typst')
pdf = compile_pdf(typst, root=Path('edition/data')) # needs typst
# Chunk a directory into JSON for a static site generator.
run = project.chunk('edition/data/letters', format='json', overwrite=True)
print(run.output_dir, [doc.name for doc in run.documents])
# Search-index records, and the ODD's coverage of the corpus.
write_jsonl(project.index('edition/data'), Path('index.jsonl'))
report = project.coverage('edition/data')
# See the XPath expressions that failed at run time.
with collect_xpath_errors() as log:
project.transform('edition/data/letter.xml')
for failure in log.ordered_failures():
print(f'{failure.count}× {failure.expression}: {failure.message}')
In a web service¶
A Project keeps the modules it has loaded and the register documents it has
parsed, so load it once and reuse it. One instance can be shared between
threads. It is a snapshot: after changing an ODD or a register, load a new one.
from lxml import etree
from opm import Project
project = Project.load('/srv/edition/opm.toml')
def render(path: str, view: str = 'div') -> str:
return project.transform(f'/srv/edition/data/{path}', parameters={'view': view})
def render_fragment(root: etree._Element, xml_id: str) -> str:
# An element parsed from a file still resolves doc() against that file.
return project.transform(root, xpath=f'id("{xml_id}")')
Paths¶
Paths in opm.toml are relative to the file's directory. Relative paths
passed to the methods are relative to the current directory, as usual in
Python. The chunk output_dir is relative to Project.root, which is the
config file's directory unless Project.load(..., root=...) says otherwise.
Settings in code¶
A project doesn't need a config file:
from pathlib import Path
from opm import Project, ProjectConfig
project = Project(ProjectConfig(parameters={'lang': 'en'}), root='build')
print_project = project.with_config(document_css=Path('print.css'))
The functions in opm.transform, opm.chunking
and opm.indexing are the layer below Project, for callers
that need finer control.
opm.project ¶
The supported way to drive opm from Python: Project.
A specific project configuration and associated methods. Each method does what the command of the same name does, taking the same settings from the config:
from opm import Project
project = Project.load() # ./opm.toml
html = project.transform('data/doc.xml') # -t web
docx = project.transform('data/doc.xml', mode='docx')
run = project.chunk('data/letters', format='json', overwrite=True)
records = project.index('data')
The lower-level functions in opm.transform, opm.chunking and
opm.indexing stay available for callers that need finer control.
ChunkRun
dataclass
¶
ChunkRun(
output_dir: Path,
documents: tuple[Path, ...],
format: str,
modules: tuple[ResolvedTransform, ...],
index_file: Path | None = None,
)
What Project.chunk wrote.
output_dir
instance-attribute
¶
Root of the output, holding one subdirectory per document.
documents
instance-attribute
¶
The XML files that were chunked, in order.
modules
instance-attribute
¶
modules: tuple[ResolvedTransform, ...]
The transform modules used: the main one first, then the fragment ones.
index_file
class-attribute
instance-attribute
¶
The index.html an HTML run writes at the output root.
Project ¶
Project(
config: ProjectConfig | None = None,
*,
root: Path | str | None = None,
)
An opm project represents a specific configuration and its associated methods.
Each method takes whatever its arguments leave open from the config, as the command of the same name does. ODDs compile on demand into the user cache, as they do for the CLI.
A project is a snapshot. It keeps the modules it has loaded and the
register documents it has parsed, so repeated calls are cheap, for
example in a web service. After editing an ODD or a register, make a new
Project. One instance can be shared between threads.
An XPath expression that fails at run time counts as false or empty, as
it does in the CLI. To see which ones failed, wrap the calls in
collect_xpath_errors:
from opm import Project, collect_xpath_errors
project = Project.load('edition/opm.toml')
with collect_xpath_errors() as log:
html = project.transform('edition/data/doc.xml')
for failure in log.ordered_failures():
print(failure.expression, failure.message)
Relative paths given to the methods are relative to the current
directory, as usual in Python. Paths inside opm.toml are relative to
the file's directory, and the chunk output_dir to root.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ProjectConfig | None
|
The project settings. Defaults to an empty config, which uses the packaged ODD. |
None
|
root
|
Path | str | None
|
The project directory: chunk output goes below it, and
|
None
|
Source code in src/opm/project.py
config
instance-attribute
¶
config: ProjectConfig = (
config if config is not None else ProjectConfig()
)
The settings from opm.toml.
root
instance-attribute
¶
The project directory.
load
classmethod
¶
load(
path: Path | str | None = None,
*,
root: Path | str | None = None,
) -> Project
Load the project with the configuration given in path.
Without path, opm.toml in the current directory is read if it
exists; if not, the project has default settings. root defaults to
the config file's directory.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
path was given but is not a file. |
Source code in src/opm/project.py
with_config ¶
with_config(**changes: Any) -> Project
A new project with some ProjectConfig fields replaced.
For example, project.with_config(document_css=Path('print.css')).
Source code in src/opm/project.py
compile ¶
compile(
mode: str | None = None, odd: Path | str | None = None
) -> ResolvedTransform
Compile the ODD for output mode, or find it in the cache.
The ODD is odd, else [transform.<mode>] odd, else
[transform] odd, else the packaged teipublisher.odd.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
str | None
|
An output mode such as |
None
|
odd
|
Path | str | None
|
The ODD to use instead of the configured one. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
mode is not an output mode. |
Source code in src/opm/project.py
transform ¶
transform(
source: Source,
*,
mode: str | None = None,
odd: Path | str | None = None,
xpath: str | None = None,
parameters: dict[str, str] | None = None,
xpath_extensions: Sequence[str] | None = None,
webcomponents: bool | None = None,
template: Path | None = None,
) -> str | bytes
Transform source, as opm transform does.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Source
|
An XML file, or a tree or element parsed with lxml. When
an element comes from a parsed file, |
required |
mode
|
str | None
|
The output mode ( |
None
|
odd
|
Path | str | None
|
The ODD to use instead of the configured one. |
None
|
xpath
|
str | None
|
XPath 3.1 expression selecting the element to transform. Unprefixed names use the document's default namespace. |
None
|
parameters
|
dict[str, str] | None
|
XPath |
None
|
xpath_extensions
|
Sequence[str] | None
|
Extension modules to use instead of the configured ones. |
None
|
webcomponents
|
bool | None
|
Enable web-component mode. |
None
|
template
|
Path | None
|
The document template to use instead of the configured one. |
None
|
Returns:
| Type | Description |
|---|---|
str | bytes
|
|
str | bytes
|
for DOCX and EPUB. |
Source code in src/opm/project.py
chunk_output_dir ¶
Where chunk writes: output_dir, else [chunking] output_dir, below root.
Source code in src/opm/project.py
chunk_modules ¶
chunk_modules(
odd: Path | str | None = None,
) -> tuple[ResolvedTransform, ...]
Compile the modules a chunk run uses: the main one, then one per fragment ODD.
The main ODD is odd, else [chunking] odd, else the packaged one.
Fragments without an ODD of their own use the main module.
Raises:
| Type | Description |
|---|---|
ValueError
|
The config has no |
Source code in src/opm/project.py
chunk ¶
chunk(
source: Path | str,
*,
format: str = "html",
output_dir: Path | str | None = None,
template: Path | None = None,
depth: int | None = None,
odd: Path | str | None = None,
doc_path: str | None = None,
webcomponents: bool | None = None,
xpath_extensions: Sequence[str] | None = None,
overwrite: bool = False,
on_document: Callable[[int, Path], None] | None = None,
on_progress: Callable[[int, int], None] | None = None,
) -> ChunkRun
Split source into pages, as opm chunk does.
source is one XML file or a directory of them. For a directory,
each document goes to a subdirectory named after it, and an HTML run
also writes index.html listing the documents. pb-view output
puts every document's data under <doc_path>/<name>.xml/ instead.
The arguments override the [chunking] settings of the same name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Path | str
|
An XML file, or a directory of XML files (not searched recursively). |
required |
format
|
str
|
|
'html'
|
output_dir
|
Path | str | None
|
Output directory, relative to |
None
|
template
|
Path | None
|
Page template for HTML output. |
None
|
depth
|
int | None
|
Maximum section depth to split at. |
None
|
odd
|
Path | str | None
|
The ODD to use instead of |
None
|
doc_path
|
str | None
|
For |
None
|
webcomponents
|
bool | None
|
Enable web-component mode for HTML output. JSON
and |
None
|
xpath_extensions
|
Sequence[str] | None
|
Extension modules to use instead of the configured ones. |
None
|
overwrite
|
bool
|
Replace the output directory if it exists. Without
it, an existing directory raises |
False
|
on_document
|
Callable[[int, Path], None] | None
|
Called with the position and path of each document before it is chunked. |
None
|
on_progress
|
Callable[[int, int], None] | None
|
Called with |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
The config has no |
FileExistsError
|
The output directory exists and overwrite does not allow replacing it. |
Source code in src/opm/project.py
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
index ¶
index(
sources: Path | str | Iterable[Path | str],
*,
odd: Path | str | None = None,
options: IndexOptions | None = None,
) -> list[dict]
Search-index records for sources, as opm index makes them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sources
|
Path | str | Iterable[Path | str]
|
An XML file, a directory (searched recursively), or a list of either. |
required |
odd
|
Path | str | None
|
The ODD to use instead of |
None
|
options
|
IndexOptions | None
|
Rollup settings. |
None
|
Write the result with opm.indexing.write_jsonl.
Source code in src/opm/project.py
coverage ¶
coverage(
sources: Path | str | Iterable[Path | str],
*,
mode: str = "json",
odd: Path | str | None = None,
parameters: dict[str, str] | None = None,
) -> CoverageReport
Measure the ODD against sources, as opm coverage does.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sources
|
Path | str | Iterable[Path | str]
|
An XML file, a directory (searched recursively), or a list of either. |
required |
mode
|
str
|
|
'json'
|
odd
|
Path | str | None
|
The ODD to use instead of |
None
|
parameters
|
dict[str, str] | None
|
XPath |
None
|
Source code in src/opm/project.py
chunk_input_files ¶
The XML files a chunk run over source reads.
That is source itself, or the *.xml files directly inside it when it
is a directory. Subdirectories are not searched: a chunk run publishes the
pages of a given set of documents.