Skip to content

pspf_builder

flavor.psp.format_2025.pspf_builder

TODO: Add module docstring.

Classes

PSPFBuilder

PSPFBuilder(spec: BuildSpec | None = None)

Immutable fluent builder interface for PSPF packages.

Provides a chainable API for constructing build specifications.

Initialize with optional starting specification.

Source code in flavor/psp/format_2025/pspf_builder.py
def __init__(self, spec: BuildSpec | None = None) -> None:
    """Initialize with optional starting specification."""
    self._spec = spec or BuildSpec()
Functions
add_slot
add_slot(
    id: str,
    data: bytes | str | Path,
    purpose: str = "data",
    lifecycle: str = "runtime",
    operations: str = "gzip",
    target: str | None = None,
    permissions: str | None = None,
) -> PSPFBuilder

Add a slot to the package.

Parameters:

Name Type Description Default
id str

Slot identifier

required
data bytes | str | Path

Slot data (bytes, string, or path to file/directory)

required
purpose str

Slot purpose (data, code, config, media)

'data'
lifecycle str

Slot lifecycle (runtime, cached, temporary)

'runtime'
operations str

Operation chain (e.g., "tar.gz", "TAR|GZIP")

'gzip'
target str | None

Target location relative to workenv (default: None)

None
permissions str | None

Unix permissions as octal string (e.g., "0755")

None
Source code in flavor/psp/format_2025/pspf_builder.py
def add_slot(
    self,
    id: str,
    data: bytes | str | Path,
    purpose: str = "data",
    lifecycle: str = "runtime",
    operations: str = "gzip",
    target: str | None = None,
    permissions: str | None = None,
) -> PSPFBuilder:
    """
    Add a slot to the package.

    Args:
        id: Slot identifier
        data: Slot data (bytes, string, or path to file/directory)
        purpose: Slot purpose (data, code, config, media)
        lifecycle: Slot lifecycle (runtime, cached, temporary)
        operations: Operation chain (e.g., "tar.gz", "TAR|GZIP")
        target: Target location relative to workenv (default: None)
        permissions: Unix permissions as octal string (e.g., "0755")
    """
    # Determine path and size
    if isinstance(data, bytes):
        # Write to temp file securely
        with tempfile.NamedTemporaryFile(delete=False) as temp_file:
            temp_file.write(data)
            temp_path = Path(temp_file.name)
        path = temp_path
        size = len(data)
    elif isinstance(data, str):
        # Write string to temp file securely
        with tempfile.NamedTemporaryFile(mode="w", delete=False, encoding="utf-8") as temp_file:
            temp_file.write(data)
            temp_path = Path(temp_file.name)
        path = temp_path
        size = len(data.encode("utf-8"))
    elif isinstance(data, Path):
        path = data
        size = path.stat().st_size if path.exists() else 0
    else:
        raise BuildError(f"Invalid data type: {type(data)}")

    # Create slot metadata
    slot = SlotMetadata(
        index=len(self._spec.slots),
        id=id,
        source=str(path) if path else "",
        target=target or id,
        size=size,
        checksum="",  # Will be calculated during build
        operations=operations,
        purpose=purpose,
        lifecycle=lifecycle,
        permissions=permissions,
    )

    new_spec = self._spec.with_slot(slot)
    return PSPFBuilder(new_spec)
build
build(output_path: str | Path) -> BuildResult

Build the package.

Parameters:

Name Type Description Default
output_path str | Path

Path where package should be written

required

Returns:

Type Description
BuildResult

BuildResult with success status and any errors

Source code in flavor/psp/format_2025/pspf_builder.py
def build(self, output_path: str | Path) -> BuildResult:
    """
    Build the package.

    Args:
        output_path: Path where package should be written

    Returns:
        BuildResult with success status and any errors
    """
    if isinstance(output_path, str):
        output_path = Path(output_path)

    try:
        # Import here to avoid circular import
        from flavor.psp.format_2025.builder import build_package

        return build_package(self._spec, output_path)
    except (BuildError, ValueError) as e:
        # Convert exceptions to BuildResult format
        from flavor.psp.format_2025.spec import BuildResult

        return BuildResult(success=False, errors=[str(e)])
create classmethod
create() -> PSPFBuilder

Create a new builder instance.

Source code in flavor/psp/format_2025/pspf_builder.py
@classmethod
def create(cls) -> PSPFBuilder:
    """Create a new builder instance."""
    return cls()
metadata
metadata(**kwargs: Any) -> PSPFBuilder

Set metadata fields.

Merges provided kwargs with existing metadata.

Source code in flavor/psp/format_2025/pspf_builder.py
def metadata(self, **kwargs: Any) -> PSPFBuilder:
    """
    Set metadata fields.

    Merges provided kwargs with existing metadata.
    """
    new_spec = self._spec.with_metadata(**kwargs)
    return PSPFBuilder(new_spec)
with_keys
with_keys(
    seed: str | None = None,
    private: bytes | None = None,
    public: bytes | None = None,
    path: Path | None = None,
) -> PSPFBuilder

Configure signing keys.

Parameters:

Name Type Description Default
seed str | None

Seed for deterministic key generation

None
private bytes | None

Explicit private key bytes

None
public bytes | None

Explicit public key bytes

None
path Path | None

Path to load keys from

None
Source code in flavor/psp/format_2025/pspf_builder.py
def with_keys(
    self,
    seed: str | None = None,
    private: bytes | None = None,
    public: bytes | None = None,
    path: Path | None = None,
) -> PSPFBuilder:
    """
    Configure signing keys.

    Args:
        seed: Seed for deterministic key generation
        private: Explicit private key bytes
        public: Explicit public key bytes
        path: Path to load keys from
    """
    key_config = KeyConfig(private_key=private, public_key=public, key_seed=seed, key_path=path)
    new_spec = self._spec.with_keys(key_config)
    return PSPFBuilder(new_spec)
with_options
with_options(**kwargs: Any) -> PSPFBuilder

Set build options.

Supported options: - enable_mmap: Enable memory-mapped access - page_aligned: Align slots to page boundaries - strip_binaries: Strip debug symbols from binaries - compression: Compression type (none, gzip) - compression_level: Compression level (0-9) - launcher_bin: Path to launcher binary - reproducible: Enable reproducible builds

Source code in flavor/psp/format_2025/pspf_builder.py
def with_options(self, **kwargs: Any) -> PSPFBuilder:
    """
    Set build options.

    Supported options:
    - enable_mmap: Enable memory-mapped access
    - page_aligned: Align slots to page boundaries
    - strip_binaries: Strip debug symbols from binaries
    - compression: Compression type (none, gzip)
    - compression_level: Compression level (0-9)
    - launcher_bin: Path to launcher binary
    - reproducible: Enable reproducible builds
    """
    # Create new options with updates
    current_options = self._spec.options
    new_options = attrs.evolve(current_options, **kwargs)
    new_spec = self._spec.with_options(new_options)
    return PSPFBuilder(new_spec)