Session API¶
Session is the main orchestration object in pypeh. It holds configured
connections, an in-memory cache of PEH model resources, optional namespace
management, and the adapters used for tabular data operations.
Import it from the package root:
from pypeh import Session
Create a Session¶
For simple local workflows, create an empty session and load resources later:
session = Session()
session.load_persisted_cache(source="config")
For explicit local file storage, pass a LocalFileConfig and make it the
default connection:
from pypeh import LocalFileConfig, Session
session = Session(
connection_config=[
LocalFileConfig(
label="local_file",
config_dict={"root_folder": "path/to/project"},
),
],
default_connection="local_file",
)
You can also load the default persisted cache during initialization:
session = Session(
connection_config=[
LocalFileConfig(
label="local_file",
config_dict={"root_folder": "path/to/project"},
),
],
default_connection="local_file",
load_from_default_connection="",
)
Environment-Configured Default Cache¶
If DEFAULT_PERSISTED_CACHE_TYPE=LocalFile is set, Session() creates a
default local-file cache connection from environment variables with the
DEFAULT_PERSISTED_CACHE_ prefix.
For example:
export DEFAULT_PERSISTED_CACHE_TYPE=LocalFile
export DEFAULT_PERSISTED_CACHE_ROOT_FOLDER=/path/to/project
Then:
session = Session()
session.load_persisted_cache()
Load PEH Resources¶
Use load_persisted_cache to load YAML resources into the session cache:
session.load_persisted_cache(
source="observations.yaml",
connection_label="local_file",
)
Use load_resource when you need one resource by identifier and type:
observation = session.load_resource(
resource_identifier="peh:OBSERVATION_ADULTS_URINE_LAB",
resource_type="Observation",
resource_path="observations.yaml",
connection_label="local_file",
)
You can retrieve already-cached resources with get_resource:
observation = session.get_resource(
"peh:OBSERVATION_ADULTS_URINE_LAB",
"Observation",
)
Import Tabular Data¶
import_tabular_dataset_series imports external tabular data into a
DatasetSeries using a PEH DataImportConfig. The import_ name is used
because this workflow needs import mapping metadata from the
DataImportConfig, not only a file path.
from peh_model.peh import (
DataImportConfig,
DataImportSectionMapping,
DataImportSectionMappingLink,
)
data_import_config = DataImportConfig(
id="peh:IMPORT_CONFIG_SAMPLE_METADATA",
layout="peh:CODEBOOK_v2.4_LAYOUT_SAMPLE_METADATA",
section_mapping=DataImportSectionMapping(
section_mapping_links=[
DataImportSectionMappingLink(
section="SAMPLE_METADATA_SECTION_SAMPLE",
observation_id_list=["peh:VALIDATION_TEST_SAMPLE_METADATA"],
),
]
),
)
dataset_series = session.import_tabular_dataset_series(
source="sample_metadata.xlsx",
data_import_config=data_import_config,
connection_label="local_file",
)
load_tabular_dataset_series is kept as a deprecated compatibility alias. It
accepts the same arguments, logs a warning, and forwards to
import_tabular_dataset_series.
The method checks loaded labels against the expected schema. By default, type
cast and schema errors are raised. Use cast_error_policy="report" or
schema_error_policy="report" to receive a ValidationErrorReportCollection
instead.
result = session.import_tabular_dataset_series(
source="sample_metadata.xlsx",
data_import_config=data_import_config,
connection_label="local_file",
cast_error_policy="report",
schema_error_policy="report",
)
Set allow_incomplete=True to allow missing labels while still reporting
undefined labels.
Persist or Export Tabular Data¶
Use dump_tabular_dataset_series to persist a tabular DatasetSeries after it
has been imported, validated, enriched, or otherwise prepared.
Use file_format="parquet" when you need pypeh semantic persistence. The
parquet format writes one file per Dataset in the series and returns the
paths that were written.
parquet_paths = session.dump_tabular_dataset_series(
dataset_series=dataset_series,
output_path="exports/sample_metadata",
file_format="parquet",
connection_label="local_file",
)
Read the files back with read_tabular_dataset_series. Pass the list returned
by dump_tabular_dataset_series, or another sequence of pypeh dataset parquet
paths.
restored_dataset_series = session.read_tabular_dataset_series(
source_paths=parquet_paths,
connection_label="local_file",
)
Reading currently supports file_format="parquet" only. It validates
foreign-key references by default; set validate_foreign_keys=False when
loading a partial subset intentionally.
Use file_format="xlsx" when you need a human-facing Excel export. The XLSX
format writes a single workbook, with one worksheet per Dataset. Each
worksheet contains the Polars dataframe stored in dataset.data. Dataset labels
are used as worksheet names.
xlsx_paths = session.dump_tabular_dataset_series(
dataset_series=dataset_series,
output_path="exports/sample_metadata.xlsx",
file_format="xlsx",
connection_label="local_file",
)
The returned list contains one workbook path. XLSX export is not a semantic
persistence format: it does not preserve DatasetSeries metadata, schemas,
foreign-key links, or context indexes for round-tripping through
read_tabular_dataset_series. Excel export requires the xlsxwriter
dependency used by Polars' native DataFrame.write_excel support.
Split Data by Observation¶
Use split_dataset_series_by_observation to normalize an imported
DatasetSeries into observation-specific datasets. This is useful when source
files were organized for collection or import, but downstream validation,
enrichment, or export workflows should operate on one observation at a time.
observation_dataset_series = session.split_dataset_series_by_observation(
source_dataset_series=dataset_series,
)
The method delegates to the registered data-operations adapter. The adapter
uses the DatasetSeries schema, observation membership, contextual field
references, and foreign-key metadata to construct a new DatasetSeries.
Datasets that contain fields for multiple observations may be split apart. If
fields for one observation are spread across multiple datasets, the adapter can
join those fields into one output dataset when the DatasetSeries declares the
required foreign-key links.
When multiple source datasets contribute fields with the same column label to
one observation dataset, label_collision_strategy controls how split output
labels are handled:
"prefix_source_dataset"is the default and preserves the historical behavior. The first occurrence keeps its label; later collisions are prefixed with the source dataset label, for exampleUAPFAS_egg_lab__pftrds."prefix_observable_property_id"prefixes collisions with the unique tail of the observable property identifier, for example01KT68...__pftrds. This is useful when provenance should be tied to the semantic variable rather than the source table."error"raises aValueErrorinstead of renaming colliding fields.
Split stores the semantic mapping from (observation, observable property) to
the actual output DatasetSchemaElement.label. Downstream dataframe operations
use that schema label, not the observable property's short_name or
ui_label, so enrichment can still find columns that were renamed during
split. Split also records the original source dataset and field in dataset
metadata so later label strategies can preserve source provenance.
Pass new_dataset_series_label when you want to control the returned series
label:
observation_dataset_series = session.split_dataset_series_by_observation(
source_dataset_series=dataset_series,
new_dataset_series_label="study_by_observation",
label_collision_strategy="prefix_source_dataset",
)
The returned DatasetSeries contains datasets organized by observation. Each
output dataset is associated with one observation and contains the fields
relevant to that observation.
Validate Tabular Data¶
Validate one dataset:
report = session.validate_tabular_dataset(
data=dataset_series["SAMPLE"],
dependent_data=dataset_series,
)
Validate every dataset in a series:
reports = session.validate_tabular_dataset_series(dataset_series)
Build validation configuration from a cached DataLayout:
data_layout = session.load_resource(
"peh:CODEBOOK_v2.4_LAYOUT_SAMPLE_METADATA",
"DataLayout",
)
validation_configs = session.build_validation_config(data_layout)
Register Adapters¶
The session can use default adapters where available. To override a workflow, register an adapter for its functionality key:
session.register_adapter("validation", validation_adapter)
session.register_adapter("dataops", dataops_adapter)
session.register_adapter("enrichment", enrichment_adapter)
session.register_adapter("aggregate", aggregation_adapter)
You can also register by import path:
session.register_adapter_by_name(
"validation",
"my_package.validation",
"MyValidationAdapter",
)
Enrichment and Aggregation¶
enrich and aggregate delegate to the registered adapter while passing a
cache view and the source and target observations.
When the target observations are stored as an ObservationGroup of
DerivedObservation resources, use unpack_derived_observation_group to
resolve each target observation and the source observation referenced by its
was_derived_from field.
observation_pairs = list(
session.unpack_derived_observation_group(
observation_group_id="peh:TARGET_DERIVED_OBSERVATIONS"
)
)
target_observations = [target for target, source in observation_pairs]
source_observations = [source for target, source in observation_pairs]
enriched = session.enrich(
source_dataset_series=dataset_series,
target_observations=target_observations,
target_derived_from=source_observations,
)
summary = session.aggregate(
source_dataset_series=dataset_series,
target_observations=target_observations,
target_derived_from=source_observations,
)
The target observation list and source observation list must have the same length.
Derived target observations can also contain multiple observable properties
whose preferred output labels are identical. By default, enrich and
aggregate reject this because dataframe adapters need concrete column names
and silently overwriting one target would make the dependency graph
inconsistent.
Use target_label_collision_strategy when duplicate target labels are expected:
"error"is the default. It raises when two target observable properties resolve to the same output label."prefix_observable_property_id"prefixes the output label with the unique tail of the observable property identifier, which is robust but less human-readable."prefix_source_dataset"prefixes the output label with the source dataset that feeds the derived calculation. This is useful when two derived variables have the sameshort_name/ui_labelbut come from different source tabs. If enrichment is performed aftersplit_dataset_series_by_observation, this strategy uses the source provenance recorded by split rather than the post-split dataset label.
enriched = session.enrich(
source_dataset_series=observation_dataset_series,
target_observations=target_observations,
target_derived_from=source_observations,
target_label_collision_strategy="prefix_source_dataset",
)
summary = session.aggregate(
source_dataset_series=observation_dataset_series,
target_observations=target_observations,
target_derived_from=source_observations,
target_label_collision_strategy="prefix_source_dataset",
)
Namespaces and Minting¶
Bind a NamespaceManager before minting new PEH resources:
from peh_model.peh import ObservableProperty
from pypeh import NamespaceManager
namespace_manager = NamespaceManager(
default_base_uri="https://w3id.org/example/id/"
)
session.bind_namespace_manager(namespace_manager)
observable_property = session.mint_and_cache(
ObservableProperty,
ui_label="cholesterol",
)
The minted resource is added to the session cache.