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. For a
[[chunking]] array this is the first run's set.
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
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 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 | |
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 odd 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
document_names ¶
document_names(
files: Sequence[Path],
expr: str | None,
config: ProjectConfig,
*,
extensions: Sequence[str] | None = None,
) -> dict[Path, str]
The output directory name of each of files: [chunking] document_name.
expr is evaluated with each document's root as context, in the project's XPath environment; its string value, with path separators replaced, is the name. Without expr every document keeps its file name.
Raises:
| Type | Description |
|---|---|
ValueError
|
A document's name is empty or not a usable directory name, or two documents get the same name. |
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.