shekel 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.
@@ -0,0 +1,47 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e ".[all,dev]"
26
+
27
+ - name: Format check (black)
28
+ run: black --check .
29
+
30
+ - name: Import sort check (isort)
31
+ run: isort --check-only .
32
+
33
+ - name: Lint (ruff)
34
+ run: ruff check .
35
+
36
+ - name: Type check (mypy)
37
+ run: mypy shekel/
38
+
39
+ - name: Test (pytest)
40
+ run: pytest tests/ -v --cov=shekel --cov-report=xml
41
+
42
+ - name: Upload coverage
43
+ uses: codecov/codecov-action@v4
44
+ if: matrix.python-version == '3.11'
45
+ with:
46
+ files: coverage.xml
47
+ fail_ci_if_error: false
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ id-token: write # required for trusted publishing
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.11"
22
+
23
+ - name: Install build
24
+ run: pip install build
25
+
26
+ - name: Build
27
+ run: python -m build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .pytest_cache/
11
+ .env
12
+ *.egg
13
+ .venv/
14
+ venv/
15
+ .coverage
16
+ coverage.xml
17
+ htmlcov/
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2024-03-08
11
+
12
+ ### Added
13
+ - `budget()` context manager with sync and async support
14
+ - `BudgetExceededError` with spend/limit/model/token details
15
+ - Monkey-patching for OpenAI `ChatCompletions.create` (sync + async + streaming)
16
+ - Monkey-patching for Anthropic `Messages.create` (sync + async + streaming)
17
+ - `ContextVar`-based isolation for thread-safe and async-safe budget tracking
18
+ - Ref-counted patching (only patches once for nested contexts)
19
+ - `warn_at` threshold with optional `on_exceed` callback
20
+ - `price_per_1k_tokens` override for custom/unlisted models
21
+ - Built-in pricing for 10 models (GPT-4o, GPT-4o-mini, o1, o1-mini, GPT-3.5-turbo, Claude 3.5 Sonnet, Claude 3 Haiku, Claude 3 Opus, Gemini 1.5 Flash, Gemini 1.5 Pro)
22
+ - Track-only mode (`budget()` with no `max_usd`)
23
+ - LangGraph integration example
24
+
25
+ [Unreleased]: https://github.com/arieradle/shekel/compare/v0.1.0...HEAD
26
+ [0.1.0]: https://github.com/arieradle/shekel/releases/tag/v0.1.0
@@ -0,0 +1,46 @@
1
+ # Contributing to shekel
2
+
3
+ Thanks for your interest in contributing!
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/arieradle/shekel
9
+ cd shekel
10
+ pip install -e ".[all,dev]"
11
+ ```
12
+
13
+ ## Running checks
14
+
15
+ ```bash
16
+ black . # format
17
+ isort . # sort imports
18
+ ruff check . # lint
19
+ mypy shekel/ # type check
20
+ pytest tests/ -v # run tests
21
+ pytest tests/ --cov=shekel --cov-report=term-missing # with coverage
22
+ ```
23
+
24
+ ## Adding a model
25
+
26
+ Edit `shekel/prices.json` and add an entry:
27
+
28
+ ```json
29
+ "model-name": {"input_per_1k": 0.001, "output_per_1k": 0.003}
30
+ ```
31
+
32
+ Add a test in `tests/test_pricing.py` to verify the new model's cost calculation.
33
+
34
+ ## Pull requests
35
+
36
+ - Keep PRs focused — one change per PR
37
+ - Add tests for new behaviour
38
+ - All CI checks must pass
39
+ - Update `CHANGELOG.md` under `[Unreleased]`
40
+
41
+ ## Reporting bugs
42
+
43
+ Open an issue at https://github.com/arieradle/shekel/issues with:
44
+ - Python version
45
+ - shekel version (`python -c "import shekel; print(shekel.__version__)"`)
46
+ - Minimal reproduction
shekel-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Arie Radle
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.
shekel-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: shekel
3
+ Version: 0.1.0
4
+ Summary: LLM cost tracking and budget enforcement. Zero config — with budget(max_usd=1.00): run_agent(). Works with LangGraph, CrewAI, raw OpenAI/Anthropic.
5
+ Project-URL: Homepage, https://github.com/arieradle/shekel
6
+ Project-URL: Repository, https://github.com/arieradle/shekel
7
+ Project-URL: Issues, https://github.com/arieradle/shekel/issues
8
+ Project-URL: Changelog, https://github.com/arieradle/shekel/blob/main/CHANGELOG.md
9
+ Author-email: Arie Radle <arie.radle@gmail.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2024 Arie Radle
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: agent budget,anthropic spend enforcement,crewai cost control,langchain cost enforcement,langgraph budget,llm budget,llm cost tracking,llm spend limit,openai budget limit
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.9
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
42
+ Requires-Python: >=3.9
43
+ Provides-Extra: all
44
+ Requires-Dist: anthropic>=0.7.0; extra == 'all'
45
+ Requires-Dist: openai>=1.0.0; extra == 'all'
46
+ Provides-Extra: anthropic
47
+ Requires-Dist: anthropic>=0.7.0; extra == 'anthropic'
48
+ Provides-Extra: dev
49
+ Requires-Dist: black>=23.0.0; extra == 'dev'
50
+ Requires-Dist: isort>=5.12.0; extra == 'dev'
51
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
52
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
53
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
54
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
55
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
56
+ Provides-Extra: openai
57
+ Requires-Dist: openai>=1.0.0; extra == 'openai'
58
+ Description-Content-Type: text/markdown
59
+
60
+ [![PyPI version](https://badge.fury.io/py/shekel.svg)](https://badge.fury.io/py/shekel)
61
+ [![Python versions](https://img.shields.io/pypi/pyversions/shekel.svg)](https://pypi.org/project/shekel/)
62
+ [![CI](https://github.com/arieradle/shekel/actions/workflows/ci.yml/badge.svg)](https://github.com/arieradle/shekel/actions/workflows/ci.yml)
63
+
64
+ ```python
65
+ with budget(max_usd=1.00):
66
+ run_my_agent() # raises BudgetExceededError if spend exceeds $1.00
67
+ ```
68
+
69
+ I spent $47 debugging a LangGraph retry loop. The agent kept failing, LangGraph kept retrying, and OpenAI kept charging — all while I slept. I built shekel so you don't have to learn that lesson yourself.
70
+
71
+ ---
72
+
73
+ ## Install
74
+
75
+ ```bash
76
+ pip install shekel[openai] # OpenAI only
77
+ pip install shekel[anthropic] # Anthropic only
78
+ pip install shekel[all] # Both SDKs
79
+ ```
80
+
81
+ *Advanced: `pip install shekel` installs the core with no SDK deps (track-only mode).*
82
+
83
+ ---
84
+
85
+ ## Quick start
86
+
87
+ ```python
88
+ from shekel import budget, BudgetExceededError
89
+ import openai
90
+
91
+ client = openai.OpenAI()
92
+
93
+ try:
94
+ with budget(max_usd=1.00, warn_at=0.8) as b:
95
+ response = client.chat.completions.create(
96
+ model="gpt-4o",
97
+ messages=[{"role": "user", "content": "Hello!"}],
98
+ )
99
+ print(f"Spent: ${b.spent:.4f} of ${b.limit:.2f}")
100
+ except BudgetExceededError as e:
101
+ print(e)
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Track-only mode
107
+
108
+ No `max_usd` = track spend without enforcing a limit. Great for profiling agents.
109
+
110
+ ```python
111
+ with budget() as b:
112
+ run_my_agent()
113
+
114
+ print(f"That run cost: ${b.spent:.4f}")
115
+ ```
116
+
117
+ ---
118
+
119
+ ## How it works
120
+
121
+ - **Monkey-patching**: When you enter a `budget()` context, shekel wraps `openai.ChatCompletions.create` and `anthropic.Messages.create` at the class level. Your code calls the real SDK — shekel intercepts the response, reads the token counts, and calculates cost. On context exit, original methods are restored.
122
+ - **ContextVar isolation**: Each `budget()` context tracks its own spend using Python's `contextvars.ContextVar`. Two concurrent agent runs never share a budget counter, even in async or multi-threaded code.
123
+ - **Zero config**: No API keys, no external services, no config files. `pip install` + `with budget(...)` is all you need.
124
+
125
+ ---
126
+
127
+ ## Model support
128
+
129
+ | Model | Input (per 1k) | Output (per 1k) |
130
+ |-------|---------------|-----------------|
131
+ | gpt-4o | $0.00250 | $0.01000 |
132
+ | gpt-4o-mini | $0.000150 | $0.000600 |
133
+ | o1 | $0.01500 | $0.06000 |
134
+ | o1-mini | $0.00300 | $0.01200 |
135
+ | gpt-3.5-turbo | $0.000500 | $0.001500 |
136
+ | claude-3-5-sonnet-20241022 | $0.00300 | $0.01500 |
137
+ | claude-3-haiku-20240307 | $0.000250 | $0.001250 |
138
+ | claude-3-opus-20240229 | $0.01500 | $0.07500 |
139
+ | gemini-1.5-flash | $0.0000750 | $0.000300 |
140
+ | gemini-1.5-pro | $0.00125 | $0.00500 |
141
+
142
+ **Model not listed?** Pass a price override:
143
+
144
+ ```python
145
+ with budget(max_usd=1.00, price_per_1k_tokens={"input": 0.001, "output": 0.003}):
146
+ run_my_agent()
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Works with LangGraph, CrewAI, and any framework
152
+
153
+ shekel is framework-agnostic. It intercepts at the SDK level, so it works with anything that calls OpenAI or Anthropic under the hood:
154
+
155
+ ```python
156
+ # LangGraph
157
+ with budget(max_usd=2.00, warn_at=0.8) as b:
158
+ result = langgraph_app.invoke({"input": "..."})
159
+ print(f"Graph run cost: ${b.spent:.4f}")
160
+
161
+ # CrewAI
162
+ with budget(max_usd=5.00) as b:
163
+ crew.kickoff()
164
+
165
+ # Raw SDK
166
+ with budget(max_usd=0.50) as b:
167
+ for _ in range(100):
168
+ client.chat.completions.create(...) # stops when budget hit
169
+ ```
170
+
171
+ ---
172
+
173
+ ## License
174
+
175
+ MIT
shekel-0.1.0/README.md ADDED
@@ -0,0 +1,116 @@
1
+ [![PyPI version](https://badge.fury.io/py/shekel.svg)](https://badge.fury.io/py/shekel)
2
+ [![Python versions](https://img.shields.io/pypi/pyversions/shekel.svg)](https://pypi.org/project/shekel/)
3
+ [![CI](https://github.com/arieradle/shekel/actions/workflows/ci.yml/badge.svg)](https://github.com/arieradle/shekel/actions/workflows/ci.yml)
4
+
5
+ ```python
6
+ with budget(max_usd=1.00):
7
+ run_my_agent() # raises BudgetExceededError if spend exceeds $1.00
8
+ ```
9
+
10
+ I spent $47 debugging a LangGraph retry loop. The agent kept failing, LangGraph kept retrying, and OpenAI kept charging — all while I slept. I built shekel so you don't have to learn that lesson yourself.
11
+
12
+ ---
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install shekel[openai] # OpenAI only
18
+ pip install shekel[anthropic] # Anthropic only
19
+ pip install shekel[all] # Both SDKs
20
+ ```
21
+
22
+ *Advanced: `pip install shekel` installs the core with no SDK deps (track-only mode).*
23
+
24
+ ---
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ from shekel import budget, BudgetExceededError
30
+ import openai
31
+
32
+ client = openai.OpenAI()
33
+
34
+ try:
35
+ with budget(max_usd=1.00, warn_at=0.8) as b:
36
+ response = client.chat.completions.create(
37
+ model="gpt-4o",
38
+ messages=[{"role": "user", "content": "Hello!"}],
39
+ )
40
+ print(f"Spent: ${b.spent:.4f} of ${b.limit:.2f}")
41
+ except BudgetExceededError as e:
42
+ print(e)
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Track-only mode
48
+
49
+ No `max_usd` = track spend without enforcing a limit. Great for profiling agents.
50
+
51
+ ```python
52
+ with budget() as b:
53
+ run_my_agent()
54
+
55
+ print(f"That run cost: ${b.spent:.4f}")
56
+ ```
57
+
58
+ ---
59
+
60
+ ## How it works
61
+
62
+ - **Monkey-patching**: When you enter a `budget()` context, shekel wraps `openai.ChatCompletions.create` and `anthropic.Messages.create` at the class level. Your code calls the real SDK — shekel intercepts the response, reads the token counts, and calculates cost. On context exit, original methods are restored.
63
+ - **ContextVar isolation**: Each `budget()` context tracks its own spend using Python's `contextvars.ContextVar`. Two concurrent agent runs never share a budget counter, even in async or multi-threaded code.
64
+ - **Zero config**: No API keys, no external services, no config files. `pip install` + `with budget(...)` is all you need.
65
+
66
+ ---
67
+
68
+ ## Model support
69
+
70
+ | Model | Input (per 1k) | Output (per 1k) |
71
+ |-------|---------------|-----------------|
72
+ | gpt-4o | $0.00250 | $0.01000 |
73
+ | gpt-4o-mini | $0.000150 | $0.000600 |
74
+ | o1 | $0.01500 | $0.06000 |
75
+ | o1-mini | $0.00300 | $0.01200 |
76
+ | gpt-3.5-turbo | $0.000500 | $0.001500 |
77
+ | claude-3-5-sonnet-20241022 | $0.00300 | $0.01500 |
78
+ | claude-3-haiku-20240307 | $0.000250 | $0.001250 |
79
+ | claude-3-opus-20240229 | $0.01500 | $0.07500 |
80
+ | gemini-1.5-flash | $0.0000750 | $0.000300 |
81
+ | gemini-1.5-pro | $0.00125 | $0.00500 |
82
+
83
+ **Model not listed?** Pass a price override:
84
+
85
+ ```python
86
+ with budget(max_usd=1.00, price_per_1k_tokens={"input": 0.001, "output": 0.003}):
87
+ run_my_agent()
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Works with LangGraph, CrewAI, and any framework
93
+
94
+ shekel is framework-agnostic. It intercepts at the SDK level, so it works with anything that calls OpenAI or Anthropic under the hood:
95
+
96
+ ```python
97
+ # LangGraph
98
+ with budget(max_usd=2.00, warn_at=0.8) as b:
99
+ result = langgraph_app.invoke({"input": "..."})
100
+ print(f"Graph run cost: ${b.spent:.4f}")
101
+
102
+ # CrewAI
103
+ with budget(max_usd=5.00) as b:
104
+ crew.kickoff()
105
+
106
+ # Raw SDK
107
+ with budget(max_usd=0.50) as b:
108
+ for _ in range(100):
109
+ client.chat.completions.create(...) # stops when budget hit
110
+ ```
111
+
112
+ ---
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,59 @@
1
+ # Requires: pip install shekel[openai] "langgraph>=0.2"
2
+ """
3
+ Minimal LangGraph demo showing shekel budget enforcement.
4
+
5
+ This example builds a simple one-node LangGraph that calls OpenAI,
6
+ wrapped in a shekel budget context to track and cap spend.
7
+ """
8
+
9
+ import os
10
+
11
+ from shekel import BudgetExceededError, budget
12
+
13
+
14
+ def main() -> None:
15
+ try:
16
+ import openai
17
+ from langgraph.graph import END, StateGraph # type: ignore[import]
18
+ from typing_extensions import TypedDict
19
+ except ImportError as e:
20
+ print(f"Missing dependency: {e}")
21
+ print("Run: pip install shekel[openai] 'langgraph>=0.2' typing_extensions")
22
+ return
23
+
24
+ api_key = os.environ.get("OPENAI_API_KEY")
25
+ if not api_key:
26
+ print("Set OPENAI_API_KEY to run this demo.")
27
+ return
28
+
29
+ client = openai.OpenAI(api_key=api_key)
30
+
31
+ class State(TypedDict):
32
+ question: str
33
+ answer: str
34
+
35
+ def call_llm(state: State) -> State:
36
+ response = client.chat.completions.create(
37
+ model="gpt-4o-mini",
38
+ messages=[{"role": "user", "content": state["question"]}],
39
+ max_tokens=50,
40
+ )
41
+ return {"question": state["question"], "answer": response.choices[0].message.content}
42
+
43
+ graph = StateGraph(State)
44
+ graph.add_node("llm", call_llm)
45
+ graph.set_entry_point("llm")
46
+ graph.add_edge("llm", END)
47
+ app = graph.compile()
48
+
49
+ try:
50
+ with budget(max_usd=0.10, warn_at=0.8) as b:
51
+ result = app.invoke({"question": "What is 2+2?", "answer": ""})
52
+ print(f"Answer: {result['answer']}")
53
+ print(f"Spent: ${b.spent:.4f} / ${b.limit:.2f}")
54
+ except BudgetExceededError as e:
55
+ print(f"Budget exceeded: {e}")
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "shekel"
7
+ version = "0.1.0"
8
+ description = "LLM cost tracking and budget enforcement. Zero config — with budget(max_usd=1.00): run_agent(). Works with LangGraph, CrewAI, raw OpenAI/Anthropic."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "Arie Radle", email = "arie.radle@gmail.com" }]
12
+ requires-python = ">=3.9"
13
+ keywords = [
14
+ "llm cost tracking",
15
+ "openai budget limit",
16
+ "anthropic spend enforcement",
17
+ "langchain cost enforcement",
18
+ "llm spend limit",
19
+ "agent budget",
20
+ "llm budget",
21
+ "langgraph budget",
22
+ "crewai cost control",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Developers",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Software Development :: Libraries :: Python Modules",
34
+ ]
35
+ # Zero hard dependencies — stdlib only
36
+ dependencies = []
37
+
38
+ [project.optional-dependencies]
39
+ openai = ["openai>=1.0.0"]
40
+ anthropic = ["anthropic>=0.7.0"]
41
+ all = ["openai>=1.0.0", "anthropic>=0.7.0"]
42
+ dev = [
43
+ "pytest>=7.0.0",
44
+ "pytest-asyncio>=0.21.0",
45
+ "ruff>=0.1.0",
46
+ "mypy>=1.0.0",
47
+ "black>=23.0.0",
48
+ "isort>=5.12.0",
49
+ "pytest-cov>=4.0.0",
50
+ ]
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/arieradle/shekel"
54
+ Repository = "https://github.com/arieradle/shekel"
55
+ Issues = "https://github.com/arieradle/shekel/issues"
56
+ Changelog = "https://github.com/arieradle/shekel/blob/main/CHANGELOG.md"
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ asyncio_mode = "auto"
61
+
62
+ [tool.black]
63
+ line-length = 100
64
+ target-version = ["py39"]
65
+
66
+ [tool.isort]
67
+ profile = "black"
68
+ line_length = 100
69
+
70
+ [tool.ruff]
71
+ target-version = "py39"
72
+ line-length = 100
73
+
74
+ [tool.ruff.lint]
75
+ select = ["E", "F", "I", "UP"]
76
+
77
+ [tool.mypy]
78
+ python_version = "3.9"
79
+ strict = true
80
+ warn_return_any = true
81
+ warn_unused_configs = true
@@ -0,0 +1,5 @@
1
+ from shekel._budget import Budget as budget
2
+ from shekel.exceptions import BudgetExceededError
3
+
4
+ __version__ = "0.1.0"
5
+ __all__ = ["budget", "BudgetExceededError"]