cloudcircuit 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ """CloudCircuit package."""
2
+
3
+ __all__ = ["__version__"]
4
+ __version__ = "0.1.0"
cloudcircuit/py.typed ADDED
File without changes
@@ -0,0 +1,115 @@
1
+ """Core cloud spend safeguard logic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Sequence
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class BudgetCheckResult:
11
+ current_spend: float
12
+ budget_limit: float
13
+ usage_ratio: float
14
+ remaining_budget: float
15
+ is_warning: bool
16
+ is_breached: bool
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class AnomalyCheckResult:
21
+ latest_spend: float
22
+ baseline_mean: float
23
+ threshold: float
24
+ is_spike: bool
25
+
26
+
27
+ @dataclass(frozen=True, slots=True)
28
+ class CircuitBreakerDecision:
29
+ is_open: bool
30
+ allow_operation: bool
31
+ retry_after_steps: int
32
+
33
+
34
+ def check_budget(
35
+ current_spend: float,
36
+ budget_limit: float,
37
+ warning_ratio: float = 0.9,
38
+ ) -> BudgetCheckResult:
39
+ """Evaluate budget usage against warning and breach thresholds."""
40
+ if budget_limit <= 0:
41
+ raise ValueError("budget_limit must be > 0")
42
+ if not (0 < warning_ratio <= 1):
43
+ raise ValueError("warning_ratio must be in (0, 1]")
44
+ if current_spend < 0:
45
+ raise ValueError("current_spend must be >= 0")
46
+
47
+ usage_ratio = current_spend / budget_limit
48
+ remaining_budget = budget_limit - current_spend
49
+ is_warning = usage_ratio >= warning_ratio
50
+ is_breached = current_spend >= budget_limit
51
+ return BudgetCheckResult(
52
+ current_spend=current_spend,
53
+ budget_limit=budget_limit,
54
+ usage_ratio=usage_ratio,
55
+ remaining_budget=remaining_budget,
56
+ is_warning=is_warning,
57
+ is_breached=is_breached,
58
+ )
59
+
60
+
61
+ def check_anomaly_spike(
62
+ spend_series: Sequence[float],
63
+ spike_multiplier: float = 1.5,
64
+ min_baseline: float = 0.0,
65
+ ) -> AnomalyCheckResult:
66
+ """
67
+ Detect whether the latest spend point is an anomalous spike.
68
+
69
+ Baseline is computed from all values except the latest.
70
+ """
71
+ if len(spend_series) < 2:
72
+ raise ValueError("spend_series must contain at least 2 points")
73
+ if spike_multiplier <= 1.0:
74
+ raise ValueError("spike_multiplier must be > 1.0")
75
+ if min_baseline < 0:
76
+ raise ValueError("min_baseline must be >= 0")
77
+ if any(value < 0 for value in spend_series):
78
+ raise ValueError("spend_series values must be >= 0")
79
+
80
+ latest_spend = float(spend_series[-1])
81
+ history = spend_series[:-1]
82
+ baseline_mean = sum(history) / len(history)
83
+ effective_baseline = max(baseline_mean, min_baseline)
84
+ threshold = effective_baseline * spike_multiplier
85
+ is_spike = latest_spend > threshold
86
+ return AnomalyCheckResult(
87
+ latest_spend=latest_spend,
88
+ baseline_mean=baseline_mean,
89
+ threshold=threshold,
90
+ is_spike=is_spike,
91
+ )
92
+
93
+
94
+ def evaluate_circuit_breaker(
95
+ consecutive_failures: int,
96
+ failure_threshold: int = 3,
97
+ cooldown_steps_remaining: int = 0,
98
+ ) -> CircuitBreakerDecision:
99
+ """Return deterministic breaker state from failure and cooldown counters."""
100
+ if consecutive_failures < 0:
101
+ raise ValueError("consecutive_failures must be >= 0")
102
+ if failure_threshold <= 0:
103
+ raise ValueError("failure_threshold must be > 0")
104
+ if cooldown_steps_remaining < 0:
105
+ raise ValueError("cooldown_steps_remaining must be >= 0")
106
+
107
+ threshold_reached = consecutive_failures >= failure_threshold
108
+ is_open = threshold_reached or cooldown_steps_remaining > 0
109
+ allow_operation = not is_open
110
+ retry_after_steps = cooldown_steps_remaining if is_open else 0
111
+ return CircuitBreakerDecision(
112
+ is_open=is_open,
113
+ allow_operation=allow_operation,
114
+ retry_after_steps=retry_after_steps,
115
+ )
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudcircuit
3
+ Version: 0.1.0
4
+ Summary: CloudCircuit Python package.
5
+ Author: CloudCircuit Contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.10
18
+ Provides-Extra: dev
19
+ Requires-Dist: build>=1.2.2; extra == 'dev'
20
+ Requires-Dist: mypy>=1.11.2; extra == 'dev'
21
+ Requires-Dist: pytest>=8.3.3; extra == 'dev'
22
+ Requires-Dist: ruff>=0.6.9; extra == 'dev'
23
+ Requires-Dist: twine>=5.1.1; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # cloudcircuit
27
+
28
+ Lightweight, deterministic safeguards you can embed into cloud spend enforcement:
29
+
30
+ - Budget usage checks (`check_budget`)
31
+ - Spend spike detection (`check_anomaly_spike`)
32
+ - Simple circuit-breaker decisioning (`evaluate_circuit_breaker`)
33
+
34
+ This package focuses on the core logic only. You bring your own metrics ingestion (billing exports, CloudWatch,
35
+ BigQuery, etc.) and enforcement (alerts, policy, throttling, kill-switches).
36
+
37
+ ## Install
38
+
39
+ From PyPI (once published):
40
+
41
+ ```bash
42
+ python -m pip install cloudcircuit
43
+ ```
44
+
45
+ For local development from this repo:
46
+
47
+ ```bash
48
+ cd cloudcircuit
49
+ python -m pip install -e ".[dev]"
50
+ pytest
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ The public APIs currently live in `cloudcircuit.safeguards`:
56
+
57
+ ```python
58
+ from cloudcircuit.safeguards import (
59
+ check_anomaly_spike,
60
+ check_budget,
61
+ evaluate_circuit_breaker,
62
+ )
63
+ ```
64
+
65
+ All functions are deterministic and raise `ValueError` on invalid inputs (negative spend, invalid thresholds, etc.).
66
+
67
+ ## Quickstart
68
+
69
+ ### 1) Budget guardrail
70
+
71
+ ```python
72
+ from cloudcircuit.safeguards import check_budget
73
+
74
+ result = check_budget(current_spend=920.0, budget_limit=1000.0, warning_ratio=0.9)
75
+
76
+ if result.is_breached:
77
+ # Hard stop: disable non-essential workloads, block expensive operations, etc.
78
+ print("BUDGET BREACHED:", result)
79
+ elif result.is_warning:
80
+ # Soft stop: alert, reduce concurrency, apply stricter policy.
81
+ print("BUDGET WARNING:", result)
82
+ else:
83
+ print("OK:", result)
84
+ ```
85
+
86
+ ### 2) Spike detection (latest point vs baseline)
87
+
88
+ ```python
89
+ from cloudcircuit.safeguards import check_anomaly_spike
90
+
91
+ # Example: daily spend totals (or hourly), latest point last.
92
+ series = [120.0, 125.0, 118.0, 260.0]
93
+
94
+ result = check_anomaly_spike(series, spike_multiplier=1.5, min_baseline=0.0)
95
+ if result.is_spike:
96
+ print("SPEND SPIKE:", result.latest_spend, "threshold:", result.threshold)
97
+ ```
98
+
99
+ ### 3) Circuit-breaker decisioning
100
+
101
+ ```python
102
+ from cloudcircuit.safeguards import evaluate_circuit_breaker
103
+
104
+ decision = evaluate_circuit_breaker(
105
+ consecutive_failures=3,
106
+ failure_threshold=3,
107
+ cooldown_steps_remaining=0,
108
+ )
109
+
110
+ if not decision.allow_operation:
111
+ print("BREAKER OPEN; retry after steps:", decision.retry_after_steps)
112
+ ```
113
+
114
+ ## Architecture
115
+
116
+ Current modules:
117
+
118
+ - `src/cloudcircuit/safeguards.py`: core dataclasses and guardrail functions:
119
+ - `BudgetCheckResult`, `check_budget`
120
+ - `AnomalyCheckResult`, `check_anomaly_spike`
121
+ - `CircuitBreakerDecision`, `evaluate_circuit_breaker`
122
+ - `tests/test_safeguards.py`: contract tests for edge cases and input validation.
123
+
124
+ Design goals:
125
+
126
+ - Deterministic, side-effect free logic (easy to unit-test and reason about)
127
+ - Minimal dependencies (safe to embed in CLIs, jobs, and services)
128
+ - Typed, explicit return objects (so callers can log/audit decisions)
129
+
130
+ Non-goals (by design):
131
+
132
+ - Cloud provider integrations (metrics ingestion / policy enforcement)
133
+ - Stateful breaker implementation (persistence, jitter/backoff, distributed locks)
134
+
135
+ ## Deployment Guide
136
+
137
+ Typical deployment looks like a thin integration layer around `cloudcircuit`:
138
+
139
+ 1. Collect spend and operational signals.
140
+ - Spend totals: billing exports, cost explorer, warehouse tables, or internal chargeback.
141
+ - Failure counters: request error rates, provider API failures, budget retrieval failures.
142
+ 2. Evaluate guardrails.
143
+ - `check_budget()` for hard/soft budget thresholds.
144
+ - `check_anomaly_spike()` to catch sudden jumps early.
145
+ - `evaluate_circuit_breaker()` to gate high-cost operations when systems are unstable.
146
+ 3. Enforce decisions.
147
+ - Alerting: Slack/email/PagerDuty with `*_Result` fields for audit context.
148
+ - Throttling: reduce concurrency, disable non-critical jobs, or switch to cheaper defaults.
149
+ - Kill-switch: block expensive actions when `is_breached` is true.
150
+ 4. Persist state externally when needed.
151
+ - Store the last N spend points, consecutive failure counts, and cooldown counters in your DB/redis.
152
+ - Run the evaluator on a schedule (cron / Airflow / Cloud Scheduler) or inline per request.
153
+
154
+ Recommended patterns:
155
+
156
+ - Treat spend series as monotonic time buckets (hourly/daily) and pass the newest point last.
157
+ - Log the full result objects (they are small and serializable) so you can explain why a safeguard tripped.
158
+ - Prefer "fail closed" for high-risk operations: if your metrics pipeline is down, open the breaker or block
159
+ expensive actions until signals recover.
160
+
161
+ ## License
162
+
163
+ MIT (see `pyproject.toml` for the current license declaration).
@@ -0,0 +1,6 @@
1
+ cloudcircuit/__init__.py,sha256=2n1PpRG-K5Gfjkwcx-OsRKS6hUQHzvtTaq6B0PV1yDc,76
2
+ cloudcircuit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ cloudcircuit/safeguards.py,sha256=0mkUbEH7Iry66OwDEUIW13rsppAcUDnBanW1OcwUUQw,3567
4
+ cloudcircuit-0.1.0.dist-info/METADATA,sha256=JGQN3YOM2foR4qshbsRCvQ7-jtOl0mxgDIcjgKBDse4,5400
5
+ cloudcircuit-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ cloudcircuit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any