# Regrest
[English](https://github.com/eycjur/regrest/blob/main/README.md) | [日本語](https://github.com/eycjur/regrest/blob/main/README_ja.md)
[](https://badge.fury.io/py/regrest)
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/eycjur/regrest/actions/workflows/test.yml)
[](https://github.com/eycjur/regrest/actions/workflows/static_analysis.yml)
[](https://deepwiki.com/eycjur/regrest)
**Regrest** is a simple and powerful regression testing tool for Python. It automatically records function outputs on the first run and validates them on subsequent runs.
## Features
- 🎯 **Simple decorator-based API** - Just add `@regrest` to your functions
- 📝 **Automatic recording** - First run records outputs, subsequent runs validate
- 🔍 **Smart comparison** - Handles floats, dicts, lists, nested structures, and custom classes
- 🛠 **CLI tools** - List, view, delete, and visualize test records
- 📊 **Web visualization** - Beautiful dashboard with JSONesque display, hierarchical navigation, and hot reload
- ⚙️ **Configurable** - Custom tolerance, storage location, and more
- 🔧 **Auto .gitignore** - Automatically creates `.regrest/.gitignore` to exclude test records on first run
## Requirements
- Python 3.9 or higher
## Installation
```bash
pip install regrest
# Optional: Install with Flask for better server performance
pip install regrest[server]
```
## Quick Start
### Basic Usage
```python
from regrest import regrest
@regrest
def calculate_price(items, discount=0):
total = sum(item['price'] for item in items)
return total * (1 - discount)
# First run: records the result
items = [{'price': 100}, {'price': 200}]
result = calculate_price(items, discount=0.1) # Returns 270.0, records it
# Output: [regrest] Recorded: __main__.calculate_price (id: abc123...)
# Second run: validates against recorded result
result = calculate_price(items, discount=0.1) # Returns 270.0, compares with record
# Output: [regrest] Passed: __main__.calculate_price (id: abc123...)
```
### Custom Tolerance
```python
@regrest(tolerance=1e-6)
def calculate_pi():
return 3.14159265359
```
### Update Mode
To update existing records instead of testing:
```python
@regrest(update=True)
def my_function():
return "new result"
```
Or set the environment variable:
```bash
REGREST_UPDATE_MODE=1 python your_script.py
```
## Environment Variables
| Variable | Description | Values | Default |
|----------|-------------|--------|---------|
| `REGREST_LOG_LEVEL` | Log level | DEBUG, INFO, WARNING, ERROR, CRITICAL | `INFO` |
| `REGREST_RAISE_ON_ERROR` | Raise exceptions on test failure | True/False | `False` |
| `REGREST_UPDATE_MODE` | Update all records | True/False | `False` |
| `REGREST_STORAGE_DIR` | Custom storage directory | Directory path | `.regrest` |
| `REGREST_FLOAT_TOLERANCE` | Float comparison tolerance | Numeric value | `1e-9` |
**Priority**: Constructor arguments > Environment variables > Default values
## CLI Commands
### List Records
```bash
regrest list # List all records
regrest list -k calculate # Filter by keyword
```
Lists all test records with module, function, arguments, results, and timestamps.
### Delete Records
```bash
regrest delete abc123def456 # Delete by ID
regrest delete --pattern "test_*" # Delete by pattern
regrest delete --all # Delete all records
```
### Serve Web UI
```bash
regrest serve # Start on localhost:8000
regrest serve --port 8080 # Custom port
regrest serve --host 0.0.0.0 # Allow external access
regrest serve --reload # Enable hot reload
```
Access the web UI at `http://localhost:8000` for:
- **Hierarchical view** - Organized by module → function → record
- **Search & filter** - Find records by keyword
- **JSONesque display** - Syntax-highlighted, readable format
- **Record management** - Delete individual or all records
## Architecture
### System Overview
```mermaid
graph TB
subgraph "User Code"
A[Decorated Function<br/>@regrest]
end
subgraph "Regrest Core"
B[Decorator<br/>decorator.py]
C[Storage<br/>storage.py]
D[Matcher<br/>matcher.py]
E[Config<br/>config.py]
F[Exceptions<br/>exceptions.py]
end
subgraph "Storage Layer"
G[JSON Files<br/>.regrest/*.json]
H[Pickle Serialization<br/>base64 encoded]
end
subgraph "CLI & Server"
I[CLI<br/>typer-based]
J[Web Server<br/>Flask/HTTP]
K[Web UI<br/>Tailwind CSS]
end
A --> B
B --> C
B --> D
B --> E
B --> F
C --> G
C --> H
I --> C
J --> C
J --> K
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#f0e1ff
style D fill:#e1ffe1
style I fill:#ffe1e1
style J fill:#ffe1e1
```
## How It Works
1. **First Run**: When you call a function decorated with `@regrest`, it executes normally and saves:
- Module and function name
- Arguments (args and kwargs)
- Return value
- Timestamp
The record is saved to `.regrest/` directory as a JSON file.
2. **Subsequent Runs**: On the next call with the same arguments:
- The function executes
- The result is compared with the recorded value
- If they match → Test passes ✅
- If they don't match → `RegressionTestError` is raised ❌
3. **Update Mode**: When you need to update the expected values:
- Use `@regrest(update=True)` or `REGREST_UPDATE_MODE=1`
- The old record is replaced with the new result
## Configuration
| Level | Usage | Example |
|-------|-------|---------|
| **Global** | Configure all tests | `from regrest import Config, set_config`<br>`config = Config(storage_dir='.my_records', float_tolerance=1e-6)`<br>`set_config(config)` |
| **Per-function** | Configure specific function | `@regrest(tolerance=1e-9)`<br>`def precise_calculation():`<br>` return 3.141592653589793` |
## Advanced Features
### Comparison Logic
The matcher intelligently compares:
- **Primitives**: Exact match for strings, booleans
- **Numbers**: Tolerance-based for floats, exact for integers
- **Collections**: Deep comparison for lists, dicts, sets
- **Nested structures**: Recursive comparison with detailed error messages
## Storage Format
### File Structure
Records are stored as JSON files in the `.regrest/` directory:
```
.regrest/
├── .gitignore # Auto-generated
├── example.calculate_price.a1b2c3d4.json # Record file
└── mymodule.process_data.e5f6g7h8.json # Record file
```
### File Naming Convention
`{module}.{function}.{record_id}.json`
| Component | Description | Example |
|-----------|-------------|---------|
| `module` | Module name where function is defined | `example`, `mymodule` |
| `function` | Function name | `calculate_price`, `process_data` |
| `record_id` | SHA256 hash of arguments (first 16 chars) | `a1b2c3d4e5f6g7h8` |
**Record ID Generation**: Records are uniquely identified by:
1. Module name
2. Function name
3. SHA256 hash of serialized arguments (args + kwargs)
This means **different argument combinations create separate records** for the same function.
### Encoding Strategy
Regrest uses a **hybrid encoding** approach for maximum compatibility and readability:
| Data Type | Storage Method | Readable | Example |
|-----------|---------------|----------|---------|
| **JSON-serializable**<br>(int, float, str, bool, list, dict, None) | JSON | ✅ Yes | `{"result": {"type": "json", "data": 270.0}}` |
| **Non-JSON-serializable**<br>(custom classes, complex objects) | Pickle + Base64 | ❌ No | `{"result": {"type": "pickle", "data": "gASV..."}}` |
**Advantages**:
- ✅ **Readable**: Simple data types are stored as JSON for easy inspection
- ✅ **Flexible**: Complex objects are automatically pickled
- ✅ **Version control friendly**: JSON format produces clean diffs
**Considerations**:
- ⚠️ **Pickle compatibility**: May have issues across different Python versions
- ⚠️ **Custom classes**: Must be pickle-serializable and implement `__eq__` for comparison
## Best Practices
| Practice | Description |
|----------|-------------|
| **Deterministic functions** | Use `@regrest` only on functions with consistent outputs (same input → same output) |
| **Auto .gitignore** | Test records are automatically excluded from git via `.regrest/.gitignore` |
| **Update workflow** | When intentionally changing behavior, update records: `REGREST_UPDATE_MODE=1 python script.py` |
## Limitations
| Limitation | Description |
|------------|-------------|
| **Python version** | Requires Python 3.9+ |
| **Non-deterministic functions** | Don't use on functions with random outputs, timestamps, or external API calls |
| **Serialization** | Data must be JSON or pickle-serializable; custom classes need `__eq__` for comparison |
| **Pickle compatibility** | May have issues across different Python versions |
## Contributing
Contributions welcome! Run `make check` before submitting PRs.
## License
MIT License
## Changelog
### 0.1.0 (Initial Release)
- **Published to PyPI** - Install with `pip install regrest`
- Core decorator functionality (`@regrest`)
- Hybrid JSON/Pickle storage system
- Smart comparison with floating-point tolerance
- CLI tools (`regrest list`, `regrest delete`)
- Custom class support
- Auto `.gitignore` generation
- Environment variable configuration
- Colorful logging output
- Python 3.9+ support
- pyproject.toml-based build system
- ruff + mypy static analysis
- Makefile task automation
- GitHub Actions CI/CD
Raw data
{
"_id": null,
"home_page": null,
"name": "regrest",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "testing, regression, test, regression-testing",
"author": null,
"author_email": "Katsuhiro Muto <63308909+eycjur@users.noreply.github.com>",
"download_url": "https://files.pythonhosted.org/packages/6c/8a/bc26fe45923b917bc84a16a39b1919088f50db76640102dcdf1f9a23dbee/regrest-0.2.0.tar.gz",
"platform": null,
"description": "# Regrest\n\n[English](https://github.com/eycjur/regrest/blob/main/README.md) | [\u65e5\u672c\u8a9e](https://github.com/eycjur/regrest/blob/main/README_ja.md)\n\n[](https://badge.fury.io/py/regrest)\n[](https://www.python.org/downloads/)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/eycjur/regrest/actions/workflows/test.yml)\n[](https://github.com/eycjur/regrest/actions/workflows/static_analysis.yml)\n[](https://deepwiki.com/eycjur/regrest)\n\n**Regrest** is a simple and powerful regression testing tool for Python. It automatically records function outputs on the first run and validates them on subsequent runs.\n\n## Features\n\n- \ud83c\udfaf **Simple decorator-based API** - Just add `@regrest` to your functions\n- \ud83d\udcdd **Automatic recording** - First run records outputs, subsequent runs validate\n- \ud83d\udd0d **Smart comparison** - Handles floats, dicts, lists, nested structures, and custom classes\n- \ud83d\udee0 **CLI tools** - List, view, delete, and visualize test records\n- \ud83d\udcca **Web visualization** - Beautiful dashboard with JSONesque display, hierarchical navigation, and hot reload\n- \u2699\ufe0f **Configurable** - Custom tolerance, storage location, and more\n- \ud83d\udd27 **Auto .gitignore** - Automatically creates `.regrest/.gitignore` to exclude test records on first run\n\n## Requirements\n\n- Python 3.9 or higher\n\n## Installation\n\n```bash\npip install regrest\n\n# Optional: Install with Flask for better server performance\npip install regrest[server]\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nfrom regrest import regrest\n\n@regrest\ndef calculate_price(items, discount=0):\n total = sum(item['price'] for item in items)\n return total * (1 - discount)\n\n# First run: records the result\nitems = [{'price': 100}, {'price': 200}]\nresult = calculate_price(items, discount=0.1) # Returns 270.0, records it\n# Output: [regrest] Recorded: __main__.calculate_price (id: abc123...)\n\n# Second run: validates against recorded result\nresult = calculate_price(items, discount=0.1) # Returns 270.0, compares with record\n# Output: [regrest] Passed: __main__.calculate_price (id: abc123...)\n```\n\n### Custom Tolerance\n\n```python\n@regrest(tolerance=1e-6)\ndef calculate_pi():\n return 3.14159265359\n```\n\n### Update Mode\n\nTo update existing records instead of testing:\n\n```python\n@regrest(update=True)\ndef my_function():\n return \"new result\"\n```\n\nOr set the environment variable:\n\n```bash\nREGREST_UPDATE_MODE=1 python your_script.py\n```\n\n## Environment Variables\n\n| Variable | Description | Values | Default |\n|----------|-------------|--------|---------|\n| `REGREST_LOG_LEVEL` | Log level | DEBUG, INFO, WARNING, ERROR, CRITICAL | `INFO` |\n| `REGREST_RAISE_ON_ERROR` | Raise exceptions on test failure | True/False | `False` |\n| `REGREST_UPDATE_MODE` | Update all records | True/False | `False` |\n| `REGREST_STORAGE_DIR` | Custom storage directory | Directory path | `.regrest` |\n| `REGREST_FLOAT_TOLERANCE` | Float comparison tolerance | Numeric value | `1e-9` |\n\n**Priority**: Constructor arguments > Environment variables > Default values\n\n## CLI Commands\n\n### List Records\n\n```bash\nregrest list # List all records\nregrest list -k calculate # Filter by keyword\n```\n\nLists all test records with module, function, arguments, results, and timestamps.\n\n### Delete Records\n\n```bash\nregrest delete abc123def456 # Delete by ID\nregrest delete --pattern \"test_*\" # Delete by pattern\nregrest delete --all # Delete all records\n```\n\n### Serve Web UI\n\n```bash\nregrest serve # Start on localhost:8000\nregrest serve --port 8080 # Custom port\nregrest serve --host 0.0.0.0 # Allow external access\nregrest serve --reload # Enable hot reload\n```\n\nAccess the web UI at `http://localhost:8000` for:\n- **Hierarchical view** - Organized by module \u2192 function \u2192 record\n- **Search & filter** - Find records by keyword\n- **JSONesque display** - Syntax-highlighted, readable format\n- **Record management** - Delete individual or all records\n\n## Architecture\n\n### System Overview\n\n```mermaid\ngraph TB\n subgraph \"User Code\"\n A[Decorated Function<br/>@regrest]\n end\n\n subgraph \"Regrest Core\"\n B[Decorator<br/>decorator.py]\n C[Storage<br/>storage.py]\n D[Matcher<br/>matcher.py]\n E[Config<br/>config.py]\n F[Exceptions<br/>exceptions.py]\n end\n\n subgraph \"Storage Layer\"\n G[JSON Files<br/>.regrest/*.json]\n H[Pickle Serialization<br/>base64 encoded]\n end\n\n subgraph \"CLI & Server\"\n I[CLI<br/>typer-based]\n J[Web Server<br/>Flask/HTTP]\n K[Web UI<br/>Tailwind CSS]\n end\n\n A --> B\n B --> C\n B --> D\n B --> E\n B --> F\n C --> G\n C --> H\n I --> C\n J --> C\n J --> K\n\n style A fill:#e1f5ff\n style B fill:#fff4e1\n style C fill:#f0e1ff\n style D fill:#e1ffe1\n style I fill:#ffe1e1\n style J fill:#ffe1e1\n```\n\n## How It Works\n\n1. **First Run**: When you call a function decorated with `@regrest`, it executes normally and saves:\n - Module and function name\n - Arguments (args and kwargs)\n - Return value\n - Timestamp\n\n The record is saved to `.regrest/` directory as a JSON file.\n\n2. **Subsequent Runs**: On the next call with the same arguments:\n - The function executes\n - The result is compared with the recorded value\n - If they match \u2192 Test passes \u2705\n - If they don't match \u2192 `RegressionTestError` is raised \u274c\n\n3. **Update Mode**: When you need to update the expected values:\n - Use `@regrest(update=True)` or `REGREST_UPDATE_MODE=1`\n - The old record is replaced with the new result\n\n## Configuration\n\n| Level | Usage | Example |\n|-------|-------|---------|\n| **Global** | Configure all tests | `from regrest import Config, set_config`<br>`config = Config(storage_dir='.my_records', float_tolerance=1e-6)`<br>`set_config(config)` |\n| **Per-function** | Configure specific function | `@regrest(tolerance=1e-9)`<br>`def precise_calculation():`<br>` return 3.141592653589793` |\n\n## Advanced Features\n\n### Comparison Logic\n\nThe matcher intelligently compares:\n- **Primitives**: Exact match for strings, booleans\n- **Numbers**: Tolerance-based for floats, exact for integers\n- **Collections**: Deep comparison for lists, dicts, sets\n- **Nested structures**: Recursive comparison with detailed error messages\n\n## Storage Format\n\n### File Structure\n\nRecords are stored as JSON files in the `.regrest/` directory:\n\n```\n.regrest/\n\u251c\u2500\u2500 .gitignore # Auto-generated\n\u251c\u2500\u2500 example.calculate_price.a1b2c3d4.json # Record file\n\u2514\u2500\u2500 mymodule.process_data.e5f6g7h8.json # Record file\n```\n\n### File Naming Convention\n\n`{module}.{function}.{record_id}.json`\n\n| Component | Description | Example |\n|-----------|-------------|---------|\n| `module` | Module name where function is defined | `example`, `mymodule` |\n| `function` | Function name | `calculate_price`, `process_data` |\n| `record_id` | SHA256 hash of arguments (first 16 chars) | `a1b2c3d4e5f6g7h8` |\n\n**Record ID Generation**: Records are uniquely identified by:\n1. Module name\n2. Function name\n3. SHA256 hash of serialized arguments (args + kwargs)\n\nThis means **different argument combinations create separate records** for the same function.\n\n### Encoding Strategy\n\nRegrest uses a **hybrid encoding** approach for maximum compatibility and readability:\n\n| Data Type | Storage Method | Readable | Example |\n|-----------|---------------|----------|---------|\n| **JSON-serializable**<br>(int, float, str, bool, list, dict, None) | JSON | \u2705 Yes | `{\"result\": {\"type\": \"json\", \"data\": 270.0}}` |\n| **Non-JSON-serializable**<br>(custom classes, complex objects) | Pickle + Base64 | \u274c No | `{\"result\": {\"type\": \"pickle\", \"data\": \"gASV...\"}}` |\n\n**Advantages**:\n- \u2705 **Readable**: Simple data types are stored as JSON for easy inspection\n- \u2705 **Flexible**: Complex objects are automatically pickled\n- \u2705 **Version control friendly**: JSON format produces clean diffs\n\n**Considerations**:\n- \u26a0\ufe0f **Pickle compatibility**: May have issues across different Python versions\n- \u26a0\ufe0f **Custom classes**: Must be pickle-serializable and implement `__eq__` for comparison\n\n## Best Practices\n\n| Practice | Description |\n|----------|-------------|\n| **Deterministic functions** | Use `@regrest` only on functions with consistent outputs (same input \u2192 same output) |\n| **Auto .gitignore** | Test records are automatically excluded from git via `.regrest/.gitignore` |\n| **Update workflow** | When intentionally changing behavior, update records: `REGREST_UPDATE_MODE=1 python script.py` |\n\n## Limitations\n\n| Limitation | Description |\n|------------|-------------|\n| **Python version** | Requires Python 3.9+ |\n| **Non-deterministic functions** | Don't use on functions with random outputs, timestamps, or external API calls |\n| **Serialization** | Data must be JSON or pickle-serializable; custom classes need `__eq__` for comparison |\n| **Pickle compatibility** | May have issues across different Python versions |\n\n## Contributing\n\nContributions welcome! Run `make check` before submitting PRs.\n\n## License\n\nMIT License\n\n## Changelog\n\n### 0.1.0 (Initial Release)\n- **Published to PyPI** - Install with `pip install regrest`\n- Core decorator functionality (`@regrest`)\n- Hybrid JSON/Pickle storage system\n- Smart comparison with floating-point tolerance\n- CLI tools (`regrest list`, `regrest delete`)\n- Custom class support\n- Auto `.gitignore` generation\n- Environment variable configuration\n- Colorful logging output\n- Python 3.9+ support\n- pyproject.toml-based build system\n- ruff + mypy static analysis\n- Makefile task automation\n- GitHub Actions CI/CD\n",
"bugtrack_url": null,
"license": null,
"summary": "Regression testing tool for Python",
"version": "0.2.0",
"project_urls": {
"Documentation": "https://github.com/eycjur/regrest#readme",
"Homepage": "https://github.com/eycjur/regrest",
"Issues": "https://github.com/eycjur/regrest/issues",
"PyPI": "https://pypi.org/project/regrest/",
"Repository": "https://github.com/eycjur/regrest"
},
"split_keywords": [
"testing",
" regression",
" test",
" regression-testing"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "5999cf2bac8b848abd72a06441cd1b47d05e22f937461d11dd301e43e92247db",
"md5": "47ee07069ac8f9752a09dbac1e6f8b4e",
"sha256": "77921b1418c6735825b4a5c6ea17577e399eb2daebbf477699df58a3eaf97fe4"
},
"downloads": -1,
"filename": "regrest-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "47ee07069ac8f9752a09dbac1e6f8b4e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 24364,
"upload_time": "2025-10-26T23:20:29",
"upload_time_iso_8601": "2025-10-26T23:20:29.833410Z",
"url": "https://files.pythonhosted.org/packages/59/99/cf2bac8b848abd72a06441cd1b47d05e22f937461d11dd301e43e92247db/regrest-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "6c8abc26fe45923b917bc84a16a39b1919088f50db76640102dcdf1f9a23dbee",
"md5": "b1e2f7cf4ffb0a8ba76c591d052118ef",
"sha256": "b8a449bcb406e8c9c0e6f9a2f80b78da9040f4601ec2520f7713d96e8074e434"
},
"downloads": -1,
"filename": "regrest-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "b1e2f7cf4ffb0a8ba76c591d052118ef",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 26873,
"upload_time": "2025-10-26T23:20:31",
"upload_time_iso_8601": "2025-10-26T23:20:31.270106Z",
"url": "https://files.pythonhosted.org/packages/6c/8a/bc26fe45923b917bc84a16a39b1919088f50db76640102dcdf1f9a23dbee/regrest-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-26 23:20:31",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "eycjur",
"github_project": "regrest#readme",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "regrest"
}