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 @@
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:
CpuContext
PlatformContext
CwdContext
GitRepositoryContext
uv lock --locked
) with CommandContext
pyproject.toml
, requirements.txt
) with FileContext
FunctionContext
EnvVarContext
UncaughtExceptionWatcher
TimeWatcher
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 outputpre-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. ContextsFor 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.
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.
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:
capsula.toml
file, in the order of appearance (from top to bottom).@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.
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.
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
capsula.ContextBase
class. This will register the class to the Capsula context registry.encapsulate
method that captures the context.default_key
method that returns the default key used to store the context in the capsule.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
capsula.WatcherBase
class. This will register the class to the Capsula watcher registry.watch
method that behaves as a context manager that watches the command/function execution.encapsulate
method that returns the watcher's output.default_key
method that returns the default key used to store the watcher's output in the capsule.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.
To create a reporter, you need to
capsula.ReporterBase
class. This will register the class to the Capsula reporter registry.report
method that reports the capsule.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.
Capsula provides several helper functions and variables:
capsula.__version__
- The version of Capsula.capsula.record
- You can record any value to the current run. Useful for quickly recording the intermediate results.capsula.current_run_name
- You can access the current run name. Useful for embedding the run name in the output files.capsula.pass_pre_run_capsule
- You can pass the pre-run capsule to the function. Useful for accessing the captured contexts such as Git SHA.capsula.search_for_project_root
- You can search for the project root directory. Useful for specifying the paths relative to the project root.Capsula provides several built-in contexts that you can capture. The following is a list of built-in contexts:
CommandContext
- Captures the output of shell commands.CpuContext
- Captures the CPU information.CwdContext
- Captures the current working directory.EnvVarContext
- Captures the environment variables.FileContext
- Captures the file information.FunctionContext
- Captures the arguments of a function.GitRepositoryContext
- Captures the Git repository information.PlatformContext
- Captures the Python version.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.
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
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 DESCRIPTIONcommand
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
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.
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.
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.
__init__(name: str)\n
PARAMETER DESCRIPTION name
Name of the environment variable
TYPE: str
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.
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
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
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.
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: ()
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
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.
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
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
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.
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":"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]
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 DESCRIPTIONcommand
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
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
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
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
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 DESCRIPTIONname
Name of the environment variable
TYPE: str
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 DESCRIPTIONpath
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
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
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 DESCRIPTIONfunction
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
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: ()
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 DESCRIPTIONname
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
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
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']
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 incapsula/_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]
RunDtoPassPreRunCapsule[P, T]
Decorated function as a Run
object.
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']
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 incapsula/_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.
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
Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], Run[P, T]]
Decorator to create a Run
object.
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]
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 incapsula/_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
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
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
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
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
Bases: Exception
CapsulaUninitializedError(*uninitialized_names: str)\n
Bases: CapsulaError
*uninitialized_names
TYPE: str
DEFAULT: ()
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 DESCRIPTIONpath
TYPE: Path | str
default
TYPE: Callable[[Any], Any] | None
DEFAULT: None
option
TYPE: int | None
DEFAULT: None
mkdir
TYPE: bool
DEFAULT: True
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
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
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
classmethod
","text":"get_subclass(name: str) -> type[ReporterBase]\n
PARAMETER DESCRIPTION name
TYPE: str
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
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: {}
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 DESCRIPTIONphase
TYPE: Literal['pre', 'in', 'post']
channel
TYPE: str
token
TYPE: str
run_name
TYPE: str
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
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
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.
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
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]
run_dto
TYPE: RunDtoPassPreRunCapsule[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoCommand
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
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
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
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]
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: {}
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 DESCRIPTIONstart
The start directory to search.
TYPE: Path | str
Path
The project root directory.
Source code incapsula/_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
name
Name of the time watcher. Used as a key in the output.
TYPE: str
DEFAULT: 'execution_time'
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 DESCRIPTIONname
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
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
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
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.
JsonDumpReporter
- Reports the capsule in JSON format.SlackReporter
- Reports the capsule to a Slack channel.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.
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
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
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.
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.
classmethod
","text":"builder(\n *, channel: str, token: str\n) -> Callable[[CapsuleParams], SlackReporter]\n
PARAMETER DESCRIPTION channel
TYPE: str
token
TYPE: str
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
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:
TimeWatcher
- Monitors the execution time.UncaughtExceptionWatcher
- Monitors uncaught exceptions.TimeWatcher
","text":"The TimeWatcher
monitors the execution time of the command/function. It can be created using the capsula.TimeWatcher.__init__
method.
__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'
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.
__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
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:
CpuContext
PlatformContext
CwdContext
GitRepositoryContext
uv lock --locked
) with CommandContext
pyproject.toml
, requirements.txt
) with FileContext
FunctionContext
EnvVarContext
UncaughtExceptionWatcher
TimeWatcher
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 outputpre-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. ContextsFor 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.
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.
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:
capsula.toml
file, in the order of appearance (from top to bottom).@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.
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.
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
capsula.ContextBase
class. This will register the class to the Capsula context registry.encapsulate
method that captures the context.default_key
method that returns the default key used to store the context in the capsule.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
capsula.WatcherBase
class. This will register the class to the Capsula watcher registry.watch
method that behaves as a context manager that watches the command/function execution.encapsulate
method that returns the watcher's output.default_key
method that returns the default key used to store the watcher's output in the capsule.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.
To create a reporter, you need to
capsula.ReporterBase
class. This will register the class to the Capsula reporter registry.report
method that reports the capsule.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.
Capsula provides several helper functions and variables:
capsula.__version__
- The version of Capsula.capsula.record
- You can record any value to the current run. Useful for quickly recording the intermediate results.capsula.current_run_name
- You can access the current run name. Useful for embedding the run name in the output files.capsula.pass_pre_run_capsule
- You can pass the pre-run capsule to the function. Useful for accessing the captured contexts such as Git SHA.capsula.search_for_project_root
- You can search for the project root directory. Useful for specifying the paths relative to the project root.Capsula provides several built-in contexts that you can capture. The following is a list of built-in contexts:
CommandContext
- Captures the output of shell commands.CpuContext
- Captures the CPU information.CwdContext
- Captures the current working directory.EnvVarContext
- Captures the environment variables.FileContext
- Captures the file information.FunctionContext
- Captures the arguments of a function.GitRepositoryContext
- Captures the Git repository information.PlatformContext
- Captures the Python version.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.
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
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 DESCRIPTIONcommand
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
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.
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.
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.
__init__(name: str)\n
PARAMETER DESCRIPTION name
Name of the environment variable
TYPE: str
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.
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
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
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.
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: ()
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
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.
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
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
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.
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":"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]
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 DESCRIPTIONcommand
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
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
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
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
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 DESCRIPTIONname
Name of the environment variable
TYPE: str
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 DESCRIPTIONpath
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
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
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 DESCRIPTIONfunction
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
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: ()
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 DESCRIPTIONname
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
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
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']
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 incapsula/_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]
RunDtoPassPreRunCapsule[P, T]
Decorated function as a Run
object.
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']
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 incapsula/_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.
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
Callable[[Callable[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoPassPreRunCapsule[P, T]], Run[P, T]]
Decorator to create a Run
object.
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]
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 incapsula/_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
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
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
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
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
Bases: Exception
CapsulaUninitializedError(*uninitialized_names: str)\n
Bases: CapsulaError
*uninitialized_names
TYPE: str
DEFAULT: ()
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 DESCRIPTIONpath
TYPE: Path | str
default
TYPE: Callable[[Any], Any] | None
DEFAULT: None
option
TYPE: int | None
DEFAULT: None
mkdir
TYPE: bool
DEFAULT: True
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
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
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
classmethod
","text":"get_subclass(name: str) -> type[ReporterBase]\n
PARAMETER DESCRIPTION name
TYPE: str
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
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: {}
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 DESCRIPTIONphase
TYPE: Literal['pre', 'in', 'post']
channel
TYPE: str
token
TYPE: str
run_name
TYPE: str
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
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
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.
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
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]
run_dto
TYPE: RunDtoPassPreRunCapsule[P, T] | RunDtoNoPassPreRunCapsule[P, T] | RunDtoCommand
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
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
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
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]
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: {}
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 DESCRIPTIONstart
The start directory to search.
TYPE: Path | str
Path
The project root directory.
Source code incapsula/_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
name
Name of the time watcher. Used as a key in the output.
TYPE: str
DEFAULT: 'execution_time'
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 DESCRIPTIONname
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
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
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
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.
JsonDumpReporter
- Reports the capsule in JSON format.SlackReporter
- Reports the capsule to a Slack channel.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.
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
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
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.
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.
classmethod
","text":"builder(\n *, channel: str, token: str\n) -> Callable[[CapsuleParams], SlackReporter]\n
PARAMETER DESCRIPTION channel
TYPE: str
token
TYPE: str
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
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:
TimeWatcher
- Monitors the execution time.UncaughtExceptionWatcher
- Monitors uncaught exceptions.TimeWatcher
","text":"The TimeWatcher
monitors the execution time of the command/function. It can be created using the capsula.TimeWatcher.__init__
method.
__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'
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.
__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
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 @@