pycasher 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. pycasher-0.1.0/.gitignore +12 -0
  2. pycasher-0.1.0/LICENSE +21 -0
  3. pycasher-0.1.0/PKG-INFO +95 -0
  4. pycasher-0.1.0/README.md +76 -0
  5. pycasher-0.1.0/documentation/00-Introduction.md +75 -0
  6. pycasher-0.1.0/documentation/10-Quick-Start.md +98 -0
  7. pycasher-0.1.0/documentation/20-API-Reference.md +133 -0
  8. pycasher-0.1.0/documentation/_last-updated_.txt +1 -0
  9. pycasher-0.1.0/idea.txt +192 -0
  10. pycasher-0.1.0/pyproject.toml +47 -0
  11. pycasher-0.1.0/src/casher/__init__.py +5 -0
  12. pycasher-0.1.0/src/casher/_runner.py +70 -0
  13. pycasher-0.1.0/src/casher/acache.py +138 -0
  14. pycasher-0.1.0/src/casher/audit_hook.py +65 -0
  15. pycasher-0.1.0/src/casher/auto_cli.py +52 -0
  16. pycasher-0.1.0/src/casher/capture.py +29 -0
  17. pycasher-0.1.0/src/casher/config.py +42 -0
  18. pycasher-0.1.0/src/casher/decorator.py +438 -0
  19. pycasher-0.1.0/src/casher/deps.py +62 -0
  20. pycasher-0.1.0/src/casher/eviction.py +81 -0
  21. pycasher-0.1.0/src/casher/hasher.py +42 -0
  22. pycasher-0.1.0/src/casher/serializer.py +121 -0
  23. pycasher-0.1.0/src/casher/store.py +218 -0
  24. pycasher-0.1.0/src/casher/strace.py +118 -0
  25. pycasher-0.1.0/src/casher/tests/__init__.py +0 -0
  26. pycasher-0.1.0/src/casher/tests/config.py +10 -0
  27. pycasher-0.1.0/src/casher/tests/test_acache.py +222 -0
  28. pycasher-0.1.0/src/casher/tests/test_audit_hook.py +103 -0
  29. pycasher-0.1.0/src/casher/tests/test_auto_cli.py +108 -0
  30. pycasher-0.1.0/src/casher/tests/test_capture.py +88 -0
  31. pycasher-0.1.0/src/casher/tests/test_decorator.py +541 -0
  32. pycasher-0.1.0/src/casher/tests/test_deps.py +132 -0
  33. pycasher-0.1.0/src/casher/tests/test_eviction.py +149 -0
  34. pycasher-0.1.0/src/casher/tests/test_hasher.py +87 -0
  35. pycasher-0.1.0/src/casher/tests/test_serializer.py +92 -0
  36. pycasher-0.1.0/src/casher/tests/test_store.py +186 -0
  37. pycasher-0.1.0/src/casher/tests/test_strace.py +144 -0
  38. pycasher-0.1.0/upload_pypi.sh +18 -0
  39. pycasher-0.1.0/uv.lock +830 -0
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ .venv/
9
+ *.egg
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ tmp_test_data/
pycasher-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 casher contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycasher
3
+ Version: 0.1.0
4
+ Summary: Cache function results and side effects (stdout, stderr, file writes) with automatic file I/O discovery via strace or audit hooks
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: POSIX :: Linux
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: loguru
13
+ Provides-Extra: pandas
14
+ Requires-Dist: pandas; extra == 'pandas'
15
+ Requires-Dist: pyarrow; extra == 'pandas'
16
+ Provides-Extra: polars
17
+ Requires-Dist: polars; extra == 'polars'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # pycasher
21
+
22
+ Cache Python function results **and their side effects** — stdout, stderr, and filesystem writes — with automatic invalidation.
23
+
24
+ ```bash
25
+ pip install pycasher
26
+ ```
27
+
28
+ ## What makes it different
29
+
30
+ Most caching libraries cache return values. casher also captures and replays:
31
+
32
+ - **stdout/stderr** printed during execution
33
+ - **Files written** by the function (restored from cache on hit)
34
+ - **Files read** by the function (used as cache keys — change an input file, cache auto-invalidates)
35
+
36
+ No manual file declarations needed. casher discovers file I/O automatically via `strace` (subprocess mode) or Python audit hooks (in-process mode).
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ from casher import cached
42
+
43
+ @cached
44
+ def train(data_path: str, output_path: str, lr: float = 0.01) -> dict:
45
+ df = read_csv(data_path)
46
+ model = fit(df, lr=lr)
47
+ save(model, output_path)
48
+ return {"accuracy": model.score}
49
+
50
+ # First call — runs function, traces file I/O, caches everything
51
+ result = train("train.csv", "model.pkl")
52
+
53
+ # Second call — instant replay from cache (model.pkl restored too)
54
+ result = train("train.csv", "model.pkl")
55
+
56
+ # Change train.csv — casher detects it, re-runs automatically
57
+ ```
58
+
59
+ Cache any shell command without code changes:
60
+
61
+ ```bash
62
+ acache -- python train.py --data train.csv
63
+ ```
64
+
65
+ ## Key features
66
+
67
+ - **Automatic file tracking**: strace (kernel-level, catches C extensions) or audit hooks (zero overhead, Python-only)
68
+ - **Dependency invalidation**: changes to imported `.py` files invalidate the cache
69
+ - **LRU eviction**: configurable via `max_cache_bytes` or `CASHER_MAX_CACHE_BYTES` env var (default 32 GB)
70
+ - **DataFrame support**: polars and pandas DataFrames serialized via Arrow IPC
71
+ - **Environment-aware**: include env vars in cache key with `env_vars=["MY_VAR"]`
72
+ - **Structured logging**: loguru INFO for every call — cache dir, hit/miss, mode, eviction
73
+
74
+ ## Configuration
75
+
76
+ | Env var | Default | Description |
77
+ |---------|---------|-------------|
78
+ | `CASHER_CACHE_DIR` | `~/.cache/casher` | Cache storage directory |
79
+ | `CASHER_MAX_CACHE_BYTES` | `34359738368` (32 GB) | Max cache size before LRU eviction |
80
+
81
+ ## Platform support
82
+
83
+ Full caching on **Linux** only (requires strace for subprocess mode, fcntl for locking). On macOS and Windows the decorator is a transparent pass-through — functions execute normally, caching is skipped with a one-time warning.
84
+
85
+ ## Documentation
86
+
87
+ See [documentation/](documentation/) for detailed docs:
88
+
89
+ - [Introduction](documentation/00-Introduction.md) — architecture, limitations, in-process vs subprocess comparison
90
+ - [Quick Start](documentation/10-Quick-Start.md) — installation, decorator options, CLI usage
91
+ - [API Reference](documentation/20-API-Reference.md) — full API surface
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,76 @@
1
+ # pycasher
2
+
3
+ Cache Python function results **and their side effects** — stdout, stderr, and filesystem writes — with automatic invalidation.
4
+
5
+ ```bash
6
+ pip install pycasher
7
+ ```
8
+
9
+ ## What makes it different
10
+
11
+ Most caching libraries cache return values. casher also captures and replays:
12
+
13
+ - **stdout/stderr** printed during execution
14
+ - **Files written** by the function (restored from cache on hit)
15
+ - **Files read** by the function (used as cache keys — change an input file, cache auto-invalidates)
16
+
17
+ No manual file declarations needed. casher discovers file I/O automatically via `strace` (subprocess mode) or Python audit hooks (in-process mode).
18
+
19
+ ## Usage
20
+
21
+ ```python
22
+ from casher import cached
23
+
24
+ @cached
25
+ def train(data_path: str, output_path: str, lr: float = 0.01) -> dict:
26
+ df = read_csv(data_path)
27
+ model = fit(df, lr=lr)
28
+ save(model, output_path)
29
+ return {"accuracy": model.score}
30
+
31
+ # First call — runs function, traces file I/O, caches everything
32
+ result = train("train.csv", "model.pkl")
33
+
34
+ # Second call — instant replay from cache (model.pkl restored too)
35
+ result = train("train.csv", "model.pkl")
36
+
37
+ # Change train.csv — casher detects it, re-runs automatically
38
+ ```
39
+
40
+ Cache any shell command without code changes:
41
+
42
+ ```bash
43
+ acache -- python train.py --data train.csv
44
+ ```
45
+
46
+ ## Key features
47
+
48
+ - **Automatic file tracking**: strace (kernel-level, catches C extensions) or audit hooks (zero overhead, Python-only)
49
+ - **Dependency invalidation**: changes to imported `.py` files invalidate the cache
50
+ - **LRU eviction**: configurable via `max_cache_bytes` or `CASHER_MAX_CACHE_BYTES` env var (default 32 GB)
51
+ - **DataFrame support**: polars and pandas DataFrames serialized via Arrow IPC
52
+ - **Environment-aware**: include env vars in cache key with `env_vars=["MY_VAR"]`
53
+ - **Structured logging**: loguru INFO for every call — cache dir, hit/miss, mode, eviction
54
+
55
+ ## Configuration
56
+
57
+ | Env var | Default | Description |
58
+ |---------|---------|-------------|
59
+ | `CASHER_CACHE_DIR` | `~/.cache/casher` | Cache storage directory |
60
+ | `CASHER_MAX_CACHE_BYTES` | `34359738368` (32 GB) | Max cache size before LRU eviction |
61
+
62
+ ## Platform support
63
+
64
+ Full caching on **Linux** only (requires strace for subprocess mode, fcntl for locking). On macOS and Windows the decorator is a transparent pass-through — functions execute normally, caching is skipped with a one-time warning.
65
+
66
+ ## Documentation
67
+
68
+ See [documentation/](documentation/) for detailed docs:
69
+
70
+ - [Introduction](documentation/00-Introduction.md) — architecture, limitations, in-process vs subprocess comparison
71
+ - [Quick Start](documentation/10-Quick-Start.md) — installation, decorator options, CLI usage
72
+ - [API Reference](documentation/20-API-Reference.md) — full API surface
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,75 @@
1
+ # casher — Function Result Cache with Side-Effect Capture
2
+
3
+ `casher` caches Python function results **and** their side effects
4
+ (stdout, stderr, filesystem writes). It automatically discovers file I/O
5
+ via `strace` (subprocess mode) or `sys.addaudithook` (in-process mode),
6
+ so cache invalidation happens without explicit file declarations.
7
+
8
+ Install from PyPI: `pip install pycasher`
9
+
10
+ ## What it provides
11
+
12
+ - A `@cached` decorator that transparently caches any Python function.
13
+ - Automatic file I/O tracking — reads become cache keys, writes are
14
+ restored on cache hit.
15
+ - Two-phase cache lookup: partial key (function + args) → full key
16
+ (partial + input file hashes).
17
+ - Dependency tracking — changes to imported modules invalidate the cache.
18
+ - LRU eviction with configurable `max_cache_bytes` (default 32 GB).
19
+ - `acache` CLI tool for caching arbitrary shell commands.
20
+ - `auto_cli` helper to generate argparse CLIs from decorated functions.
21
+ - Polars and pandas DataFrame serialization via Arrow IPC.
22
+ - Structured logging via loguru at INFO level.
23
+
24
+ ## Configuration via environment variables
25
+
26
+ | Variable | Default | Description |
27
+ |----------|---------|-------------|
28
+ | `CASHER_CACHE_DIR` | `~/.cache/casher` | Cache storage directory |
29
+ | `CASHER_MAX_CACHE_BYTES` | `34359738368` (32 GB) | Max total cache size before LRU eviction |
30
+
31
+ These are used when no explicit value is passed to the `@cached` decorator
32
+ or the `acache` CLI.
33
+
34
+ ## Cross-platform behavior
35
+
36
+ casher's full feature set (strace-based file tracking, `fcntl` locking)
37
+ requires Linux. On **macOS** and **Windows**, caching is automatically
38
+ disabled with a `logger.warning`. The decorated function still runs
39
+ normally — it just never caches.
40
+
41
+ ## In-process vs. subprocess mode
42
+
43
+ | Aspect | `subprocess=True` (default) | `subprocess=False` |
44
+ |--------|----------------------------|---------------------|
45
+ | File tracking | strace — kernel-level, catches all I/O including from C extensions and child processes | `sys.addaudithook` — Python-level, catches only `open()` calls from Python code |
46
+ | Dynamic imports | Fully captured (subprocess loads fresh) | May be missed if imported after function entry |
47
+ | Isolation | Function runs in a separate process — no side effects on caller's globals | Function runs in the same process — shared state, faster startup |
48
+ | Overhead | ~5–15% from strace tracing | Near-zero |
49
+ | Requirements | strace installed, Linux | Python 3.12+, any OS (but caching only active on Linux) |
50
+ | Best for | Data pipelines, scripts with C extensions, shell commands | Pure-Python functions, fast inner loops, environments without strace |
51
+
52
+ **Why not always use in-process?** The audit hook only intercepts Python's
53
+ built-in `open()`. File I/O from C extensions (numpy, pandas, polars
54
+ native readers), subprocess calls, or `os.read()`/`os.write()` is
55
+ invisible to it. strace intercepts at the kernel level, so nothing escapes.
56
+
57
+ ## Limitations
58
+
59
+ - **Linux only** for caching. On macOS/Windows the decorator is a
60
+ transparent pass-through with a one-time warning.
61
+ - **strace in Docker**: requires `--cap-add=SYS_PTRACE` or an equivalent
62
+ seccomp profile that allows the `ptrace` syscall.
63
+ - **Security**: cache entries use pickle. Never point `cache_dir` at a
64
+ shared or untrusted directory. Keep cache directories user-writable only.
65
+ - **strace overhead**: ~5–15% for typical data-processing functions.
66
+ Negligible for functions taking seconds or longer. For sub-millisecond
67
+ functions, use `subprocess=False`.
68
+ - **No exception caching.** Failed function calls are never cached.
69
+ - **No concurrent write safety** beyond `fcntl.flock`. Do not share
70
+ cache directories across networked filesystems.
71
+
72
+ ## Runtime dependencies
73
+
74
+ Only `loguru`. Polars and pandas support is optional — detected at
75
+ runtime via `type(val).__module__`.
@@ -0,0 +1,98 @@
1
+ # Quick Start
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ pip install pycasher
7
+ # Optional DataFrame support:
8
+ pip install pycasher[polars]
9
+ ```
10
+
11
+ ## Decorator usage
12
+
13
+ ```python
14
+ from casher import cached
15
+
16
+ @cached
17
+ def transform(input_path: str, output_path: str, threshold: float = 0.5) -> int:
18
+ import polars as pl
19
+ df = pl.read_csv(input_path)
20
+ df = df.filter(pl.col("score") > threshold)
21
+ df.write_parquet(output_path)
22
+ return len(df)
23
+
24
+ # First call — runs the function, caches result + side effects
25
+ result = transform("data.csv", "output.parquet", threshold=0.3)
26
+
27
+ # Second call — cache hit, restores output.parquet from cache
28
+ result = transform("data.csv", "output.parquet", threshold=0.3)
29
+ ```
30
+
31
+ ## Decorator options
32
+
33
+ ```python
34
+ @cached(
35
+ cache_dir="~/.cache/casher", # default cache location
36
+ subprocess=True, # True=strace (default), False=audit hooks
37
+ enabled=True, # set False to bypass caching
38
+ dep_roots=None, # directories to scan for dependency changes
39
+ input_files=None, # extra files to include in cache key
40
+ ignore_files=None, # glob patterns to exclude from tracking
41
+ env_vars=None, # environment variables to include in cache key
42
+ max_cache_bytes=0, # 0=unlimited, default 32 GB via env var
43
+ )
44
+ ```
45
+
46
+ ## In-process mode
47
+
48
+ When strace is unavailable or overhead matters, use audit hooks:
49
+
50
+ ```python
51
+ @cached(subprocess=False)
52
+ def process(path: str) -> str:
53
+ with open(path) as f:
54
+ return f.read().upper()
55
+ ```
56
+
57
+ File I/O is tracked via `sys.addaudithook`. No subprocess is spawned.
58
+
59
+ ## CLI caching with acache
60
+
61
+ Cache any shell command:
62
+
63
+ ```bash
64
+ # First run — traces file I/O, caches result
65
+ acache -- python train.py --data train.csv
66
+
67
+ # Second run — cache hit if train.csv unchanged
68
+ acache -- python train.py --data train.csv
69
+
70
+ # Custom cache directory
71
+ acache --cache-dir /tmp/mycache -- python train.py --data train.csv
72
+ ```
73
+
74
+ ## Auto-CLI from decorated functions
75
+
76
+ ```python
77
+ # transform.py
78
+ from casher import cached
79
+ from casher.auto_cli import run
80
+
81
+ @cached
82
+ def transform(input_path: str, output_path: str, threshold: float = 0.5) -> int:
83
+ ...
84
+
85
+ if __name__ == "__main__":
86
+ run(transform)
87
+ ```
88
+
89
+ ```bash
90
+ python transform.py data.csv output.parquet --threshold 0.3
91
+ ```
92
+
93
+ ## Running tests
94
+
95
+ ```bash
96
+ cd casher
97
+ ./test.sh
98
+ ```
@@ -0,0 +1,133 @@
1
+ # API Reference
2
+
3
+ ## `casher.cached`
4
+
5
+ ```python
6
+ from casher import cached
7
+
8
+ @cached(
9
+ cache_dir: str | Path = "~/.cache/casher",
10
+ subprocess: bool = True,
11
+ enabled: bool = True,
12
+ dep_roots: list[str] | None = None,
13
+ input_files: list[str] | None = None,
14
+ ignore_files: list[str] | None = None,
15
+ env_vars: list[str] | None = None,
16
+ max_cache_bytes: int = 0,
17
+ )
18
+ def my_func(...) -> ...:
19
+ ...
20
+ ```
21
+
22
+ | Parameter | Description |
23
+ | ----------------- | ------------------------------------------------------------------------------------------------------ |
24
+ | `cache_dir` | Directory for cache storage. Default: `CASHER_CACHE_DIR` env var, then `~/.cache/casher`. |
25
+ | `subprocess` | `True`: run in subprocess with strace tracking. `False`: run in-process with audit hooks. |
26
+ | `enabled` | Set `False` to bypass caching entirely. Also auto-disabled on non-Linux. |
27
+ | `dep_roots` | Directories to scan for Python dependency changes. `None` = auto-detect from function module location. |
28
+ | `input_files` | Extra files to include in cache key beyond auto-discovered ones. |
29
+ | `ignore_files` | Glob patterns for files to exclude from cache key (e.g., `["*.log"]`). |
30
+ | `env_vars` | Environment variable names to include in cache key. |
31
+ | `max_cache_bytes` | Maximum total cache size in bytes. Default: `CASHER_MAX_CACHE_BYTES` env var, then 32 GB. `0` = unlimited. |
32
+
33
+ ## Cache key computation
34
+
35
+ 1. **Partial key** = `sha256(module + func_name + canonical_args + dep_hash + env_hash)`
36
+ 2. **Full key** = `sha256(partial_key + input_file_hashes)`
37
+
38
+ Cache directory layout: `<cache_dir>/<partial_key>/<full_key>/meta.json`
39
+
40
+ ## `casher.auto_cli.run`
41
+
42
+ ```python
43
+ from casher.auto_cli import run
44
+ run(func)
45
+ ```
46
+
47
+ Generates an `argparse.ArgumentParser` from the function's signature and
48
+ type annotations, parses `sys.argv`, and calls the function.
49
+
50
+ | Annotation | argparse type |
51
+ | ---------- | ---------------------------- |
52
+ | `str` | `str` |
53
+ | `int` | `int` |
54
+ | `float` | `float` |
55
+ | `bool` | `store_true` / `store_false` |
56
+ | `Path` | `Path` |
57
+
58
+ ## `acache` CLI
59
+
60
+ ```
61
+ acache [--cache-dir DIR] [--max-cache-bytes N] -- command...
62
+ ```
63
+
64
+ Caches arbitrary CLI programs using strace for I/O tracking. Requires
65
+ strace to be installed.
66
+
67
+ ## `casher.store`
68
+
69
+ ```python
70
+ from casher.store import find_cached, store_result, invalidate, clear_cache
71
+
72
+ # Look up cache entry
73
+ result = find_cached(cache_dir, partial_key) # -> (CacheEntry, Path) | None
74
+
75
+ # Store a new entry
76
+ store_result(cache_dir, partial_key, file_hash, entry, max_bytes=0)
77
+
78
+ # Remove entries for a partial key
79
+ invalidate(cache_dir, partial_key) # -> bool
80
+
81
+ # Clear entire cache
82
+ clear_cache(cache_dir) # -> int (entries removed)
83
+ ```
84
+
85
+ ## `casher.store.CacheEntry`
86
+
87
+ ```python
88
+ @dataclasses.dataclass
89
+ class CacheEntry:
90
+ return_value: object
91
+ stdout: str
92
+ stderr: str
93
+ output_files: dict[str, Path]
94
+ input_files: dict[str, str]
95
+ created_at: float
96
+ func_module: str
97
+ func_name: str
98
+ ```
99
+
100
+ ## `casher.strace`
101
+
102
+ ```python
103
+ from casher.strace import run_with_strace, is_strace_available
104
+
105
+ available = is_strace_available() # -> bool
106
+ result = run_with_strace(["python", "script.py"])
107
+ # result.read_files: list[Path]
108
+ # result.write_files: list[Path]
109
+ # result.stdout: str
110
+ # result.stderr: str
111
+ # result.returncode: int
112
+ ```
113
+
114
+ ## `casher.audit_hook`
115
+
116
+ ```python
117
+ from casher.audit_hook import track_file_io
118
+
119
+ with track_file_io() as tracker:
120
+ with open("data.txt") as f:
121
+ f.read()
122
+ # tracker.read_files: set[Path]
123
+ # tracker.write_files: set[Path]
124
+ ```
125
+
126
+ ## `casher.eviction`
127
+
128
+ ```python
129
+ from casher.eviction import evict_if_needed, get_cache_size
130
+
131
+ evicted = evict_if_needed(cache_dir, max_bytes) # -> int
132
+ total = get_cache_size(cache_dir) # -> int (bytes)
133
+ ```
@@ -0,0 +1 @@
1
+ 6ec95a80 # initial casher documentation