Metadata-Version: 2.4
Name: temporal-code
Version: 0.1.0
Summary: Temporal Code - Time-native programming where function behavior evolves over time
Author-email: Zhenhua Jia <ziyoudeshizi@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/maomao/temporal-code
Keywords: temporal,evolving,canary,ab-test,rollout,feature-flag
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# Temporal Code

**Time-Native Programming for Python**

[![PyPI version](https://img.shields.io/pypi/v/temporal-code.svg)](https://pypi.org/project/temporal-code/)
[![Python](https://img.shields.io/pypi/pyversions/temporal-code.svg)](https://pypi.org/project/temporal-code/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

---

**Temporal Code** introduces a programming paradigm where function behavior *evolves over time*. Instead of deploying new code and flipping a switch, you define multiple variants of a function and let a strategy decide which one runs — gradually shifting traffic, running canary tests, A/B comparisons, or switching at a scheduled time.

Zero external dependencies. Pure Python 3.10+.

## Features

- **`@evolving` decorator** — Make any function time-aware with a single line
- **Variant registration** — Add new implementations via `@func.variant("v2")`
- **4 built-in rollout strategies:**
  - `GradualStrategy` — Linear traffic shift over N days
  - `CanaryStrategy` — Small % traffic with auto-promote/rollback
  - `ABTestStrategy` — Even split with comparison reporting
  - `ScheduledStrategy` — Hard switch at a specific timestamp
- **Automatic fallback** — If the new variant throws, falls back to v1
- **Performance tracking** — Latency, success rate, call count per variant
- **`EvolutionTracker`** — Persistent JSON Lines log of all evolution events
- **CLI tool (`tc`)** — Inspect evolution history from the terminal

## Installation

```bash
pip install temporal-code
```

## Quick Start

```python
from temporal_code import evolving

@evolving(start="2026-06-01", duration_days=14)
def rank_results(items):
    """v1: Simple sort."""
    return sorted(items)

@rank_results.variant("v2")
def rank_results_v2(items):
    """v2: ML-powered ranking (gradually takes over in 14 days)."""
    return ml_sort(items)

# Just call it — the strategy picks the variant automatically
results = rank_results(["banana", "apple", "cherry"])
```

During the 14-day window starting June 1st, traffic gradually shifts from `v1` to `v2`. If `v2` raises an exception, `v1` handles the request as a fallback.

## Strategies

### GradualStrategy

Linearly shifts traffic from the old variant to the new one over a configurable duration.

```python
from temporal_code import evolving, GradualStrategy

@evolving(strategy=GradualStrategy(start="2026-06-01", duration_days=7))
def my_func(x):
    return old_logic(x)

@my_func.variant("v2")
def my_func_v2(x):
    return new_logic(x)
```

| Day | v1 traffic | v2 traffic |
|-----|-----------|-----------|
| 0   | 100%      | 0%        |
| 3   | ~57%      | ~43%      |
| 7   | 0%        | 100%      |

### CanaryStrategy

Routes a small fixed percentage of traffic to the new variant. Automatically promotes after N successful calls, or rolls back if the success rate drops below a threshold.

```python
from temporal_code import evolving, CanaryStrategy

strategy = CanaryStrategy(
    canary_weight=0.1,         # 10% traffic to new variant
    promote_after=100,         # Decide after 100 calls
    rollback_threshold=0.95,   # Roll back if success < 95%
)

@evolving(strategy=strategy)
def process_payment(amount):
    return legacy_processor(amount)

@process_payment.variant("v2")
def process_payment_v2(amount):
    return new_processor(amount)

# After 100 calls to v2:
# - If success_rate >= 95%: auto-promote v2 to 100%
# - If success_rate < 95%: auto-rollback to v1
```

### ABTestStrategy

Splits traffic evenly (or with custom weights) and collects comparison metrics. Does **not** auto-promote — you decide based on the report.

```python
from temporal_code import evolving, ABTestStrategy

ab = ABTestStrategy(split=[0.5, 0.5])

@evolving(strategy=ab, name="greeting")
def greet(user):
    return f"Dear {user}, welcome."

@greet.variant("casual")
def greet_casual(user):
    return f"Hey {user}!"

# Run for a while, then check results:
report = ab.report(greet.variants)
# {'variants': [...], 'recommendation': "Recommend 'casual': 100.0% success, 0ms avg"}
```

### ScheduledStrategy

Hard switch at a specific point in time. Before the timestamp, v1 runs; after it, v2 runs.

```python
from temporal_code import scheduled

@scheduled(switch_at="2026-07-01")
def get_pricing(product):
    return {"model": "flat", "price": 99}

@get_pricing.variant("v2")
def get_pricing_v2(product):
    return {"model": "tiered", "base": 49, "premium": 149}
```

For multi-phase schedules, use the `@temporal` decorator:

```python
from temporal_code import temporal

@temporal(schedule=[
    ("2026-06-01", "v1"),
    ("2026-07-01", "v2"),
    ("2026-09-01", "v3"),
])
def pricing(item):
    return item.base_price  # v1
```

## EvolutionTracker

Track all evolution events (calls, promotions, rollbacks) to a persistent JSON Lines file:

```python
from pathlib import Path
from temporal_code import EvolutionTracker
from temporal_code.decorators import set_tracker

# Enable persistent tracking
tracker = EvolutionTracker(Path("./evolution_logs"))
set_tracker(tracker)

# All @evolving functions now log to ./evolution_logs/evolution.jsonl
# Query history:
history = tracker.get_history(func_name="rank_results", event_type="call", limit=50)
summary = tracker.summary("rank_results")
```

Each log entry is a JSON object:

```json
{"event": "call", "function": "rank_results", "variant": "v2", "success": true, "latency_ms": 1.23, "timestamp": "2026-06-05T10:30:00+00:00"}
```

## API Reference

| Symbol | Type | Description |
|--------|------|-------------|
| `@evolving` | Decorator | Make a function evolve over time |
| `@temporal` | Decorator | Multi-phase scheduled switching |
| `@scheduled` | Decorator | Simple two-phase switch at a timestamp |
| `Variant` | Class | A versioned implementation of a function |
| `VariantResult` | Dataclass | Result of a single variant execution |
| `EvolutionTracker` | Class | Persistent evolution event logger |
| `GradualStrategy` | Class | Linear traffic shift over N days |
| `CanaryStrategy` | Class | Canary deployment with auto-promote/rollback |
| `ABTestStrategy` | Class | A/B split test with reporting |
| `ScheduledStrategy` | Class | Time-based variant switching |

## License

[MIT](LICENSE)
