Skip to content

File I/O in Incan

Use std.fs.Path as the entry point for files and directories. File operations return Result, so handle recoverable failures with match and propagate boundary failures with ? from functions that return Result.

from std.fs import IoError, Path

Build Paths

from std.fs import Path

def config_path(root: Path) -> Path:
    return root / "config.toml"

def artifact_path(root: Path, name: str) -> Path:
    return root.joinpath(name)

Path construction and joins are lexical. They do not prove that anything exists on disk.

Check Existence

Use bool predicates for quick branches:

from std.fs import Path

def has_cache(root: Path) -> bool:
    return root.joinpath("cache.bin").is_file()

Use try_exists() when the difference between "missing" and "the check failed" matters:

from std.fs import IoError, Path

def require_input(path: Path) -> Result[Path, IoError]:
    if path.try_exists()?:
        return Ok(path)
    return Err(IoError(path=path, kind="not_found", detail="input path does not exist"))

Create Directories

from std.fs import IoError, Path

def prepare_output_dir(path: Path) -> Result[None, IoError]:
    path.mkdir(parents=True, exist_ok=True)?
    return Ok(None)

parents=True creates missing parent directories. exist_ok=True treats an existing directory as success.

Read and Write Small Files

Whole-file helpers are convenient for configuration files, test fixtures, and payloads that comfortably fit in memory.

from std.fs import IoError, Path

def copy_small_file(source: Path, target: Path) -> Result[None, IoError]:
    data = source.read_bytes()?
    target.write_bytes(data)?
    return Ok(None)

def save_text(path: Path, text: str) -> Result[None, IoError]:
    path.write_text(text, "utf-8", "strict", None)?
    return Ok(None)

Use read_text("utf-8", "strict") and write_text(..., "utf-8", "strict", None) for normal UTF-8 text files.

Stream Large Files

For large files, open a handle and read bounded chunks.

from std.fs import IoError, Path

def copy_in_chunks(source: Path, target: Path) -> Result[None, IoError]:
    input = source.open("rb", -1, None, None, None)?
    output = target.open("wb", -1, None, None, None)?

    loop:
        chunk = input.read_bytes(8192)?
        if len(chunk) == 0:
            break
        output.write_bytes(chunk)?

    output.sync_data()?
    return Ok(None)

sync_data() requests durable file data. Use sync() when metadata durability matters too.

Read a Fixed Header

from std.fs import IoError, Path

def read_magic(path: Path) -> Result[bytes, IoError]:
    file = path.open("rb", -1, None, None, None)?
    return file.read_exact(4)

read_exact(size) fails on short reads, which is useful for file headers and binary protocol frames.

Seek Within a File

from std.fs import IoError, Path

def read_footer(path: Path, size: int) -> Result[bytes, IoError]:
    file = path.open("rb", -1, None, None, None)?
    file.seek(0 - size, 2)?
    return file.read_exact(size)

Use tell() when you need to save or report the current cursor.

Copy, Move, and Clean Up

from std.fs import IoError, Path

def publish_tree(build_dir: Path, release_dir: Path) -> Result[Path, IoError]:
    copied = build_dir.copy_into(release_dir, follow_symlinks=True, preserve_metadata=False)?
    copied.joinpath("READY").touch(exist_ok=True)?
    return Ok(copied)

def replace_file(source: Path, target: Path) -> Result[Path, IoError]:
    return source.move(target)

def remove_workspace(path: Path) -> Result[None, IoError]:
    path.remove_tree()?
    return Ok(None)

remove_tree() is for directories. Use unlink() for files and symlinks.

Directory Listings

from std.fs import IoError, Path

def list_inputs(root: Path) -> Result[list[Path], IoError]:
    return root.glob("*.incn")

def list_all_inputs(root: Path) -> Result[list[Path], IoError]:
    return root.rglob("*.incn")

Use scandir() when you want directory entries that can answer is_file(), is_dir(), and metadata().

Temporary-File Layout

Temporary location creation belongs to std.tempfile; ordinary operations on those locations belong to std.fs.

from std.fs import IoError, Path

def write_temp_payload(tmp_dir: Path, data: bytes) -> Result[Path, IoError]:
    path = tmp_dir.joinpath("payload.bin")
    path.write_bytes(data)?
    return Ok(path)

std.tempfile.SpooledTemporaryFile is a follow-up API for RFC 010. It should compose with std.fs paths and file handles instead of being folded into std.fs.

See Also