diff --git a/dev/reference/capsula/index.html b/dev/reference/capsula/index.html index 70f3b1d0..3c609289 100644 --- a/dev/reference/capsula/index.html +++ b/dev/reference/capsula/index.html @@ -7101,7 +7101,7 @@

run_dto.add_watcher(watcher, append_left=False) for reporter in reversed(config[phase_key].get("reporters", [])): # type: ignore[literal-required] assert phase in {"pre", "in", "post"}, f"Invalid phase for reporter: {phase}" - run_dto.add_reporter(reporter, mode=phase, append_left=True) # type: ignore[arg-type] + run_dto.add_reporter(reporter, mode=phase, append_left=True) run_dto.vault_dir = config["vault-dir"] if run_dto.vault_dir is None else run_dto.vault_dir diff --git a/dev/search/search_index.json b/dev/search/search_index.json index ba6f8b72..f78cbf6a 100644 --- a/dev/search/search_index.json +++ b/dev/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Capsula","text":"

Capsula, a Latin word meaning box, is a Python package designed to help researchers and developers easily capture their command/function execution context for reproducibility.

With Capsula, you can capture:

The captured contexts are dumped into JSON files for future reference and reproduction.

"},{"location":"#usage-example","title":"Usage example","text":"

For project-wide settings, prepare a capsula.toml file in the root directory of your project. An example of the capsula.toml file is as follows:

[pre-run]\ncontexts = [\n    { type = \"CwdContext\" },\n    { type = \"CpuContext\" },\n    { type = \"PlatformContext\" },\n    { type = \"GitRepositoryContext\", name = \"capsula\", path = \".\", path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv lock --locked\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"pyproject.toml\", copy = true, path_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"uv.lock\", copy = true, path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv export > requirements.txt\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"requirements.txt\", move = true, path_relative_to_project_root = true },\n    { type = \"EnvVarContext\", name = \"HOME\" },\n]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[in-run]\nwatchers = [{ type = \"UncaughtExceptionWatcher\" }, { type = \"TimeWatcher\" }]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[post-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n

Then, all you need to do is decorate your Python function with the @capsula.run() decorator. You can also use the @capsula.context() decorator to add a context specific to the function.

The following is an example of a Python script that estimates the value of \u03c0 using the Monte Carlo method:

import random\nimport capsula\n\n@capsula.run()\n@capsula.context(capsula.FunctionContext.builder(), mode=\"pre\")\n@capsula.context(capsula.FileContext.builder(\"pi.txt\", move=True), mode=\"post\")\ndef calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n    random.seed(seed)\n    xs = (random.random() for _ in range(n_samples))\n    ys = (random.random() for _ in range(n_samples))\n    inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n\n    # You can record values to the capsule using the `record` method.\n    capsula.record(\"inside\", inside)\n\n    pi_estimate = (4.0 * inside) / n_samples\n    print(f\"Pi estimate: {pi_estimate}\")\n    capsula.record(\"pi_estimate\", pi_estimate)\n    print(f\"Run name: {capsula.current_run_name()}\")\n\n    with open(\"pi.txt\", \"w\") as output_file:\n        output_file.write(f\"Pi estimate: {pi_estimate}.\")\n\nif __name__ == \"__main__\":\n    calculate_pi(n_samples=1_000)\n

After running the script, a directory (calculate_pi_20240913_194900_2lxL in this example) will be created under the <project-root>/vault directory, and you will find the output files in the directory:

$ tree vault/calculate_pi_20240913_194900_2lxL\nvault/calculate_pi_20240913_194900_2lxL\n\u251c\u2500\u2500 in-run-report.json    # Generated by the `JsonDumpReporter` in `capsula.toml` (`in-run` section)\n\u251c\u2500\u2500 pi.txt                # Moved by the `FileContext` specified with the decorator in the script\n\u251c\u2500\u2500 uv.lock           # Copied by the `FileContext` specified in `capsula.toml` (`pre-run` section)\n\u251c\u2500\u2500 post-run-report.json  # Generated by the `JsonDumpReporter` in `capsula.toml` (`post-run` section)\n\u251c\u2500\u2500 pre-run-report.json   # Generated by the `JsonDumpReporter` in `capsula.toml` (`pre-run` section)\n\u251c\u2500\u2500 pyproject.toml        # Copied by the `FileContext` specified in `capsula.toml` (`pre-run` section)\n\u2514\u2500\u2500 requirements.txt      # Moved by the `FileContext` specified in `capsula.toml` (`pre-run` section)\n

The contents of the JSON files are as follows:

Example of output pre-run-report.json:
{\n  \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n  \"cpu\": {\n    \"python_version\": \"3.8.20.final.0 (64 bit)\",\n    \"cpuinfo_version\": [\n      9,\n      0,\n      0\n    ],\n    \"cpuinfo_version_string\": \"9.0.0\",\n    \"arch\": \"ARM_8\",\n    \"bits\": 64,\n    \"count\": 16,\n    \"arch_string_raw\": \"arm64\",\n    \"brand_raw\": \"Apple M3 Max\"\n  },\n  \"platform\": {\n    \"machine\": \"arm64\",\n    \"node\": \"MacBook-Pro.local\",\n    \"platform\": \"macOS-14.6.1-arm64-arm-64bit\",\n    \"release\": \"23.6.0\",\n    \"version\": \"Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:46 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T6031\",\n    \"system\": \"Darwin\",\n    \"processor\": \"arm\",\n    \"python\": {\n      \"executable_architecture\": {\n        \"bits\": \"64bit\",\n        \"linkage\": \"\"\n      },\n      \"build_no\": \"default\",\n      \"build_date\": \"Sep  9 2024 22:25:40\",\n      \"compiler\": \"Clang 18.1.8 \",\n      \"branch\": \"\",\n      \"implementation\": \"CPython\",\n      \"version\": \"3.8.20\"\n    }\n  },\n  \"git\": {\n    \"capsula\": {\n      \"working_dir\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n      \"sha\": \"4ff5b9b9e5f6b527b0c2c660a5cb1a12937599b5\",\n      \"remotes\": {\n        \"origin\": \"ssh://git@github.com/shunichironomura/capsula.git\"\n      },\n      \"branch\": \"gitbutler/workspace\",\n      \"is_dirty\": true,\n      \"diff_file\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL/capsula.diff\"\n    }\n  },\n  \"command\": {\n    \"uv lock --locked\": {\n      \"command\": \"uv lock --locked\",\n      \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n      \"returncode\": 0,\n      \"stdout\": \"\",\n      \"stderr\": \"Resolved 73 packages in 0.35ms\\n\"\n    },\n    \"uv export > requirements.txt\": {\n      \"command\": \"uv export > requirements.txt\",\n      \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n      \"returncode\": 0,\n      \"stdout\": \"\",\n      \"stderr\": \"Resolved 73 packages in 0.32ms\\n\"\n    }\n  },\n  \"file\": {\n    \"/Users/nomura/ghq/github.com/shunichironomura/capsula/pyproject.toml\": {\n      \"copied_to\": [\n        \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL/pyproject.toml\"\n      ],\n      \"moved_to\": null,\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"e331c7998167d64e4e90c9f2aa2c2fe9c9c3afe1cf8348f1d61998042b75040a\"\n      }\n    },\n    \"/Users/nomura/ghq/github.com/shunichironomura/capsula/uv.lock\": {\n      \"copied_to\": [\n        \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL/uv.lock\"\n      ],\n      \"moved_to\": null,\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"62e5b7a5125778dd664ee2dc0cb3c10640d15db3e55b40240c4d652f8afe40fe\"\n      }\n    },\n    \"/Users/nomura/ghq/github.com/shunichironomura/capsula/requirements.txt\": {\n      \"copied_to\": [],\n      \"moved_to\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL\",\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"3ba457abcefb0010a7b350e8a2567b8ac890726608b99ce85defbb5d06e197de\"\n      }\n    }\n  },\n  \"env\": {\n    \"HOME\": \"/Users/nomura\"\n  },\n  \"function\": {\n    \"calculate_pi\": {\n      \"file_path\": \"examples/simple_decorator.py\",\n      \"first_line_no\": 15,\n      \"bound_args\": {\n        \"n_samples\": 1000,\n        \"seed\": 42\n      }\n    }\n  }\n}
Example of output in-run-report.json:
{\n  \"inside\": 782,\n  \"pi_estimate\": 3.128,\n  \"time\": {\n    \"execution_time\": \"0:00:00.000271\"\n  },\n  \"exception\": {\n    \"exception\": {\n      \"exc_type\": null,\n      \"exc_value\": null,\n      \"traceback\": null\n    }\n  }\n}
Example of output post-run-report.json:
{\n  \"file\": {\n    \"pi.txt\": {\n      \"copied_to\": [],\n      \"moved_to\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL\",\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"a64c761cb6b6f9ef1bc1f6afa6ba44d796c5c51d14df0bdc9d3ab9ced7982a74\"\n      }\n    }\n  }\n}
"},{"location":"#installation","title":"Installation","text":"

You can install Capsula via pip:

pip install capsula\n

Or via conda:

conda install conda-forge::capsula\n
"},{"location":"#licensing","title":"Licensing","text":"

This project is licensed under the terms of the MIT.

Additionally, this project includes code derived from the Python programming language, which is licensed under the Python Software Foundation License Version 2 (PSF-2.0). For details, see the LICENSE file.

"},{"location":"concepts/","title":"Concepts","text":""},{"location":"concepts/#context","title":"Context","text":"

A context encapsulates a piece of information at a specific point in time. It can be anything from the current working directory (CwdContext) to the output of a command (CommandContext). Contexts are used to capture the state of the environment in which a command/function is executed.

See the Contexts section for the list of all the built-in contexts.

"},{"location":"concepts/#watcher","title":"Watcher","text":"

A watcher encapsulates a piece of information by monitoring the execution of a command/function. It can be used to detect uncaught exceptions (UncaughtExceptionWatcher) or measure the execution time (TimeWatcher). Watchers are used to monitor the execution of a command/function.

See the Watchers section for more information.

"},{"location":"concepts/#encapsulator-and-capsule","title":"Encapsulator and Capsule","text":"

You register multiple contexts and/or watchers to an encapsulator. Then, the encapsulator encapsulates the information encapsulated by the contexts and/or watchers into a \"capsule\".

"},{"location":"concepts/#reporter","title":"Reporter","text":"

A reporter reports the capsule in a specific format. For example, the JsonDumpReporter reports the capsule in JSON format.

See the Reporters section for more information.

"},{"location":"concepts/#run","title":"Run","text":"

A run is a single execution of a command/function. It has three encapsulators: pre-run, in-run, and post-run.

Encapsulator When to encapsulate Allowed registrations Pre-run encapsulator Before the execution of the command/function. Contexts In-run encapsulator During the execution of the command/function. Watchers Post-run encapsulator After the execution of the command/function. Contexts

For each run, a run directory is created in the vault directory. This directory is used to store the files generated by contexts, watchers, and reporters.

"},{"location":"config/","title":"Configuration","text":""},{"location":"config/#capsulatoml-file","title":"capsula.toml file","text":"

For project-wide settings, prepare a capsula.toml file in the root directory of your project. An example of the capsula.toml file is as follows:

[pre-run]\ncontexts = [\n    { type = \"CwdContext\" },\n    { type = \"CpuContext\" },\n    { type = \"GitRepositoryContext\", name = \"capsula\", path = \".\", path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv lock --locked\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"pyproject.toml\", copy = true, path_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"uv.lock\", copy = true, path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv export > requirements.txt\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"requirements.txt\", move = true, path_relative_to_project_root = true },\n]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[in-run]\nwatchers = [{ type = \"UncaughtExceptionWatcher\" }, { type = \"TimeWatcher\" }]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[post-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n

This configuration file specifies the contexts, watchers, and reporters to be used in the pre-run, in-run, and post-run encapsulators. The JsonDumpReporter is used to dump the captured contexts into JSON files.

For each context, watcher, or reporter, the type field specifies the class name of the context, watcher, or reporter. The other fields are used as the keyword arguments to the builder method of the class to create an instance of the class. If the class does not implement the builder method, the __init__ method is used instead.

"},{"location":"config/#decorators","title":"Decorators","text":"

For encapsulating the pre-run, in-run, and post-run capsules for a specific function, you can use the @capsula.run() decorator. You can also use the @capsula.context(), @capsula.watcher(), and @capsula.reporter() decorators to add a context, watcher, or reporter that is specific to the function.

The following is an example of a Python script that estimates the value of \u03c0 using the Monte Carlo method. The pi.txt file generated inside the function is encapsulated using the FileContext context in the post-run encapsulator.

import random\nimport capsula\n\n@capsula.run()\n# Register a `FileContext` to the post-run encapsulator.\n@capsula.context(capsula.FileContext.builder(\"pi.txt\", move=True), mode=\"post\")\ndef calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n    random.seed(seed)\n    xs = (random.random() for _ in range(n_samples))\n    ys = (random.random() for _ in range(n_samples))\n    inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n    pi_estimate = (4.0 * inside) / n_samples\n\n    with open(\"pi.txt\", \"w\") as output_file:\n        output_file.write(f\"Pi estimate: {pi_estimate}.\")\n\nif __name__ == \"__main__\":\n    calculate_pi(n_samples=1_000)\n
"},{"location":"config/#order-of-encapsulation","title":"Order of encapsulation","text":"

For each encapsulators, the order of encapsulation is as follows:

  1. Contexts, watchers, and reporters specified in the capsula.toml file, in the order of appearance (from top to bottom).
  2. Contexts, watchers, and reporters specified using the @capsula.context() and @capsula.watcher() decorators, in the order of appearance (from top to bottom).

Note

For watchers, the order of encapsulation here means the order of entering the watch context manager of the watcher. The order of exiting the watch context manager is the reverse of the order of entering the watch context manager.

"},{"location":"config/#builder-method-or-__init__-method","title":"builder method or __init__ method?","text":"

The reason for using the builder method instead of the __init__ method to create an instance of a context, watcher, or reporter is to use the runtime information, such as the run directory, to create the instance. This is why the configuration specified in the capsula.toml file by default uses the builder method to create instances of contexts, watchers, and reporters.

The builder method returns, instead of an instance of the class, a function that takes the runtime information (capsula.CapsuleParams) as an argument and returns an instance of the class.

"},{"location":"extending/","title":"Creating your own contexts, reporters, and watchers","text":"

You can extend Capsula by creating your own contexts, reporters, and watchers. You only need to create a class that inherits from the base class of the corresponding type.

"},{"location":"extending/#contexts","title":"Contexts","text":"

To create a context, you need to

Here's an example of a custom context that captures the current time:

import capsula\nfrom zoneinfo import ZoneInfo\nfrom datetime import datetime\n\nclass TimeContext(capsula.ContextBase):\n    def __init__(self, timezone: str = \"UTC\"):\n        self.timezone = ZoneInfo(timezone)\n\n    def encapsulate(self) -> dict[str, datetime]:\n        return {\"time\": datetime.now(self.timezone)}\n\n    def default_key(self) -> str:\n        return \"time\"\n

Note

The above example uses the zoneinfo module, which is available in Python 3.9 and later.

Optionally, you can implement the builder method that returns a function that creates the context. This is useful when you need to access the runtime information (capsula.CapsuleParams) when creating the context.

Here's an example of a custom context that captures the project root path:

import capsula\n\nclass ProjectRootContext(capsula.ContextBase):\n    def __init__(self, path: str | Path):\n        self.path = str(path)\n\n    def encapsulate(self) -> dict[str, str]:\n        return {\"project_root\": self.path}\n\n    def default_key(self) -> str:\n        return \"project_root\"\n\n    @classmethod\n    def builder(cls):\n        def build(params: capsula.CapsuleParams) -> \"ProjectRootContext\":\n            return ProjectRootContext(params.project_root)\n        return build\n
"},{"location":"extending/#watchers","title":"Watchers","text":"

To create a watcher, you need to

Here's an example of a custom watcher that watches the execution time of a command/function (from the implementation of the TimeWatcher class):

import capsula\nimport time\nfrom datetime import timedelta\nfrom collections.abc import Iterator\nfrom contextlib import contextmanager\n\nclass TimeWatcher(capsula.WatcherBase):\n    def __init__(\n        self,\n        name: str = \"execution_time\",\n    ) -> None:\n        self._name = name\n        self._duration: timedelta | None = None\n\n    def encapsulate(self) -> timedelta | None:\n        return self._duration\n\n    @contextmanager\n    def watch(self) -> Iterator[None]:\n        start = time.perf_counter()\n        try:\n            yield\n        finally:\n            end = time.perf_counter()\n            self._duration = timedelta(seconds=end - start)\n\n    def default_key(self) -> tuple[str, str]:\n        return (\"time\", self._name)\n

Like contexts, you can implement the builder method to create the watcher using the runtime information.

"},{"location":"extending/#reporters","title":"Reporters","text":"

To create a reporter, you need to

Here's an example of a custom reporter that dumps the capsule to a pickle file:

import capsula\nimport pickle\nfrom pathlib import Path\n\nclass PickleDumpReporter(capsula.ReporterBase):\n    def __init__(self, path: str | Path):\n        self.path = Path(path)\n\n    def report(self, capsule: capsula.Capsule) -> None:\n        with open(self.path, \"wb\") as file:\n            pickle.dump(capsule, file)\n\n    @classmethod\n    def builder(cls):\n        def build(params: capsula.CapsuleParams) -> \"PickleDumpReporter\":\n            return PickleDumpReporter(params.run_dir / \"capsule.pkl\")\n        return build\n

As shown in the example, you can implement the builder method to create the reporter using the runtime information.

"},{"location":"helpers/","title":"Helper Functions and Variables","text":"

Capsula provides several helper functions and variables:

"},{"location":"contexts/","title":"Built-in contexts","text":"

Capsula provides several built-in contexts that you can capture. The following is a list of built-in contexts:

"},{"location":"contexts/command/","title":"CommandContext","text":"

The CommandContext captures the output of shell commands. It can be created using the capsula.CommandContext.builder method or the capsula.CommandContext.__init__ method.

"},{"location":"contexts/command/#capsula.CommandContext.builder","title":"capsula.CommandContext.builder classmethod","text":"
builder(\n    command: str,\n    *,\n    cwd: Path | str | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    cwd_relative_to_project_root: bool = False,\n    shell: bool = True\n) -> Callable[[CapsuleParams], CommandContext]\n
PARAMETER DESCRIPTION command

Command to run

TYPE: str

cwd

Working directory for the command, passed to the cwd argument of subprocess.run

TYPE: Path | str | None DEFAULT: None

check

Whether to raise an exception if the command returns a non-zero exit code, passed to the check argument of `subprocess.run

TYPE: bool DEFAULT: True

abort_on_error

Whether to abort the encapsulation if the command returns a non-zero exit code

TYPE: bool DEFAULT: True

cwd_relative_to_project_root

Whether cwd argument is relative to the project root. Will be ignored if cwd is None or absolute. If True, it will be interpreted as relative to the project root. If False, cwd will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

shell

Whether to run the command using the shell. If True, the command will be run using the shell. If False, the command will be run directly. For more information, see the shell argument of subprocess.run.

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
@classmethod\ndef builder(\n    cls,\n    command: Annotated[str, Doc(\"Command to run\")],\n    *,\n    cwd: Annotated[\n        Path | str | None,\n        Doc(\"Working directory for the command, passed to the `cwd` argument of `subprocess.run`\"),\n    ] = None,\n    check: Annotated[\n        bool,\n        Doc(\n            \"Whether to raise an exception if the command returns a non-zero exit code, passed to the `check` \"\n            \"argument of `subprocess.run\",\n        ),\n    ] = True,\n    abort_on_error: Annotated[\n        bool,\n        Doc(\"Whether to abort the encapsulation if the command returns a non-zero exit code\"),\n    ] = True,\n    cwd_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `cwd` argument is relative to the project root. Will be ignored if `cwd` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `cwd` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    shell: Annotated[\n        bool,\n        Doc(\n            \"Whether to run the command using the shell. If True, the command will be run using the shell. \"\n            \"If False, the command will be run directly. \"\n            \"For more information, see the `shell` argument of `subprocess.run`. \",\n        ),\n    ] = True,\n) -> Callable[[CapsuleParams], CommandContext]:\n    def build(params: CapsuleParams) -> CommandContext:\n        if cwd_relative_to_project_root and cwd is not None and not Path(cwd).is_absolute():\n            cwd_path: Path | None = params.project_root / cwd\n        elif cwd_relative_to_project_root and cwd is None:\n            cwd_path = params.project_root\n        else:\n            cwd_path = Path(cwd) if cwd is not None else None\n\n        return cls(\n            command,\n            cwd=cwd_path,\n            check=check,\n            abort_on_error=abort_on_error,\n            shell=shell,\n        )\n\n    return build\n
"},{"location":"contexts/command/#capsula.CommandContext.__init__","title":"capsula.CommandContext.__init__","text":"
__init__(\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True\n)\n

Initialize the command context.

PARAMETER DESCRIPTION command

TYPE: str

cwd

TYPE: Path | None DEFAULT: None

check

TYPE: bool DEFAULT: True

abort_on_error

TYPE: bool DEFAULT: True

shell

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
def __init__(\n    self,\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True,\n) -> None:\n    \"\"\"Initialize the command context.\"\"\"\n    self._command = command\n    self._cwd = cwd\n    self._check = check\n    self._abort_on_error = abort_on_error\n    self._shell = shell\n
"},{"location":"contexts/command/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/command/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"CommandContext\", command = \"uv lock --locked\", cwd = \".\", cwd_relative_to_project_root = true },\n]\n
"},{"location":"contexts/command/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\nPROJECT_ROOT = capsula.search_for_project_root(__file__)\n\n@capsula.run()\n@capsula.context(capsula.CommandContext(\"uv lock --locked\", cwd=PROJECT_ROOT), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/command/#output-example","title":"Output example","text":"

The following is an example of the output of the CommandContext, reported by the JsonDumpReporter:

\"uv lock --locked\": {\n  \"command\": \"uv lock --locked\",\n  \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n  \"returncode\": 0,\n  \"stdout\": \"\",\n  \"stderr\": \"Resolved 73 packages in 0.35ms\\n\"\n}\n
"},{"location":"contexts/cpu/","title":"CpuContext","text":"

The CpuContext captures the CPU information. It can be created by capsula.CpuContext() with no arguments.

It internally uses the py-cpuinfo package to get the CPU information.

"},{"location":"contexts/cpu/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/cpu/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"CpuContext\" },\n]\n
"},{"location":"contexts/cpu/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.CpuContext(), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/cpu/#output-example","title":"Output example","text":"

The following is an example of the output of the CpuContext, reported by the JsonDumpReporter:

\"cpu\": {\n  \"python_version\": \"3.8.17.final.0 (64 bit)\",\n  \"cpuinfo_version\": [\n    9,\n    0,\n    0\n  ],\n  \"cpuinfo_version_string\": \"9.0.0\",\n  \"arch\": \"X86_64\",\n  \"bits\": 64,\n  \"count\": 12,\n  \"arch_string_raw\": \"x86_64\",\n  \"vendor_id_raw\": \"GenuineIntel\",\n  \"brand_raw\": \"Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz\",\n  \"hz_advertised_friendly\": \"2.9000 GHz\",\n  \"hz_actual_friendly\": \"2.9040 GHz\",\n  \"hz_advertised\": [\n    2900000000,\n    0\n  ],\n  \"hz_actual\": [\n    2904008000,\n    0\n  ],\n  \"stepping\": 5,\n  \"model\": 165,\n  \"family\": 6,\n  \"flags\": [\n    \"3dnowprefetch\",\n    \"abm\",\n    \"adx\",\n    \"aes\",\n    \"apic\",\n    \"arch_capabilities\",\n    \"arch_perfmon\",\n    \"avx\",\n    \"avx2\",\n    \"bmi1\",\n    \"bmi2\",\n    \"clflush\",\n    \"clflushopt\",\n    \"cmov\",\n    \"constant_tsc\",\n    \"cpuid\",\n    \"cx16\",\n    \"cx8\",\n    \"de\",\n    \"ept\",\n    \"ept_ad\",\n    \"erms\",\n    \"f16c\",\n    \"flush_l1d\",\n    \"fma\",\n    \"fpu\",\n    \"fsgsbase\",\n    \"fxsr\",\n    \"ht\",\n    \"hypervisor\",\n    \"ibpb\",\n    \"ibrs\",\n    \"ibrs_enhanced\",\n    \"invpcid\",\n    \"invpcid_single\",\n    \"lahf_lm\",\n    \"lm\",\n    \"mca\",\n    \"mce\",\n    \"md_clear\",\n    \"mmx\",\n    \"movbe\",\n    \"msr\",\n    \"mtrr\",\n    \"nopl\",\n    \"nx\",\n    \"osxsave\",\n    \"pae\",\n    \"pat\",\n    \"pcid\",\n    \"pclmulqdq\",\n    \"pdcm\",\n    \"pdpe1gb\",\n    \"pge\",\n    \"pni\",\n    \"popcnt\",\n    \"pse\",\n    \"pse36\",\n    \"rdrand\",\n    \"rdrnd\",\n    \"rdseed\",\n    \"rdtscp\",\n    \"rep_good\",\n    \"sep\",\n    \"smap\",\n    \"smep\",\n    \"ss\",\n    \"ssbd\",\n    \"sse\",\n    \"sse2\",\n    \"sse4_1\",\n    \"sse4_2\",\n    \"ssse3\",\n    \"stibp\",\n    \"syscall\",\n    \"tpr_shadow\",\n    \"tsc\",\n    \"vme\",\n    \"vmx\",\n    \"vnmi\",\n    \"vpid\",\n    \"x2apic\",\n    \"xgetbv1\",\n    \"xsave\",\n    \"xsavec\",\n    \"xsaveopt\",\n    \"xsaves\",\n    \"xtopology\"\n  ],\n  \"l3_cache_size\": 12582912,\n  \"l2_cache_size\": \"1.5 MiB\",\n  \"l1_data_cache_size\": 196608,\n  \"l1_instruction_cache_size\": 196608,\n  \"l2_cache_line_size\": 256,\n  \"l2_cache_associativity\": 6\n}\n
"},{"location":"contexts/cwd/","title":"CwdContext","text":"

The CwdContext captures the current working directory. It can be created by capsula.CwdContext() with no arguments.

"},{"location":"contexts/cwd/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/cwd/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"CwdContext\" },\n]\n
"},{"location":"contexts/cwd/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.CwdContext(), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/cwd/#output-example","title":"Output example","text":"

The following is an example of the output of the CwdContext, reported by the JsonDumpReporter:

\"cwd\": \"/home/nomura/ghq/github.com/shunichironomura/capsula\"\n
"},{"location":"contexts/envvar/","title":"EnvVarContext","text":"

The EnvVarContext captures an environment variable. It can be created using the capsula.EnvVarContext.__init__ method.

"},{"location":"contexts/envvar/#capsula.EnvVarContext.__init__","title":"capsula.EnvVarContext.__init__","text":"
__init__(name: str)\n
PARAMETER DESCRIPTION name

Name of the environment variable

TYPE: str

Source code in capsula/_context/_envvar.py
def __init__(self, name: Annotated[str, Doc(\"Name of the environment variable\")]) -> None:\n    self.name = name\n
"},{"location":"contexts/envvar/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/envvar/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"EnvVarContext\", name = \"HOME\" },\n]\n
"},{"location":"contexts/envvar/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/envvar/#output-example","title":"Output example","text":"

The following is an example of the output of the EnvVarContext, reported by the JsonDumpReporter:

\"env\": {\n  \"HOME\": \"/home/nomura\"\n}\n
"},{"location":"contexts/file/","title":"FileContext","text":"

The FileContext captures the file information. It can be created using the capsula.FileContext.builder method (recommended) or the capsula.FileContext.__init__ method.

"},{"location":"contexts/file/#capsula.FileContext.builder","title":"capsula.FileContext.builder classmethod","text":"
builder(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy: bool = False,\n    move: bool = False,\n    ignore_missing: bool = False,\n    path_relative_to_project_root: bool = False\n) -> Callable[[CapsuleParams], FileContext]\n
PARAMETER DESCRIPTION path

Path to the file

TYPE: Path | str

compute_hash

Whether to compute the hash of the file

TYPE: bool DEFAULT: True

hash_algorithm

Hash algorithm to use. This will be fed to hashlib.file_digest as the digest argument. If not provided, sha256 will be used.

TYPE: str | None DEFAULT: None

copy

Whether to copy the file to the run directory

TYPE: bool DEFAULT: False

move

Whether to move the file to the run directory

TYPE: bool DEFAULT: False

ignore_missing

Whether to ignore if the file does not exist

TYPE: bool DEFAULT: False

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
@classmethod\ndef builder(\n    cls,\n    path: Annotated[Path | str, Doc(\"Path to the file\")],\n    *,\n    compute_hash: Annotated[bool, Doc(\"Whether to compute the hash of the file\")] = True,\n    hash_algorithm: Annotated[\n        str | None,\n        Doc(\n            \"Hash algorithm to use. This will be fed to `hashlib.file_digest` as the `digest` argument. \"\n            \"If not provided, `sha256` will be used.\",\n        ),\n    ] = None,\n    copy: Annotated[bool, Doc(\"Whether to copy the file to the run directory\")] = False,\n    move: Annotated[bool, Doc(\"Whether to move the file to the run directory\")] = False,\n    ignore_missing: Annotated[bool, Doc(\"Whether to ignore if the file does not exist\")] = False,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n) -> Callable[[CapsuleParams], FileContext]:\n    if copy and move:\n        warnings.warn(\"Both copy and move are True. Only move will be performed.\", UserWarning, stacklevel=2)\n        move = True\n        copy = False\n\n    def build(params: CapsuleParams) -> FileContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            file_path = params.project_root / path\n        else:\n            file_path = Path(path)\n\n        return cls(\n            path=file_path,\n            compute_hash=compute_hash,\n            hash_algorithm=hash_algorithm,\n            copy_to=params.run_dir if copy else None,\n            move_to=params.run_dir if move else None,\n            ignore_missing=ignore_missing,\n        )\n\n    return build\n
"},{"location":"contexts/file/#capsula.FileContext.__init__","title":"capsula.FileContext.__init__","text":"
__init__(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: (\n        Iterable[Path | str] | Path | str | None\n    ) = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False\n)\n
PARAMETER DESCRIPTION path

TYPE: Path | str

compute_hash

TYPE: bool DEFAULT: True

hash_algorithm

TYPE: str | None DEFAULT: None

copy_to

TYPE: Iterable[Path | str] | Path | str | None DEFAULT: None

move_to

TYPE: Path | str | None DEFAULT: None

ignore_missing

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: Iterable[Path | str] | Path | str | None = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False,\n) -> None:\n    self._path = Path(path)\n    self._hash_algorithm = self._default_hash_algorithm if hash_algorithm is None else hash_algorithm\n    self._compute_hash = compute_hash\n    self._move_to = None if move_to is None else Path(move_to)\n    self._ignore_missing = ignore_missing\n\n    if copy_to is None:\n        self._copy_to: tuple[Path, ...] = ()\n    elif isinstance(copy_to, (str, Path)):\n        self._copy_to = (Path(copy_to),)\n    else:\n        self._copy_to = tuple(Path(p) for p in copy_to)\n
"},{"location":"contexts/file/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/file/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"FileContext\", path = \"pyproject.toml\", copy = true, path_relative_to_project_root = true },\n]\n
"},{"location":"contexts/file/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"

@capsula.context decorator is useful to move the output file to the run directory after the function execution.

import capsula\nPROJECT_ROOT = capsula.search_for_project_root(__file__)\n\n@capsula.run()\n@capsula.context(capsula.FileContext.builder(PROJECT_ROOT / \"pyproject.toml\", copy=True), mode=\"pre\")\n@capsula.context(capsula.FileContext.builder(\"output.txt\", move=True), mode=\"post\")\ndef func():\n  with open(\"output.txt\", \"w\") as output_file:\n    output_file.write(\"Hello, world!\")\n
"},{"location":"contexts/file/#output-example","title":"Output example","text":"

The following is an example of the output of the FileContext, reported by the JsonDumpReporter:

\"file\": {\n  \"/home/nomura/ghq/github.com/shunichironomura/capsula/pyproject.toml\": {\n    \"copied_to\": [\n      \"/home/nomura/ghq/github.com/shunichironomura/capsula/vault/20240708_024409_coj0/pyproject.toml\"\n    ],\n    \"moved_to\": null,\n    \"hash\": {\n      \"algorithm\": \"sha256\",\n      \"digest\": \"1ecab310035eea9c07fad2a8b22a16f999cd4d8c59fa1732c088f754af548ad9\"\n    }\n  },\n}\n
"},{"location":"contexts/function/","title":"FunctionContext","text":"

The FunctionContext captures the arguments of a function. It can be created using the capsula.FunctionContext.builder method or the capsula.FunctionContext.__init__ method.

"},{"location":"contexts/function/#capsula.FunctionContext.builder","title":"capsula.FunctionContext.builder classmethod","text":"
builder(\n    *, ignore: Container[str] = ()\n) -> Callable[[CapsuleParams], FunctionContext]\n
PARAMETER DESCRIPTION ignore

Parameters to ignore when capturing the arguments. This is useful when you pass values that you don't want to be in the output, such as a large data structure or a function that is not serializable, or a secret.

TYPE: Container[str] DEFAULT: ()

Source code in capsula/_context/_function.py
@classmethod\ndef builder(\n    cls,\n    *,\n    ignore: Annotated[\n        Container[str],\n        Doc(\n            \"Parameters to ignore when capturing the arguments. \"\n            \"This is useful when you pass values that you don't want to be in the output, \"\n            \"such as a large data structure or a function that is not serializable, or a secret.\",\n        ),\n    ] = (),\n) -> Callable[[CapsuleParams], FunctionContext]:\n    def build(params: CapsuleParams) -> FunctionContext:\n        if not isinstance(params.exec_info, FuncInfo):\n            msg = \"FunctionContext can only be built from a FuncInfo.\"\n            raise TypeError(msg)\n\n        return cls(\n            params.exec_info.func,\n            args=params.exec_info.args,\n            kwargs=params.exec_info.kwargs,\n            ignore=ignore,\n            _remove_pre_run_capsule_before_binding=params.exec_info.pass_pre_run_capsule,\n        )\n\n    return build\n
"},{"location":"contexts/function/#capsula.FunctionContext.__init__","title":"capsula.FunctionContext.__init__","text":"
__init__(\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False\n)\n
PARAMETER DESCRIPTION function

TYPE: Callable[..., Any]

args

TYPE: Sequence[Any]

kwargs

TYPE: Mapping[str, Any]

ignore

TYPE: Container[str] DEFAULT: ()

_remove_pre_run_capsule_before_binding

TYPE: bool DEFAULT: False

Source code in capsula/_context/_function.py
def __init__(\n    self,\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False,\n) -> None:\n    self._function = function\n    self._args = args\n    self._kwargs = kwargs\n    self._ignore = ignore\n    self._remove_pre_run_capsule_before_binding = _remove_pre_run_capsule_before_binding\n
"},{"location":"contexts/function/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/function/#via-capsulatoml","title":"Via capsula.toml","text":"

Warning

Configuring the FunctionContext via capsula.toml is not recommended because capsula enc will fail as there is no target function to capture.

[pre-run]\ncontexts = [\n  { type = \"FunctionContext\" },\n]\n
"},{"location":"contexts/function/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.FunctionContext.builder(), mode=\"pre\")\ndef func(arg1, arg2): ...\n
"},{"location":"contexts/function/#output-example","title":"Output example","text":"

The following is an example of the output of the FunctionContext, reported by the JsonDumpReporter:

\"function\": {\n  \"calculate_pi\": {\n    \"file_path\": \"examples/simple_decorator.py\",\n    \"first_line_no\": 6,\n    \"bound_args\": {\n      \"n_samples\": 1000,\n      \"seed\": 42\n    }\n  }\n}\n
"},{"location":"contexts/git/","title":"GitRepositoryContext","text":"

The GitRepositoryContext captures the information of a Git repository. It can be created using the capsula.GitRepositoryContext.builder method or the capsula.GitRepositoryContext.__init__ method.

"},{"location":"contexts/git/#capsula.GitRepositoryContext.builder","title":"capsula.GitRepositoryContext.builder classmethod","text":"
builder(\n    name: str | None = None,\n    *,\n    path: Path | str | None = None,\n    path_relative_to_project_root: bool = False,\n    allow_dirty: bool = True\n) -> Callable[[CapsuleParams], GitRepositoryContext]\n
PARAMETER DESCRIPTION name

Name of the Git repository. If not provided, the name of the working directory will be used.

TYPE: str | None DEFAULT: None

path

Path to the Git repository. If not provided, the parent directories of the file where the function is defined will be searched for a Git repository.

TYPE: Path | str | None DEFAULT: None

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is None or absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

allow_dirty

Whether to allow the repository to be dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
@classmethod\ndef builder(\n    cls,\n    name: Annotated[\n        str | None,\n        Doc(\"Name of the Git repository. If not provided, the name of the working directory will be used.\"),\n    ] = None,\n    *,\n    path: Annotated[\n        Path | str | None,\n        Doc(\n            \"Path to the Git repository. If not provided, the parent directories of the file where the function is \"\n            \"defined will be searched for a Git repository.\",\n        ),\n    ] = None,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    allow_dirty: Annotated[bool, Doc(\"Whether to allow the repository to be dirty\")] = True,\n) -> Callable[[CapsuleParams], GitRepositoryContext]:\n    def build(params: CapsuleParams) -> GitRepositoryContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            repository_path: Path | None = params.project_root / path\n        else:\n            repository_path = Path(path) if path is not None else None\n\n        if repository_path is not None:\n            repo = Repo(repository_path, search_parent_directories=False)\n        else:\n            if isinstance(params.exec_info, FuncInfo):\n                repo_search_start_path = Path(inspect.getfile(params.exec_info.func)).parent\n            elif isinstance(params.exec_info, CommandInfo) or params.exec_info is None:\n                repo_search_start_path = Path.cwd()\n            else:\n                msg = f\"exec_info must be an instance of FuncInfo or CommandInfo, not {type(params.exec_info)}.\"\n                raise TypeError(msg)\n            repo = Repo(repo_search_start_path, search_parent_directories=True)\n\n        repo_name = Path(repo.working_dir).name\n\n        return cls(\n            name=Path(repo.working_dir).name if name is None else name,\n            path=Path(repo.working_dir),\n            diff_file=params.run_dir / f\"{repo_name}.diff\",\n            search_parent_directories=False,\n            allow_dirty=allow_dirty,\n        )\n\n    return build\n
"},{"location":"contexts/git/#capsula.GitRepositoryContext.__init__","title":"capsula.GitRepositoryContext.__init__","text":"
__init__(\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True\n)\n
PARAMETER DESCRIPTION name

TYPE: str

path

TYPE: Path | str

diff_file

TYPE: Path | str | None DEFAULT: None

search_parent_directories

TYPE: bool DEFAULT: False

allow_dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
def __init__(\n    self,\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True,\n) -> None:\n    self._name = name\n    self._path = Path(path)\n    self._search_parent_directories = search_parent_directories\n    self._allow_dirty = allow_dirty\n    self._diff_file = None if diff_file is None else Path(diff_file)\n
"},{"location":"contexts/git/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/git/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"GitRepositoryContext\", name = \"capsula\", path = \".\", path_relative_to_project_root = true },\n]\n
"},{"location":"contexts/git/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.GitRepositoryContext.builder(\"capsula\"), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/git/#output-example","title":"Output example","text":"

The following is an example of the output of the GitRepositoryContext, reported by the JsonDumpReporter:

\"git\": {\n  \"capsula\": {\n    \"working_dir\": \"/home/nomura/ghq/github.com/shunichironomura/capsula\",\n    \"sha\": \"2fa930db2b9c00c467b4627e7d1c7dfb06d41279\",\n    \"remotes\": {\n      \"origin\": \"ssh://git@github.com/shunichironomura/capsula.git\"\n    },\n    \"branch\": \"improve-docs-index\",\n    \"is_dirty\": true,\n    \"diff_file\": \"/home/nomura/ghq/github.com/shunichironomura/capsula/vault/20240708_024409_coj0/capsula.diff\"\n  }\n}\n
"},{"location":"contexts/platform/","title":"PlatformContext","text":"

The PlatformContext captures the Python version. It can be created by capsula.PlatformContext() with no arguments.

"},{"location":"contexts/platform/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/platform/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"PlatformContext\" },\n]\n
"},{"location":"contexts/platform/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.PlatformContext(), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/platform/#output-example","title":"Output example","text":"

The following is an example of the output of the PlatformContext, reported by the JsonDumpReporter:

\"platform\": {\n  \"machine\": \"x86_64\",\n  \"node\": \"SHUN-DESKTOP\",\n  \"platform\": \"Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.34\",\n  \"release\": \"5.15.153.1-microsoft-standard-WSL2\",\n  \"version\": \"#1 SMP Fri Mar 29 23:14:13 UTC 2024\",\n  \"system\": \"Linux\",\n  \"processor\": \"x86_64\",\n  \"python\": {\n    \"executable_architecture\": {\n      \"bits\": \"64bit\",\n      \"linkage\": \"ELF\"\n    },\n    \"build_no\": \"default\",\n    \"build_date\": \"Jul  7 2024 07:23:53\",\n    \"compiler\": \"GCC 11.4.0\",\n    \"branch\": \"\",\n    \"implementation\": \"CPython\",\n    \"version\": \"3.8.19\"\n  }\n}\n
"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":""},{"location":"reference/capsula/","title":"capsula","text":""},{"location":"reference/capsula/#capsula","title":"capsula","text":""},{"location":"reference/capsula/#capsula.Capsule","title":"Capsule","text":"
Capsule(\n    data: Mapping[_ContextKey, Any],\n    fails: Mapping[_ContextKey, ExceptionInfo],\n)\n
PARAMETER DESCRIPTION data

TYPE: Mapping[_ContextKey, Any]

fails

TYPE: Mapping[_ContextKey, ExceptionInfo]

Source code in capsula/_capsule.py
def __init__(\n    self,\n    data: Mapping[_ContextKey, Any],\n    fails: Mapping[_ContextKey, ExceptionInfo],\n) -> None:\n    self.data = dict(data)\n    self.fails = dict(fails)\n
"},{"location":"reference/capsula/#capsula.Capsule.data","title":"data instance-attribute","text":"
data = dict(data)\n
"},{"location":"reference/capsula/#capsula.Capsule.fails","title":"fails instance-attribute","text":"
fails = dict(fails)\n
"},{"location":"reference/capsula/#capsula.CommandContext","title":"CommandContext","text":"
CommandContext(\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True\n)\n

Bases: ContextBase

Context to capture the output of a command run in a subprocess.

Initialize the command context.

PARAMETER DESCRIPTION command

TYPE: str

cwd

TYPE: Path | None DEFAULT: None

check

TYPE: bool DEFAULT: True

abort_on_error

TYPE: bool DEFAULT: True

shell

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
def __init__(\n    self,\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True,\n) -> None:\n    \"\"\"Initialize the command context.\"\"\"\n    self._command = command\n    self._cwd = cwd\n    self._check = check\n    self._abort_on_error = abort_on_error\n    self._shell = shell\n
"},{"location":"reference/capsula/#capsula.CommandContext.builder","title":"builder classmethod","text":"
builder(\n    command: str,\n    *,\n    cwd: Path | str | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    cwd_relative_to_project_root: bool = False,\n    shell: bool = True\n) -> Callable[[CapsuleParams], CommandContext]\n
PARAMETER DESCRIPTION command

Command to run

TYPE: str

cwd

Working directory for the command, passed to the cwd argument of subprocess.run

TYPE: Path | str | None DEFAULT: None

check

Whether to raise an exception if the command returns a non-zero exit code, passed to the check argument of `subprocess.run

TYPE: bool DEFAULT: True

abort_on_error

Whether to abort the encapsulation if the command returns a non-zero exit code

TYPE: bool DEFAULT: True

cwd_relative_to_project_root

Whether cwd argument is relative to the project root. Will be ignored if cwd is None or absolute. If True, it will be interpreted as relative to the project root. If False, cwd will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

shell

Whether to run the command using the shell. If True, the command will be run using the shell. If False, the command will be run directly. For more information, see the shell argument of subprocess.run.

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
@classmethod\ndef builder(\n    cls,\n    command: Annotated[str, Doc(\"Command to run\")],\n    *,\n    cwd: Annotated[\n        Path | str | None,\n        Doc(\"Working directory for the command, passed to the `cwd` argument of `subprocess.run`\"),\n    ] = None,\n    check: Annotated[\n        bool,\n        Doc(\n            \"Whether to raise an exception if the command returns a non-zero exit code, passed to the `check` \"\n            \"argument of `subprocess.run\",\n        ),\n    ] = True,\n    abort_on_error: Annotated[\n        bool,\n        Doc(\"Whether to abort the encapsulation if the command returns a non-zero exit code\"),\n    ] = True,\n    cwd_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `cwd` argument is relative to the project root. Will be ignored if `cwd` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `cwd` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    shell: Annotated[\n        bool,\n        Doc(\n            \"Whether to run the command using the shell. If True, the command will be run using the shell. \"\n            \"If False, the command will be run directly. \"\n            \"For more information, see the `shell` argument of `subprocess.run`. \",\n        ),\n    ] = True,\n) -> Callable[[CapsuleParams], CommandContext]:\n    def build(params: CapsuleParams) -> CommandContext:\n        if cwd_relative_to_project_root and cwd is not None and not Path(cwd).is_absolute():\n            cwd_path: Path | None = params.project_root / cwd\n        elif cwd_relative_to_project_root and cwd is None:\n            cwd_path = params.project_root\n        else:\n            cwd_path = Path(cwd) if cwd is not None else None\n\n        return cls(\n            command,\n            cwd=cwd_path,\n            check=check,\n            abort_on_error=abort_on_error,\n            shell=shell,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.CommandContext.abort_on_error","title":"abort_on_error property","text":"
abort_on_error: bool\n
"},{"location":"reference/capsula/#capsula.CommandContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _CommandContextData\n
Source code in capsula/_context/_command.py
def encapsulate(self) -> _CommandContextData:\n    logger.debug(f\"Running command: {self._command}\")\n    output = subprocess.run(  # noqa: S603\n        self._command,\n        shell=self._shell,\n        text=True,\n        capture_output=True,\n        cwd=self._cwd,\n        check=self._check,\n    )\n    logger.debug(f\"Ran command: {self._command}. Result: {output}\")\n    return {\n        \"command\": self._command,\n        \"cwd\": self._cwd,\n        \"returncode\": output.returncode,\n        \"stdout\": output.stdout,\n        \"stderr\": output.stderr,\n    }\n
"},{"location":"reference/capsula/#capsula.CommandContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_command.py
def default_key(self) -> tuple[str, str]:\n    return (\"command\", self._command)\n
"},{"location":"reference/capsula/#capsula.ContextBase","title":"ContextBase","text":"

Bases: CapsuleItem

"},{"location":"reference/capsula/#capsula.ContextBase.abort_on_error","title":"abort_on_error property","text":"
abort_on_error: bool\n
"},{"location":"reference/capsula/#capsula.ContextBase.get_subclass","title":"get_subclass classmethod","text":"
get_subclass(name: str) -> type[ContextBase]\n
PARAMETER DESCRIPTION name

TYPE: str

Source code in capsula/_context/_base.py
@classmethod\ndef get_subclass(cls, name: str) -> type[ContextBase]:\n    return cls._subclass_registry[name]\n
"},{"location":"reference/capsula/#capsula.CpuContext","title":"CpuContext","text":"

Bases: ContextBase

Context to capture CPU information.

"},{"location":"reference/capsula/#capsula.CpuContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> dict[str, Any]\n
Source code in capsula/_context/_cpu.py
def encapsulate(self) -> dict[str, Any]:\n    return get_cpu_info()  # type: ignore[no-any-return]\n
"},{"location":"reference/capsula/#capsula.CpuContext.default_key","title":"default_key","text":"
default_key() -> str\n
Source code in capsula/_context/_cpu.py
def default_key(self) -> str:\n    return \"cpu\"\n
"},{"location":"reference/capsula/#capsula.CwdContext","title":"CwdContext","text":"

Bases: ContextBase

Context to capture the current working directory.

"},{"location":"reference/capsula/#capsula.CwdContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> Path\n
Source code in capsula/_context/_cwd.py
def encapsulate(self) -> Path:\n    return Path.cwd()\n
"},{"location":"reference/capsula/#capsula.CwdContext.default_key","title":"default_key","text":"
default_key() -> str\n
Source code in capsula/_context/_cwd.py
def default_key(self) -> str:\n    return \"cwd\"\n
"},{"location":"reference/capsula/#capsula.EnvVarContext","title":"EnvVarContext","text":"
EnvVarContext(name: str)\n

Bases: ContextBase

Context to capture an environment variable.

PARAMETER DESCRIPTION name

Name of the environment variable

TYPE: str

Source code in capsula/_context/_envvar.py
def __init__(self, name: Annotated[str, Doc(\"Name of the environment variable\")]) -> None:\n    self.name = name\n
"},{"location":"reference/capsula/#capsula.EnvVarContext.name","title":"name instance-attribute","text":"
name = name\n
"},{"location":"reference/capsula/#capsula.EnvVarContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> str | None\n
Source code in capsula/_context/_envvar.py
def encapsulate(self) -> str | None:\n    return os.getenv(self.name)\n
"},{"location":"reference/capsula/#capsula.EnvVarContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_envvar.py
def default_key(self) -> tuple[str, str]:\n    return (\"env\", self.name)\n
"},{"location":"reference/capsula/#capsula.FileContext","title":"FileContext","text":"
FileContext(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: (\n        Iterable[Path | str] | Path | str | None\n    ) = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False\n)\n

Bases: ContextBase

Context to capture a file.

PARAMETER DESCRIPTION path

TYPE: Path | str

compute_hash

TYPE: bool DEFAULT: True

hash_algorithm

TYPE: str | None DEFAULT: None

copy_to

TYPE: Iterable[Path | str] | Path | str | None DEFAULT: None

move_to

TYPE: Path | str | None DEFAULT: None

ignore_missing

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: Iterable[Path | str] | Path | str | None = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False,\n) -> None:\n    self._path = Path(path)\n    self._hash_algorithm = self._default_hash_algorithm if hash_algorithm is None else hash_algorithm\n    self._compute_hash = compute_hash\n    self._move_to = None if move_to is None else Path(move_to)\n    self._ignore_missing = ignore_missing\n\n    if copy_to is None:\n        self._copy_to: tuple[Path, ...] = ()\n    elif isinstance(copy_to, (str, Path)):\n        self._copy_to = (Path(copy_to),)\n    else:\n        self._copy_to = tuple(Path(p) for p in copy_to)\n
"},{"location":"reference/capsula/#capsula.FileContext.builder","title":"builder classmethod","text":"
builder(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy: bool = False,\n    move: bool = False,\n    ignore_missing: bool = False,\n    path_relative_to_project_root: bool = False\n) -> Callable[[CapsuleParams], FileContext]\n
PARAMETER DESCRIPTION path

Path to the file

TYPE: Path | str

compute_hash

Whether to compute the hash of the file

TYPE: bool DEFAULT: True

hash_algorithm

Hash algorithm to use. This will be fed to hashlib.file_digest as the digest argument. If not provided, sha256 will be used.

TYPE: str | None DEFAULT: None

copy

Whether to copy the file to the run directory

TYPE: bool DEFAULT: False

move

Whether to move the file to the run directory

TYPE: bool DEFAULT: False

ignore_missing

Whether to ignore if the file does not exist

TYPE: bool DEFAULT: False

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
@classmethod\ndef builder(\n    cls,\n    path: Annotated[Path | str, Doc(\"Path to the file\")],\n    *,\n    compute_hash: Annotated[bool, Doc(\"Whether to compute the hash of the file\")] = True,\n    hash_algorithm: Annotated[\n        str | None,\n        Doc(\n            \"Hash algorithm to use. This will be fed to `hashlib.file_digest` as the `digest` argument. \"\n            \"If not provided, `sha256` will be used.\",\n        ),\n    ] = None,\n    copy: Annotated[bool, Doc(\"Whether to copy the file to the run directory\")] = False,\n    move: Annotated[bool, Doc(\"Whether to move the file to the run directory\")] = False,\n    ignore_missing: Annotated[bool, Doc(\"Whether to ignore if the file does not exist\")] = False,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n) -> Callable[[CapsuleParams], FileContext]:\n    if copy and move:\n        warnings.warn(\"Both copy and move are True. Only move will be performed.\", UserWarning, stacklevel=2)\n        move = True\n        copy = False\n\n    def build(params: CapsuleParams) -> FileContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            file_path = params.project_root / path\n        else:\n            file_path = Path(path)\n\n        return cls(\n            path=file_path,\n            compute_hash=compute_hash,\n            hash_algorithm=hash_algorithm,\n            copy_to=params.run_dir if copy else None,\n            move_to=params.run_dir if move else None,\n            ignore_missing=ignore_missing,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.FileContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _FileContextData\n
Source code in capsula/_context/_file.py
def encapsulate(self) -> _FileContextData:\n    if not self._path.exists():\n        if self._ignore_missing:\n            logger.warning(f\"File {self._path} does not exist. Ignoring.\")\n            return _FileContextData(copied_to=(), moved_to=None, hash=None)\n        else:\n            msg = f\"File {self._path} does not exist.\"\n            raise FileNotFoundError(msg)\n    self._copy_to = tuple(self._normalize_copy_dst_path(p) for p in self._copy_to)\n\n    if self._compute_hash:\n        with self._path.open(\"rb\") as f:\n            digest = file_digest(f, self._hash_algorithm).hexdigest()\n        hash_data = {\n            \"algorithm\": self._hash_algorithm,\n            \"digest\": digest,\n        }\n    else:\n        hash_data = None\n\n    info: _FileContextData = {\n        \"copied_to\": self._copy_to,\n        \"moved_to\": self._move_to,\n        \"hash\": hash_data,\n    }\n\n    for path in self._copy_to:\n        copyfile(self._path, path)\n    if self._move_to is not None:\n        move(str(self._path), self._move_to)\n\n    return info\n
"},{"location":"reference/capsula/#capsula.FileContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_file.py
def default_key(self) -> tuple[str, str]:\n    return (\"file\", str(self._path))\n
"},{"location":"reference/capsula/#capsula.FunctionContext","title":"FunctionContext","text":"
FunctionContext(\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False\n)\n

Bases: ContextBase

Context to capture a function call.

PARAMETER DESCRIPTION function

TYPE: Callable[..., Any]

args

TYPE: Sequence[Any]

kwargs

TYPE: Mapping[str, Any]

ignore

TYPE: Container[str] DEFAULT: ()

_remove_pre_run_capsule_before_binding

TYPE: bool DEFAULT: False

Source code in capsula/_context/_function.py
def __init__(\n    self,\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False,\n) -> None:\n    self._function = function\n    self._args = args\n    self._kwargs = kwargs\n    self._ignore = ignore\n    self._remove_pre_run_capsule_before_binding = _remove_pre_run_capsule_before_binding\n
"},{"location":"reference/capsula/#capsula.FunctionContext.builder","title":"builder classmethod","text":"
builder(\n    *, ignore: Container[str] = ()\n) -> Callable[[CapsuleParams], FunctionContext]\n
PARAMETER DESCRIPTION ignore

Parameters to ignore when capturing the arguments. This is useful when you pass values that you don't want to be in the output, such as a large data structure or a function that is not serializable, or a secret.

TYPE: Container[str] DEFAULT: ()

Source code in capsula/_context/_function.py
@classmethod\ndef builder(\n    cls,\n    *,\n    ignore: Annotated[\n        Container[str],\n        Doc(\n            \"Parameters to ignore when capturing the arguments. \"\n            \"This is useful when you pass values that you don't want to be in the output, \"\n            \"such as a large data structure or a function that is not serializable, or a secret.\",\n        ),\n    ] = (),\n) -> Callable[[CapsuleParams], FunctionContext]:\n    def build(params: CapsuleParams) -> FunctionContext:\n        if not isinstance(params.exec_info, FuncInfo):\n            msg = \"FunctionContext can only be built from a FuncInfo.\"\n            raise TypeError(msg)\n\n        return cls(\n            params.exec_info.func,\n            args=params.exec_info.args,\n            kwargs=params.exec_info.kwargs,\n            ignore=ignore,\n            _remove_pre_run_capsule_before_binding=params.exec_info.pass_pre_run_capsule,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.FunctionContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _FunctionContextData\n
Source code in capsula/_context/_function.py
def encapsulate(self) -> _FunctionContextData:\n    file_path = Path(inspect.getfile(self._function))\n    _, first_line_no = inspect.getsourcelines(self._function)\n    sig = inspect.signature(self._function)\n\n    if self._remove_pre_run_capsule_before_binding:\n        sig = sig.replace(parameters=tuple(p for p in sig.parameters.values() if p.name != \"pre_run_capsule\"))\n\n    ba = sig.bind(*self._args, **self._kwargs)\n    ba.apply_defaults()\n    ba_wo_ignore = {k: v for k, v in ba.arguments.items() if k not in self._ignore}\n    return {\n        \"file_path\": file_path,\n        \"first_line_no\": first_line_no,\n        \"bound_args\": ba_wo_ignore,\n    }\n
"},{"location":"reference/capsula/#capsula.FunctionContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_function.py
def default_key(self) -> tuple[str, str]:\n    return (\"function\", self._function.__name__)\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext","title":"GitRepositoryContext","text":"
GitRepositoryContext(\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True\n)\n

Bases: ContextBase

Context to capture a Git repository.

PARAMETER DESCRIPTION name

TYPE: str

path

TYPE: Path | str

diff_file

TYPE: Path | str | None DEFAULT: None

search_parent_directories

TYPE: bool DEFAULT: False

allow_dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
def __init__(\n    self,\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True,\n) -> None:\n    self._name = name\n    self._path = Path(path)\n    self._search_parent_directories = search_parent_directories\n    self._allow_dirty = allow_dirty\n    self._diff_file = None if diff_file is None else Path(diff_file)\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext.builder","title":"builder classmethod","text":"
builder(\n    name: str | None = None,\n    *,\n    path: Path | str | None = None,\n    path_relative_to_project_root: bool = False,\n    allow_dirty: bool = True\n) -> Callable[[CapsuleParams], GitRepositoryContext]\n
PARAMETER DESCRIPTION name

Name of the Git repository. If not provided, the name of the working directory will be used.

TYPE: str | None DEFAULT: None

path

Path to the Git repository. If not provided, the parent directories of the file where the function is defined will be searched for a Git repository.

TYPE: Path | str | None DEFAULT: None

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is None or absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

allow_dirty

Whether to allow the repository to be dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
@classmethod\ndef builder(\n    cls,\n    name: Annotated[\n        str | None,\n        Doc(\"Name of the Git repository. If not provided, the name of the working directory will be used.\"),\n    ] = None,\n    *,\n    path: Annotated[\n        Path | str | None,\n        Doc(\n            \"Path to the Git repository. If not provided, the parent directories of the file where the function is \"\n            \"defined will be searched for a Git repository.\",\n        ),\n    ] = None,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    allow_dirty: Annotated[bool, Doc(\"Whether to allow the repository to be dirty\")] = True,\n) -> Callable[[CapsuleParams], GitRepositoryContext]:\n    def build(params: CapsuleParams) -> GitRepositoryContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            repository_path: Path | None = params.project_root / path\n        else:\n            repository_path = Path(path) if path is not None else None\n\n        if repository_path is not None:\n            repo = Repo(repository_path, search_parent_directories=False)\n        else:\n            if isinstance(params.exec_info, FuncInfo):\n                repo_search_start_path = Path(inspect.getfile(params.exec_info.func)).parent\n            elif isinstance(params.exec_info, CommandInfo) or params.exec_info is None:\n                repo_search_start_path = Path.cwd()\n            else:\n                msg = f\"exec_info must be an instance of FuncInfo or CommandInfo, not {type(params.exec_info)}.\"\n                raise TypeError(msg)\n            repo = Repo(repo_search_start_path, search_parent_directories=True)\n\n        repo_name = Path(repo.working_dir).name\n\n        return cls(\n            name=Path(repo.working_dir).name if name is None else name,\n            path=Path(repo.working_dir),\n            diff_file=params.run_dir / f\"{repo_name}.diff\",\n            search_parent_directories=False,\n            allow_dirty=allow_dirty,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _GitRepositoryContextData\n
Source code in capsula/_context/_git.py
def encapsulate(self) -> _GitRepositoryContextData:\n    repo = Repo(self._path, search_parent_directories=self._search_parent_directories)\n    if not self._allow_dirty and repo.is_dirty():\n        raise GitRepositoryDirtyError(repo)\n\n    def get_optional_branch_name(repo: Repo) -> str | None:\n        try:\n            return repo.active_branch.name\n        except TypeError:\n            return None\n\n    info: _GitRepositoryContextData = {\n        \"working_dir\": repo.working_dir,\n        \"sha\": repo.head.commit.hexsha,\n        \"remotes\": {remote.name: remote.url for remote in repo.remotes},\n        \"branch\": get_optional_branch_name(repo),\n        \"is_dirty\": repo.is_dirty(),\n        \"diff_file\": None,\n    }\n\n    diff_txt = repo.git.diff()\n    if diff_txt:\n        assert self._diff_file is not None, \"diff_file is None\"\n        with self._diff_file.open(\"w\") as f:\n            f.write(diff_txt)\n        logger.debug(f\"Wrote diff to {self._diff_file}\")\n        info[\"diff_file\"] = self._diff_file\n    return info\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_git.py
def default_key(self) -> tuple[str, str]:\n    return (\"git\", self._name)\n
"},{"location":"reference/capsula/#capsula.PlatformContext","title":"PlatformContext","text":"

Bases: ContextBase

Context to capture platform information, including Python version.

"},{"location":"reference/capsula/#capsula.PlatformContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _PlatformContextData\n
Source code in capsula/_context/_platform.py
def encapsulate(self) -> _PlatformContextData:\n    return {\n        \"machine\": pf.machine(),\n        \"node\": pf.node(),\n        \"platform\": pf.platform(),\n        \"release\": pf.release(),\n        \"version\": pf.version(),\n        \"system\": pf.system(),\n        \"processor\": pf.processor(),\n        \"python\": {\n            \"executable_architecture\": {\n                \"bits\": pf.architecture()[0],\n                \"linkage\": pf.architecture()[1],\n            },\n            \"build_no\": pf.python_build()[0],\n            \"build_date\": pf.python_build()[1],\n            \"compiler\": pf.python_compiler(),\n            \"branch\": pf.python_branch(),\n            \"implementation\": pf.python_implementation(),\n            \"version\": pf.python_version(),\n        },\n    }\n
"},{"location":"reference/capsula/#capsula.PlatformContext.default_key","title":"default_key","text":"
default_key() -> str\n
Source code in capsula/_context/_platform.py
def default_key(self) -> str:\n    return \"platform\"\n
"},{"location":"reference/capsula/#capsula.context","title":"context","text":"
context(\n    context: (\n        ContextBase | Callable[[CapsuleParams], ContextBase]\n    ),\n    mode: Literal[\"pre\", \"post\", \"all\"],\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    RunDtoNoPassPreRunCapsule[P, T]\n    | RunDtoPassPreRunCapsule[P, T],\n]\n

Decorator to add a context to the specified phase of the run.

Example:

import capsula\n\n@capsula.run()\n@capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\ndef func() -> None: ...\n

PARAMETER DESCRIPTION context

Context or a builder function to create a context from the capsule parameters.

TYPE: ContextBase | Callable[[CapsuleParams], ContextBase]

mode

Phase to add the context. Specify 'all' to add to all phases.

TYPE: Literal['pre', 'post', 'all']

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]]

Decorator to add a context to the specified phase of the run.

Source code in capsula/_decorator.py
def context(\n    context: Annotated[\n        ContextBase | Callable[[CapsuleParams], ContextBase],\n        Doc(\"Context or a builder function to create a context from the capsule parameters.\"),\n    ],\n    mode: Annotated[\n        Literal[\"pre\", \"post\", \"all\"],\n        Doc(\"Phase to add the context. Specify 'all' to add to all phases.\"),\n    ],\n) -> Annotated[\n    Callable[\n        [Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]],\n        RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ],\n    Doc(\"Decorator to add a context to the specified phase of the run.\"),\n]:\n    \"\"\"Decorator to add a context to the specified phase of the run.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\n    def func() -> None: ...\n    ```\n\n    \"\"\"\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]:\n        run = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoNoPassPreRunCapsule, RunDtoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        run.add_context(context, mode=mode, append_left=True)\n        return run\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.pass_pre_run_capsule","title":"pass_pre_run_capsule","text":"
pass_pre_run_capsule(\n    func: Callable[Concatenate[Capsule, P], T]\n) -> RunDtoPassPreRunCapsule[P, T]\n

Decorator to pass the pre-run capsule to the function.

This decorator must be placed closer to the function than the other decorators such as run, context, watcher, and reporter.

The decorated function's first argument must be a Capsule object to receive the pre-run capsule.

With this decorator, you can access the pre-run capsule in the function to get the data recorded by the contexts, such as the Git repository context. Here is an example, assuming the GitRepositoryContext for the \"my-repo\" repository is added to the pre-run phase:

import capsula\n\n@capsula.run()\n@capsula.pass_pre_run_capsule\ndef func(pre_run_capsule: capsula.Capsule) -> None:\n    git_sha = pre_run_capsule.data[(\"git\", \"my-repo\")][\"sha\"]\n
PARAMETER DESCRIPTION func

Function to decorate.

TYPE: Callable[Concatenate[Capsule, P], T]

RETURNS DESCRIPTION RunDtoPassPreRunCapsule[P, T]

Decorated function as a Run object.

Source code in capsula/_decorator.py
def pass_pre_run_capsule(\n    func: Annotated[Callable[Concatenate[Capsule, P], T], Doc(\"Function to decorate.\")],\n) -> Annotated[RunDtoPassPreRunCapsule[P, T], Doc(\"Decorated function as a `Run` object.\")]:\n    \"\"\"Decorator to pass the pre-run capsule to the function.\n\n    This decorator must be placed closer to the function than the other decorators such as\n    `run`, `context`, `watcher`, and `reporter`.\n\n    The decorated function's first argument must be a `Capsule` object to receive the pre-run capsule.\n\n    With this decorator, you can access the pre-run capsule in the function to get the data recorded by the contexts,\n    such as the Git repository context. Here is an example, assuming the `GitRepositoryContext` for the \"my-repo\"\n    repository is added to the pre-run phase:\n\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.pass_pre_run_capsule\n    def func(pre_run_capsule: capsula.Capsule) -> None:\n        git_sha = pre_run_capsule.data[(\"git\", \"my-repo\")][\"sha\"]\n    ```\n    \"\"\"\n    return RunDtoPassPreRunCapsule(func=func)\n
"},{"location":"reference/capsula/#capsula.reporter","title":"reporter","text":"
reporter(\n    reporter: (\n        ReporterBase\n        | Callable[[CapsuleParams], ReporterBase]\n    ),\n    mode: Literal[\"pre\", \"in\", \"post\", \"all\"],\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    RunDtoNoPassPreRunCapsule[P, T]\n    | RunDtoPassPreRunCapsule[P, T],\n]\n

Decorator to add a reporter to the specified phase of the run.

Example:

import capsula\n\n@capsula.run()\n@capsula.reporter(capsula.JsonDumpReporter.builder(), mode=\"all\")\ndef func() -> None: ...\n

PARAMETER DESCRIPTION reporter

Reporter or a builder function to create a reporter from the capsule parameters.

TYPE: ReporterBase | Callable[[CapsuleParams], ReporterBase]

mode

Phase to add the reporter. Specify 'all' to add to all phases.

TYPE: Literal['pre', 'in', 'post', 'all']

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]]

Decorator to add a reporter to the specified phase of the run.

Source code in capsula/_decorator.py
def reporter(\n    reporter: Annotated[\n        ReporterBase | Callable[[CapsuleParams], ReporterBase],\n        Doc(\"Reporter or a builder function to create a reporter from the capsule parameters.\"),\n    ],\n    mode: Annotated[\n        Literal[\"pre\", \"in\", \"post\", \"all\"],\n        Doc(\"Phase to add the reporter. Specify 'all' to add to all phases.\"),\n    ],\n) -> Annotated[\n    Callable[\n        [Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]],\n        RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ],\n    Doc(\"Decorator to add a reporter to the specified phase of the run.\"),\n]:\n    \"\"\"Decorator to add a reporter to the specified phase of the run.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.reporter(capsula.JsonDumpReporter.builder(), mode=\"all\")\n    def func() -> None: ...\n    ```\n\n    \"\"\"\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]:\n        run = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoPassPreRunCapsule, RunDtoNoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        run.add_reporter(reporter, mode=mode, append_left=True)\n        return run\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.run","title":"run","text":"
run(\n    *,\n    run_name_factory: (\n        Callable[[FuncInfo, str, datetime], str] | None\n    ) = None,\n    ignore_config: bool = False,\n    config_path: Path | str | None = None,\n    vault_dir: Path | str | None = None\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    Run[P, T],\n]\n

Decorator to create a Run object.

Place this decorator at the outermost position of the decorators.

Example:

import capsula\n\n@capsula.run() # This should come first\n@capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\n...  # Add more decorators\ndef func() -> None: ...\n

The vault directory is determined by the following priority: 1. If vault_dir argument is set, it will be used as the vault directory. 2. If ignore_config argument is False and vault-dir field is present in the config file, it will be used as the vault directory. 3. The default vault directory is used.

The run name factory is determined by the following priority: 1. If run_name_factory argument is set, it will be used as the run name. 2. The default run name factory is used.

PARAMETER DESCRIPTION run_name_factory

Function to generate the run name. If not specified, the default run name factory will be used.

TYPE: Callable[[FuncInfo, str, datetime], str] | None DEFAULT: None

ignore_config

Whether to ignore the configuration file.

TYPE: bool DEFAULT: False

config_path

Path to the configuration file. If not specified, the default configuration file will be used.

TYPE: Path | str | None DEFAULT: None

vault_dir

Path to the vault directory.

TYPE: Path | str | None DEFAULT: None

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], Run[P, T]]

Decorator to create a Run object.

Source code in capsula/_decorator.py
def run(  # noqa: C901\n    *,\n    run_name_factory: Annotated[\n        Callable[[FuncInfo, str, datetime], str] | None,\n        Doc(\"Function to generate the run name. If not specified, the default run name factory will be used.\"),\n    ] = None,\n    ignore_config: Annotated[bool, Doc(\"Whether to ignore the configuration file.\")] = False,\n    config_path: Annotated[\n        Path | str | None,\n        Doc(\"Path to the configuration file. If not specified, the default configuration file will be used.\"),\n    ] = None,\n    vault_dir: Annotated[\n        Path | str | None,\n        Doc(\"Path to the vault directory.\"),\n    ] = None,\n) -> Annotated[\n    Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], Run[P, T]],\n    Doc(\"Decorator to create a `Run` object.\"),\n]:\n    \"\"\"Decorator to create a `Run` object.\n\n    Place this decorator at the outermost position of the decorators.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run() # This should come first\n    @capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\n    ...  # Add more decorators\n    def func() -> None: ...\n    ```\n\n    The vault directory is determined by the following priority:\n    1. If `vault_dir` argument is set, it will be used as the vault directory.\n    2. If `ignore_config` argument is False and `vault-dir` field is present in the config file,\n       it will be used as the vault directory.\n    3. The default vault directory is used.\n\n    The run name factory is determined by the following priority:\n    1. If `run_name_factory` argument is set, it will be used as the run name.\n    2. The default run name factory is used.\n\n    \"\"\"\n    if run_name_factory is not None:\n        # Adjust the function signature of the run name factory\n        def _run_name_factory_adjusted(info: ExecInfo | None, random_str: str, timestamp: datetime, /) -> str:\n            if isinstance(info, FuncInfo):\n                return run_name_factory(info, random_str, timestamp)\n            raise TypeError(\"The run name factory must accept the `FuncInfo` object as the first argument.\")\n    else:\n        _run_name_factory_adjusted = default_run_name_factory\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> Run[P, T]:\n        run_dto = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoNoPassPreRunCapsule, RunDtoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        run_dto.run_name_factory = _run_name_factory_adjusted\n        run_dto.vault_dir = Path(vault_dir) if vault_dir is not None else None\n\n        if not ignore_config:\n            config = load_config(get_default_config_path() if config_path is None else Path(config_path))\n            for phase in (\"pre\", \"in\", \"post\"):\n                phase_key = f\"{phase}-run\"\n                if phase_key not in config:\n                    continue\n                for context in reversed(config[phase_key].get(\"contexts\", [])):  # type: ignore[literal-required]\n                    assert phase in {\"pre\", \"post\"}, f\"Invalid phase for context: {phase}\"\n                    run_dto.add_context(context, mode=phase, append_left=True)  # type: ignore[arg-type]\n                for watcher in reversed(config[phase_key].get(\"watchers\", [])):  # type: ignore[literal-required]\n                    assert phase == \"in\", \"Watcher can only be added to the in-run phase.\"\n                    # No need to set append_left=True here, as watchers are added as the outermost context manager\n                    run_dto.add_watcher(watcher, append_left=False)\n                for reporter in reversed(config[phase_key].get(\"reporters\", [])):  # type: ignore[literal-required]\n                    assert phase in {\"pre\", \"in\", \"post\"}, f\"Invalid phase for reporter: {phase}\"\n                    run_dto.add_reporter(reporter, mode=phase, append_left=True)  # type: ignore[arg-type]\n\n            run_dto.vault_dir = config[\"vault-dir\"] if run_dto.vault_dir is None else run_dto.vault_dir\n\n        # Set the vault directory if it is not set by the config file\n        if run_dto.vault_dir is None:\n            assert run_dto.func is not None\n            project_root = search_for_project_root(Path(inspect.getfile(run_dto.func)))\n            run_dto.vault_dir = project_root / \"vault\"\n\n        return Run(run_dto)\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.watcher","title":"watcher","text":"
watcher(\n    watcher: (\n        WatcherBase | Callable[[CapsuleParams], WatcherBase]\n    )\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    RunDtoNoPassPreRunCapsule[P, T]\n    | RunDtoPassPreRunCapsule[P, T],\n]\n

Decorator to add a watcher to the in-run phase of the run.

Example:

import capsula\n\n@capsula.run()\n@capsula.watcher(capsula.UncaughtExceptionWatcher())\ndef func() -> None: ...\n

PARAMETER DESCRIPTION watcher

Watcher or a builder function to create a watcher from the capsule parameters.

TYPE: WatcherBase | Callable[[CapsuleParams], WatcherBase]

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]]

Decorator to add a watcher to the in-run phase of the run.

Source code in capsula/_decorator.py
def watcher(\n    watcher: Annotated[\n        WatcherBase | Callable[[CapsuleParams], WatcherBase],\n        Doc(\"Watcher or a builder function to create a watcher from the capsule parameters.\"),\n    ],\n) -> Annotated[\n    Callable[\n        [Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]],\n        RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ],\n    Doc(\"Decorator to add a watcher to the in-run phase of the run.\"),\n]:\n    \"\"\"Decorator to add a watcher to the in-run phase of the run.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.watcher(capsula.UncaughtExceptionWatcher())\n    def func() -> None: ...\n    ```\n\n    \"\"\"\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]:\n        run_dto = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoNoPassPreRunCapsule, RunDtoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        # No need to set append_left=True here, as watchers are added as the outermost context manager\n        run_dto.add_watcher(watcher, append_left=False)\n        return run_dto\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.Encapsulator","title":"Encapsulator","text":"
Encapsulator()\n
Source code in capsula/_encapsulator.py
def __init__(self) -> None:\n    self.contexts: OrderedDict[_CapsuleItemKey, ContextBase] = OrderedDict()\n    self.watchers: OrderedDict[_CapsuleItemKey, WatcherBase] = OrderedDict()\n
"},{"location":"reference/capsula/#capsula.Encapsulator.get_current","title":"get_current classmethod","text":"
get_current() -> Self | None\n
Source code in capsula/_encapsulator.py
@classmethod\ndef get_current(cls) -> Self | None:\n    try:\n        return cls._get_context_stack().queue[-1]\n    except IndexError:\n        return None\n
"},{"location":"reference/capsula/#capsula.Encapsulator.contexts","title":"contexts instance-attribute","text":"
contexts: OrderedDict[_CapsuleItemKey, ContextBase] = (\n    OrderedDict()\n)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.watchers","title":"watchers instance-attribute","text":"
watchers: OrderedDict[_CapsuleItemKey, WatcherBase] = (\n    OrderedDict()\n)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.__enter__","title":"__enter__","text":"
__enter__() -> Self\n
Source code in capsula/_encapsulator.py
def __enter__(self) -> Self:\n    self._get_context_stack().put(self)\n    return self\n
"},{"location":"reference/capsula/#capsula.Encapsulator.__exit__","title":"__exit__","text":"
__exit__(\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None\n
PARAMETER DESCRIPTION exc_type

TYPE: type[BaseException] | None

exc_value

TYPE: BaseException | None

traceback

TYPE: TracebackType | None

Source code in capsula/_encapsulator.py
def __exit__(\n    self,\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None:\n    self._get_context_stack().get(block=False)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.add_context","title":"add_context","text":"
add_context(\n    context: ContextBase, key: _CapsuleItemKey | None = None\n) -> None\n
PARAMETER DESCRIPTION context

TYPE: ContextBase

key

TYPE: _CapsuleItemKey | None DEFAULT: None

Source code in capsula/_encapsulator.py
def add_context(self, context: ContextBase, key: _CapsuleItemKey | None = None) -> None:\n    if key is None:\n        key = context.default_key()\n    if key in self.contexts or key in self.watchers:\n        raise KeyConflictError(key)\n    self.contexts[key] = context\n
"},{"location":"reference/capsula/#capsula.Encapsulator.record","title":"record","text":"
record(key: _CapsuleItemKey, record: Any) -> None\n
PARAMETER DESCRIPTION key

TYPE: _CapsuleItemKey

record

TYPE: Any

Source code in capsula/_encapsulator.py
def record(self, key: _CapsuleItemKey, record: Any) -> None:\n    self.add_context(ObjectContext(record), key)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.add_watcher","title":"add_watcher","text":"
add_watcher(\n    watcher: WatcherBase, key: _CapsuleItemKey | None = None\n) -> None\n
PARAMETER DESCRIPTION watcher

TYPE: WatcherBase

key

TYPE: _CapsuleItemKey | None DEFAULT: None

Source code in capsula/_encapsulator.py
def add_watcher(self, watcher: WatcherBase, key: _CapsuleItemKey | None = None) -> None:\n    if key is None:\n        key = watcher.default_key()\n    if key in self.contexts or key in self.watchers:\n        raise KeyConflictError(key)\n    self.watchers[key] = watcher\n
"},{"location":"reference/capsula/#capsula.Encapsulator.encapsulate","title":"encapsulate","text":"
encapsulate() -> Capsule\n
Source code in capsula/_encapsulator.py
def encapsulate(self) -> Capsule:\n    data = {}\n    fails = {}\n    for key, capsule_item in chain(self.contexts.items(), self.watchers.items()):\n        try:\n            data[key] = capsule_item.encapsulate()\n        except Exception as e:  # noqa: PERF203\n            if capsule_item.abort_on_error:\n                raise\n            warnings.warn(f\"Error occurred during encapsulation of {key}: {e}. Skipping.\", stacklevel=3)\n            fails[key] = ExceptionInfo.from_exception(e)\n    return Capsule(data, fails)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.watch","title":"watch","text":"
watch() -> WatcherGroup[_CapsuleItemKey, WatcherBase]\n
Source code in capsula/_encapsulator.py
def watch(self) -> WatcherGroup[_CapsuleItemKey, WatcherBase]:\n    return WatcherGroup(self.watchers)\n
"},{"location":"reference/capsula/#capsula.CapsulaConfigurationError","title":"CapsulaConfigurationError","text":"

Bases: CapsulaError

"},{"location":"reference/capsula/#capsula.CapsulaError","title":"CapsulaError","text":"

Bases: Exception

"},{"location":"reference/capsula/#capsula.CapsulaUninitializedError","title":"CapsulaUninitializedError","text":"
CapsulaUninitializedError(*uninitialized_names: str)\n

Bases: CapsulaError

PARAMETER DESCRIPTION *uninitialized_names

TYPE: str DEFAULT: ()

Source code in capsula/_exceptions.py
def __init__(self, *uninitialized_names: str) -> None:\n    super().__init__(f\"Uninitialized objects: {', '.join(uninitialized_names)}\")\n
"},{"location":"reference/capsula/#capsula.JsonDumpReporter","title":"JsonDumpReporter","text":"
JsonDumpReporter(\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True\n)\n

Bases: ReporterBase

Reporter to dump the capsule to a JSON file.

PARAMETER DESCRIPTION path

TYPE: Path | str

default

TYPE: Callable[[Any], Any] | None DEFAULT: None

option

TYPE: int | None DEFAULT: None

mkdir

TYPE: bool DEFAULT: True

Source code in capsula/_reporter/_json.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True,\n) -> None:\n    self._path = Path(path)\n    if mkdir:\n        self._path.parent.mkdir(parents=True, exist_ok=True)\n\n    if default is None:\n        self._default_for_encoder = default_preset\n    else:\n\n        def _default(obj: Any) -> Any:\n            try:\n                return default_preset(obj)\n            except TypeError:\n                return default(obj)\n\n        self._default_for_encoder = _default\n\n    self._option = option\n
"},{"location":"reference/capsula/#capsula.JsonDumpReporter.builder","title":"builder classmethod","text":"
builder(\n    *, option: int | None = None\n) -> Callable[[CapsuleParams], JsonDumpReporter]\n
PARAMETER DESCRIPTION option

Option to pass to orjson.dumps. If not provided, orjson.OPT_INDENT_2 will be used.

TYPE: int | None DEFAULT: None

Source code in capsula/_reporter/_json.py
@classmethod\ndef builder(\n    cls,\n    *,\n    option: Annotated[\n        int | None,\n        Doc(\"Option to pass to `orjson.dumps`. If not provided, `orjson.OPT_INDENT_2` will be used.\"),\n    ] = None,\n) -> Callable[[CapsuleParams], JsonDumpReporter]:\n    def build(params: CapsuleParams) -> JsonDumpReporter:\n        return cls(\n            params.run_dir / f\"{params.phase}-run-report.json\",\n            option=orjson.OPT_INDENT_2 if option is None else option,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.JsonDumpReporter.report","title":"report","text":"
report(capsule: Capsule) -> None\n
PARAMETER DESCRIPTION capsule

TYPE: Capsule

Source code in capsula/_reporter/_json.py
def report(self, capsule: Capsule) -> None:\n    logger.debug(f\"Dumping capsule to {self._path}\")\n\n    def _str_to_tuple(s: str | tuple[str, ...]) -> tuple[str, ...]:\n        if isinstance(s, str):\n            return (s,)\n        return s\n\n    nested_data = to_nested_dict({_str_to_tuple(k): v for k, v in capsule.data.items()})\n    if capsule.fails:\n        nested_data[\"__fails\"] = to_nested_dict({_str_to_tuple(k): v for k, v in capsule.fails.items()})\n\n    json_bytes = orjson.dumps(nested_data, default=self._default_for_encoder, option=self._option)\n    self._path.write_bytes(json_bytes)\n
"},{"location":"reference/capsula/#capsula.ReporterBase","title":"ReporterBase","text":"

Bases: ABC

"},{"location":"reference/capsula/#capsula.ReporterBase.get_subclass","title":"get_subclass classmethod","text":"
get_subclass(name: str) -> type[ReporterBase]\n
PARAMETER DESCRIPTION name

TYPE: str

Source code in capsula/_reporter/_base.py
@classmethod\ndef get_subclass(cls, name: str) -> type[ReporterBase]:\n    return cls._subclass_registry[name]\n
"},{"location":"reference/capsula/#capsula.ReporterBase.report","title":"report abstractmethod","text":"
report(capsule: Capsule) -> None\n
PARAMETER DESCRIPTION capsule

TYPE: Capsule

Source code in capsula/_reporter/_base.py
@abstractmethod\ndef report(self, capsule: Capsule) -> None:\n    raise NotImplementedError\n
"},{"location":"reference/capsula/#capsula.ReporterBase.builder","title":"builder classmethod","text":"
builder(\n    *args: Any, **kwargs: Any\n) -> Callable[[CapsuleParams], Self]\n
PARAMETER DESCRIPTION *args

TYPE: Any DEFAULT: ()

**kwargs

TYPE: Any DEFAULT: {}

Source code in capsula/_reporter/_base.py
@classmethod\ndef builder(cls, *args: Any, **kwargs: Any) -> Callable[[CapsuleParams], Self]:\n    def build(params: CapsuleParams) -> Self:  # type: ignore[type-var,misc] # noqa: ARG001\n        return cls(*args, **kwargs)\n\n    return build\n
"},{"location":"reference/capsula/#capsula.SlackReporter","title":"SlackReporter","text":"
SlackReporter(\n    *,\n    phase: Literal[\"pre\", \"in\", \"post\"],\n    channel: str,\n    token: str,\n    run_name: str\n)\n

Bases: ReporterBase

Reporter to send a message to a Slack channel.

PARAMETER DESCRIPTION phase

TYPE: Literal['pre', 'in', 'post']

channel

TYPE: str

token

TYPE: str

run_name

TYPE: str

Source code in capsula/_reporter/_slack.py
def __init__(self, *, phase: Literal[\"pre\", \"in\", \"post\"], channel: str, token: str, run_name: str) -> None:\n    self._phase = phase\n    self._channel = channel\n    self._token = token\n    self._run_name = run_name\n
"},{"location":"reference/capsula/#capsula.SlackReporter.builder","title":"builder classmethod","text":"
builder(\n    *, channel: str, token: str\n) -> Callable[[CapsuleParams], SlackReporter]\n
PARAMETER DESCRIPTION channel

TYPE: str

token

TYPE: str

Source code in capsula/_reporter/_slack.py
@classmethod\ndef builder(\n    cls,\n    *,\n    channel: str,\n    token: str,\n) -> Callable[[CapsuleParams], SlackReporter]:\n    def build(params: CapsuleParams) -> SlackReporter:\n        return cls(phase=params.phase, channel=channel, token=token, run_name=params.run_name)\n\n    return build\n
"},{"location":"reference/capsula/#capsula.SlackReporter.report","title":"report","text":"
report(capsule: Capsule) -> None\n
PARAMETER DESCRIPTION capsule

TYPE: Capsule

Source code in capsula/_reporter/_slack.py
def report(self, capsule: Capsule) -> None:  # noqa: ARG002\n    client = WebClient(token=self._token)\n    thread_ts = SlackReporter._run_name_to_thread_ts.get(self._run_name)\n    if self._phase == \"pre\":\n        message = f\"Capsule run `{self._run_name}` started\"\n        response = client.chat_postMessage(channel=self._channel, text=message, thread_ts=thread_ts)\n        SlackReporter._run_name_to_thread_ts[self._run_name] = response[\"ts\"]\n    elif self._phase == \"in\":\n        pass  # Do nothing for now\n    elif self._phase == \"post\":\n        message = f\"Capsule run `{self._run_name}` completed\"\n        response = client.chat_postMessage(\n            channel=self._channel,\n            text=message,\n            thread_ts=thread_ts,\n        )\n        SlackReporter._run_name_to_thread_ts[self._run_name] = response[\"ts\"]\n
"},{"location":"reference/capsula/#capsula.current_run_name","title":"current_run_name","text":"
current_run_name() -> str\n

Get the name of the current run, which is also the name of the run directory in the vault directory.

RuntimeError will be raised if no active run is found.

Source code in capsula/_root.py
def current_run_name() -> str:\n    \"\"\"Get the name of the current run, which is also the name of the run directory in the `vault` directory.\n\n    `RuntimeError` will be raised if no active run is found.\n    \"\"\"\n    run: Run[Any, Any] | None = Run.get_current()\n    if run is None:\n        msg = \"No active run found.\"\n        raise RuntimeError(msg)\n    return run.run_dir.name\n
"},{"location":"reference/capsula/#capsula.record","title":"record","text":"
record(key: _CapsuleItemKey, value: Any) -> None\n

Record a value to the current encapsulator.

See the example below:

import random\nimport capsula\n\n@capsula.run()\ndef calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n    random.seed(seed)\n    xs = (random.random() for _ in range(n_samples))\n    ys = (random.random() for _ in range(n_samples))\n    inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n    pi_estimate = (4.0 * inside) / n_samples\n\n    # You can record values to the capsule using the `record` method.\n    capsula.record(\"pi_estimate\", pi_estimate)\n\nif __name__ == \"__main__\":\n    calculate_pi(n_samples=1_000)\n

The in-run-report.json generated by the JsonDumpReporter will contain the following:

{\n\"pi_estimate\": 3.128\n}\n
PARAMETER DESCRIPTION key

The key to use for the value.

TYPE: _CapsuleItemKey

value

The value to record.

TYPE: Any

Source code in capsula/_root.py
def record(\n    key: Annotated[_CapsuleItemKey, Doc(\"The key to use for the value.\")],\n    value: Annotated[Any, Doc(\"The value to record.\")],\n) -> None:\n    \"\"\"Record a value to the current encapsulator.\n\n    See the example below:\n\n    ```python\n    import random\n    import capsula\n\n    @capsula.run()\n    def calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n        random.seed(seed)\n        xs = (random.random() for _ in range(n_samples))\n        ys = (random.random() for _ in range(n_samples))\n        inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n        pi_estimate = (4.0 * inside) / n_samples\n\n        # You can record values to the capsule using the `record` method.\n        capsula.record(\"pi_estimate\", pi_estimate)\n\n    if __name__ == \"__main__\":\n        calculate_pi(n_samples=1_000)\n    ```\n\n    The `in-run-report.json` generated by the `JsonDumpReporter` will contain the following:\n\n    ```json\n    {\n    \"pi_estimate\": 3.128\n    }\n    ```\n    \"\"\"\n    enc = Encapsulator.get_current()\n    if enc is None:\n        msg = \"No active encapsulator found.\"\n        raise RuntimeError(msg)\n    enc.record(key, value)\n
"},{"location":"reference/capsula/#capsula.CapsuleParams","title":"CapsuleParams dataclass","text":"
CapsuleParams(\n    exec_info: FuncInfo | CommandInfo | None,\n    run_name: str,\n    run_dir: Path,\n    phase: Literal[\"pre\", \"in\", \"post\"],\n    project_root: Path,\n)\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.exec_info","title":"exec_info instance-attribute","text":"
exec_info: FuncInfo | CommandInfo | None\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.run_name","title":"run_name instance-attribute","text":"
run_name: str\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.run_dir","title":"run_dir instance-attribute","text":"
run_dir: Path\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.phase","title":"phase instance-attribute","text":"
phase: Literal['pre', 'in', 'post']\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.project_root","title":"project_root instance-attribute","text":"
project_root: Path\n
"},{"location":"reference/capsula/#capsula.CommandInfo","title":"CommandInfo dataclass","text":"
CommandInfo(command: tuple[str, ...])\n
"},{"location":"reference/capsula/#capsula.CommandInfo.command","title":"command instance-attribute","text":"
command: tuple[str, ...]\n
"},{"location":"reference/capsula/#capsula.FuncInfo","title":"FuncInfo dataclass","text":"
FuncInfo(\n    func: Callable[..., Any],\n    args: tuple[Any, ...],\n    kwargs: dict[str, Any],\n    pass_pre_run_capsule: bool,\n)\n
"},{"location":"reference/capsula/#capsula.FuncInfo.func","title":"func instance-attribute","text":"
func: Callable[..., Any]\n
"},{"location":"reference/capsula/#capsula.FuncInfo.args","title":"args instance-attribute","text":"
args: tuple[Any, ...]\n
"},{"location":"reference/capsula/#capsula.FuncInfo.kwargs","title":"kwargs instance-attribute","text":"
kwargs: dict[str, Any]\n
"},{"location":"reference/capsula/#capsula.FuncInfo.pass_pre_run_capsule","title":"pass_pre_run_capsule instance-attribute","text":"
pass_pre_run_capsule: bool\n
"},{"location":"reference/capsula/#capsula.FuncInfo.bound_args","title":"bound_args property","text":"
bound_args: OrderedDict[str, Any]\n
"},{"location":"reference/capsula/#capsula.Run","title":"Run","text":"
Run(\n    run_dto: (\n        RunDtoPassPreRunCapsule[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoCommand\n    )\n)\n

Bases: Generic[P, T]

PARAMETER DESCRIPTION run_dto

TYPE: RunDtoPassPreRunCapsule[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoCommand

Source code in capsula/_run.py
def __init__(\n    self,\n    run_dto: RunDtoPassPreRunCapsule[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoCommand,\n    /,\n) -> None:\n    self._pre_run_context_generators = run_dto.pre_run_context_generators\n    self._in_run_watcher_generators = run_dto.in_run_watcher_generators\n    self._post_run_context_generators = run_dto.post_run_context_generators\n\n    self._pre_run_reporter_generators = run_dto.pre_run_reporter_generators\n    self._in_run_reporter_generators = run_dto.in_run_reporter_generators\n    self._post_run_reporter_generators = run_dto.post_run_reporter_generators\n\n    self._pass_pre_run_capsule: bool = isinstance(run_dto, RunDtoPassPreRunCapsule)\n\n    if run_dto.run_name_factory is None:\n        raise CapsulaUninitializedError(\"run_name_factory\")\n    self._run_name_factory: Callable[[ExecInfo | None, str, datetime], str] = run_dto.run_name_factory\n\n    if run_dto.vault_dir is None:\n        raise CapsulaUninitializedError(\"vault_dir\")\n    self._vault_dir: Path = run_dto.vault_dir\n\n    self._run_dir: Path | None = None\n\n    if isinstance(run_dto, RunDtoCommand):\n        if run_dto.command is None:\n            raise CapsulaUninitializedError(\"command\")\n        self._func: Callable[P, T] | Callable[Concatenate[Capsule, P], T] | None = None\n        self._command: tuple[str, ...] | None = run_dto.command\n    elif isinstance(run_dto, (RunDtoPassPreRunCapsule, RunDtoNoPassPreRunCapsule)):\n        if run_dto.func is None:\n            raise CapsulaUninitializedError(\"func\")\n        self._func = run_dto.func\n        self._command = None\n    else:\n        msg = \"run_dto must be an instance of RunDtoCommand, RunDtoPassPreRunCapsule, or RunDtoNoPassPreRunCapsule,\"\n        \" not {type(run_dto)}.\"\n        raise TypeError(msg)\n
"},{"location":"reference/capsula/#capsula.Run.get_current","title":"get_current classmethod","text":"
get_current() -> Self\n
Source code in capsula/_run.py
@classmethod\ndef get_current(cls) -> Self:\n    try:\n        return cls._get_run_stack().queue[-1]\n    except IndexError as e:\n        raise CapsulaNoRunError from e\n
"},{"location":"reference/capsula/#capsula.Run.run_dir","title":"run_dir property","text":"
run_dir: Path\n
"},{"location":"reference/capsula/#capsula.Run.__enter__","title":"__enter__","text":"
__enter__() -> Self\n
Source code in capsula/_run.py
def __enter__(self) -> Self:\n    self._get_run_stack().put(self)\n    return self\n
"},{"location":"reference/capsula/#capsula.Run.__exit__","title":"__exit__","text":"
__exit__(\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None\n
PARAMETER DESCRIPTION exc_type

TYPE: type[BaseException] | None

exc_value

TYPE: BaseException | None

traceback

TYPE: TracebackType | None

Source code in capsula/_run.py
def __exit__(\n    self,\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None:\n    self._get_run_stack().get(block=False)\n
"},{"location":"reference/capsula/#capsula.Run.pre_run","title":"pre_run","text":"
pre_run(\n    exec_info: ExecInfo,\n) -> tuple[CapsuleParams, Capsule]\n
PARAMETER DESCRIPTION exec_info

TYPE: ExecInfo

Source code in capsula/_run.py
def pre_run(self, exec_info: ExecInfo) -> tuple[CapsuleParams, Capsule]:\n    if self._vault_dir.exists():\n        if not self._vault_dir.is_dir():\n            msg = f\"Vault directory {self._vault_dir} exists but is not a directory.\"\n            raise CapsulaError(msg)\n    else:\n        self._vault_dir.mkdir(parents=True, exist_ok=False)\n        # If this is a new vault directory, create a .gitignore file in it\n        # and write \"*\" to it\n        gitignore_path = self._vault_dir / \".gitignore\"\n        with gitignore_path.open(\"w\") as gitignore_file:\n            gitignore_file.write(\"*\\n\")\n    logger.info(f\"Vault directory: {self._vault_dir}\")\n\n    # Generate the run name\n    run_name = self._run_name_factory(\n        exec_info,\n        \"\".join(choices(ascii_letters + digits, k=4)),\n        datetime.now(timezone.utc),\n    )\n    logger.info(f\"Run name: {run_name}\")\n\n    self._run_dir = self._vault_dir / run_name\n    try:\n        self._run_dir.mkdir(parents=True, exist_ok=False)\n    except FileExistsError:\n        logger.exception(\n            f\"Run directory {self._run_dir} already exists. Aborting to prevent overwriting existing data. \"\n            \"Make sure that run_name_factory produces unique names.\",\n        )\n        raise\n    logger.info(f\"Run directory: {self._run_dir}\")\n\n    params = CapsuleParams(\n        exec_info=exec_info,\n        run_name=run_name,\n        run_dir=self._run_dir,\n        phase=\"pre\",\n        project_root=get_project_root(exec_info),\n    )\n\n    pre_run_enc = Encapsulator()\n    for context_generator in self._pre_run_context_generators:\n        context = context_generator(params)\n        pre_run_enc.add_context(context)\n    pre_run_capsule = pre_run_enc.encapsulate()\n    for reporter_generator in self._pre_run_reporter_generators:\n        reporter = reporter_generator(params)\n        reporter.report(pre_run_capsule)\n\n    return params, pre_run_capsule\n
"},{"location":"reference/capsula/#capsula.Run.post_run","title":"post_run","text":"
post_run(params: CapsuleParams) -> Capsule\n
PARAMETER DESCRIPTION params

TYPE: CapsuleParams

Source code in capsula/_run.py
def post_run(self, params: CapsuleParams) -> Capsule:\n    params.phase = \"post\"\n    post_run_enc = Encapsulator()\n    for context_generator in self._post_run_context_generators:\n        context = context_generator(params)\n        post_run_enc.add_context(context)\n    post_run_capsule = post_run_enc.encapsulate()\n    for reporter_generator in self._post_run_reporter_generators:\n        reporter = reporter_generator(params)\n        try:\n            reporter.report(post_run_capsule)\n        except Exception:\n            logger.exception(f\"Failed to report post-run capsule with reporter {reporter}.\")\n\n    return post_run_capsule\n
"},{"location":"reference/capsula/#capsula.Run.in_run","title":"in_run","text":"
in_run(params: CapsuleParams, func: Callable[[], _T]) -> _T\n
PARAMETER DESCRIPTION params

TYPE: CapsuleParams

func

TYPE: Callable[[], _T]

Source code in capsula/_run.py
def in_run(self, params: CapsuleParams, func: Callable[[], _T]) -> _T:\n    params.phase = \"in\"\n    in_run_enc = Encapsulator()\n    for watcher_generator in self._in_run_watcher_generators:\n        watcher = watcher_generator(params)\n        in_run_enc.add_watcher(watcher)\n\n    with self, in_run_enc, in_run_enc.watch():\n        result = func()\n\n    in_run_capsule = in_run_enc.encapsulate()\n    for reporter_generator in self._in_run_reporter_generators:\n        reporter = reporter_generator(params)\n        try:\n            reporter.report(in_run_capsule)\n        except Exception:\n            logger.exception(f\"Failed to report in-run capsule with reporter {reporter}.\")\n\n    return result\n
"},{"location":"reference/capsula/#capsula.Run.__call__","title":"__call__","text":"
__call__(*args: args, **kwargs: kwargs) -> T\n
PARAMETER DESCRIPTION *args

TYPE: args DEFAULT: ()

**kwargs

TYPE: kwargs DEFAULT: {}

Source code in capsula/_run.py
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:\n    assert self._func is not None\n    func_info = FuncInfo(func=self._func, args=args, kwargs=kwargs, pass_pre_run_capsule=self._pass_pre_run_capsule)\n    params, pre_run_capsule = self.pre_run(func_info)\n\n    if self._pass_pre_run_capsule:\n\n        def _func_1() -> T:\n            assert self._func is not None\n            return self._func(pre_run_capsule, *args, **kwargs)  # type: ignore[arg-type]\n\n        func = _func_1\n    else:\n\n        def _func_2() -> T:\n            assert self._func is not None\n            return self._func(*args, **kwargs)  # type: ignore[arg-type]\n\n        func = _func_2\n\n    try:\n        result = self.in_run(params, func)\n    finally:\n        _post_run_capsule = self.post_run(params)\n\n    return result\n
"},{"location":"reference/capsula/#capsula.Run.exec_command","title":"exec_command","text":"
exec_command() -> (\n    tuple[CompletedProcess[str], CapsuleParams]\n)\n
Source code in capsula/_run.py
def exec_command(self) -> tuple[subprocess.CompletedProcess[str], CapsuleParams]:\n    assert self._command is not None\n    command_info = CommandInfo(command=self._command)\n    params, _pre_run_capsule = self.pre_run(command_info)\n\n    def func() -> subprocess.CompletedProcess[str]:\n        assert self._command is not None\n        return subprocess.run(self._command, check=False, capture_output=True, text=True)  # noqa: S603\n\n    try:\n        result = self.in_run(params, func)\n    finally:\n        _post_run_capsule = self.post_run(params)\n\n    return result, params\n
"},{"location":"reference/capsula/#capsula.search_for_project_root","title":"search_for_project_root","text":"
search_for_project_root(start: Path | str) -> Path\n

Search for the project root directory by looking for pyproject.toml.

PARAMETER DESCRIPTION start

The start directory to search.

TYPE: Path | str

RETURNS DESCRIPTION Path

The project root directory.

Source code in capsula/_utils.py
def search_for_project_root(\n    start: Annotated[Path | str, Doc(\"The start directory to search.\")],\n) -> Annotated[Path, Doc(\"The project root directory.\")]:\n    \"\"\"Search for the project root directory by looking for pyproject.toml.\"\"\"\n    # TODO: Allow projects without pyproject.toml file\n    start = Path(start)\n    if (start / \"pyproject.toml\").exists():\n        return start\n    if start == start.parent:\n        msg = \"Project root not found.\"\n        raise FileNotFoundError(msg)\n    return search_for_project_root(start.resolve().parent)\n
"},{"location":"reference/capsula/#capsula.__version__","title":"__version__ module-attribute","text":"
__version__: str = version('capsula')\n

Capsula version.

"},{"location":"reference/capsula/#capsula.TimeWatcher","title":"TimeWatcher","text":"
TimeWatcher(name: str = 'execution_time')\n

Bases: WatcherBase

PARAMETER DESCRIPTION name

Name of the time watcher. Used as a key in the output.

TYPE: str DEFAULT: 'execution_time'

Source code in capsula/_watcher/_time.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the time watcher. Used as a key in the output.\")] = \"execution_time\",\n) -> None:\n    self._name = name\n    self._duration: timedelta | None = None\n
"},{"location":"reference/capsula/#capsula.TimeWatcher.encapsulate","title":"encapsulate","text":"
encapsulate() -> timedelta | None\n
Source code in capsula/_watcher/_time.py
def encapsulate(self) -> timedelta | None:\n    return self._duration\n
"},{"location":"reference/capsula/#capsula.TimeWatcher.watch","title":"watch","text":"
watch() -> Iterator[None]\n
Source code in capsula/_watcher/_time.py
@contextmanager\ndef watch(self) -> Iterator[None]:\n    start = time.perf_counter()\n    try:\n        yield\n    finally:\n        end = time.perf_counter()\n        self._duration = timedelta(seconds=end - start)\n        logger.debug(f\"TimeWatcher: {self._name} took {self._duration}.\")\n
"},{"location":"reference/capsula/#capsula.TimeWatcher.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_watcher/_time.py
def default_key(self) -> tuple[str, str]:\n    return (\"time\", self._name)\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher","title":"UncaughtExceptionWatcher","text":"
UncaughtExceptionWatcher(\n    name: str = \"exception\",\n    *,\n    base: type[BaseException] = Exception\n)\n

Bases: WatcherBase

Watcher to capture an uncaught exception.

This watcher captures an uncaught exception and stores it in the context. Note that it does not consume the exception, so it will still be raised.

PARAMETER DESCRIPTION name

Name of the exception. Used as a key in the output.

TYPE: str DEFAULT: 'exception'

base

Base exception class to catch.

TYPE: type[BaseException] DEFAULT: Exception

Source code in capsula/_watcher/_exception.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the exception. Used as a key in the output.\")] = \"exception\",\n    *,\n    base: Annotated[type[BaseException], Doc(\"Base exception class to catch.\")] = Exception,\n) -> None:\n    self._name = name\n    self._base = base\n    self._exception: BaseException | None = None\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher.encapsulate","title":"encapsulate","text":"
encapsulate() -> ExceptionInfo\n
Source code in capsula/_watcher/_exception.py
def encapsulate(self) -> ExceptionInfo:\n    return ExceptionInfo.from_exception(self._exception)\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher.watch","title":"watch","text":"
watch() -> Iterator[None]\n
Source code in capsula/_watcher/_exception.py
@contextmanager\ndef watch(self) -> Iterator[None]:\n    self._exception = None\n    try:\n        yield\n    except self._base as e:\n        logger.debug(f\"UncaughtExceptionWatcher: {self._name} observed exception: {e}\")\n        self._exception = e\n        raise\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_watcher/_exception.py
def default_key(self) -> tuple[str, str]:\n    return (\"exception\", self._name)\n
"},{"location":"reference/capsula/#capsula.WatcherBase","title":"WatcherBase","text":"

Bases: CapsuleItem, ABC

"},{"location":"reference/capsula/#capsula.WatcherBase.abort_on_error","title":"abort_on_error property","text":"
abort_on_error: bool\n
"},{"location":"reference/capsula/#capsula.WatcherBase.get_subclass","title":"get_subclass classmethod","text":"
get_subclass(name: str) -> type[WatcherBase]\n
PARAMETER DESCRIPTION name

TYPE: str

Source code in capsula/_watcher/_base.py
@classmethod\ndef get_subclass(cls, name: str) -> type[WatcherBase]:\n    return cls._subclass_registry[name]\n
"},{"location":"reference/capsula/#capsula.WatcherBase.watch","title":"watch abstractmethod","text":"
watch() -> AbstractContextManager[None]\n
Source code in capsula/_watcher/_base.py
@abstractmethod\ndef watch(self) -> AbstractContextManager[None]:\n    raise NotImplementedError\n
"},{"location":"reporters/","title":"Built-in reporters","text":"

Capsula provides several built-in reporters that report the captured contexts.

"},{"location":"reporters/json_dump/","title":"JsonDumpReporter","text":"

The JsonDumpReporter reports the capsule in JSON format. It can be created using the capsula.JsonDumpReporter.builder method or the capsula.JsonDumpReporter.__init__ method.

"},{"location":"reporters/json_dump/#capsula.JsonDumpReporter.builder","title":"capsula.JsonDumpReporter.builder classmethod","text":"
builder(\n    *, option: int | None = None\n) -> Callable[[CapsuleParams], JsonDumpReporter]\n
PARAMETER DESCRIPTION option

Option to pass to orjson.dumps. If not provided, orjson.OPT_INDENT_2 will be used.

TYPE: int | None DEFAULT: None

Source code in capsula/_reporter/_json.py
@classmethod\ndef builder(\n    cls,\n    *,\n    option: Annotated[\n        int | None,\n        Doc(\"Option to pass to `orjson.dumps`. If not provided, `orjson.OPT_INDENT_2` will be used.\"),\n    ] = None,\n) -> Callable[[CapsuleParams], JsonDumpReporter]:\n    def build(params: CapsuleParams) -> JsonDumpReporter:\n        return cls(\n            params.run_dir / f\"{params.phase}-run-report.json\",\n            option=orjson.OPT_INDENT_2 if option is None else option,\n        )\n\n    return build\n
"},{"location":"reporters/json_dump/#capsula.JsonDumpReporter.__init__","title":"capsula.JsonDumpReporter.__init__","text":"
__init__(\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True\n)\n
PARAMETER DESCRIPTION path

TYPE: Path | str

default

TYPE: Callable[[Any], Any] | None DEFAULT: None

option

TYPE: int | None DEFAULT: None

mkdir

TYPE: bool DEFAULT: True

Source code in capsula/_reporter/_json.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True,\n) -> None:\n    self._path = Path(path)\n    if mkdir:\n        self._path.parent.mkdir(parents=True, exist_ok=True)\n\n    if default is None:\n        self._default_for_encoder = default_preset\n    else:\n\n        def _default(obj: Any) -> Any:\n            try:\n                return default_preset(obj)\n            except TypeError:\n                return default(obj)\n\n        self._default_for_encoder = _default\n\n    self._option = option\n
"},{"location":"reporters/json_dump/#configuration-example","title":"Configuration example","text":""},{"location":"reporters/json_dump/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[in-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[post-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n
"},{"location":"reporters/json_dump/#via-capsulareporter-decorator","title":"Via @capsula.reporter decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.reporter(capsula.JsonDumpReporter.builder(), mode=\"all\")\ndef func(): ...\n
"},{"location":"reporters/json_dump/#output","title":"Output","text":"

It will output the pre-run, in-run, and post-run capsules to in-run-report.json, pre-run-report.json, and post-run-report.json in the run directory, respectively.

"},{"location":"reporters/slack/","title":"SlackReporter","text":"

Note

The SlackReporter is still in development and lacks some features, such as specifying the token via environment variables, file attachments, detailed reporting, and reporting of the in-run capsule.

The SlackReporter reports the capsule to a Slack channel. Capsules with the same run name will be reported to the same thread.

It can be created using the capsula.SlackReporter.builder method or the capsula.SlackReporter.__init__ method.

"},{"location":"reporters/slack/#capsula.SlackReporter.builder","title":"capsula.SlackReporter.builder classmethod","text":"
builder(\n    *, channel: str, token: str\n) -> Callable[[CapsuleParams], SlackReporter]\n
PARAMETER DESCRIPTION channel

TYPE: str

token

TYPE: str

Source code in capsula/_reporter/_slack.py
@classmethod\ndef builder(\n    cls,\n    *,\n    channel: str,\n    token: str,\n) -> Callable[[CapsuleParams], SlackReporter]:\n    def build(params: CapsuleParams) -> SlackReporter:\n        return cls(phase=params.phase, channel=channel, token=token, run_name=params.run_name)\n\n    return build\n
"},{"location":"reporters/slack/#capsula.SlackReporter.__init__","title":"capsula.SlackReporter.__init__","text":"
__init__(\n    *,\n    phase: Literal[\"pre\", \"in\", \"post\"],\n    channel: str,\n    token: str,\n    run_name: str\n)\n
PARAMETER DESCRIPTION phase

TYPE: Literal['pre', 'in', 'post']

channel

TYPE: str

token

TYPE: str

run_name

TYPE: str

Source code in capsula/_reporter/_slack.py
def __init__(self, *, phase: Literal[\"pre\", \"in\", \"post\"], channel: str, token: str, run_name: str) -> None:\n    self._phase = phase\n    self._channel = channel\n    self._token = token\n    self._run_name = run_name\n
"},{"location":"reporters/slack/#configuration-example","title":"Configuration example","text":""},{"location":"reporters/slack/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\nreporters = [{ type = \"SlackReporter\", channel = \"<channel>\", token = \"<token>\" }]\n\n[in-run] # the reporting of an in-run capsule is not yet supported\nreporters = [{ type = \"SlackReporter\", channel = \"<channel>\", token = \"<token>\" }]\n\n[post-run]\nreporters = [{ type = \"SlackReporter\", channel = \"<channel>\", token = \"<token>\" }]\n
"},{"location":"reporters/slack/#via-capsulareporter-decorator","title":"Via @capsula.reporter decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.reporter(capsula.SlackReporter.builder(channel=\"<channel>\", token=\"<token>\"), mode=\"all\")\ndef func(): ...\n
"},{"location":"reporters/slack/#output","title":"Output","text":"

It will send a simple message to the specified Slack channel.

For example, for the pre-run capsule:

Capsule run `calculate_pi_n_samples_1000_seed_42_20240923_195220_Tplk` started\n

For the post-run capsule:

Capsule run `calculate_pi_n_samples_1000_seed_42_20240923_195356_GNDq` completed\n
"},{"location":"watchers/","title":"Built-in watchers","text":"

Capsula provides several built-in watchers that you can use to monitor the execution of your command/function. The following is a list of built-in watchers:

"},{"location":"watchers/time/","title":"TimeWatcher","text":"

The TimeWatcher monitors the execution time of the command/function. It can be created using the capsula.TimeWatcher.__init__ method.

"},{"location":"watchers/time/#capsula.TimeWatcher.__init__","title":"capsula.TimeWatcher.__init__","text":"
__init__(name: str = 'execution_time')\n
PARAMETER DESCRIPTION name

Name of the time watcher. Used as a key in the output.

TYPE: str DEFAULT: 'execution_time'

Source code in capsula/_watcher/_time.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the time watcher. Used as a key in the output.\")] = \"execution_time\",\n) -> None:\n    self._name = name\n    self._duration: timedelta | None = None\n
"},{"location":"watchers/time/#configuration-example","title":"Configuration example","text":""},{"location":"watchers/time/#via-capsulatoml","title":"Via capsula.toml","text":"
[in-run]\nwatchers = [\n  { type = \"TimeWatcher\" },\n]\n
"},{"location":"watchers/time/#via-capsulawatcher-decorator","title":"Via @capsula.watcher decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.watcher(capsula.TimeWatcher(\"calculation_time\"))\ndef func(): ...\n
"},{"location":"watchers/time/#output-example","title":"Output example","text":"

The following is an example of the output of the TimeWatcher, reported by the JsonDumpReporter:

\"time\": {\n  \"calculation_time\": \"0:00:00.000798\"\n}\n
"},{"location":"watchers/uncaught_exception/","title":"UncaughtExceptionWatcher","text":"

The UncaughtExceptionWatcher monitors uncaught exceptions in the command/function. It can be created using the capsula.UncaughtExceptionWatcher.__init__ method.

"},{"location":"watchers/uncaught_exception/#capsula.UncaughtExceptionWatcher.__init__","title":"capsula.UncaughtExceptionWatcher.__init__","text":"
__init__(\n    name: str = \"exception\",\n    *,\n    base: type[BaseException] = Exception\n)\n
PARAMETER DESCRIPTION name

Name of the exception. Used as a key in the output.

TYPE: str DEFAULT: 'exception'

base

Base exception class to catch.

TYPE: type[BaseException] DEFAULT: Exception

Source code in capsula/_watcher/_exception.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the exception. Used as a key in the output.\")] = \"exception\",\n    *,\n    base: Annotated[type[BaseException], Doc(\"Base exception class to catch.\")] = Exception,\n) -> None:\n    self._name = name\n    self._base = base\n    self._exception: BaseException | None = None\n
"},{"location":"watchers/uncaught_exception/#configuration-example","title":"Configuration example","text":""},{"location":"watchers/uncaught_exception/#via-capsulatoml","title":"Via capsula.toml","text":"
[in-run]\nwatchers = [\n  { type = \"UncaughtExceptionWatcher\" },\n]\n
"},{"location":"watchers/uncaught_exception/#via-capsulawatcher-decorator","title":"Via @capsula.watcher decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.watcher(capsula.UncaughtExceptionWatcher(\"exception\"))\ndef func(): ...\n
"},{"location":"watchers/uncaught_exception/#output-example","title":"Output example","text":"

The following is an example of the output of the UncaughtExceptionWatcher, reported by the JsonDumpReporter:

\"exception\": {\n  \"exception\": {\n    \"exc_type\": null,\n    \"exc_value\": null,\n    \"traceback\": null\n  }\n}\n

If an exception is caught, the following is an example of the output:

\"exception\": {\n  \"exception\": {\n    \"exc_type\": \"ZeroDivisionError\",\n    \"exc_value\": \"float division by zero\",\n    \"traceback\": \"  File \\\"/home/nomura/ghq/github.com/shunichironomura/capsula/capsula/_run.py\\\", line 288, in __call__\\n    result = self._func(*args, **kwargs)\\n  File \\\"examples/simple_decorator.py\\\", line 21, in calculate_pi\\n    x = 10.0 / 0.0\\n\"\n  }\n}\n
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Capsula","text":"

Capsula, a Latin word meaning box, is a Python package designed to help researchers and developers easily capture their command/function execution context for reproducibility.

With Capsula, you can capture:

The captured contexts are dumped into JSON files for future reference and reproduction.

"},{"location":"#usage-example","title":"Usage example","text":"

For project-wide settings, prepare a capsula.toml file in the root directory of your project. An example of the capsula.toml file is as follows:

[pre-run]\ncontexts = [\n    { type = \"CwdContext\" },\n    { type = \"CpuContext\" },\n    { type = \"PlatformContext\" },\n    { type = \"GitRepositoryContext\", name = \"capsula\", path = \".\", path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv lock --locked\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"pyproject.toml\", copy = true, path_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"uv.lock\", copy = true, path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv export > requirements.txt\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"requirements.txt\", move = true, path_relative_to_project_root = true },\n    { type = \"EnvVarContext\", name = \"HOME\" },\n]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[in-run]\nwatchers = [{ type = \"UncaughtExceptionWatcher\" }, { type = \"TimeWatcher\" }]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[post-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n

Then, all you need to do is decorate your Python function with the @capsula.run() decorator. You can also use the @capsula.context() decorator to add a context specific to the function.

The following is an example of a Python script that estimates the value of \u03c0 using the Monte Carlo method:

import random\nimport capsula\n\n@capsula.run()\n@capsula.context(capsula.FunctionContext.builder(), mode=\"pre\")\n@capsula.context(capsula.FileContext.builder(\"pi.txt\", move=True), mode=\"post\")\ndef calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n    random.seed(seed)\n    xs = (random.random() for _ in range(n_samples))\n    ys = (random.random() for _ in range(n_samples))\n    inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n\n    # You can record values to the capsule using the `record` method.\n    capsula.record(\"inside\", inside)\n\n    pi_estimate = (4.0 * inside) / n_samples\n    print(f\"Pi estimate: {pi_estimate}\")\n    capsula.record(\"pi_estimate\", pi_estimate)\n    print(f\"Run name: {capsula.current_run_name()}\")\n\n    with open(\"pi.txt\", \"w\") as output_file:\n        output_file.write(f\"Pi estimate: {pi_estimate}.\")\n\nif __name__ == \"__main__\":\n    calculate_pi(n_samples=1_000)\n

After running the script, a directory (calculate_pi_20240913_194900_2lxL in this example) will be created under the <project-root>/vault directory, and you will find the output files in the directory:

$ tree vault/calculate_pi_20240913_194900_2lxL\nvault/calculate_pi_20240913_194900_2lxL\n\u251c\u2500\u2500 in-run-report.json    # Generated by the `JsonDumpReporter` in `capsula.toml` (`in-run` section)\n\u251c\u2500\u2500 pi.txt                # Moved by the `FileContext` specified with the decorator in the script\n\u251c\u2500\u2500 uv.lock           # Copied by the `FileContext` specified in `capsula.toml` (`pre-run` section)\n\u251c\u2500\u2500 post-run-report.json  # Generated by the `JsonDumpReporter` in `capsula.toml` (`post-run` section)\n\u251c\u2500\u2500 pre-run-report.json   # Generated by the `JsonDumpReporter` in `capsula.toml` (`pre-run` section)\n\u251c\u2500\u2500 pyproject.toml        # Copied by the `FileContext` specified in `capsula.toml` (`pre-run` section)\n\u2514\u2500\u2500 requirements.txt      # Moved by the `FileContext` specified in `capsula.toml` (`pre-run` section)\n

The contents of the JSON files are as follows:

Example of output pre-run-report.json:
{\n  \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n  \"cpu\": {\n    \"python_version\": \"3.8.20.final.0 (64 bit)\",\n    \"cpuinfo_version\": [\n      9,\n      0,\n      0\n    ],\n    \"cpuinfo_version_string\": \"9.0.0\",\n    \"arch\": \"ARM_8\",\n    \"bits\": 64,\n    \"count\": 16,\n    \"arch_string_raw\": \"arm64\",\n    \"brand_raw\": \"Apple M3 Max\"\n  },\n  \"platform\": {\n    \"machine\": \"arm64\",\n    \"node\": \"MacBook-Pro.local\",\n    \"platform\": \"macOS-14.6.1-arm64-arm-64bit\",\n    \"release\": \"23.6.0\",\n    \"version\": \"Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:46 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T6031\",\n    \"system\": \"Darwin\",\n    \"processor\": \"arm\",\n    \"python\": {\n      \"executable_architecture\": {\n        \"bits\": \"64bit\",\n        \"linkage\": \"\"\n      },\n      \"build_no\": \"default\",\n      \"build_date\": \"Sep  9 2024 22:25:40\",\n      \"compiler\": \"Clang 18.1.8 \",\n      \"branch\": \"\",\n      \"implementation\": \"CPython\",\n      \"version\": \"3.8.20\"\n    }\n  },\n  \"git\": {\n    \"capsula\": {\n      \"working_dir\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n      \"sha\": \"4ff5b9b9e5f6b527b0c2c660a5cb1a12937599b5\",\n      \"remotes\": {\n        \"origin\": \"ssh://git@github.com/shunichironomura/capsula.git\"\n      },\n      \"branch\": \"gitbutler/workspace\",\n      \"is_dirty\": true,\n      \"diff_file\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL/capsula.diff\"\n    }\n  },\n  \"command\": {\n    \"uv lock --locked\": {\n      \"command\": \"uv lock --locked\",\n      \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n      \"returncode\": 0,\n      \"stdout\": \"\",\n      \"stderr\": \"Resolved 73 packages in 0.35ms\\n\"\n    },\n    \"uv export > requirements.txt\": {\n      \"command\": \"uv export > requirements.txt\",\n      \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n      \"returncode\": 0,\n      \"stdout\": \"\",\n      \"stderr\": \"Resolved 73 packages in 0.32ms\\n\"\n    }\n  },\n  \"file\": {\n    \"/Users/nomura/ghq/github.com/shunichironomura/capsula/pyproject.toml\": {\n      \"copied_to\": [\n        \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL/pyproject.toml\"\n      ],\n      \"moved_to\": null,\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"e331c7998167d64e4e90c9f2aa2c2fe9c9c3afe1cf8348f1d61998042b75040a\"\n      }\n    },\n    \"/Users/nomura/ghq/github.com/shunichironomura/capsula/uv.lock\": {\n      \"copied_to\": [\n        \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL/uv.lock\"\n      ],\n      \"moved_to\": null,\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"62e5b7a5125778dd664ee2dc0cb3c10640d15db3e55b40240c4d652f8afe40fe\"\n      }\n    },\n    \"/Users/nomura/ghq/github.com/shunichironomura/capsula/requirements.txt\": {\n      \"copied_to\": [],\n      \"moved_to\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL\",\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"3ba457abcefb0010a7b350e8a2567b8ac890726608b99ce85defbb5d06e197de\"\n      }\n    }\n  },\n  \"env\": {\n    \"HOME\": \"/Users/nomura\"\n  },\n  \"function\": {\n    \"calculate_pi\": {\n      \"file_path\": \"examples/simple_decorator.py\",\n      \"first_line_no\": 15,\n      \"bound_args\": {\n        \"n_samples\": 1000,\n        \"seed\": 42\n      }\n    }\n  }\n}
Example of output in-run-report.json:
{\n  \"inside\": 782,\n  \"pi_estimate\": 3.128,\n  \"time\": {\n    \"execution_time\": \"0:00:00.000271\"\n  },\n  \"exception\": {\n    \"exception\": {\n      \"exc_type\": null,\n      \"exc_value\": null,\n      \"traceback\": null\n    }\n  }\n}
Example of output post-run-report.json:
{\n  \"file\": {\n    \"pi.txt\": {\n      \"copied_to\": [],\n      \"moved_to\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula/vault/calculate_pi_20240913_194900_2lxL\",\n      \"hash\": {\n        \"algorithm\": \"sha256\",\n        \"digest\": \"a64c761cb6b6f9ef1bc1f6afa6ba44d796c5c51d14df0bdc9d3ab9ced7982a74\"\n      }\n    }\n  }\n}
"},{"location":"#installation","title":"Installation","text":"

You can install Capsula via pip:

pip install capsula\n

Or via conda:

conda install conda-forge::capsula\n
"},{"location":"#licensing","title":"Licensing","text":"

This project is licensed under the terms of the MIT.

Additionally, this project includes code derived from the Python programming language, which is licensed under the Python Software Foundation License Version 2 (PSF-2.0). For details, see the LICENSE file.

"},{"location":"concepts/","title":"Concepts","text":""},{"location":"concepts/#context","title":"Context","text":"

A context encapsulates a piece of information at a specific point in time. It can be anything from the current working directory (CwdContext) to the output of a command (CommandContext). Contexts are used to capture the state of the environment in which a command/function is executed.

See the Contexts section for the list of all the built-in contexts.

"},{"location":"concepts/#watcher","title":"Watcher","text":"

A watcher encapsulates a piece of information by monitoring the execution of a command/function. It can be used to detect uncaught exceptions (UncaughtExceptionWatcher) or measure the execution time (TimeWatcher). Watchers are used to monitor the execution of a command/function.

See the Watchers section for more information.

"},{"location":"concepts/#encapsulator-and-capsule","title":"Encapsulator and Capsule","text":"

You register multiple contexts and/or watchers to an encapsulator. Then, the encapsulator encapsulates the information encapsulated by the contexts and/or watchers into a \"capsule\".

"},{"location":"concepts/#reporter","title":"Reporter","text":"

A reporter reports the capsule in a specific format. For example, the JsonDumpReporter reports the capsule in JSON format.

See the Reporters section for more information.

"},{"location":"concepts/#run","title":"Run","text":"

A run is a single execution of a command/function. It has three encapsulators: pre-run, in-run, and post-run.

Encapsulator When to encapsulate Allowed registrations Pre-run encapsulator Before the execution of the command/function. Contexts In-run encapsulator During the execution of the command/function. Watchers Post-run encapsulator After the execution of the command/function. Contexts

For each run, a run directory is created in the vault directory. This directory is used to store the files generated by contexts, watchers, and reporters.

"},{"location":"config/","title":"Configuration","text":""},{"location":"config/#capsulatoml-file","title":"capsula.toml file","text":"

For project-wide settings, prepare a capsula.toml file in the root directory of your project. An example of the capsula.toml file is as follows:

[pre-run]\ncontexts = [\n    { type = \"CwdContext\" },\n    { type = \"CpuContext\" },\n    { type = \"GitRepositoryContext\", name = \"capsula\", path = \".\", path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv lock --locked\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"pyproject.toml\", copy = true, path_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"uv.lock\", copy = true, path_relative_to_project_root = true },\n    { type = \"CommandContext\", command = \"uv export > requirements.txt\", cwd = \".\", cwd_relative_to_project_root = true },\n    { type = \"FileContext\", path = \"requirements.txt\", move = true, path_relative_to_project_root = true },\n]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[in-run]\nwatchers = [{ type = \"UncaughtExceptionWatcher\" }, { type = \"TimeWatcher\" }]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[post-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n

This configuration file specifies the contexts, watchers, and reporters to be used in the pre-run, in-run, and post-run encapsulators. The JsonDumpReporter is used to dump the captured contexts into JSON files.

For each context, watcher, or reporter, the type field specifies the class name of the context, watcher, or reporter. The other fields are used as the keyword arguments to the builder method of the class to create an instance of the class. If the class does not implement the builder method, the __init__ method is used instead.

"},{"location":"config/#decorators","title":"Decorators","text":"

For encapsulating the pre-run, in-run, and post-run capsules for a specific function, you can use the @capsula.run() decorator. You can also use the @capsula.context(), @capsula.watcher(), and @capsula.reporter() decorators to add a context, watcher, or reporter that is specific to the function.

The following is an example of a Python script that estimates the value of \u03c0 using the Monte Carlo method. The pi.txt file generated inside the function is encapsulated using the FileContext context in the post-run encapsulator.

import random\nimport capsula\n\n@capsula.run()\n# Register a `FileContext` to the post-run encapsulator.\n@capsula.context(capsula.FileContext.builder(\"pi.txt\", move=True), mode=\"post\")\ndef calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n    random.seed(seed)\n    xs = (random.random() for _ in range(n_samples))\n    ys = (random.random() for _ in range(n_samples))\n    inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n    pi_estimate = (4.0 * inside) / n_samples\n\n    with open(\"pi.txt\", \"w\") as output_file:\n        output_file.write(f\"Pi estimate: {pi_estimate}.\")\n\nif __name__ == \"__main__\":\n    calculate_pi(n_samples=1_000)\n
"},{"location":"config/#order-of-encapsulation","title":"Order of encapsulation","text":"

For each encapsulators, the order of encapsulation is as follows:

  1. Contexts, watchers, and reporters specified in the capsula.toml file, in the order of appearance (from top to bottom).
  2. Contexts, watchers, and reporters specified using the @capsula.context() and @capsula.watcher() decorators, in the order of appearance (from top to bottom).

Note

For watchers, the order of encapsulation here means the order of entering the watch context manager of the watcher. The order of exiting the watch context manager is the reverse of the order of entering the watch context manager.

"},{"location":"config/#builder-method-or-__init__-method","title":"builder method or __init__ method?","text":"

The reason for using the builder method instead of the __init__ method to create an instance of a context, watcher, or reporter is to use the runtime information, such as the run directory, to create the instance. This is why the configuration specified in the capsula.toml file by default uses the builder method to create instances of contexts, watchers, and reporters.

The builder method returns, instead of an instance of the class, a function that takes the runtime information (capsula.CapsuleParams) as an argument and returns an instance of the class.

"},{"location":"extending/","title":"Creating your own contexts, reporters, and watchers","text":"

You can extend Capsula by creating your own contexts, reporters, and watchers. You only need to create a class that inherits from the base class of the corresponding type.

"},{"location":"extending/#contexts","title":"Contexts","text":"

To create a context, you need to

Here's an example of a custom context that captures the current time:

import capsula\nfrom zoneinfo import ZoneInfo\nfrom datetime import datetime\n\nclass TimeContext(capsula.ContextBase):\n    def __init__(self, timezone: str = \"UTC\"):\n        self.timezone = ZoneInfo(timezone)\n\n    def encapsulate(self) -> dict[str, datetime]:\n        return {\"time\": datetime.now(self.timezone)}\n\n    def default_key(self) -> str:\n        return \"time\"\n

Note

The above example uses the zoneinfo module, which is available in Python 3.9 and later.

Optionally, you can implement the builder method that returns a function that creates the context. This is useful when you need to access the runtime information (capsula.CapsuleParams) when creating the context.

Here's an example of a custom context that captures the project root path:

import capsula\n\nclass ProjectRootContext(capsula.ContextBase):\n    def __init__(self, path: str | Path):\n        self.path = str(path)\n\n    def encapsulate(self) -> dict[str, str]:\n        return {\"project_root\": self.path}\n\n    def default_key(self) -> str:\n        return \"project_root\"\n\n    @classmethod\n    def builder(cls):\n        def build(params: capsula.CapsuleParams) -> \"ProjectRootContext\":\n            return ProjectRootContext(params.project_root)\n        return build\n
"},{"location":"extending/#watchers","title":"Watchers","text":"

To create a watcher, you need to

Here's an example of a custom watcher that watches the execution time of a command/function (from the implementation of the TimeWatcher class):

import capsula\nimport time\nfrom datetime import timedelta\nfrom collections.abc import Iterator\nfrom contextlib import contextmanager\n\nclass TimeWatcher(capsula.WatcherBase):\n    def __init__(\n        self,\n        name: str = \"execution_time\",\n    ) -> None:\n        self._name = name\n        self._duration: timedelta | None = None\n\n    def encapsulate(self) -> timedelta | None:\n        return self._duration\n\n    @contextmanager\n    def watch(self) -> Iterator[None]:\n        start = time.perf_counter()\n        try:\n            yield\n        finally:\n            end = time.perf_counter()\n            self._duration = timedelta(seconds=end - start)\n\n    def default_key(self) -> tuple[str, str]:\n        return (\"time\", self._name)\n

Like contexts, you can implement the builder method to create the watcher using the runtime information.

"},{"location":"extending/#reporters","title":"Reporters","text":"

To create a reporter, you need to

Here's an example of a custom reporter that dumps the capsule to a pickle file:

import capsula\nimport pickle\nfrom pathlib import Path\n\nclass PickleDumpReporter(capsula.ReporterBase):\n    def __init__(self, path: str | Path):\n        self.path = Path(path)\n\n    def report(self, capsule: capsula.Capsule) -> None:\n        with open(self.path, \"wb\") as file:\n            pickle.dump(capsule, file)\n\n    @classmethod\n    def builder(cls):\n        def build(params: capsula.CapsuleParams) -> \"PickleDumpReporter\":\n            return PickleDumpReporter(params.run_dir / \"capsule.pkl\")\n        return build\n

As shown in the example, you can implement the builder method to create the reporter using the runtime information.

"},{"location":"helpers/","title":"Helper Functions and Variables","text":"

Capsula provides several helper functions and variables:

"},{"location":"contexts/","title":"Built-in contexts","text":"

Capsula provides several built-in contexts that you can capture. The following is a list of built-in contexts:

"},{"location":"contexts/command/","title":"CommandContext","text":"

The CommandContext captures the output of shell commands. It can be created using the capsula.CommandContext.builder method or the capsula.CommandContext.__init__ method.

"},{"location":"contexts/command/#capsula.CommandContext.builder","title":"capsula.CommandContext.builder classmethod","text":"
builder(\n    command: str,\n    *,\n    cwd: Path | str | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    cwd_relative_to_project_root: bool = False,\n    shell: bool = True\n) -> Callable[[CapsuleParams], CommandContext]\n
PARAMETER DESCRIPTION command

Command to run

TYPE: str

cwd

Working directory for the command, passed to the cwd argument of subprocess.run

TYPE: Path | str | None DEFAULT: None

check

Whether to raise an exception if the command returns a non-zero exit code, passed to the check argument of `subprocess.run

TYPE: bool DEFAULT: True

abort_on_error

Whether to abort the encapsulation if the command returns a non-zero exit code

TYPE: bool DEFAULT: True

cwd_relative_to_project_root

Whether cwd argument is relative to the project root. Will be ignored if cwd is None or absolute. If True, it will be interpreted as relative to the project root. If False, cwd will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

shell

Whether to run the command using the shell. If True, the command will be run using the shell. If False, the command will be run directly. For more information, see the shell argument of subprocess.run.

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
@classmethod\ndef builder(\n    cls,\n    command: Annotated[str, Doc(\"Command to run\")],\n    *,\n    cwd: Annotated[\n        Path | str | None,\n        Doc(\"Working directory for the command, passed to the `cwd` argument of `subprocess.run`\"),\n    ] = None,\n    check: Annotated[\n        bool,\n        Doc(\n            \"Whether to raise an exception if the command returns a non-zero exit code, passed to the `check` \"\n            \"argument of `subprocess.run\",\n        ),\n    ] = True,\n    abort_on_error: Annotated[\n        bool,\n        Doc(\"Whether to abort the encapsulation if the command returns a non-zero exit code\"),\n    ] = True,\n    cwd_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `cwd` argument is relative to the project root. Will be ignored if `cwd` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `cwd` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    shell: Annotated[\n        bool,\n        Doc(\n            \"Whether to run the command using the shell. If True, the command will be run using the shell. \"\n            \"If False, the command will be run directly. \"\n            \"For more information, see the `shell` argument of `subprocess.run`. \",\n        ),\n    ] = True,\n) -> Callable[[CapsuleParams], CommandContext]:\n    def build(params: CapsuleParams) -> CommandContext:\n        if cwd_relative_to_project_root and cwd is not None and not Path(cwd).is_absolute():\n            cwd_path: Path | None = params.project_root / cwd\n        elif cwd_relative_to_project_root and cwd is None:\n            cwd_path = params.project_root\n        else:\n            cwd_path = Path(cwd) if cwd is not None else None\n\n        return cls(\n            command,\n            cwd=cwd_path,\n            check=check,\n            abort_on_error=abort_on_error,\n            shell=shell,\n        )\n\n    return build\n
"},{"location":"contexts/command/#capsula.CommandContext.__init__","title":"capsula.CommandContext.__init__","text":"
__init__(\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True\n)\n

Initialize the command context.

PARAMETER DESCRIPTION command

TYPE: str

cwd

TYPE: Path | None DEFAULT: None

check

TYPE: bool DEFAULT: True

abort_on_error

TYPE: bool DEFAULT: True

shell

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
def __init__(\n    self,\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True,\n) -> None:\n    \"\"\"Initialize the command context.\"\"\"\n    self._command = command\n    self._cwd = cwd\n    self._check = check\n    self._abort_on_error = abort_on_error\n    self._shell = shell\n
"},{"location":"contexts/command/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/command/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"CommandContext\", command = \"uv lock --locked\", cwd = \".\", cwd_relative_to_project_root = true },\n]\n
"},{"location":"contexts/command/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\nPROJECT_ROOT = capsula.search_for_project_root(__file__)\n\n@capsula.run()\n@capsula.context(capsula.CommandContext(\"uv lock --locked\", cwd=PROJECT_ROOT), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/command/#output-example","title":"Output example","text":"

The following is an example of the output of the CommandContext, reported by the JsonDumpReporter:

\"uv lock --locked\": {\n  \"command\": \"uv lock --locked\",\n  \"cwd\": \"/Users/nomura/ghq/github.com/shunichironomura/capsula\",\n  \"returncode\": 0,\n  \"stdout\": \"\",\n  \"stderr\": \"Resolved 73 packages in 0.35ms\\n\"\n}\n
"},{"location":"contexts/cpu/","title":"CpuContext","text":"

The CpuContext captures the CPU information. It can be created by capsula.CpuContext() with no arguments.

It internally uses the py-cpuinfo package to get the CPU information.

"},{"location":"contexts/cpu/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/cpu/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"CpuContext\" },\n]\n
"},{"location":"contexts/cpu/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.CpuContext(), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/cpu/#output-example","title":"Output example","text":"

The following is an example of the output of the CpuContext, reported by the JsonDumpReporter:

\"cpu\": {\n  \"python_version\": \"3.8.17.final.0 (64 bit)\",\n  \"cpuinfo_version\": [\n    9,\n    0,\n    0\n  ],\n  \"cpuinfo_version_string\": \"9.0.0\",\n  \"arch\": \"X86_64\",\n  \"bits\": 64,\n  \"count\": 12,\n  \"arch_string_raw\": \"x86_64\",\n  \"vendor_id_raw\": \"GenuineIntel\",\n  \"brand_raw\": \"Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz\",\n  \"hz_advertised_friendly\": \"2.9000 GHz\",\n  \"hz_actual_friendly\": \"2.9040 GHz\",\n  \"hz_advertised\": [\n    2900000000,\n    0\n  ],\n  \"hz_actual\": [\n    2904008000,\n    0\n  ],\n  \"stepping\": 5,\n  \"model\": 165,\n  \"family\": 6,\n  \"flags\": [\n    \"3dnowprefetch\",\n    \"abm\",\n    \"adx\",\n    \"aes\",\n    \"apic\",\n    \"arch_capabilities\",\n    \"arch_perfmon\",\n    \"avx\",\n    \"avx2\",\n    \"bmi1\",\n    \"bmi2\",\n    \"clflush\",\n    \"clflushopt\",\n    \"cmov\",\n    \"constant_tsc\",\n    \"cpuid\",\n    \"cx16\",\n    \"cx8\",\n    \"de\",\n    \"ept\",\n    \"ept_ad\",\n    \"erms\",\n    \"f16c\",\n    \"flush_l1d\",\n    \"fma\",\n    \"fpu\",\n    \"fsgsbase\",\n    \"fxsr\",\n    \"ht\",\n    \"hypervisor\",\n    \"ibpb\",\n    \"ibrs\",\n    \"ibrs_enhanced\",\n    \"invpcid\",\n    \"invpcid_single\",\n    \"lahf_lm\",\n    \"lm\",\n    \"mca\",\n    \"mce\",\n    \"md_clear\",\n    \"mmx\",\n    \"movbe\",\n    \"msr\",\n    \"mtrr\",\n    \"nopl\",\n    \"nx\",\n    \"osxsave\",\n    \"pae\",\n    \"pat\",\n    \"pcid\",\n    \"pclmulqdq\",\n    \"pdcm\",\n    \"pdpe1gb\",\n    \"pge\",\n    \"pni\",\n    \"popcnt\",\n    \"pse\",\n    \"pse36\",\n    \"rdrand\",\n    \"rdrnd\",\n    \"rdseed\",\n    \"rdtscp\",\n    \"rep_good\",\n    \"sep\",\n    \"smap\",\n    \"smep\",\n    \"ss\",\n    \"ssbd\",\n    \"sse\",\n    \"sse2\",\n    \"sse4_1\",\n    \"sse4_2\",\n    \"ssse3\",\n    \"stibp\",\n    \"syscall\",\n    \"tpr_shadow\",\n    \"tsc\",\n    \"vme\",\n    \"vmx\",\n    \"vnmi\",\n    \"vpid\",\n    \"x2apic\",\n    \"xgetbv1\",\n    \"xsave\",\n    \"xsavec\",\n    \"xsaveopt\",\n    \"xsaves\",\n    \"xtopology\"\n  ],\n  \"l3_cache_size\": 12582912,\n  \"l2_cache_size\": \"1.5 MiB\",\n  \"l1_data_cache_size\": 196608,\n  \"l1_instruction_cache_size\": 196608,\n  \"l2_cache_line_size\": 256,\n  \"l2_cache_associativity\": 6\n}\n
"},{"location":"contexts/cwd/","title":"CwdContext","text":"

The CwdContext captures the current working directory. It can be created by capsula.CwdContext() with no arguments.

"},{"location":"contexts/cwd/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/cwd/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"CwdContext\" },\n]\n
"},{"location":"contexts/cwd/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.CwdContext(), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/cwd/#output-example","title":"Output example","text":"

The following is an example of the output of the CwdContext, reported by the JsonDumpReporter:

\"cwd\": \"/home/nomura/ghq/github.com/shunichironomura/capsula\"\n
"},{"location":"contexts/envvar/","title":"EnvVarContext","text":"

The EnvVarContext captures an environment variable. It can be created using the capsula.EnvVarContext.__init__ method.

"},{"location":"contexts/envvar/#capsula.EnvVarContext.__init__","title":"capsula.EnvVarContext.__init__","text":"
__init__(name: str)\n
PARAMETER DESCRIPTION name

Name of the environment variable

TYPE: str

Source code in capsula/_context/_envvar.py
def __init__(self, name: Annotated[str, Doc(\"Name of the environment variable\")]) -> None:\n    self.name = name\n
"},{"location":"contexts/envvar/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/envvar/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"EnvVarContext\", name = \"HOME\" },\n]\n
"},{"location":"contexts/envvar/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/envvar/#output-example","title":"Output example","text":"

The following is an example of the output of the EnvVarContext, reported by the JsonDumpReporter:

\"env\": {\n  \"HOME\": \"/home/nomura\"\n}\n
"},{"location":"contexts/file/","title":"FileContext","text":"

The FileContext captures the file information. It can be created using the capsula.FileContext.builder method (recommended) or the capsula.FileContext.__init__ method.

"},{"location":"contexts/file/#capsula.FileContext.builder","title":"capsula.FileContext.builder classmethod","text":"
builder(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy: bool = False,\n    move: bool = False,\n    ignore_missing: bool = False,\n    path_relative_to_project_root: bool = False\n) -> Callable[[CapsuleParams], FileContext]\n
PARAMETER DESCRIPTION path

Path to the file

TYPE: Path | str

compute_hash

Whether to compute the hash of the file

TYPE: bool DEFAULT: True

hash_algorithm

Hash algorithm to use. This will be fed to hashlib.file_digest as the digest argument. If not provided, sha256 will be used.

TYPE: str | None DEFAULT: None

copy

Whether to copy the file to the run directory

TYPE: bool DEFAULT: False

move

Whether to move the file to the run directory

TYPE: bool DEFAULT: False

ignore_missing

Whether to ignore if the file does not exist

TYPE: bool DEFAULT: False

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
@classmethod\ndef builder(\n    cls,\n    path: Annotated[Path | str, Doc(\"Path to the file\")],\n    *,\n    compute_hash: Annotated[bool, Doc(\"Whether to compute the hash of the file\")] = True,\n    hash_algorithm: Annotated[\n        str | None,\n        Doc(\n            \"Hash algorithm to use. This will be fed to `hashlib.file_digest` as the `digest` argument. \"\n            \"If not provided, `sha256` will be used.\",\n        ),\n    ] = None,\n    copy: Annotated[bool, Doc(\"Whether to copy the file to the run directory\")] = False,\n    move: Annotated[bool, Doc(\"Whether to move the file to the run directory\")] = False,\n    ignore_missing: Annotated[bool, Doc(\"Whether to ignore if the file does not exist\")] = False,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n) -> Callable[[CapsuleParams], FileContext]:\n    if copy and move:\n        warnings.warn(\"Both copy and move are True. Only move will be performed.\", UserWarning, stacklevel=2)\n        move = True\n        copy = False\n\n    def build(params: CapsuleParams) -> FileContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            file_path = params.project_root / path\n        else:\n            file_path = Path(path)\n\n        return cls(\n            path=file_path,\n            compute_hash=compute_hash,\n            hash_algorithm=hash_algorithm,\n            copy_to=params.run_dir if copy else None,\n            move_to=params.run_dir if move else None,\n            ignore_missing=ignore_missing,\n        )\n\n    return build\n
"},{"location":"contexts/file/#capsula.FileContext.__init__","title":"capsula.FileContext.__init__","text":"
__init__(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: (\n        Iterable[Path | str] | Path | str | None\n    ) = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False\n)\n
PARAMETER DESCRIPTION path

TYPE: Path | str

compute_hash

TYPE: bool DEFAULT: True

hash_algorithm

TYPE: str | None DEFAULT: None

copy_to

TYPE: Iterable[Path | str] | Path | str | None DEFAULT: None

move_to

TYPE: Path | str | None DEFAULT: None

ignore_missing

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: Iterable[Path | str] | Path | str | None = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False,\n) -> None:\n    self._path = Path(path)\n    self._hash_algorithm = self._default_hash_algorithm if hash_algorithm is None else hash_algorithm\n    self._compute_hash = compute_hash\n    self._move_to = None if move_to is None else Path(move_to)\n    self._ignore_missing = ignore_missing\n\n    if copy_to is None:\n        self._copy_to: tuple[Path, ...] = ()\n    elif isinstance(copy_to, (str, Path)):\n        self._copy_to = (Path(copy_to),)\n    else:\n        self._copy_to = tuple(Path(p) for p in copy_to)\n
"},{"location":"contexts/file/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/file/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"FileContext\", path = \"pyproject.toml\", copy = true, path_relative_to_project_root = true },\n]\n
"},{"location":"contexts/file/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"

@capsula.context decorator is useful to move the output file to the run directory after the function execution.

import capsula\nPROJECT_ROOT = capsula.search_for_project_root(__file__)\n\n@capsula.run()\n@capsula.context(capsula.FileContext.builder(PROJECT_ROOT / \"pyproject.toml\", copy=True), mode=\"pre\")\n@capsula.context(capsula.FileContext.builder(\"output.txt\", move=True), mode=\"post\")\ndef func():\n  with open(\"output.txt\", \"w\") as output_file:\n    output_file.write(\"Hello, world!\")\n
"},{"location":"contexts/file/#output-example","title":"Output example","text":"

The following is an example of the output of the FileContext, reported by the JsonDumpReporter:

\"file\": {\n  \"/home/nomura/ghq/github.com/shunichironomura/capsula/pyproject.toml\": {\n    \"copied_to\": [\n      \"/home/nomura/ghq/github.com/shunichironomura/capsula/vault/20240708_024409_coj0/pyproject.toml\"\n    ],\n    \"moved_to\": null,\n    \"hash\": {\n      \"algorithm\": \"sha256\",\n      \"digest\": \"1ecab310035eea9c07fad2a8b22a16f999cd4d8c59fa1732c088f754af548ad9\"\n    }\n  },\n}\n
"},{"location":"contexts/function/","title":"FunctionContext","text":"

The FunctionContext captures the arguments of a function. It can be created using the capsula.FunctionContext.builder method or the capsula.FunctionContext.__init__ method.

"},{"location":"contexts/function/#capsula.FunctionContext.builder","title":"capsula.FunctionContext.builder classmethod","text":"
builder(\n    *, ignore: Container[str] = ()\n) -> Callable[[CapsuleParams], FunctionContext]\n
PARAMETER DESCRIPTION ignore

Parameters to ignore when capturing the arguments. This is useful when you pass values that you don't want to be in the output, such as a large data structure or a function that is not serializable, or a secret.

TYPE: Container[str] DEFAULT: ()

Source code in capsula/_context/_function.py
@classmethod\ndef builder(\n    cls,\n    *,\n    ignore: Annotated[\n        Container[str],\n        Doc(\n            \"Parameters to ignore when capturing the arguments. \"\n            \"This is useful when you pass values that you don't want to be in the output, \"\n            \"such as a large data structure or a function that is not serializable, or a secret.\",\n        ),\n    ] = (),\n) -> Callable[[CapsuleParams], FunctionContext]:\n    def build(params: CapsuleParams) -> FunctionContext:\n        if not isinstance(params.exec_info, FuncInfo):\n            msg = \"FunctionContext can only be built from a FuncInfo.\"\n            raise TypeError(msg)\n\n        return cls(\n            params.exec_info.func,\n            args=params.exec_info.args,\n            kwargs=params.exec_info.kwargs,\n            ignore=ignore,\n            _remove_pre_run_capsule_before_binding=params.exec_info.pass_pre_run_capsule,\n        )\n\n    return build\n
"},{"location":"contexts/function/#capsula.FunctionContext.__init__","title":"capsula.FunctionContext.__init__","text":"
__init__(\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False\n)\n
PARAMETER DESCRIPTION function

TYPE: Callable[..., Any]

args

TYPE: Sequence[Any]

kwargs

TYPE: Mapping[str, Any]

ignore

TYPE: Container[str] DEFAULT: ()

_remove_pre_run_capsule_before_binding

TYPE: bool DEFAULT: False

Source code in capsula/_context/_function.py
def __init__(\n    self,\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False,\n) -> None:\n    self._function = function\n    self._args = args\n    self._kwargs = kwargs\n    self._ignore = ignore\n    self._remove_pre_run_capsule_before_binding = _remove_pre_run_capsule_before_binding\n
"},{"location":"contexts/function/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/function/#via-capsulatoml","title":"Via capsula.toml","text":"

Warning

Configuring the FunctionContext via capsula.toml is not recommended because capsula enc will fail as there is no target function to capture.

[pre-run]\ncontexts = [\n  { type = \"FunctionContext\" },\n]\n
"},{"location":"contexts/function/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.FunctionContext.builder(), mode=\"pre\")\ndef func(arg1, arg2): ...\n
"},{"location":"contexts/function/#output-example","title":"Output example","text":"

The following is an example of the output of the FunctionContext, reported by the JsonDumpReporter:

\"function\": {\n  \"calculate_pi\": {\n    \"file_path\": \"examples/simple_decorator.py\",\n    \"first_line_no\": 6,\n    \"bound_args\": {\n      \"n_samples\": 1000,\n      \"seed\": 42\n    }\n  }\n}\n
"},{"location":"contexts/git/","title":"GitRepositoryContext","text":"

The GitRepositoryContext captures the information of a Git repository. It can be created using the capsula.GitRepositoryContext.builder method or the capsula.GitRepositoryContext.__init__ method.

"},{"location":"contexts/git/#capsula.GitRepositoryContext.builder","title":"capsula.GitRepositoryContext.builder classmethod","text":"
builder(\n    name: str | None = None,\n    *,\n    path: Path | str | None = None,\n    path_relative_to_project_root: bool = False,\n    allow_dirty: bool = True\n) -> Callable[[CapsuleParams], GitRepositoryContext]\n
PARAMETER DESCRIPTION name

Name of the Git repository. If not provided, the name of the working directory will be used.

TYPE: str | None DEFAULT: None

path

Path to the Git repository. If not provided, the parent directories of the file where the function is defined will be searched for a Git repository.

TYPE: Path | str | None DEFAULT: None

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is None or absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

allow_dirty

Whether to allow the repository to be dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
@classmethod\ndef builder(\n    cls,\n    name: Annotated[\n        str | None,\n        Doc(\"Name of the Git repository. If not provided, the name of the working directory will be used.\"),\n    ] = None,\n    *,\n    path: Annotated[\n        Path | str | None,\n        Doc(\n            \"Path to the Git repository. If not provided, the parent directories of the file where the function is \"\n            \"defined will be searched for a Git repository.\",\n        ),\n    ] = None,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    allow_dirty: Annotated[bool, Doc(\"Whether to allow the repository to be dirty\")] = True,\n) -> Callable[[CapsuleParams], GitRepositoryContext]:\n    def build(params: CapsuleParams) -> GitRepositoryContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            repository_path: Path | None = params.project_root / path\n        else:\n            repository_path = Path(path) if path is not None else None\n\n        if repository_path is not None:\n            repo = Repo(repository_path, search_parent_directories=False)\n        else:\n            if isinstance(params.exec_info, FuncInfo):\n                repo_search_start_path = Path(inspect.getfile(params.exec_info.func)).parent\n            elif isinstance(params.exec_info, CommandInfo) or params.exec_info is None:\n                repo_search_start_path = Path.cwd()\n            else:\n                msg = f\"exec_info must be an instance of FuncInfo or CommandInfo, not {type(params.exec_info)}.\"\n                raise TypeError(msg)\n            repo = Repo(repo_search_start_path, search_parent_directories=True)\n\n        repo_name = Path(repo.working_dir).name\n\n        return cls(\n            name=Path(repo.working_dir).name if name is None else name,\n            path=Path(repo.working_dir),\n            diff_file=params.run_dir / f\"{repo_name}.diff\",\n            search_parent_directories=False,\n            allow_dirty=allow_dirty,\n        )\n\n    return build\n
"},{"location":"contexts/git/#capsula.GitRepositoryContext.__init__","title":"capsula.GitRepositoryContext.__init__","text":"
__init__(\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True\n)\n
PARAMETER DESCRIPTION name

TYPE: str

path

TYPE: Path | str

diff_file

TYPE: Path | str | None DEFAULT: None

search_parent_directories

TYPE: bool DEFAULT: False

allow_dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
def __init__(\n    self,\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True,\n) -> None:\n    self._name = name\n    self._path = Path(path)\n    self._search_parent_directories = search_parent_directories\n    self._allow_dirty = allow_dirty\n    self._diff_file = None if diff_file is None else Path(diff_file)\n
"},{"location":"contexts/git/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/git/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"GitRepositoryContext\", name = \"capsula\", path = \".\", path_relative_to_project_root = true },\n]\n
"},{"location":"contexts/git/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.GitRepositoryContext.builder(\"capsula\"), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/git/#output-example","title":"Output example","text":"

The following is an example of the output of the GitRepositoryContext, reported by the JsonDumpReporter:

\"git\": {\n  \"capsula\": {\n    \"working_dir\": \"/home/nomura/ghq/github.com/shunichironomura/capsula\",\n    \"sha\": \"2fa930db2b9c00c467b4627e7d1c7dfb06d41279\",\n    \"remotes\": {\n      \"origin\": \"ssh://git@github.com/shunichironomura/capsula.git\"\n    },\n    \"branch\": \"improve-docs-index\",\n    \"is_dirty\": true,\n    \"diff_file\": \"/home/nomura/ghq/github.com/shunichironomura/capsula/vault/20240708_024409_coj0/capsula.diff\"\n  }\n}\n
"},{"location":"contexts/platform/","title":"PlatformContext","text":"

The PlatformContext captures the Python version. It can be created by capsula.PlatformContext() with no arguments.

"},{"location":"contexts/platform/#configuration-example","title":"Configuration example","text":""},{"location":"contexts/platform/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\ncontexts = [\n  { type = \"PlatformContext\" },\n]\n
"},{"location":"contexts/platform/#via-capsulacontext-decorator","title":"Via @capsula.context decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.context(capsula.PlatformContext(), mode=\"pre\")\ndef func(): ...\n
"},{"location":"contexts/platform/#output-example","title":"Output example","text":"

The following is an example of the output of the PlatformContext, reported by the JsonDumpReporter:

\"platform\": {\n  \"machine\": \"x86_64\",\n  \"node\": \"SHUN-DESKTOP\",\n  \"platform\": \"Linux-5.15.153.1-microsoft-standard-WSL2-x86_64-with-glibc2.34\",\n  \"release\": \"5.15.153.1-microsoft-standard-WSL2\",\n  \"version\": \"#1 SMP Fri Mar 29 23:14:13 UTC 2024\",\n  \"system\": \"Linux\",\n  \"processor\": \"x86_64\",\n  \"python\": {\n    \"executable_architecture\": {\n      \"bits\": \"64bit\",\n      \"linkage\": \"ELF\"\n    },\n    \"build_no\": \"default\",\n    \"build_date\": \"Jul  7 2024 07:23:53\",\n    \"compiler\": \"GCC 11.4.0\",\n    \"branch\": \"\",\n    \"implementation\": \"CPython\",\n    \"version\": \"3.8.19\"\n  }\n}\n
"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":""},{"location":"reference/capsula/","title":"capsula","text":""},{"location":"reference/capsula/#capsula","title":"capsula","text":""},{"location":"reference/capsula/#capsula.Capsule","title":"Capsule","text":"
Capsule(\n    data: Mapping[_ContextKey, Any],\n    fails: Mapping[_ContextKey, ExceptionInfo],\n)\n
PARAMETER DESCRIPTION data

TYPE: Mapping[_ContextKey, Any]

fails

TYPE: Mapping[_ContextKey, ExceptionInfo]

Source code in capsula/_capsule.py
def __init__(\n    self,\n    data: Mapping[_ContextKey, Any],\n    fails: Mapping[_ContextKey, ExceptionInfo],\n) -> None:\n    self.data = dict(data)\n    self.fails = dict(fails)\n
"},{"location":"reference/capsula/#capsula.Capsule.data","title":"data instance-attribute","text":"
data = dict(data)\n
"},{"location":"reference/capsula/#capsula.Capsule.fails","title":"fails instance-attribute","text":"
fails = dict(fails)\n
"},{"location":"reference/capsula/#capsula.CommandContext","title":"CommandContext","text":"
CommandContext(\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True\n)\n

Bases: ContextBase

Context to capture the output of a command run in a subprocess.

Initialize the command context.

PARAMETER DESCRIPTION command

TYPE: str

cwd

TYPE: Path | None DEFAULT: None

check

TYPE: bool DEFAULT: True

abort_on_error

TYPE: bool DEFAULT: True

shell

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
def __init__(\n    self,\n    command: str,\n    *,\n    cwd: Path | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    shell: bool = True,\n) -> None:\n    \"\"\"Initialize the command context.\"\"\"\n    self._command = command\n    self._cwd = cwd\n    self._check = check\n    self._abort_on_error = abort_on_error\n    self._shell = shell\n
"},{"location":"reference/capsula/#capsula.CommandContext.builder","title":"builder classmethod","text":"
builder(\n    command: str,\n    *,\n    cwd: Path | str | None = None,\n    check: bool = True,\n    abort_on_error: bool = True,\n    cwd_relative_to_project_root: bool = False,\n    shell: bool = True\n) -> Callable[[CapsuleParams], CommandContext]\n
PARAMETER DESCRIPTION command

Command to run

TYPE: str

cwd

Working directory for the command, passed to the cwd argument of subprocess.run

TYPE: Path | str | None DEFAULT: None

check

Whether to raise an exception if the command returns a non-zero exit code, passed to the check argument of `subprocess.run

TYPE: bool DEFAULT: True

abort_on_error

Whether to abort the encapsulation if the command returns a non-zero exit code

TYPE: bool DEFAULT: True

cwd_relative_to_project_root

Whether cwd argument is relative to the project root. Will be ignored if cwd is None or absolute. If True, it will be interpreted as relative to the project root. If False, cwd will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

shell

Whether to run the command using the shell. If True, the command will be run using the shell. If False, the command will be run directly. For more information, see the shell argument of subprocess.run.

TYPE: bool DEFAULT: True

Source code in capsula/_context/_command.py
@classmethod\ndef builder(\n    cls,\n    command: Annotated[str, Doc(\"Command to run\")],\n    *,\n    cwd: Annotated[\n        Path | str | None,\n        Doc(\"Working directory for the command, passed to the `cwd` argument of `subprocess.run`\"),\n    ] = None,\n    check: Annotated[\n        bool,\n        Doc(\n            \"Whether to raise an exception if the command returns a non-zero exit code, passed to the `check` \"\n            \"argument of `subprocess.run\",\n        ),\n    ] = True,\n    abort_on_error: Annotated[\n        bool,\n        Doc(\"Whether to abort the encapsulation if the command returns a non-zero exit code\"),\n    ] = True,\n    cwd_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `cwd` argument is relative to the project root. Will be ignored if `cwd` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `cwd` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    shell: Annotated[\n        bool,\n        Doc(\n            \"Whether to run the command using the shell. If True, the command will be run using the shell. \"\n            \"If False, the command will be run directly. \"\n            \"For more information, see the `shell` argument of `subprocess.run`. \",\n        ),\n    ] = True,\n) -> Callable[[CapsuleParams], CommandContext]:\n    def build(params: CapsuleParams) -> CommandContext:\n        if cwd_relative_to_project_root and cwd is not None and not Path(cwd).is_absolute():\n            cwd_path: Path | None = params.project_root / cwd\n        elif cwd_relative_to_project_root and cwd is None:\n            cwd_path = params.project_root\n        else:\n            cwd_path = Path(cwd) if cwd is not None else None\n\n        return cls(\n            command,\n            cwd=cwd_path,\n            check=check,\n            abort_on_error=abort_on_error,\n            shell=shell,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.CommandContext.abort_on_error","title":"abort_on_error property","text":"
abort_on_error: bool\n
"},{"location":"reference/capsula/#capsula.CommandContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _CommandContextData\n
Source code in capsula/_context/_command.py
def encapsulate(self) -> _CommandContextData:\n    logger.debug(f\"Running command: {self._command}\")\n    output = subprocess.run(  # noqa: S603\n        self._command,\n        shell=self._shell,\n        text=True,\n        capture_output=True,\n        cwd=self._cwd,\n        check=self._check,\n    )\n    logger.debug(f\"Ran command: {self._command}. Result: {output}\")\n    return {\n        \"command\": self._command,\n        \"cwd\": self._cwd,\n        \"returncode\": output.returncode,\n        \"stdout\": output.stdout,\n        \"stderr\": output.stderr,\n    }\n
"},{"location":"reference/capsula/#capsula.CommandContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_command.py
def default_key(self) -> tuple[str, str]:\n    return (\"command\", self._command)\n
"},{"location":"reference/capsula/#capsula.ContextBase","title":"ContextBase","text":"

Bases: CapsuleItem

"},{"location":"reference/capsula/#capsula.ContextBase.abort_on_error","title":"abort_on_error property","text":"
abort_on_error: bool\n
"},{"location":"reference/capsula/#capsula.ContextBase.get_subclass","title":"get_subclass classmethod","text":"
get_subclass(name: str) -> type[ContextBase]\n
PARAMETER DESCRIPTION name

TYPE: str

Source code in capsula/_context/_base.py
@classmethod\ndef get_subclass(cls, name: str) -> type[ContextBase]:\n    return cls._subclass_registry[name]\n
"},{"location":"reference/capsula/#capsula.CpuContext","title":"CpuContext","text":"

Bases: ContextBase

Context to capture CPU information.

"},{"location":"reference/capsula/#capsula.CpuContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> dict[str, Any]\n
Source code in capsula/_context/_cpu.py
def encapsulate(self) -> dict[str, Any]:\n    return get_cpu_info()  # type: ignore[no-any-return]\n
"},{"location":"reference/capsula/#capsula.CpuContext.default_key","title":"default_key","text":"
default_key() -> str\n
Source code in capsula/_context/_cpu.py
def default_key(self) -> str:\n    return \"cpu\"\n
"},{"location":"reference/capsula/#capsula.CwdContext","title":"CwdContext","text":"

Bases: ContextBase

Context to capture the current working directory.

"},{"location":"reference/capsula/#capsula.CwdContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> Path\n
Source code in capsula/_context/_cwd.py
def encapsulate(self) -> Path:\n    return Path.cwd()\n
"},{"location":"reference/capsula/#capsula.CwdContext.default_key","title":"default_key","text":"
default_key() -> str\n
Source code in capsula/_context/_cwd.py
def default_key(self) -> str:\n    return \"cwd\"\n
"},{"location":"reference/capsula/#capsula.EnvVarContext","title":"EnvVarContext","text":"
EnvVarContext(name: str)\n

Bases: ContextBase

Context to capture an environment variable.

PARAMETER DESCRIPTION name

Name of the environment variable

TYPE: str

Source code in capsula/_context/_envvar.py
def __init__(self, name: Annotated[str, Doc(\"Name of the environment variable\")]) -> None:\n    self.name = name\n
"},{"location":"reference/capsula/#capsula.EnvVarContext.name","title":"name instance-attribute","text":"
name = name\n
"},{"location":"reference/capsula/#capsula.EnvVarContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> str | None\n
Source code in capsula/_context/_envvar.py
def encapsulate(self) -> str | None:\n    return os.getenv(self.name)\n
"},{"location":"reference/capsula/#capsula.EnvVarContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_envvar.py
def default_key(self) -> tuple[str, str]:\n    return (\"env\", self.name)\n
"},{"location":"reference/capsula/#capsula.FileContext","title":"FileContext","text":"
FileContext(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: (\n        Iterable[Path | str] | Path | str | None\n    ) = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False\n)\n

Bases: ContextBase

Context to capture a file.

PARAMETER DESCRIPTION path

TYPE: Path | str

compute_hash

TYPE: bool DEFAULT: True

hash_algorithm

TYPE: str | None DEFAULT: None

copy_to

TYPE: Iterable[Path | str] | Path | str | None DEFAULT: None

move_to

TYPE: Path | str | None DEFAULT: None

ignore_missing

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy_to: Iterable[Path | str] | Path | str | None = None,\n    move_to: Path | str | None = None,\n    ignore_missing: bool = False,\n) -> None:\n    self._path = Path(path)\n    self._hash_algorithm = self._default_hash_algorithm if hash_algorithm is None else hash_algorithm\n    self._compute_hash = compute_hash\n    self._move_to = None if move_to is None else Path(move_to)\n    self._ignore_missing = ignore_missing\n\n    if copy_to is None:\n        self._copy_to: tuple[Path, ...] = ()\n    elif isinstance(copy_to, (str, Path)):\n        self._copy_to = (Path(copy_to),)\n    else:\n        self._copy_to = tuple(Path(p) for p in copy_to)\n
"},{"location":"reference/capsula/#capsula.FileContext.builder","title":"builder classmethod","text":"
builder(\n    path: Path | str,\n    *,\n    compute_hash: bool = True,\n    hash_algorithm: str | None = None,\n    copy: bool = False,\n    move: bool = False,\n    ignore_missing: bool = False,\n    path_relative_to_project_root: bool = False\n) -> Callable[[CapsuleParams], FileContext]\n
PARAMETER DESCRIPTION path

Path to the file

TYPE: Path | str

compute_hash

Whether to compute the hash of the file

TYPE: bool DEFAULT: True

hash_algorithm

Hash algorithm to use. This will be fed to hashlib.file_digest as the digest argument. If not provided, sha256 will be used.

TYPE: str | None DEFAULT: None

copy

Whether to copy the file to the run directory

TYPE: bool DEFAULT: False

move

Whether to move the file to the run directory

TYPE: bool DEFAULT: False

ignore_missing

Whether to ignore if the file does not exist

TYPE: bool DEFAULT: False

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

Source code in capsula/_context/_file.py
@classmethod\ndef builder(\n    cls,\n    path: Annotated[Path | str, Doc(\"Path to the file\")],\n    *,\n    compute_hash: Annotated[bool, Doc(\"Whether to compute the hash of the file\")] = True,\n    hash_algorithm: Annotated[\n        str | None,\n        Doc(\n            \"Hash algorithm to use. This will be fed to `hashlib.file_digest` as the `digest` argument. \"\n            \"If not provided, `sha256` will be used.\",\n        ),\n    ] = None,\n    copy: Annotated[bool, Doc(\"Whether to copy the file to the run directory\")] = False,\n    move: Annotated[bool, Doc(\"Whether to move the file to the run directory\")] = False,\n    ignore_missing: Annotated[bool, Doc(\"Whether to ignore if the file does not exist\")] = False,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n) -> Callable[[CapsuleParams], FileContext]:\n    if copy and move:\n        warnings.warn(\"Both copy and move are True. Only move will be performed.\", UserWarning, stacklevel=2)\n        move = True\n        copy = False\n\n    def build(params: CapsuleParams) -> FileContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            file_path = params.project_root / path\n        else:\n            file_path = Path(path)\n\n        return cls(\n            path=file_path,\n            compute_hash=compute_hash,\n            hash_algorithm=hash_algorithm,\n            copy_to=params.run_dir if copy else None,\n            move_to=params.run_dir if move else None,\n            ignore_missing=ignore_missing,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.FileContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _FileContextData\n
Source code in capsula/_context/_file.py
def encapsulate(self) -> _FileContextData:\n    if not self._path.exists():\n        if self._ignore_missing:\n            logger.warning(f\"File {self._path} does not exist. Ignoring.\")\n            return _FileContextData(copied_to=(), moved_to=None, hash=None)\n        else:\n            msg = f\"File {self._path} does not exist.\"\n            raise FileNotFoundError(msg)\n    self._copy_to = tuple(self._normalize_copy_dst_path(p) for p in self._copy_to)\n\n    if self._compute_hash:\n        with self._path.open(\"rb\") as f:\n            digest = file_digest(f, self._hash_algorithm).hexdigest()\n        hash_data = {\n            \"algorithm\": self._hash_algorithm,\n            \"digest\": digest,\n        }\n    else:\n        hash_data = None\n\n    info: _FileContextData = {\n        \"copied_to\": self._copy_to,\n        \"moved_to\": self._move_to,\n        \"hash\": hash_data,\n    }\n\n    for path in self._copy_to:\n        copyfile(self._path, path)\n    if self._move_to is not None:\n        move(str(self._path), self._move_to)\n\n    return info\n
"},{"location":"reference/capsula/#capsula.FileContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_file.py
def default_key(self) -> tuple[str, str]:\n    return (\"file\", str(self._path))\n
"},{"location":"reference/capsula/#capsula.FunctionContext","title":"FunctionContext","text":"
FunctionContext(\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False\n)\n

Bases: ContextBase

Context to capture a function call.

PARAMETER DESCRIPTION function

TYPE: Callable[..., Any]

args

TYPE: Sequence[Any]

kwargs

TYPE: Mapping[str, Any]

ignore

TYPE: Container[str] DEFAULT: ()

_remove_pre_run_capsule_before_binding

TYPE: bool DEFAULT: False

Source code in capsula/_context/_function.py
def __init__(\n    self,\n    function: Callable[..., Any],\n    *,\n    args: Sequence[Any],\n    kwargs: Mapping[str, Any],\n    ignore: Container[str] = (),\n    _remove_pre_run_capsule_before_binding: bool = False,\n) -> None:\n    self._function = function\n    self._args = args\n    self._kwargs = kwargs\n    self._ignore = ignore\n    self._remove_pre_run_capsule_before_binding = _remove_pre_run_capsule_before_binding\n
"},{"location":"reference/capsula/#capsula.FunctionContext.builder","title":"builder classmethod","text":"
builder(\n    *, ignore: Container[str] = ()\n) -> Callable[[CapsuleParams], FunctionContext]\n
PARAMETER DESCRIPTION ignore

Parameters to ignore when capturing the arguments. This is useful when you pass values that you don't want to be in the output, such as a large data structure or a function that is not serializable, or a secret.

TYPE: Container[str] DEFAULT: ()

Source code in capsula/_context/_function.py
@classmethod\ndef builder(\n    cls,\n    *,\n    ignore: Annotated[\n        Container[str],\n        Doc(\n            \"Parameters to ignore when capturing the arguments. \"\n            \"This is useful when you pass values that you don't want to be in the output, \"\n            \"such as a large data structure or a function that is not serializable, or a secret.\",\n        ),\n    ] = (),\n) -> Callable[[CapsuleParams], FunctionContext]:\n    def build(params: CapsuleParams) -> FunctionContext:\n        if not isinstance(params.exec_info, FuncInfo):\n            msg = \"FunctionContext can only be built from a FuncInfo.\"\n            raise TypeError(msg)\n\n        return cls(\n            params.exec_info.func,\n            args=params.exec_info.args,\n            kwargs=params.exec_info.kwargs,\n            ignore=ignore,\n            _remove_pre_run_capsule_before_binding=params.exec_info.pass_pre_run_capsule,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.FunctionContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _FunctionContextData\n
Source code in capsula/_context/_function.py
def encapsulate(self) -> _FunctionContextData:\n    file_path = Path(inspect.getfile(self._function))\n    _, first_line_no = inspect.getsourcelines(self._function)\n    sig = inspect.signature(self._function)\n\n    if self._remove_pre_run_capsule_before_binding:\n        sig = sig.replace(parameters=tuple(p for p in sig.parameters.values() if p.name != \"pre_run_capsule\"))\n\n    ba = sig.bind(*self._args, **self._kwargs)\n    ba.apply_defaults()\n    ba_wo_ignore = {k: v for k, v in ba.arguments.items() if k not in self._ignore}\n    return {\n        \"file_path\": file_path,\n        \"first_line_no\": first_line_no,\n        \"bound_args\": ba_wo_ignore,\n    }\n
"},{"location":"reference/capsula/#capsula.FunctionContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_function.py
def default_key(self) -> tuple[str, str]:\n    return (\"function\", self._function.__name__)\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext","title":"GitRepositoryContext","text":"
GitRepositoryContext(\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True\n)\n

Bases: ContextBase

Context to capture a Git repository.

PARAMETER DESCRIPTION name

TYPE: str

path

TYPE: Path | str

diff_file

TYPE: Path | str | None DEFAULT: None

search_parent_directories

TYPE: bool DEFAULT: False

allow_dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
def __init__(\n    self,\n    name: str,\n    *,\n    path: Path | str,\n    diff_file: Path | str | None = None,\n    search_parent_directories: bool = False,\n    allow_dirty: bool = True,\n) -> None:\n    self._name = name\n    self._path = Path(path)\n    self._search_parent_directories = search_parent_directories\n    self._allow_dirty = allow_dirty\n    self._diff_file = None if diff_file is None else Path(diff_file)\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext.builder","title":"builder classmethod","text":"
builder(\n    name: str | None = None,\n    *,\n    path: Path | str | None = None,\n    path_relative_to_project_root: bool = False,\n    allow_dirty: bool = True\n) -> Callable[[CapsuleParams], GitRepositoryContext]\n
PARAMETER DESCRIPTION name

Name of the Git repository. If not provided, the name of the working directory will be used.

TYPE: str | None DEFAULT: None

path

Path to the Git repository. If not provided, the parent directories of the file where the function is defined will be searched for a Git repository.

TYPE: Path | str | None DEFAULT: None

path_relative_to_project_root

Whether path is relative to the project root. Will be ignored if path is None or absolute. If True, it will be interpreted as relative to the project root. If False, path will be interpreted as relative to the current working directory. It is recommended to set this to True in the configuration file.

TYPE: bool DEFAULT: False

allow_dirty

Whether to allow the repository to be dirty

TYPE: bool DEFAULT: True

Source code in capsula/_context/_git.py
@classmethod\ndef builder(\n    cls,\n    name: Annotated[\n        str | None,\n        Doc(\"Name of the Git repository. If not provided, the name of the working directory will be used.\"),\n    ] = None,\n    *,\n    path: Annotated[\n        Path | str | None,\n        Doc(\n            \"Path to the Git repository. If not provided, the parent directories of the file where the function is \"\n            \"defined will be searched for a Git repository.\",\n        ),\n    ] = None,\n    path_relative_to_project_root: Annotated[\n        bool,\n        Doc(\n            \"Whether `path` is relative to the project root. Will be ignored if `path` is None or absolute. \"\n            \"If True, it will be interpreted as relative to the project root. \"\n            \"If False, `path` will be interpreted as relative to the current working directory. \"\n            \"It is recommended to set this to True in the configuration file.\",\n        ),\n    ] = False,\n    allow_dirty: Annotated[bool, Doc(\"Whether to allow the repository to be dirty\")] = True,\n) -> Callable[[CapsuleParams], GitRepositoryContext]:\n    def build(params: CapsuleParams) -> GitRepositoryContext:\n        if path_relative_to_project_root and path is not None and not Path(path).is_absolute():\n            repository_path: Path | None = params.project_root / path\n        else:\n            repository_path = Path(path) if path is not None else None\n\n        if repository_path is not None:\n            repo = Repo(repository_path, search_parent_directories=False)\n        else:\n            if isinstance(params.exec_info, FuncInfo):\n                repo_search_start_path = Path(inspect.getfile(params.exec_info.func)).parent\n            elif isinstance(params.exec_info, CommandInfo) or params.exec_info is None:\n                repo_search_start_path = Path.cwd()\n            else:\n                msg = f\"exec_info must be an instance of FuncInfo or CommandInfo, not {type(params.exec_info)}.\"\n                raise TypeError(msg)\n            repo = Repo(repo_search_start_path, search_parent_directories=True)\n\n        repo_name = Path(repo.working_dir).name\n\n        return cls(\n            name=Path(repo.working_dir).name if name is None else name,\n            path=Path(repo.working_dir),\n            diff_file=params.run_dir / f\"{repo_name}.diff\",\n            search_parent_directories=False,\n            allow_dirty=allow_dirty,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _GitRepositoryContextData\n
Source code in capsula/_context/_git.py
def encapsulate(self) -> _GitRepositoryContextData:\n    repo = Repo(self._path, search_parent_directories=self._search_parent_directories)\n    if not self._allow_dirty and repo.is_dirty():\n        raise GitRepositoryDirtyError(repo)\n\n    def get_optional_branch_name(repo: Repo) -> str | None:\n        try:\n            return repo.active_branch.name\n        except TypeError:\n            return None\n\n    info: _GitRepositoryContextData = {\n        \"working_dir\": repo.working_dir,\n        \"sha\": repo.head.commit.hexsha,\n        \"remotes\": {remote.name: remote.url for remote in repo.remotes},\n        \"branch\": get_optional_branch_name(repo),\n        \"is_dirty\": repo.is_dirty(),\n        \"diff_file\": None,\n    }\n\n    diff_txt = repo.git.diff()\n    if diff_txt:\n        assert self._diff_file is not None, \"diff_file is None\"\n        with self._diff_file.open(\"w\") as f:\n            f.write(diff_txt)\n        logger.debug(f\"Wrote diff to {self._diff_file}\")\n        info[\"diff_file\"] = self._diff_file\n    return info\n
"},{"location":"reference/capsula/#capsula.GitRepositoryContext.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_context/_git.py
def default_key(self) -> tuple[str, str]:\n    return (\"git\", self._name)\n
"},{"location":"reference/capsula/#capsula.PlatformContext","title":"PlatformContext","text":"

Bases: ContextBase

Context to capture platform information, including Python version.

"},{"location":"reference/capsula/#capsula.PlatformContext.encapsulate","title":"encapsulate","text":"
encapsulate() -> _PlatformContextData\n
Source code in capsula/_context/_platform.py
def encapsulate(self) -> _PlatformContextData:\n    return {\n        \"machine\": pf.machine(),\n        \"node\": pf.node(),\n        \"platform\": pf.platform(),\n        \"release\": pf.release(),\n        \"version\": pf.version(),\n        \"system\": pf.system(),\n        \"processor\": pf.processor(),\n        \"python\": {\n            \"executable_architecture\": {\n                \"bits\": pf.architecture()[0],\n                \"linkage\": pf.architecture()[1],\n            },\n            \"build_no\": pf.python_build()[0],\n            \"build_date\": pf.python_build()[1],\n            \"compiler\": pf.python_compiler(),\n            \"branch\": pf.python_branch(),\n            \"implementation\": pf.python_implementation(),\n            \"version\": pf.python_version(),\n        },\n    }\n
"},{"location":"reference/capsula/#capsula.PlatformContext.default_key","title":"default_key","text":"
default_key() -> str\n
Source code in capsula/_context/_platform.py
def default_key(self) -> str:\n    return \"platform\"\n
"},{"location":"reference/capsula/#capsula.context","title":"context","text":"
context(\n    context: (\n        ContextBase | Callable[[CapsuleParams], ContextBase]\n    ),\n    mode: Literal[\"pre\", \"post\", \"all\"],\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    RunDtoNoPassPreRunCapsule[P, T]\n    | RunDtoPassPreRunCapsule[P, T],\n]\n

Decorator to add a context to the specified phase of the run.

Example:

import capsula\n\n@capsula.run()\n@capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\ndef func() -> None: ...\n

PARAMETER DESCRIPTION context

Context or a builder function to create a context from the capsule parameters.

TYPE: ContextBase | Callable[[CapsuleParams], ContextBase]

mode

Phase to add the context. Specify 'all' to add to all phases.

TYPE: Literal['pre', 'post', 'all']

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]]

Decorator to add a context to the specified phase of the run.

Source code in capsula/_decorator.py
def context(\n    context: Annotated[\n        ContextBase | Callable[[CapsuleParams], ContextBase],\n        Doc(\"Context or a builder function to create a context from the capsule parameters.\"),\n    ],\n    mode: Annotated[\n        Literal[\"pre\", \"post\", \"all\"],\n        Doc(\"Phase to add the context. Specify 'all' to add to all phases.\"),\n    ],\n) -> Annotated[\n    Callable[\n        [Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]],\n        RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ],\n    Doc(\"Decorator to add a context to the specified phase of the run.\"),\n]:\n    \"\"\"Decorator to add a context to the specified phase of the run.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\n    def func() -> None: ...\n    ```\n\n    \"\"\"\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]:\n        run = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoNoPassPreRunCapsule, RunDtoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        run.add_context(context, mode=mode, append_left=True)\n        return run\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.pass_pre_run_capsule","title":"pass_pre_run_capsule","text":"
pass_pre_run_capsule(\n    func: Callable[Concatenate[Capsule, P], T]\n) -> RunDtoPassPreRunCapsule[P, T]\n

Decorator to pass the pre-run capsule to the function.

This decorator must be placed closer to the function than the other decorators such as run, context, watcher, and reporter.

The decorated function's first argument must be a Capsule object to receive the pre-run capsule.

With this decorator, you can access the pre-run capsule in the function to get the data recorded by the contexts, such as the Git repository context. Here is an example, assuming the GitRepositoryContext for the \"my-repo\" repository is added to the pre-run phase:

import capsula\n\n@capsula.run()\n@capsula.pass_pre_run_capsule\ndef func(pre_run_capsule: capsula.Capsule) -> None:\n    git_sha = pre_run_capsule.data[(\"git\", \"my-repo\")][\"sha\"]\n
PARAMETER DESCRIPTION func

Function to decorate.

TYPE: Callable[Concatenate[Capsule, P], T]

RETURNS DESCRIPTION RunDtoPassPreRunCapsule[P, T]

Decorated function as a Run object.

Source code in capsula/_decorator.py
def pass_pre_run_capsule(\n    func: Annotated[Callable[Concatenate[Capsule, P], T], Doc(\"Function to decorate.\")],\n) -> Annotated[RunDtoPassPreRunCapsule[P, T], Doc(\"Decorated function as a `Run` object.\")]:\n    \"\"\"Decorator to pass the pre-run capsule to the function.\n\n    This decorator must be placed closer to the function than the other decorators such as\n    `run`, `context`, `watcher`, and `reporter`.\n\n    The decorated function's first argument must be a `Capsule` object to receive the pre-run capsule.\n\n    With this decorator, you can access the pre-run capsule in the function to get the data recorded by the contexts,\n    such as the Git repository context. Here is an example, assuming the `GitRepositoryContext` for the \"my-repo\"\n    repository is added to the pre-run phase:\n\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.pass_pre_run_capsule\n    def func(pre_run_capsule: capsula.Capsule) -> None:\n        git_sha = pre_run_capsule.data[(\"git\", \"my-repo\")][\"sha\"]\n    ```\n    \"\"\"\n    return RunDtoPassPreRunCapsule(func=func)\n
"},{"location":"reference/capsula/#capsula.reporter","title":"reporter","text":"
reporter(\n    reporter: (\n        ReporterBase\n        | Callable[[CapsuleParams], ReporterBase]\n    ),\n    mode: Literal[\"pre\", \"in\", \"post\", \"all\"],\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    RunDtoNoPassPreRunCapsule[P, T]\n    | RunDtoPassPreRunCapsule[P, T],\n]\n

Decorator to add a reporter to the specified phase of the run.

Example:

import capsula\n\n@capsula.run()\n@capsula.reporter(capsula.JsonDumpReporter.builder(), mode=\"all\")\ndef func() -> None: ...\n

PARAMETER DESCRIPTION reporter

Reporter or a builder function to create a reporter from the capsule parameters.

TYPE: ReporterBase | Callable[[CapsuleParams], ReporterBase]

mode

Phase to add the reporter. Specify 'all' to add to all phases.

TYPE: Literal['pre', 'in', 'post', 'all']

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]]

Decorator to add a reporter to the specified phase of the run.

Source code in capsula/_decorator.py
def reporter(\n    reporter: Annotated[\n        ReporterBase | Callable[[CapsuleParams], ReporterBase],\n        Doc(\"Reporter or a builder function to create a reporter from the capsule parameters.\"),\n    ],\n    mode: Annotated[\n        Literal[\"pre\", \"in\", \"post\", \"all\"],\n        Doc(\"Phase to add the reporter. Specify 'all' to add to all phases.\"),\n    ],\n) -> Annotated[\n    Callable[\n        [Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]],\n        RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ],\n    Doc(\"Decorator to add a reporter to the specified phase of the run.\"),\n]:\n    \"\"\"Decorator to add a reporter to the specified phase of the run.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.reporter(capsula.JsonDumpReporter.builder(), mode=\"all\")\n    def func() -> None: ...\n    ```\n\n    \"\"\"\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]:\n        run = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoPassPreRunCapsule, RunDtoNoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        run.add_reporter(reporter, mode=mode, append_left=True)\n        return run\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.run","title":"run","text":"
run(\n    *,\n    run_name_factory: (\n        Callable[[FuncInfo, str, datetime], str] | None\n    ) = None,\n    ignore_config: bool = False,\n    config_path: Path | str | None = None,\n    vault_dir: Path | str | None = None\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    Run[P, T],\n]\n

Decorator to create a Run object.

Place this decorator at the outermost position of the decorators.

Example:

import capsula\n\n@capsula.run() # This should come first\n@capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\n...  # Add more decorators\ndef func() -> None: ...\n

The vault directory is determined by the following priority: 1. If vault_dir argument is set, it will be used as the vault directory. 2. If ignore_config argument is False and vault-dir field is present in the config file, it will be used as the vault directory. 3. The default vault directory is used.

The run name factory is determined by the following priority: 1. If run_name_factory argument is set, it will be used as the run name. 2. The default run name factory is used.

PARAMETER DESCRIPTION run_name_factory

Function to generate the run name. If not specified, the default run name factory will be used.

TYPE: Callable[[FuncInfo, str, datetime], str] | None DEFAULT: None

ignore_config

Whether to ignore the configuration file.

TYPE: bool DEFAULT: False

config_path

Path to the configuration file. If not specified, the default configuration file will be used.

TYPE: Path | str | None DEFAULT: None

vault_dir

Path to the vault directory.

TYPE: Path | str | None DEFAULT: None

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], Run[P, T]]

Decorator to create a Run object.

Source code in capsula/_decorator.py
def run(  # noqa: C901\n    *,\n    run_name_factory: Annotated[\n        Callable[[FuncInfo, str, datetime], str] | None,\n        Doc(\"Function to generate the run name. If not specified, the default run name factory will be used.\"),\n    ] = None,\n    ignore_config: Annotated[bool, Doc(\"Whether to ignore the configuration file.\")] = False,\n    config_path: Annotated[\n        Path | str | None,\n        Doc(\"Path to the configuration file. If not specified, the default configuration file will be used.\"),\n    ] = None,\n    vault_dir: Annotated[\n        Path | str | None,\n        Doc(\"Path to the vault directory.\"),\n    ] = None,\n) -> Annotated[\n    Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], Run[P, T]],\n    Doc(\"Decorator to create a `Run` object.\"),\n]:\n    \"\"\"Decorator to create a `Run` object.\n\n    Place this decorator at the outermost position of the decorators.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run() # This should come first\n    @capsula.context(capsula.EnvVarContext(\"HOME\"), mode=\"pre\")\n    ...  # Add more decorators\n    def func() -> None: ...\n    ```\n\n    The vault directory is determined by the following priority:\n    1. If `vault_dir` argument is set, it will be used as the vault directory.\n    2. If `ignore_config` argument is False and `vault-dir` field is present in the config file,\n       it will be used as the vault directory.\n    3. The default vault directory is used.\n\n    The run name factory is determined by the following priority:\n    1. If `run_name_factory` argument is set, it will be used as the run name.\n    2. The default run name factory is used.\n\n    \"\"\"\n    if run_name_factory is not None:\n        # Adjust the function signature of the run name factory\n        def _run_name_factory_adjusted(info: ExecInfo | None, random_str: str, timestamp: datetime, /) -> str:\n            if isinstance(info, FuncInfo):\n                return run_name_factory(info, random_str, timestamp)\n            raise TypeError(\"The run name factory must accept the `FuncInfo` object as the first argument.\")\n    else:\n        _run_name_factory_adjusted = default_run_name_factory\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> Run[P, T]:\n        run_dto = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoNoPassPreRunCapsule, RunDtoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        run_dto.run_name_factory = _run_name_factory_adjusted\n        run_dto.vault_dir = Path(vault_dir) if vault_dir is not None else None\n\n        if not ignore_config:\n            config = load_config(get_default_config_path() if config_path is None else Path(config_path))\n            for phase in (\"pre\", \"in\", \"post\"):\n                phase_key = f\"{phase}-run\"\n                if phase_key not in config:\n                    continue\n                for context in reversed(config[phase_key].get(\"contexts\", [])):  # type: ignore[literal-required]\n                    assert phase in {\"pre\", \"post\"}, f\"Invalid phase for context: {phase}\"\n                    run_dto.add_context(context, mode=phase, append_left=True)  # type: ignore[arg-type]\n                for watcher in reversed(config[phase_key].get(\"watchers\", [])):  # type: ignore[literal-required]\n                    assert phase == \"in\", \"Watcher can only be added to the in-run phase.\"\n                    # No need to set append_left=True here, as watchers are added as the outermost context manager\n                    run_dto.add_watcher(watcher, append_left=False)\n                for reporter in reversed(config[phase_key].get(\"reporters\", [])):  # type: ignore[literal-required]\n                    assert phase in {\"pre\", \"in\", \"post\"}, f\"Invalid phase for reporter: {phase}\"\n                    run_dto.add_reporter(reporter, mode=phase, append_left=True)\n\n            run_dto.vault_dir = config[\"vault-dir\"] if run_dto.vault_dir is None else run_dto.vault_dir\n\n        # Set the vault directory if it is not set by the config file\n        if run_dto.vault_dir is None:\n            assert run_dto.func is not None\n            project_root = search_for_project_root(Path(inspect.getfile(run_dto.func)))\n            run_dto.vault_dir = project_root / \"vault\"\n\n        return Run(run_dto)\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.watcher","title":"watcher","text":"
watcher(\n    watcher: (\n        WatcherBase | Callable[[CapsuleParams], WatcherBase]\n    )\n) -> Callable[\n    [\n        Callable[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoPassPreRunCapsule[P, T]\n    ],\n    RunDtoNoPassPreRunCapsule[P, T]\n    | RunDtoPassPreRunCapsule[P, T],\n]\n

Decorator to add a watcher to the in-run phase of the run.

Example:

import capsula\n\n@capsula.run()\n@capsula.watcher(capsula.UncaughtExceptionWatcher())\ndef func() -> None: ...\n

PARAMETER DESCRIPTION watcher

Watcher or a builder function to create a watcher from the capsule parameters.

TYPE: WatcherBase | Callable[[CapsuleParams], WatcherBase]

RETURNS DESCRIPTION Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]]

Decorator to add a watcher to the in-run phase of the run.

Source code in capsula/_decorator.py
def watcher(\n    watcher: Annotated[\n        WatcherBase | Callable[[CapsuleParams], WatcherBase],\n        Doc(\"Watcher or a builder function to create a watcher from the capsule parameters.\"),\n    ],\n) -> Annotated[\n    Callable[\n        [Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]],\n        RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ],\n    Doc(\"Decorator to add a watcher to the in-run phase of the run.\"),\n]:\n    \"\"\"Decorator to add a watcher to the in-run phase of the run.\n\n    Example:\n    ```python\n    import capsula\n\n    @capsula.run()\n    @capsula.watcher(capsula.UncaughtExceptionWatcher())\n    def func() -> None: ...\n    ```\n\n    \"\"\"\n\n    def decorator(\n        func_or_run: Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T],\n    ) -> RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]:\n        run_dto = (\n            func_or_run\n            if isinstance(func_or_run, (RunDtoNoPassPreRunCapsule, RunDtoPassPreRunCapsule))\n            else RunDtoNoPassPreRunCapsule(func=func_or_run)\n        )\n        # No need to set append_left=True here, as watchers are added as the outermost context manager\n        run_dto.add_watcher(watcher, append_left=False)\n        return run_dto\n\n    return decorator\n
"},{"location":"reference/capsula/#capsula.Encapsulator","title":"Encapsulator","text":"
Encapsulator()\n
Source code in capsula/_encapsulator.py
def __init__(self) -> None:\n    self.contexts: OrderedDict[_CapsuleItemKey, ContextBase] = OrderedDict()\n    self.watchers: OrderedDict[_CapsuleItemKey, WatcherBase] = OrderedDict()\n
"},{"location":"reference/capsula/#capsula.Encapsulator.get_current","title":"get_current classmethod","text":"
get_current() -> Self | None\n
Source code in capsula/_encapsulator.py
@classmethod\ndef get_current(cls) -> Self | None:\n    try:\n        return cls._get_context_stack().queue[-1]\n    except IndexError:\n        return None\n
"},{"location":"reference/capsula/#capsula.Encapsulator.contexts","title":"contexts instance-attribute","text":"
contexts: OrderedDict[_CapsuleItemKey, ContextBase] = (\n    OrderedDict()\n)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.watchers","title":"watchers instance-attribute","text":"
watchers: OrderedDict[_CapsuleItemKey, WatcherBase] = (\n    OrderedDict()\n)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.__enter__","title":"__enter__","text":"
__enter__() -> Self\n
Source code in capsula/_encapsulator.py
def __enter__(self) -> Self:\n    self._get_context_stack().put(self)\n    return self\n
"},{"location":"reference/capsula/#capsula.Encapsulator.__exit__","title":"__exit__","text":"
__exit__(\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None\n
PARAMETER DESCRIPTION exc_type

TYPE: type[BaseException] | None

exc_value

TYPE: BaseException | None

traceback

TYPE: TracebackType | None

Source code in capsula/_encapsulator.py
def __exit__(\n    self,\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None:\n    self._get_context_stack().get(block=False)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.add_context","title":"add_context","text":"
add_context(\n    context: ContextBase, key: _CapsuleItemKey | None = None\n) -> None\n
PARAMETER DESCRIPTION context

TYPE: ContextBase

key

TYPE: _CapsuleItemKey | None DEFAULT: None

Source code in capsula/_encapsulator.py
def add_context(self, context: ContextBase, key: _CapsuleItemKey | None = None) -> None:\n    if key is None:\n        key = context.default_key()\n    if key in self.contexts or key in self.watchers:\n        raise KeyConflictError(key)\n    self.contexts[key] = context\n
"},{"location":"reference/capsula/#capsula.Encapsulator.record","title":"record","text":"
record(key: _CapsuleItemKey, record: Any) -> None\n
PARAMETER DESCRIPTION key

TYPE: _CapsuleItemKey

record

TYPE: Any

Source code in capsula/_encapsulator.py
def record(self, key: _CapsuleItemKey, record: Any) -> None:\n    self.add_context(ObjectContext(record), key)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.add_watcher","title":"add_watcher","text":"
add_watcher(\n    watcher: WatcherBase, key: _CapsuleItemKey | None = None\n) -> None\n
PARAMETER DESCRIPTION watcher

TYPE: WatcherBase

key

TYPE: _CapsuleItemKey | None DEFAULT: None

Source code in capsula/_encapsulator.py
def add_watcher(self, watcher: WatcherBase, key: _CapsuleItemKey | None = None) -> None:\n    if key is None:\n        key = watcher.default_key()\n    if key in self.contexts or key in self.watchers:\n        raise KeyConflictError(key)\n    self.watchers[key] = watcher\n
"},{"location":"reference/capsula/#capsula.Encapsulator.encapsulate","title":"encapsulate","text":"
encapsulate() -> Capsule\n
Source code in capsula/_encapsulator.py
def encapsulate(self) -> Capsule:\n    data = {}\n    fails = {}\n    for key, capsule_item in chain(self.contexts.items(), self.watchers.items()):\n        try:\n            data[key] = capsule_item.encapsulate()\n        except Exception as e:  # noqa: PERF203\n            if capsule_item.abort_on_error:\n                raise\n            warnings.warn(f\"Error occurred during encapsulation of {key}: {e}. Skipping.\", stacklevel=3)\n            fails[key] = ExceptionInfo.from_exception(e)\n    return Capsule(data, fails)\n
"},{"location":"reference/capsula/#capsula.Encapsulator.watch","title":"watch","text":"
watch() -> WatcherGroup[_CapsuleItemKey, WatcherBase]\n
Source code in capsula/_encapsulator.py
def watch(self) -> WatcherGroup[_CapsuleItemKey, WatcherBase]:\n    return WatcherGroup(self.watchers)\n
"},{"location":"reference/capsula/#capsula.CapsulaConfigurationError","title":"CapsulaConfigurationError","text":"

Bases: CapsulaError

"},{"location":"reference/capsula/#capsula.CapsulaError","title":"CapsulaError","text":"

Bases: Exception

"},{"location":"reference/capsula/#capsula.CapsulaUninitializedError","title":"CapsulaUninitializedError","text":"
CapsulaUninitializedError(*uninitialized_names: str)\n

Bases: CapsulaError

PARAMETER DESCRIPTION *uninitialized_names

TYPE: str DEFAULT: ()

Source code in capsula/_exceptions.py
def __init__(self, *uninitialized_names: str) -> None:\n    super().__init__(f\"Uninitialized objects: {', '.join(uninitialized_names)}\")\n
"},{"location":"reference/capsula/#capsula.JsonDumpReporter","title":"JsonDumpReporter","text":"
JsonDumpReporter(\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True\n)\n

Bases: ReporterBase

Reporter to dump the capsule to a JSON file.

PARAMETER DESCRIPTION path

TYPE: Path | str

default

TYPE: Callable[[Any], Any] | None DEFAULT: None

option

TYPE: int | None DEFAULT: None

mkdir

TYPE: bool DEFAULT: True

Source code in capsula/_reporter/_json.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True,\n) -> None:\n    self._path = Path(path)\n    if mkdir:\n        self._path.parent.mkdir(parents=True, exist_ok=True)\n\n    if default is None:\n        self._default_for_encoder = default_preset\n    else:\n\n        def _default(obj: Any) -> Any:\n            try:\n                return default_preset(obj)\n            except TypeError:\n                return default(obj)\n\n        self._default_for_encoder = _default\n\n    self._option = option\n
"},{"location":"reference/capsula/#capsula.JsonDumpReporter.builder","title":"builder classmethod","text":"
builder(\n    *, option: int | None = None\n) -> Callable[[CapsuleParams], JsonDumpReporter]\n
PARAMETER DESCRIPTION option

Option to pass to orjson.dumps. If not provided, orjson.OPT_INDENT_2 will be used.

TYPE: int | None DEFAULT: None

Source code in capsula/_reporter/_json.py
@classmethod\ndef builder(\n    cls,\n    *,\n    option: Annotated[\n        int | None,\n        Doc(\"Option to pass to `orjson.dumps`. If not provided, `orjson.OPT_INDENT_2` will be used.\"),\n    ] = None,\n) -> Callable[[CapsuleParams], JsonDumpReporter]:\n    def build(params: CapsuleParams) -> JsonDumpReporter:\n        return cls(\n            params.run_dir / f\"{params.phase}-run-report.json\",\n            option=orjson.OPT_INDENT_2 if option is None else option,\n        )\n\n    return build\n
"},{"location":"reference/capsula/#capsula.JsonDumpReporter.report","title":"report","text":"
report(capsule: Capsule) -> None\n
PARAMETER DESCRIPTION capsule

TYPE: Capsule

Source code in capsula/_reporter/_json.py
def report(self, capsule: Capsule) -> None:\n    logger.debug(f\"Dumping capsule to {self._path}\")\n\n    def _str_to_tuple(s: str | tuple[str, ...]) -> tuple[str, ...]:\n        if isinstance(s, str):\n            return (s,)\n        return s\n\n    nested_data = to_nested_dict({_str_to_tuple(k): v for k, v in capsule.data.items()})\n    if capsule.fails:\n        nested_data[\"__fails\"] = to_nested_dict({_str_to_tuple(k): v for k, v in capsule.fails.items()})\n\n    json_bytes = orjson.dumps(nested_data, default=self._default_for_encoder, option=self._option)\n    self._path.write_bytes(json_bytes)\n
"},{"location":"reference/capsula/#capsula.ReporterBase","title":"ReporterBase","text":"

Bases: ABC

"},{"location":"reference/capsula/#capsula.ReporterBase.get_subclass","title":"get_subclass classmethod","text":"
get_subclass(name: str) -> type[ReporterBase]\n
PARAMETER DESCRIPTION name

TYPE: str

Source code in capsula/_reporter/_base.py
@classmethod\ndef get_subclass(cls, name: str) -> type[ReporterBase]:\n    return cls._subclass_registry[name]\n
"},{"location":"reference/capsula/#capsula.ReporterBase.report","title":"report abstractmethod","text":"
report(capsule: Capsule) -> None\n
PARAMETER DESCRIPTION capsule

TYPE: Capsule

Source code in capsula/_reporter/_base.py
@abstractmethod\ndef report(self, capsule: Capsule) -> None:\n    raise NotImplementedError\n
"},{"location":"reference/capsula/#capsula.ReporterBase.builder","title":"builder classmethod","text":"
builder(\n    *args: Any, **kwargs: Any\n) -> Callable[[CapsuleParams], Self]\n
PARAMETER DESCRIPTION *args

TYPE: Any DEFAULT: ()

**kwargs

TYPE: Any DEFAULT: {}

Source code in capsula/_reporter/_base.py
@classmethod\ndef builder(cls, *args: Any, **kwargs: Any) -> Callable[[CapsuleParams], Self]:\n    def build(params: CapsuleParams) -> Self:  # type: ignore[type-var,misc] # noqa: ARG001\n        return cls(*args, **kwargs)\n\n    return build\n
"},{"location":"reference/capsula/#capsula.SlackReporter","title":"SlackReporter","text":"
SlackReporter(\n    *,\n    phase: Literal[\"pre\", \"in\", \"post\"],\n    channel: str,\n    token: str,\n    run_name: str\n)\n

Bases: ReporterBase

Reporter to send a message to a Slack channel.

PARAMETER DESCRIPTION phase

TYPE: Literal['pre', 'in', 'post']

channel

TYPE: str

token

TYPE: str

run_name

TYPE: str

Source code in capsula/_reporter/_slack.py
def __init__(self, *, phase: Literal[\"pre\", \"in\", \"post\"], channel: str, token: str, run_name: str) -> None:\n    self._phase = phase\n    self._channel = channel\n    self._token = token\n    self._run_name = run_name\n
"},{"location":"reference/capsula/#capsula.SlackReporter.builder","title":"builder classmethod","text":"
builder(\n    *, channel: str, token: str\n) -> Callable[[CapsuleParams], SlackReporter]\n
PARAMETER DESCRIPTION channel

TYPE: str

token

TYPE: str

Source code in capsula/_reporter/_slack.py
@classmethod\ndef builder(\n    cls,\n    *,\n    channel: str,\n    token: str,\n) -> Callable[[CapsuleParams], SlackReporter]:\n    def build(params: CapsuleParams) -> SlackReporter:\n        return cls(phase=params.phase, channel=channel, token=token, run_name=params.run_name)\n\n    return build\n
"},{"location":"reference/capsula/#capsula.SlackReporter.report","title":"report","text":"
report(capsule: Capsule) -> None\n
PARAMETER DESCRIPTION capsule

TYPE: Capsule

Source code in capsula/_reporter/_slack.py
def report(self, capsule: Capsule) -> None:  # noqa: ARG002\n    client = WebClient(token=self._token)\n    thread_ts = SlackReporter._run_name_to_thread_ts.get(self._run_name)\n    if self._phase == \"pre\":\n        message = f\"Capsule run `{self._run_name}` started\"\n        response = client.chat_postMessage(channel=self._channel, text=message, thread_ts=thread_ts)\n        SlackReporter._run_name_to_thread_ts[self._run_name] = response[\"ts\"]\n    elif self._phase == \"in\":\n        pass  # Do nothing for now\n    elif self._phase == \"post\":\n        message = f\"Capsule run `{self._run_name}` completed\"\n        response = client.chat_postMessage(\n            channel=self._channel,\n            text=message,\n            thread_ts=thread_ts,\n        )\n        SlackReporter._run_name_to_thread_ts[self._run_name] = response[\"ts\"]\n
"},{"location":"reference/capsula/#capsula.current_run_name","title":"current_run_name","text":"
current_run_name() -> str\n

Get the name of the current run, which is also the name of the run directory in the vault directory.

RuntimeError will be raised if no active run is found.

Source code in capsula/_root.py
def current_run_name() -> str:\n    \"\"\"Get the name of the current run, which is also the name of the run directory in the `vault` directory.\n\n    `RuntimeError` will be raised if no active run is found.\n    \"\"\"\n    run: Run[Any, Any] | None = Run.get_current()\n    if run is None:\n        msg = \"No active run found.\"\n        raise RuntimeError(msg)\n    return run.run_dir.name\n
"},{"location":"reference/capsula/#capsula.record","title":"record","text":"
record(key: _CapsuleItemKey, value: Any) -> None\n

Record a value to the current encapsulator.

See the example below:

import random\nimport capsula\n\n@capsula.run()\ndef calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n    random.seed(seed)\n    xs = (random.random() for _ in range(n_samples))\n    ys = (random.random() for _ in range(n_samples))\n    inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n    pi_estimate = (4.0 * inside) / n_samples\n\n    # You can record values to the capsule using the `record` method.\n    capsula.record(\"pi_estimate\", pi_estimate)\n\nif __name__ == \"__main__\":\n    calculate_pi(n_samples=1_000)\n

The in-run-report.json generated by the JsonDumpReporter will contain the following:

{\n\"pi_estimate\": 3.128\n}\n
PARAMETER DESCRIPTION key

The key to use for the value.

TYPE: _CapsuleItemKey

value

The value to record.

TYPE: Any

Source code in capsula/_root.py
def record(\n    key: Annotated[_CapsuleItemKey, Doc(\"The key to use for the value.\")],\n    value: Annotated[Any, Doc(\"The value to record.\")],\n) -> None:\n    \"\"\"Record a value to the current encapsulator.\n\n    See the example below:\n\n    ```python\n    import random\n    import capsula\n\n    @capsula.run()\n    def calculate_pi(n_samples: int = 1_000, seed: int = 42) -> None:\n        random.seed(seed)\n        xs = (random.random() for _ in range(n_samples))\n        ys = (random.random() for _ in range(n_samples))\n        inside = sum(x * x + y * y <= 1.0 for x, y in zip(xs, ys))\n        pi_estimate = (4.0 * inside) / n_samples\n\n        # You can record values to the capsule using the `record` method.\n        capsula.record(\"pi_estimate\", pi_estimate)\n\n    if __name__ == \"__main__\":\n        calculate_pi(n_samples=1_000)\n    ```\n\n    The `in-run-report.json` generated by the `JsonDumpReporter` will contain the following:\n\n    ```json\n    {\n    \"pi_estimate\": 3.128\n    }\n    ```\n    \"\"\"\n    enc = Encapsulator.get_current()\n    if enc is None:\n        msg = \"No active encapsulator found.\"\n        raise RuntimeError(msg)\n    enc.record(key, value)\n
"},{"location":"reference/capsula/#capsula.CapsuleParams","title":"CapsuleParams dataclass","text":"
CapsuleParams(\n    exec_info: FuncInfo | CommandInfo | None,\n    run_name: str,\n    run_dir: Path,\n    phase: Literal[\"pre\", \"in\", \"post\"],\n    project_root: Path,\n)\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.exec_info","title":"exec_info instance-attribute","text":"
exec_info: FuncInfo | CommandInfo | None\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.run_name","title":"run_name instance-attribute","text":"
run_name: str\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.run_dir","title":"run_dir instance-attribute","text":"
run_dir: Path\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.phase","title":"phase instance-attribute","text":"
phase: Literal['pre', 'in', 'post']\n
"},{"location":"reference/capsula/#capsula.CapsuleParams.project_root","title":"project_root instance-attribute","text":"
project_root: Path\n
"},{"location":"reference/capsula/#capsula.CommandInfo","title":"CommandInfo dataclass","text":"
CommandInfo(command: tuple[str, ...])\n
"},{"location":"reference/capsula/#capsula.CommandInfo.command","title":"command instance-attribute","text":"
command: tuple[str, ...]\n
"},{"location":"reference/capsula/#capsula.FuncInfo","title":"FuncInfo dataclass","text":"
FuncInfo(\n    func: Callable[..., Any],\n    args: tuple[Any, ...],\n    kwargs: dict[str, Any],\n    pass_pre_run_capsule: bool,\n)\n
"},{"location":"reference/capsula/#capsula.FuncInfo.func","title":"func instance-attribute","text":"
func: Callable[..., Any]\n
"},{"location":"reference/capsula/#capsula.FuncInfo.args","title":"args instance-attribute","text":"
args: tuple[Any, ...]\n
"},{"location":"reference/capsula/#capsula.FuncInfo.kwargs","title":"kwargs instance-attribute","text":"
kwargs: dict[str, Any]\n
"},{"location":"reference/capsula/#capsula.FuncInfo.pass_pre_run_capsule","title":"pass_pre_run_capsule instance-attribute","text":"
pass_pre_run_capsule: bool\n
"},{"location":"reference/capsula/#capsula.FuncInfo.bound_args","title":"bound_args property","text":"
bound_args: OrderedDict[str, Any]\n
"},{"location":"reference/capsula/#capsula.Run","title":"Run","text":"
Run(\n    run_dto: (\n        RunDtoPassPreRunCapsule[P, T]\n        | RunDtoNoPassPreRunCapsule[P, T]\n        | RunDtoCommand\n    )\n)\n

Bases: Generic[P, T]

PARAMETER DESCRIPTION run_dto

TYPE: RunDtoPassPreRunCapsule[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoCommand

Source code in capsula/_run.py
def __init__(\n    self,\n    run_dto: RunDtoPassPreRunCapsule[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoCommand,\n    /,\n) -> None:\n    self._pre_run_context_generators = run_dto.pre_run_context_generators\n    self._in_run_watcher_generators = run_dto.in_run_watcher_generators\n    self._post_run_context_generators = run_dto.post_run_context_generators\n\n    self._pre_run_reporter_generators = run_dto.pre_run_reporter_generators\n    self._in_run_reporter_generators = run_dto.in_run_reporter_generators\n    self._post_run_reporter_generators = run_dto.post_run_reporter_generators\n\n    self._pass_pre_run_capsule: bool = isinstance(run_dto, RunDtoPassPreRunCapsule)\n\n    if run_dto.run_name_factory is None:\n        raise CapsulaUninitializedError(\"run_name_factory\")\n    self._run_name_factory: Callable[[ExecInfo | None, str, datetime], str] = run_dto.run_name_factory\n\n    if run_dto.vault_dir is None:\n        raise CapsulaUninitializedError(\"vault_dir\")\n    self._vault_dir: Path = run_dto.vault_dir\n\n    self._run_dir: Path | None = None\n\n    if isinstance(run_dto, RunDtoCommand):\n        if run_dto.command is None:\n            raise CapsulaUninitializedError(\"command\")\n        self._func: Callable[P, T] | Callable[Concatenate[Capsule, P], T] | None = None\n        self._command: tuple[str, ...] | None = run_dto.command\n    elif isinstance(run_dto, (RunDtoPassPreRunCapsule, RunDtoNoPassPreRunCapsule)):\n        if run_dto.func is None:\n            raise CapsulaUninitializedError(\"func\")\n        self._func = run_dto.func\n        self._command = None\n    else:\n        msg = \"run_dto must be an instance of RunDtoCommand, RunDtoPassPreRunCapsule, or RunDtoNoPassPreRunCapsule,\"\n        \" not {type(run_dto)}.\"\n        raise TypeError(msg)\n
"},{"location":"reference/capsula/#capsula.Run.get_current","title":"get_current classmethod","text":"
get_current() -> Self\n
Source code in capsula/_run.py
@classmethod\ndef get_current(cls) -> Self:\n    try:\n        return cls._get_run_stack().queue[-1]\n    except IndexError as e:\n        raise CapsulaNoRunError from e\n
"},{"location":"reference/capsula/#capsula.Run.run_dir","title":"run_dir property","text":"
run_dir: Path\n
"},{"location":"reference/capsula/#capsula.Run.__enter__","title":"__enter__","text":"
__enter__() -> Self\n
Source code in capsula/_run.py
def __enter__(self) -> Self:\n    self._get_run_stack().put(self)\n    return self\n
"},{"location":"reference/capsula/#capsula.Run.__exit__","title":"__exit__","text":"
__exit__(\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None\n
PARAMETER DESCRIPTION exc_type

TYPE: type[BaseException] | None

exc_value

TYPE: BaseException | None

traceback

TYPE: TracebackType | None

Source code in capsula/_run.py
def __exit__(\n    self,\n    exc_type: type[BaseException] | None,\n    exc_value: BaseException | None,\n    traceback: TracebackType | None,\n) -> None:\n    self._get_run_stack().get(block=False)\n
"},{"location":"reference/capsula/#capsula.Run.pre_run","title":"pre_run","text":"
pre_run(\n    exec_info: ExecInfo,\n) -> tuple[CapsuleParams, Capsule]\n
PARAMETER DESCRIPTION exec_info

TYPE: ExecInfo

Source code in capsula/_run.py
def pre_run(self, exec_info: ExecInfo) -> tuple[CapsuleParams, Capsule]:\n    if self._vault_dir.exists():\n        if not self._vault_dir.is_dir():\n            msg = f\"Vault directory {self._vault_dir} exists but is not a directory.\"\n            raise CapsulaError(msg)\n    else:\n        self._vault_dir.mkdir(parents=True, exist_ok=False)\n        # If this is a new vault directory, create a .gitignore file in it\n        # and write \"*\" to it\n        gitignore_path = self._vault_dir / \".gitignore\"\n        with gitignore_path.open(\"w\") as gitignore_file:\n            gitignore_file.write(\"*\\n\")\n    logger.info(f\"Vault directory: {self._vault_dir}\")\n\n    # Generate the run name\n    run_name = self._run_name_factory(\n        exec_info,\n        \"\".join(choices(ascii_letters + digits, k=4)),\n        datetime.now(timezone.utc),\n    )\n    logger.info(f\"Run name: {run_name}\")\n\n    self._run_dir = self._vault_dir / run_name\n    try:\n        self._run_dir.mkdir(parents=True, exist_ok=False)\n    except FileExistsError:\n        logger.exception(\n            f\"Run directory {self._run_dir} already exists. Aborting to prevent overwriting existing data. \"\n            \"Make sure that run_name_factory produces unique names.\",\n        )\n        raise\n    logger.info(f\"Run directory: {self._run_dir}\")\n\n    params = CapsuleParams(\n        exec_info=exec_info,\n        run_name=run_name,\n        run_dir=self._run_dir,\n        phase=\"pre\",\n        project_root=get_project_root(exec_info),\n    )\n\n    pre_run_enc = Encapsulator()\n    for context_generator in self._pre_run_context_generators:\n        context = context_generator(params)\n        pre_run_enc.add_context(context)\n    pre_run_capsule = pre_run_enc.encapsulate()\n    for reporter_generator in self._pre_run_reporter_generators:\n        reporter = reporter_generator(params)\n        reporter.report(pre_run_capsule)\n\n    return params, pre_run_capsule\n
"},{"location":"reference/capsula/#capsula.Run.post_run","title":"post_run","text":"
post_run(params: CapsuleParams) -> Capsule\n
PARAMETER DESCRIPTION params

TYPE: CapsuleParams

Source code in capsula/_run.py
def post_run(self, params: CapsuleParams) -> Capsule:\n    params.phase = \"post\"\n    post_run_enc = Encapsulator()\n    for context_generator in self._post_run_context_generators:\n        context = context_generator(params)\n        post_run_enc.add_context(context)\n    post_run_capsule = post_run_enc.encapsulate()\n    for reporter_generator in self._post_run_reporter_generators:\n        reporter = reporter_generator(params)\n        try:\n            reporter.report(post_run_capsule)\n        except Exception:\n            logger.exception(f\"Failed to report post-run capsule with reporter {reporter}.\")\n\n    return post_run_capsule\n
"},{"location":"reference/capsula/#capsula.Run.in_run","title":"in_run","text":"
in_run(params: CapsuleParams, func: Callable[[], _T]) -> _T\n
PARAMETER DESCRIPTION params

TYPE: CapsuleParams

func

TYPE: Callable[[], _T]

Source code in capsula/_run.py
def in_run(self, params: CapsuleParams, func: Callable[[], _T]) -> _T:\n    params.phase = \"in\"\n    in_run_enc = Encapsulator()\n    for watcher_generator in self._in_run_watcher_generators:\n        watcher = watcher_generator(params)\n        in_run_enc.add_watcher(watcher)\n\n    with self, in_run_enc, in_run_enc.watch():\n        result = func()\n\n    in_run_capsule = in_run_enc.encapsulate()\n    for reporter_generator in self._in_run_reporter_generators:\n        reporter = reporter_generator(params)\n        try:\n            reporter.report(in_run_capsule)\n        except Exception:\n            logger.exception(f\"Failed to report in-run capsule with reporter {reporter}.\")\n\n    return result\n
"},{"location":"reference/capsula/#capsula.Run.__call__","title":"__call__","text":"
__call__(*args: args, **kwargs: kwargs) -> T\n
PARAMETER DESCRIPTION *args

TYPE: args DEFAULT: ()

**kwargs

TYPE: kwargs DEFAULT: {}

Source code in capsula/_run.py
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:\n    assert self._func is not None\n    func_info = FuncInfo(func=self._func, args=args, kwargs=kwargs, pass_pre_run_capsule=self._pass_pre_run_capsule)\n    params, pre_run_capsule = self.pre_run(func_info)\n\n    if self._pass_pre_run_capsule:\n\n        def _func_1() -> T:\n            assert self._func is not None\n            return self._func(pre_run_capsule, *args, **kwargs)  # type: ignore[arg-type]\n\n        func = _func_1\n    else:\n\n        def _func_2() -> T:\n            assert self._func is not None\n            return self._func(*args, **kwargs)  # type: ignore[arg-type]\n\n        func = _func_2\n\n    try:\n        result = self.in_run(params, func)\n    finally:\n        _post_run_capsule = self.post_run(params)\n\n    return result\n
"},{"location":"reference/capsula/#capsula.Run.exec_command","title":"exec_command","text":"
exec_command() -> (\n    tuple[CompletedProcess[str], CapsuleParams]\n)\n
Source code in capsula/_run.py
def exec_command(self) -> tuple[subprocess.CompletedProcess[str], CapsuleParams]:\n    assert self._command is not None\n    command_info = CommandInfo(command=self._command)\n    params, _pre_run_capsule = self.pre_run(command_info)\n\n    def func() -> subprocess.CompletedProcess[str]:\n        assert self._command is not None\n        return subprocess.run(self._command, check=False, capture_output=True, text=True)  # noqa: S603\n\n    try:\n        result = self.in_run(params, func)\n    finally:\n        _post_run_capsule = self.post_run(params)\n\n    return result, params\n
"},{"location":"reference/capsula/#capsula.search_for_project_root","title":"search_for_project_root","text":"
search_for_project_root(start: Path | str) -> Path\n

Search for the project root directory by looking for pyproject.toml.

PARAMETER DESCRIPTION start

The start directory to search.

TYPE: Path | str

RETURNS DESCRIPTION Path

The project root directory.

Source code in capsula/_utils.py
def search_for_project_root(\n    start: Annotated[Path | str, Doc(\"The start directory to search.\")],\n) -> Annotated[Path, Doc(\"The project root directory.\")]:\n    \"\"\"Search for the project root directory by looking for pyproject.toml.\"\"\"\n    # TODO: Allow projects without pyproject.toml file\n    start = Path(start)\n    if (start / \"pyproject.toml\").exists():\n        return start\n    if start == start.parent:\n        msg = \"Project root not found.\"\n        raise FileNotFoundError(msg)\n    return search_for_project_root(start.resolve().parent)\n
"},{"location":"reference/capsula/#capsula.__version__","title":"__version__ module-attribute","text":"
__version__: str = version('capsula')\n

Capsula version.

"},{"location":"reference/capsula/#capsula.TimeWatcher","title":"TimeWatcher","text":"
TimeWatcher(name: str = 'execution_time')\n

Bases: WatcherBase

PARAMETER DESCRIPTION name

Name of the time watcher. Used as a key in the output.

TYPE: str DEFAULT: 'execution_time'

Source code in capsula/_watcher/_time.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the time watcher. Used as a key in the output.\")] = \"execution_time\",\n) -> None:\n    self._name = name\n    self._duration: timedelta | None = None\n
"},{"location":"reference/capsula/#capsula.TimeWatcher.encapsulate","title":"encapsulate","text":"
encapsulate() -> timedelta | None\n
Source code in capsula/_watcher/_time.py
def encapsulate(self) -> timedelta | None:\n    return self._duration\n
"},{"location":"reference/capsula/#capsula.TimeWatcher.watch","title":"watch","text":"
watch() -> Iterator[None]\n
Source code in capsula/_watcher/_time.py
@contextmanager\ndef watch(self) -> Iterator[None]:\n    start = time.perf_counter()\n    try:\n        yield\n    finally:\n        end = time.perf_counter()\n        self._duration = timedelta(seconds=end - start)\n        logger.debug(f\"TimeWatcher: {self._name} took {self._duration}.\")\n
"},{"location":"reference/capsula/#capsula.TimeWatcher.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_watcher/_time.py
def default_key(self) -> tuple[str, str]:\n    return (\"time\", self._name)\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher","title":"UncaughtExceptionWatcher","text":"
UncaughtExceptionWatcher(\n    name: str = \"exception\",\n    *,\n    base: type[BaseException] = Exception\n)\n

Bases: WatcherBase

Watcher to capture an uncaught exception.

This watcher captures an uncaught exception and stores it in the context. Note that it does not consume the exception, so it will still be raised.

PARAMETER DESCRIPTION name

Name of the exception. Used as a key in the output.

TYPE: str DEFAULT: 'exception'

base

Base exception class to catch.

TYPE: type[BaseException] DEFAULT: Exception

Source code in capsula/_watcher/_exception.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the exception. Used as a key in the output.\")] = \"exception\",\n    *,\n    base: Annotated[type[BaseException], Doc(\"Base exception class to catch.\")] = Exception,\n) -> None:\n    self._name = name\n    self._base = base\n    self._exception: BaseException | None = None\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher.encapsulate","title":"encapsulate","text":"
encapsulate() -> ExceptionInfo\n
Source code in capsula/_watcher/_exception.py
def encapsulate(self) -> ExceptionInfo:\n    return ExceptionInfo.from_exception(self._exception)\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher.watch","title":"watch","text":"
watch() -> Iterator[None]\n
Source code in capsula/_watcher/_exception.py
@contextmanager\ndef watch(self) -> Iterator[None]:\n    self._exception = None\n    try:\n        yield\n    except self._base as e:\n        logger.debug(f\"UncaughtExceptionWatcher: {self._name} observed exception: {e}\")\n        self._exception = e\n        raise\n
"},{"location":"reference/capsula/#capsula.UncaughtExceptionWatcher.default_key","title":"default_key","text":"
default_key() -> tuple[str, str]\n
Source code in capsula/_watcher/_exception.py
def default_key(self) -> tuple[str, str]:\n    return (\"exception\", self._name)\n
"},{"location":"reference/capsula/#capsula.WatcherBase","title":"WatcherBase","text":"

Bases: CapsuleItem, ABC

"},{"location":"reference/capsula/#capsula.WatcherBase.abort_on_error","title":"abort_on_error property","text":"
abort_on_error: bool\n
"},{"location":"reference/capsula/#capsula.WatcherBase.get_subclass","title":"get_subclass classmethod","text":"
get_subclass(name: str) -> type[WatcherBase]\n
PARAMETER DESCRIPTION name

TYPE: str

Source code in capsula/_watcher/_base.py
@classmethod\ndef get_subclass(cls, name: str) -> type[WatcherBase]:\n    return cls._subclass_registry[name]\n
"},{"location":"reference/capsula/#capsula.WatcherBase.watch","title":"watch abstractmethod","text":"
watch() -> AbstractContextManager[None]\n
Source code in capsula/_watcher/_base.py
@abstractmethod\ndef watch(self) -> AbstractContextManager[None]:\n    raise NotImplementedError\n
"},{"location":"reporters/","title":"Built-in reporters","text":"

Capsula provides several built-in reporters that report the captured contexts.

"},{"location":"reporters/json_dump/","title":"JsonDumpReporter","text":"

The JsonDumpReporter reports the capsule in JSON format. It can be created using the capsula.JsonDumpReporter.builder method or the capsula.JsonDumpReporter.__init__ method.

"},{"location":"reporters/json_dump/#capsula.JsonDumpReporter.builder","title":"capsula.JsonDumpReporter.builder classmethod","text":"
builder(\n    *, option: int | None = None\n) -> Callable[[CapsuleParams], JsonDumpReporter]\n
PARAMETER DESCRIPTION option

Option to pass to orjson.dumps. If not provided, orjson.OPT_INDENT_2 will be used.

TYPE: int | None DEFAULT: None

Source code in capsula/_reporter/_json.py
@classmethod\ndef builder(\n    cls,\n    *,\n    option: Annotated[\n        int | None,\n        Doc(\"Option to pass to `orjson.dumps`. If not provided, `orjson.OPT_INDENT_2` will be used.\"),\n    ] = None,\n) -> Callable[[CapsuleParams], JsonDumpReporter]:\n    def build(params: CapsuleParams) -> JsonDumpReporter:\n        return cls(\n            params.run_dir / f\"{params.phase}-run-report.json\",\n            option=orjson.OPT_INDENT_2 if option is None else option,\n        )\n\n    return build\n
"},{"location":"reporters/json_dump/#capsula.JsonDumpReporter.__init__","title":"capsula.JsonDumpReporter.__init__","text":"
__init__(\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True\n)\n
PARAMETER DESCRIPTION path

TYPE: Path | str

default

TYPE: Callable[[Any], Any] | None DEFAULT: None

option

TYPE: int | None DEFAULT: None

mkdir

TYPE: bool DEFAULT: True

Source code in capsula/_reporter/_json.py
def __init__(\n    self,\n    path: Path | str,\n    *,\n    default: Callable[[Any], Any] | None = None,\n    option: int | None = None,\n    mkdir: bool = True,\n) -> None:\n    self._path = Path(path)\n    if mkdir:\n        self._path.parent.mkdir(parents=True, exist_ok=True)\n\n    if default is None:\n        self._default_for_encoder = default_preset\n    else:\n\n        def _default(obj: Any) -> Any:\n            try:\n                return default_preset(obj)\n            except TypeError:\n                return default(obj)\n\n        self._default_for_encoder = _default\n\n    self._option = option\n
"},{"location":"reporters/json_dump/#configuration-example","title":"Configuration example","text":""},{"location":"reporters/json_dump/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[in-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n\n[post-run]\nreporters = [{ type = \"JsonDumpReporter\" }]\n
"},{"location":"reporters/json_dump/#via-capsulareporter-decorator","title":"Via @capsula.reporter decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.reporter(capsula.JsonDumpReporter.builder(), mode=\"all\")\ndef func(): ...\n
"},{"location":"reporters/json_dump/#output","title":"Output","text":"

It will output the pre-run, in-run, and post-run capsules to in-run-report.json, pre-run-report.json, and post-run-report.json in the run directory, respectively.

"},{"location":"reporters/slack/","title":"SlackReporter","text":"

Note

The SlackReporter is still in development and lacks some features, such as specifying the token via environment variables, file attachments, detailed reporting, and reporting of the in-run capsule.

The SlackReporter reports the capsule to a Slack channel. Capsules with the same run name will be reported to the same thread.

It can be created using the capsula.SlackReporter.builder method or the capsula.SlackReporter.__init__ method.

"},{"location":"reporters/slack/#capsula.SlackReporter.builder","title":"capsula.SlackReporter.builder classmethod","text":"
builder(\n    *, channel: str, token: str\n) -> Callable[[CapsuleParams], SlackReporter]\n
PARAMETER DESCRIPTION channel

TYPE: str

token

TYPE: str

Source code in capsula/_reporter/_slack.py
@classmethod\ndef builder(\n    cls,\n    *,\n    channel: str,\n    token: str,\n) -> Callable[[CapsuleParams], SlackReporter]:\n    def build(params: CapsuleParams) -> SlackReporter:\n        return cls(phase=params.phase, channel=channel, token=token, run_name=params.run_name)\n\n    return build\n
"},{"location":"reporters/slack/#capsula.SlackReporter.__init__","title":"capsula.SlackReporter.__init__","text":"
__init__(\n    *,\n    phase: Literal[\"pre\", \"in\", \"post\"],\n    channel: str,\n    token: str,\n    run_name: str\n)\n
PARAMETER DESCRIPTION phase

TYPE: Literal['pre', 'in', 'post']

channel

TYPE: str

token

TYPE: str

run_name

TYPE: str

Source code in capsula/_reporter/_slack.py
def __init__(self, *, phase: Literal[\"pre\", \"in\", \"post\"], channel: str, token: str, run_name: str) -> None:\n    self._phase = phase\n    self._channel = channel\n    self._token = token\n    self._run_name = run_name\n
"},{"location":"reporters/slack/#configuration-example","title":"Configuration example","text":""},{"location":"reporters/slack/#via-capsulatoml","title":"Via capsula.toml","text":"
[pre-run]\nreporters = [{ type = \"SlackReporter\", channel = \"<channel>\", token = \"<token>\" }]\n\n[in-run] # the reporting of an in-run capsule is not yet supported\nreporters = [{ type = \"SlackReporter\", channel = \"<channel>\", token = \"<token>\" }]\n\n[post-run]\nreporters = [{ type = \"SlackReporter\", channel = \"<channel>\", token = \"<token>\" }]\n
"},{"location":"reporters/slack/#via-capsulareporter-decorator","title":"Via @capsula.reporter decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.reporter(capsula.SlackReporter.builder(channel=\"<channel>\", token=\"<token>\"), mode=\"all\")\ndef func(): ...\n
"},{"location":"reporters/slack/#output","title":"Output","text":"

It will send a simple message to the specified Slack channel.

For example, for the pre-run capsule:

Capsule run `calculate_pi_n_samples_1000_seed_42_20240923_195220_Tplk` started\n

For the post-run capsule:

Capsule run `calculate_pi_n_samples_1000_seed_42_20240923_195356_GNDq` completed\n
"},{"location":"watchers/","title":"Built-in watchers","text":"

Capsula provides several built-in watchers that you can use to monitor the execution of your command/function. The following is a list of built-in watchers:

"},{"location":"watchers/time/","title":"TimeWatcher","text":"

The TimeWatcher monitors the execution time of the command/function. It can be created using the capsula.TimeWatcher.__init__ method.

"},{"location":"watchers/time/#capsula.TimeWatcher.__init__","title":"capsula.TimeWatcher.__init__","text":"
__init__(name: str = 'execution_time')\n
PARAMETER DESCRIPTION name

Name of the time watcher. Used as a key in the output.

TYPE: str DEFAULT: 'execution_time'

Source code in capsula/_watcher/_time.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the time watcher. Used as a key in the output.\")] = \"execution_time\",\n) -> None:\n    self._name = name\n    self._duration: timedelta | None = None\n
"},{"location":"watchers/time/#configuration-example","title":"Configuration example","text":""},{"location":"watchers/time/#via-capsulatoml","title":"Via capsula.toml","text":"
[in-run]\nwatchers = [\n  { type = \"TimeWatcher\" },\n]\n
"},{"location":"watchers/time/#via-capsulawatcher-decorator","title":"Via @capsula.watcher decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.watcher(capsula.TimeWatcher(\"calculation_time\"))\ndef func(): ...\n
"},{"location":"watchers/time/#output-example","title":"Output example","text":"

The following is an example of the output of the TimeWatcher, reported by the JsonDumpReporter:

\"time\": {\n  \"calculation_time\": \"0:00:00.000798\"\n}\n
"},{"location":"watchers/uncaught_exception/","title":"UncaughtExceptionWatcher","text":"

The UncaughtExceptionWatcher monitors uncaught exceptions in the command/function. It can be created using the capsula.UncaughtExceptionWatcher.__init__ method.

"},{"location":"watchers/uncaught_exception/#capsula.UncaughtExceptionWatcher.__init__","title":"capsula.UncaughtExceptionWatcher.__init__","text":"
__init__(\n    name: str = \"exception\",\n    *,\n    base: type[BaseException] = Exception\n)\n
PARAMETER DESCRIPTION name

Name of the exception. Used as a key in the output.

TYPE: str DEFAULT: 'exception'

base

Base exception class to catch.

TYPE: type[BaseException] DEFAULT: Exception

Source code in capsula/_watcher/_exception.py
def __init__(\n    self,\n    name: Annotated[str, Doc(\"Name of the exception. Used as a key in the output.\")] = \"exception\",\n    *,\n    base: Annotated[type[BaseException], Doc(\"Base exception class to catch.\")] = Exception,\n) -> None:\n    self._name = name\n    self._base = base\n    self._exception: BaseException | None = None\n
"},{"location":"watchers/uncaught_exception/#configuration-example","title":"Configuration example","text":""},{"location":"watchers/uncaught_exception/#via-capsulatoml","title":"Via capsula.toml","text":"
[in-run]\nwatchers = [\n  { type = \"UncaughtExceptionWatcher\" },\n]\n
"},{"location":"watchers/uncaught_exception/#via-capsulawatcher-decorator","title":"Via @capsula.watcher decorator","text":"
import capsula\n\n@capsula.run()\n@capsula.watcher(capsula.UncaughtExceptionWatcher(\"exception\"))\ndef func(): ...\n
"},{"location":"watchers/uncaught_exception/#output-example","title":"Output example","text":"

The following is an example of the output of the UncaughtExceptionWatcher, reported by the JsonDumpReporter:

\"exception\": {\n  \"exception\": {\n    \"exc_type\": null,\n    \"exc_value\": null,\n    \"traceback\": null\n  }\n}\n

If an exception is caught, the following is an example of the output:

\"exception\": {\n  \"exception\": {\n    \"exc_type\": \"ZeroDivisionError\",\n    \"exc_value\": \"float division by zero\",\n    \"traceback\": \"  File \\\"/home/nomura/ghq/github.com/shunichironomura/capsula/capsula/_run.py\\\", line 288, in __call__\\n    result = self._func(*args, **kwargs)\\n  File \\\"examples/simple_decorator.py\\\", line 21, in calculate_pi\\n    x = 10.0 / 0.0\\n\"\n  }\n}\n
"}]} \ No newline at end of file diff --git a/dev/sitemap.xml b/dev/sitemap.xml index c049125f..1cb0b813 100644 --- a/dev/sitemap.xml +++ b/dev/sitemap.xml @@ -2,90 +2,90 @@ https://shunichironomura.github.io/capsula/dev/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/concepts/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/config/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/extending/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/helpers/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/command/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/cpu/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/cwd/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/envvar/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/file/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/function/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/git/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/contexts/platform/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/reference/SUMMARY/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/reference/capsula/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/reporters/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/reporters/json_dump/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/reporters/slack/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/watchers/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/watchers/time/ - 2024-12-21 + 2024-12-25 https://shunichironomura.github.io/capsula/dev/watchers/uncaught_exception/ - 2024-12-21 + 2024-12-25 \ No newline at end of file diff --git a/dev/sitemap.xml.gz b/dev/sitemap.xml.gz index c970e204..dcafdf2c 100644 Binary files a/dev/sitemap.xml.gz and b/dev/sitemap.xml.gz differ