Skip to content

Commit

Permalink
add docs to arguments of primary methods
Browse files Browse the repository at this point in the history
  • Loading branch information
shunichironomura committed Jul 7, 2024
1 parent fce327a commit 3491ec9
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 23 deletions.
34 changes: 29 additions & 5 deletions capsula/_context/_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from pathlib import Path
from typing import TYPE_CHECKING, Callable, TypedDict

from typing_extensions import Annotated, Doc

from ._base import ContextBase

if TYPE_CHECKING:
Expand All @@ -23,15 +25,37 @@ class _CommandContextData(TypedDict):


class CommandContext(ContextBase):
"""Context to capture the output of a command run in a subprocess."""

@classmethod
def builder(
cls,
command: str,
command: Annotated[str, Doc("Command to run")],
*,
cwd: Path | str | None = None,
check: bool = True,
abort_on_error: bool = True,
cwd_relative_to_project_root: bool = False,
cwd: Annotated[
Path | str | None,
Doc("Working directory for the command, passed to the `cwd` argument of `subprocess.run`"),
] = None,
check: Annotated[
bool,
Doc(
"Whether to raise an exception if the command returns a non-zero exit code, passed to the `check` "
"argument of `subprocess.run",
),
] = True,
abort_on_error: Annotated[
bool,
Doc("Whether to abort the encapsulation if the command returns a non-zero exit code"),
] = True,
cwd_relative_to_project_root: Annotated[
bool,
Doc(
"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.",
),
] = False,
) -> Callable[[CapsuleParams], CommandContext]:
def callback(params: CapsuleParams) -> CommandContext:
if cwd_relative_to_project_root and cwd is not None and not Path(cwd).is_absolute():
Expand Down
2 changes: 2 additions & 0 deletions capsula/_context/_cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@


class CpuContext(ContextBase):
"""Context to capture CPU information."""

def encapsulate(self) -> dict[str, Any]:
return get_cpu_info() # type: ignore[no-any-return]

Expand Down
2 changes: 2 additions & 0 deletions capsula/_context/_cwd.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


class CwdContext(ContextBase):
"""Context to capture the current working directory."""

def encapsulate(self) -> Path:
return Path.cwd()

Expand Down
6 changes: 5 additions & 1 deletion capsula/_context/_envvar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import os

from typing_extensions import Annotated, Doc

from ._base import ContextBase


class EnvVarContext(ContextBase):
def __init__(self, name: str) -> None:
"""Context to capture an environment variable."""

def __init__(self, name: Annotated[str, Doc("Name of the environment variable")]) -> None:
self.name = name

def encapsulate(self) -> str | None:
Expand Down
29 changes: 22 additions & 7 deletions capsula/_context/_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from shutil import copyfile, move
from typing import TYPE_CHECKING, Callable, Iterable, TypedDict

from typing_extensions import Annotated, Doc

from capsula._backport import file_digest

from ._base import ContextBase
Expand All @@ -23,19 +25,32 @@ class _FileContextData(TypedDict):


class FileContext(ContextBase):
"""Context to capture a file."""

_default_hash_algorithm = "sha256"

@classmethod
def builder(
cls,
path: Path | str,
path: Annotated[Path | str, Doc("Path to the file")],
*,
compute_hash: bool = True,
hash_algorithm: str | None = None,
copy: bool = False,
move: bool = False,
ignore_missing: bool = False,
path_relative_to_project_root: bool = False,
compute_hash: Annotated[bool, Doc("Whether to compute the hash of the file")] = True,
hash_algorithm: Annotated[
str | None,
Doc("Hash algorithm to use. This will be fed to `hashlib.file_digest` as the `digest` argument."),
] = None,
copy: Annotated[bool, Doc("Whether to copy the file to the run directory")] = False,
move: Annotated[bool, Doc("Whether to move the file to the run directory")] = False,
ignore_missing: Annotated[bool, Doc("Whether to ignore if the file does not exist")] = False,
path_relative_to_project_root: Annotated[
bool,
Doc(
"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.",
),
] = False,
) -> Callable[[CapsuleParams], FileContext]:
if copy and move:
warnings.warn("Both copy and move are True. Only move will be performed.", UserWarning, stacklevel=2)
Expand Down
21 changes: 16 additions & 5 deletions capsula/_context/_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import TYPE_CHECKING, Callable, TypedDict

from git.repo import Repo
from typing_extensions import Annotated, Doc

from capsula._exceptions import CapsulaError
from capsula._run import CommandInfo, FuncInfo
Expand Down Expand Up @@ -36,14 +37,24 @@ class _GitRepositoryContextData(TypedDict):


class GitRepositoryContext(ContextBase):
"""Context to capture a Git repository."""

@classmethod
def builder(
cls,
name: str | None = None,
name: Annotated[str | None, Doc("Name of the Git repository")] = None,
*,
path: Path | str | None = None,
path_relative_to_project_root: bool = False,
allow_dirty: bool | None = None,
path: Annotated[Path | str | None, Doc("Path to the Git repository")] = None,
path_relative_to_project_root: Annotated[
bool,
Doc(
"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.",
),
] = False,
allow_dirty: Annotated[bool, Doc("Whether to allow the repository to be dirty")] = True,
) -> Callable[[CapsuleParams], GitRepositoryContext]:
def callback(params: CapsuleParams) -> GitRepositoryContext:
if path_relative_to_project_root and path is not None and not Path(path).is_absolute():
Expand All @@ -70,7 +81,7 @@ def callback(params: CapsuleParams) -> GitRepositoryContext:
path=Path(repo.working_dir),
diff_file=params.run_dir / f"{repo_name}.diff",
search_parent_directories=False,
allow_dirty=True if allow_dirty is None else allow_dirty,
allow_dirty=allow_dirty,
)

return callback
Expand Down
2 changes: 2 additions & 0 deletions capsula/_context/_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class _PlatformContextData(TypedDict):


class PlatformContext(ContextBase):
"""Context to capture platform information, including Python version."""

def encapsulate(self) -> _PlatformContextData:
return {
"machine": pf.machine(),
Expand Down
8 changes: 7 additions & 1 deletion capsula/_reporter/_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import TYPE_CHECKING, Any, Callable

import orjson
from typing_extensions import Annotated, Doc

from capsula._utils import to_nested_dict

Expand Down Expand Up @@ -35,11 +36,16 @@ def default_preset(obj: Any) -> Any:


class JsonDumpReporter(ReporterBase):
"""Reporter to dump the capsule to a JSON file."""

@classmethod
def builder(
cls,
*,
option: int | None = None,
option: Annotated[
int | None,
Doc("Option to pass to `orjson.dumps`. If not provided, `orjson.OPT_INDENT_2` will be used."),
] = None,
) -> Callable[[CapsuleParams], JsonDumpReporter]:
def callback(params: CapsuleParams) -> JsonDumpReporter:
return cls(
Expand Down
12 changes: 10 additions & 2 deletions capsula/_watcher/_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from contextlib import contextmanager
from typing import TYPE_CHECKING

from typing_extensions import Annotated, Doc

if TYPE_CHECKING:
from collections.abc import Iterator

Expand All @@ -15,11 +17,17 @@


class UncaughtExceptionWatcher(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.
"""

def __init__(
self,
name: str = "exception",
name: Annotated[str, Doc("Name of the exception. Used as a key in the output.")] = "exception",
*,
base: type[BaseException] = Exception,
base: Annotated[type[BaseException], Doc("Base exception class to catch.")] = Exception,
) -> None:
self._name = name
self._base = base
Expand Down
7 changes: 6 additions & 1 deletion capsula/_watcher/_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from datetime import timedelta
from typing import TYPE_CHECKING

from typing_extensions import Annotated, Doc

if TYPE_CHECKING:
from collections.abc import Iterator

Expand All @@ -15,7 +17,10 @@


class TimeWatcher(WatcherBase):
def __init__(self, name: str = "execution_time") -> None:
def __init__(
self,
name: Annotated[str, Doc("Name of the time watcher. Used as a key in the output.")] = "execution_time",
) -> None:
self._name = name
self._duration: timedelta | None = None

Expand Down
2 changes: 1 addition & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ reporters = [{ type = "JsonDumpReporter" }]

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 the keyword arguments to the `builder` method of the class.
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.

## Decorators

Expand Down

0 comments on commit 3491ec9

Please sign in to comment.