stratified_packager.processing.project_builder
Embedded per-stratum project construction (SPEC §13).
Runs on the algorithm thread during Phase C — never against
instance(). The fresh project re-points included layers at
the stratum GeoPackage tables and the data/ payload copies, restores the layer-tree
structure (groups, order) and each node’s presentation state (check state, expanded state,
legend customizations) restricted to included layers, applies the full (rewritten) styles,
remaps relations among included layers, and carries the project CRS, transform context,
title and the source’s initial map view (so it opens at the same position and zoom).
Paths are stored relative: the caller builds the
stratum inside a directory tree that mirrors the zip layout, so Qt’s relative-path
storage produces portable ./… sources (SPEC §13).
Functions
|
Build and write one stratum's embedded project (SPEC §13). |
|
Index the columns this stratum's re-pointed layers join on, in its gpkg (SPEC §13). |
|
Read the last-saved map-canvas extent from a project file. |
|
Resolve where the source project opens, for the embedded projects (§13). |
|
Serialize the embedded-only layers once for the whole run (SPEC §13). |
|
Check the §4 |
Classes
|
Everything needed to build one stratum's embedded project. |
- class stratified_packager.processing.project_builder.StratumProjectPlan(title, mode, gpkg_path, qgz_path=None, vector_tables=<factory>, data_sources=<factory>, embedded_only=(), repointed=frozenset({}), embedded_xml=<factory>, styles_qml=<factory>, subsets=<factory>, display_names=<factory>, initial_view=None)[source]
Everything needed to build one stratum’s embedded project.
- Parameters:
title (str)
mode (ProjectInclusion)
gpkg_path (Path)
qgz_path (Path | None)
vector_tables (dict[str, str])
data_sources (dict[str, Path])
embedded_only (tuple[str, …])
repointed (frozenset[str])
embedded_xml (dict[str, str])
styles_qml (dict[str, str])
subsets (dict[str, str])
display_names (dict[str, str])
initial_view (QgsReferencedRectangle | None)
- __init__(title, mode, gpkg_path, qgz_path=None, vector_tables=<factory>, data_sources=<factory>, embedded_only=(), repointed=frozenset({}), embedded_xml=<factory>, styles_qml=<factory>, subsets=<factory>, display_names=<factory>, initial_view=None)
- Parameters:
title (str)
mode (ProjectInclusion)
gpkg_path (Path)
qgz_path (Path | None)
vector_tables (dict[str, str])
data_sources (dict[str, Path])
embedded_only (tuple[str, …])
repointed (frozenset[str])
embedded_xml (dict[str, str])
styles_qml (dict[str, str])
subsets (dict[str, str])
display_names (dict[str, str])
initial_view (QgsReferencedRectangle | None)
- data_sources: dict[str, Path]
Included payload layers: source layer id -> absolute path of the
data/copy inside the zip-mirror tree.
- display_names: dict[str, str]
Source layer id -> custom display name for this stratum (SPEC §4
layer_name, already evaluated). Absent ids keep the original layer name.
- embedded_only: tuple[str, ...] = ()
Source layer ids riding only in the project (remote sources, annotations).
- embedded_xml: dict[str, str]
The run’s
snapshot_embedded_layers()result: source layer id -> serialized<maplayer>document. Shared by every stratum, so a remote layer is reproduced without reopening its source. Absent ids fall back toclone().
- initial_view: QgsReferencedRectangle | None = None
The source project’s initial map view (§13) — its saved canvas extent, else its configured default view extent — applied to the embedded project’s view settings so it opens at the same position and zoom.
Noneleaves QGIS’s full-extent default.
- mode: ProjectInclusion
gpkg(project storage inside the GeoPackage) orqgz(file beside it).
- repointed: frozenset[str] = frozenset({})
Source layer ids marked
matching_method = project_only(§4) — a subset ofembedded_onlywhose data source is re-pointed at this stratum’s gpkg (§13) instead of carried over unchanged.
- stratified_packager.processing.project_builder._append_unplaced(fresh, fresh_root, replacements, placed)[source]
Append included layers that had no source tree node (legend-less additions).
- Parameters:
fresh (
QgsProject) – The project under construction.fresh_root (
QgsLayerTreeGroup) – Its layer-tree root.replacements (
Mapping[str,QgsMapLayer]) – Source layer id -> replacement layer.placed (
set[str]) – Layer ids already placed by the tree walk.
- Return type:
- stratified_packager.processing.project_builder._apply_initial_view(fresh, plan)[source]
Set the embedded project’s initial map view from the plan (§13).
A headless
write()emits no<mapcanvas>element, so the written project has no per-canvas saved extent;QgsProjectViewSettings’ default view extent is therefore honoured on open, landing the canvas on the source project’s view instead of the full layer extent.- Parameters:
fresh (
QgsProject) – The project under construction.plan (
StratumProjectPlan) – The stratum’s project plan.
- Return type:
- stratified_packager.processing.project_builder._apply_styles_and_subsets(plan, replacements, feedback)[source]
Apply the rewritten QML styles and re-apply subset strings.
- Parameters:
plan (
StratumProjectPlan) – The stratum’s project plan.replacements (
Mapping[str,QgsMapLayer]) – Source layer id -> replacement layer.feedback (
QgsProcessingFeedback) – Execution feedback channel.
- Return type:
- stratified_packager.processing.project_builder._build_layers(source, plan, feedback)[source]
Create the fresh project’s layers, keyed by their source layer id.
Broken layers are never included (SPEC §13 bad-layer policy) — a re-pointed layer that fails to open is dropped with a warning.
- Parameters:
source (
QgsProject) – The open project being packaged.plan (
StratumProjectPlan) – The stratum’s project plan.feedback (
QgsProcessingFeedback) – Execution feedback channel.
- Return type:
- Returns:
Source layer id -> replacement layer, and one
(seconds, name)pair per layer opened, for the caller’s timing line.
- stratified_packager.processing.project_builder._capture_log()[source]
Collect
QgsMessageLogentries emitted on the current thread.Surfaces why
write()failed: it returns only a bool while the cause is logged. The write runs synchronously on the algorithm thread, so its emissions invoke the handler before the block exits. The handler is a plain callable with no receiver thread affinity, so an unrelated emission from another thread during this window would run it on that thread — a tolerated, GIL-safe append of a stray line.- Yield:
The list each entry (
[tag] message) is appended to.
- stratified_packager.processing.project_builder._copy_node_state(source_node, target_node)[source]
Mirror one layer-tree node’s presentation state onto its replacement (SPEC §13).
Carries the collapsed/expanded state, the check state, and every custom property the node holds. That property bag is where QGIS keeps the rest of what the Layers panel shows — the legend feature counts (
showFeatureCount) and the legend-node customizations (renamed, reordered and hidden classes, underlegend/…) — so the packaged project’s panel reads like the original’s. It is copied wholesale rather than key by key so this does not drift as QGIS grows new node state; only the keys that would point QGIS back at the source machine are dropped (_TREE_NODE_SKIP_PROPERTIES).- Parameters:
source_node (
QgsLayerTreeNode) – The node in the project being packaged.target_node (
QgsLayerTreeNode) – The freshly created node in the embedded project.
- Return type:
- stratified_packager.processing.project_builder._embedded_replacement(original, source, plan, feedback)[source]
Build the embedded-only replacement for one layer.
A §4
project_onlylayer keeps its uri options and only swaps the file path for this stratum’s gpkg (_repoint_layer_source()); a live virtual layer is re-pointed source by source (_rebuild_virtual_layer()). Everything else is reproduced from the run’ssnapshot_embedded_layers()XML, which keeps the original source without reopening it; only a layer type the snapshot cannot reproduce falls back toclone().- Parameters:
original (QgsMapLayer) – The source project’s embedded-only layer.
source (QgsProject) – The open project being packaged.
plan (StratumProjectPlan) – The stratum’s project plan.
feedback (QgsProcessingFeedback) – Execution feedback channel.
- Return type:
QgsMapLayer | None
- Returns:
The replacement layer, or
Noneto drop it.
- stratified_packager.processing.project_builder._fast_open()[source]
Build the layer options of a re-pointed embedded-project layer.
loadDefaultStyle=False: the style is applied explicitly afterwards from the exported QML (the very payload the gpkglayer_stylesrows carry — _finalize_layer writes both from the same source), so the per-layer default-style lookup is a redundant read.skipCrsValidation=True: the CRS comes from the just-written gpkg (or the virtual query); there is nothing to interactively validate on a worker thread.- Return type:
- Returns:
The options for
QgsVectorLayerconstruction.
- stratified_packager.processing.project_builder._index_source_table(gpkg_path, table, candidates, layer_name, feedback)[source]
Index the candidates that are real columns of table (SPEC §13).
The intersection with the table’s own columns is what keeps
equality_operands()a scan instead of a parser: a literal or alias it also matched has no column here and is dropped.- Parameters:
- Return type:
- stratified_packager.processing.project_builder._primary_map_canvas(document)[source]
Find the primary
<mapcanvas>element, preferring thetheMapCanvasmain view.- Parameters:
document (QDomDocument) – The parsed project XML.
- Return type:
QDomElement | None
- Returns:
The chosen element, or
Nonewhen the project has no<mapcanvas>.
- stratified_packager.processing.project_builder._read_project_document(project_file)[source]
Load a project file’s XML —
.qgsdirectly, the.qgzarchive’s inner.qgs.- Parameters:
project_file (Path) – The source project file.
- Return type:
QDomDocument | None
- Returns:
The parsed document, or
Nonewhen the file is missing, is not a.qgs/.qgz, or does not parse as XML.
- stratified_packager.processing.project_builder._rebuild_virtual_layer(original, source, plan, feedback)[source]
Re-point a live virtual layer’s sources at this stratum’s gpkg tables.
Each source the virtual layer queries is rewritten to the GeoPackage table that holds that layer in this stratum; the query, subset, uid and geometry definition are preserved, and the layer is left without a computed extent so the build never runs the query. The layer is dropped (returning
None) when any source has no table in this stratum (e.g. an empty layer omitted underKEEP_EMPTY_LAYERS=False). Style and attribute-form config ride along by cloning the original and only swapping its data source.- Parameters:
original (QgsMapLayer) – The source project’s virtual layer.
source (QgsProject) – The open project being packaged (resolves source references).
plan (StratumProjectPlan) – The stratum’s project plan.
feedback (QgsProcessingFeedback) – Execution feedback channel.
- Return type:
QgsMapLayer | None
- Returns:
The re-pointed virtual layer, or
Noneto drop it.
- stratified_packager.processing.project_builder._remap_relations(source, fresh, replacements, feedback)[source]
Recreate the relations whose both ends are included.
Relations touching excluded layers are dropped.
- Parameters:
source (
QgsProject) – The open project being packaged.fresh (
QgsProject) – The project under construction.replacements (
Mapping[str,QgsMapLayer]) – Source layer id -> replacement layer.feedback (
QgsProcessingFeedback) – Execution feedback channel.
- Return type:
- stratified_packager.processing.project_builder._rename_layer_element(document, element, name)[source]
Rewrite a serialized layer’s
<layername>in place.- Parameters:
document (
QDomDocument) – The document owning element (creates the node when absent).element (
QDomElement) – The<maplayer>element.name (
str) – The display name to store.
- Return type:
- stratified_packager.processing.project_builder._replicate_tree(source, fresh, replacements, feedback)[source]
Replicate the layer tree (groups, order, node state) for included layers.
- Parameters:
source (
QgsProject) – The open project being packaged.fresh (
QgsProject) – The project under construction.replacements (
Mapping[str,QgsMapLayer]) – Source layer id -> replacement layer.feedback (
QgsProcessingFeedback) – Execution feedback channel.
- Return type:
- stratified_packager.processing.project_builder._repoint_layer_source(original, plan, feedback)[source]
Re-point a §4
project_onlylayer’s data source at this stratum’s gpkg (§13).Only the file path changes: every uri option after it rides along verbatim — including a
|subset=holding a wholeSELECT, which is the layer’s entire definition. Such a layer is authored against the package, naming the tables and columns the run writes, so rewriting anything else would break it. Nothing here reads the original, which is the point: its own source need not exist on the packaging machine.- Parameters:
original (QgsMapLayer) – The source project’s project-only layer.
plan (StratumProjectPlan) – The stratum’s project plan.
feedback (QgsProcessingFeedback) – Execution feedback channel.
- Return type:
QgsMapLayer | None
- Returns:
The re-pointed layer, or
Noneto drop it.
- stratified_packager.processing.project_builder._report_slowest_steps(title, elapsed, feedback)[source]
Push one line breaking this stratum’s embedded-project build down by step.
Read against the caller’s total for the whole phase: when the two agree the cost is in a layer this line names, and when they diverge it is in the tree/style/relation work between them. That pairing is what makes a stall in here attributable at all — see
slowest_summary().
- stratified_packager.processing.project_builder._resolve_virtual_source_table(src, source, plan)[source]
Find the stratum gpkg table backing one virtual-layer source.
References by layer id resolve directly; embedded sources match the first packaged layer with the same provider and source string.
- Parameters:
src (
SourceLayer) – A source layer of the virtual definition.source (
QgsProject) – The open project being packaged.plan (
StratumProjectPlan) – The stratum’s project plan.
- Return type:
- Returns:
The table name, or
Nonewhen the source is not packaged here.
- stratified_packager.processing.project_builder._revive_embedded_layer(original, xml, plan)[source]
Rebuild one embedded-only layer from its snapshot XML, without opening its source.
FlagDontResolveLayersfills the layer’s state from the document but skips provider construction, so no network request is made. The layer therefore has no provider to serialize itself back from, which is whatsetOriginalXmlProperties()covers:write()emits those bytes verbatim. That also makes the stored document — not the layer object — authoritative, so a §4layer_nameoverride has to be patched into the XML rather than applied withsetName.- Parameters:
original (QgsMapLayer) – The source project’s layer (its type picks the empty layer to fill).
xml (str) – The layer’s snapshot document.
plan (StratumProjectPlan) – The stratum’s project plan.
- Return type:
QgsMapLayer | None
- Returns:
The replacement layer, or
Nonefor the caller to fall back to cloning.
- stratified_packager.processing.project_builder._source_options(layer)[source]
Return layer’s uri options: everything from the first
|of its data source on.Read from the stored uri text rather than from the provider, so it answers for a layer whose source does not exist on this machine — the normal state of a §4
project_onlylayer, and the reason it is never opened.- Parameters:
layer (
QgsMapLayer) – The layer whose data source is inspected.- Return type:
- Returns:
|layername=…|subset=…and friends, leading separator included; empty when the uri carries no options.
- stratified_packager.processing.project_builder._write_failure_detail(plan, messages)[source]
Describe why
write()failed, for the raised exception.Prefers the captured log; falls back to filesystem facts about the destination when the log stayed silent.
- Parameters:
plan (
StratumProjectPlan) – The stratum’s project plan.messages (
Sequence[str]) – Captured log entries from_capture_log().
- Return type:
- Returns:
A single-line detail string.
- stratified_packager.processing.project_builder.build_stratum_project(source, plan, feedback)[source]
Build and write one stratum’s embedded project (SPEC §13).
- Parameters:
source (
QgsProject) – The open project being packaged (read-only here).plan (
StratumProjectPlan) – The stratum’s project plan.feedback (
QgsProcessingFeedback) – Execution feedback channel.
- Raises:
QgsProcessingException – If writing the project fails.
- Return type:
- stratified_packager.processing.project_builder.index_join_columns(source, plan, feedback)[source]
Index the columns this stratum’s re-pointed layers join on, in its gpkg (SPEC §13).
Both routes that keep a query live against the package — a live virtual layer and a §4
project_onlylayer whose|subset=is aSELECT— re-run that whole query for every feature count and every render (the canvas extent filter wraps the query rather than entering it), and QGIS pushes each equality in it down as one filtered request per outer row. Against the columnsQgsVectorFileWriterleaves unindexed, every one of those requests is a full scan of the inner table, so the recipient pays a nested-loop join each time the layer is drawn. An index turns each request into a b-tree seek: measured 35x end to end on a 73k x 61k join (a feature count of 446s down to 13s), for indexes built in hundredths of a second and about one percent of the GeoPackage’s size.One single-column index per column rather than one composite: QGIS stops at the first usable constraint, so only ever one column is pushed down and a composite index would serve only its leading column.
Best effort by design — an index that cannot be created costs the recipient speed, never correctness, so a failure is reported and the stratum ships regardless.
- Parameters:
source (
QgsProject) – The open project being packaged.plan (
StratumProjectPlan) – The stratum’s project plan, whose gpkg is already written.feedback (
QgsProcessingFeedback) – Execution feedback channel.
- Return type:
- stratified_packager.processing.project_builder.read_saved_view_extent(project_file)[source]
Read the last-saved map-canvas extent from a project file.
QGIS Desktop stores the canvas view under a top-level
<mapcanvas>element; a headlesswrite()never emits one, which is why generated projects open on the full layer extent rather than where the original was saved. This parses that element (preferring the primarytheMapCanvas) straight from the.qgsXML — extracting it from the.qgzarchive first — and returns its extent.
- stratified_packager.processing.project_builder.resolve_initial_view(project)[source]
Resolve where the source project opens, for the embedded projects (§13).
Reproduces the original’s initial view: its saved map-canvas extent (read from the project file — the normal GUI-saved case), else its configured default view extent, else
None(QGIS falls back to the full extent of the layers). The rectangle is returned in the project CRS, which the caller has already set on the embedded project, so applying it needs no reprojection.- Parameters:
project (QgsProject) – The open project being packaged.
- Return type:
QgsReferencedRectangle | None
- Returns:
The initial view rectangle, or
Nonewhen none can be determined.
- stratified_packager.processing.project_builder.snapshot_embedded_layers(layers)[source]
Serialize the embedded-only layers once for the whole run (SPEC §13).
Every stratum’s project carries the same embedded-only layers, and rebuilding one with
clone()re-constructs its provider from the URI. For a remote provider that construction is a blocking network request — a WMS layer fetches GetCapabilities — so cloning charges the run one round-trip per layer per stratum, against a server whose answer never changes and which may not answer at all.Serializing here instead makes
_revive_embedded_layer()a pure XML operation. Live virtual layers are excluded: they are re-pointed at each stratum’s gpkg, not reused.- Parameters:
layers (
Iterable[QgsMapLayer]) – The run’s embedded-only layers.- Return type:
- Returns:
Layer id -> serialized
<maplayer>document, for the layers that can be reproduced; the rest are absent and keep the clone path.
- stratified_packager.processing.project_builder.validate_repointed_sources(layers, tables, /)[source]
Check the §4
project_onlylayers can be re-pointed at the stratum gpkg (§13).Both guards are fatal at run start rather than at project-build time, where the only remaining move is to drop the layer — which ships a package quietly missing it.
The provider guard is the load-bearing one: re-pointing replaces everything before the first
|, so a source that is not a file path (a database connection string) would be replaced whole, leaving a valid layer over the gpkg’s first table — wrong data, no error. The table guard catches the drift that makes such a layer stop resolving: it is authored against the names Phase A mints from the layer names, so renaming a packaged layer (or a §12 duplicate earning a_2suffix) silently breaks it.- Parameters:
layers (
Iterable[QgsMapLayer]) – The run’sproject_onlylayers.tables (
Collection[str]) – The table names Phase A minted for this run.
- Raises:
qgis.core.QgsProcessingException – If a layer’s provider is not
ogr, or its query reads a table this run does not create.- Return type:
- stratified_packager.processing.project_builder._REVIVABLE_LAYER_TYPES: Final[dict[Qgis.LayerType, Callable[[], QgsMapLayer]]] = {qgis.core.Qgis.LayerType.Raster: qgis.core.QgsRasterLayer, qgis.core.Qgis.LayerType.Vector: qgis.core.QgsVectorLayer, qgis.core.Qgis.LayerType.VectorTile: qgis.core.QgsVectorTileLayer}
Layer types
snapshot_embedded_layers()can reproduce from XML.Each entry must construct an empty, source-less layer that
readLayerXml()can then fill. Types absent here keep theclone()path — correct but, for a remote provider, a blocking network round-trip per stratum.
- stratified_packager.processing.project_builder._TREE_NODE_SKIP_PROPERTIES: Final[frozenset[str]] = frozenset({'embedded', 'embedded_project'})
Layer-tree node custom properties never carried into the embedded project.
They mark a node whose children QGIS reloads from another project file on open (
createEmbeddedGroup()), by an absolute path that does not ship beside the packaged data — the tree walk flattens such a node’s children into a plain group instead, and carrying the markers would send QGIS looking for the source machine’s project.
- stratified_packager.processing.project_builder._WRITE_STEP: Final = '<project write>'
Name
_report_slowest_steps()gives the project write, alongside the layer names.Angle-bracketed like the SPEC’s other pseudo-names (
<full>,<unmatched>), so it cannot collide with a layer.