spanlens 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,38 @@
1
+ node_modules/
2
+ .next/
3
+ dist/
4
+ build/
5
+ .turbo/
6
+ *.tsbuildinfo
7
+
8
+ # env
9
+ .env
10
+ .env.local
11
+ .env.production
12
+ .env.*.local
13
+
14
+ # supabase
15
+ supabase/.branches/
16
+ supabase/.temp/
17
+
18
+ # pnpm
19
+ .pnpm-store/
20
+
21
+ # npm auth tokens (never commit)
22
+ .npmrc
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # logs
29
+ *.log
30
+ npm-debug.log*
31
+
32
+ # IDE
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+
37
+ # Vercel
38
+ .vercel
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release of the Spanlens Python SDK.
6
+
7
+ * `SpanlensClient`, `TraceHandle`, `SpanHandle` — core tracing primitives
8
+ * Context-manager support so `end()` is called automatically
9
+ * `observe()`, `observe_openai()`, `observe_anthropic()`, `observe_gemini()`
10
+ — boilerplate-free helpers with auto-parsed usage
11
+ * `create_openai()`, `create_anthropic()`, `create_gemini()`,
12
+ `configure_gemini()` — proxy-mode integrations
13
+ * Background ingest with timeout + ordering guarantees so observability
14
+ never blocks user code or loses spans to race conditions
15
+ * Sync **and** async callables supported by every `observe*()` helper
spanlens-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Spanlens
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,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: spanlens
3
+ Version: 0.1.0
4
+ Summary: Spanlens SDK — agent tracing, LLM usage capture, and cost observability for Python.
5
+ Project-URL: Homepage, https://spanlens.io
6
+ Project-URL: Documentation, https://spanlens.io/docs/sdk
7
+ Project-URL: Repository, https://github.com/sunes26/Spanlens
8
+ Project-URL: Issues, https://github.com/sunes26/Spanlens/issues
9
+ Author-email: Spanlens <support@spanlens.io>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent-tracing,ai-agents,anthropic,claude,cost-tracking,gemini,helicone-alternative,langchain,langfuse-alternative,langsmith-alternative,llamaindex,llm,llm-monitoring,llm-observability,llm-tracing,observability,openai,proxy,spanlens,token-tracking,tracing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: System :: Monitoring
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Requires-Dist: httpx>=0.24.0
29
+ Provides-Extra: all
30
+ Requires-Dist: anthropic>=0.18.0; extra == 'all'
31
+ Requires-Dist: google-generativeai>=0.5.0; extra == 'all'
32
+ Requires-Dist: openai>=1.0.0; extra == 'all'
33
+ Provides-Extra: anthropic
34
+ Requires-Dist: anthropic>=0.18.0; extra == 'anthropic'
35
+ Provides-Extra: dev
36
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
37
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
38
+ Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
39
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
40
+ Requires-Dist: respx>=0.20.0; extra == 'dev'
41
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
42
+ Provides-Extra: gemini
43
+ Requires-Dist: google-generativeai>=0.5.0; extra == 'gemini'
44
+ Provides-Extra: openai
45
+ Requires-Dist: openai>=1.0.0; extra == 'openai'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # Spanlens Python SDK
49
+
50
+ LLM observability for Python. Trace agent runs, capture token usage and cost,
51
+ and link calls back to your Spanlens dashboard with one line of code.
52
+
53
+ [![PyPI](https://img.shields.io/pypi/v/spanlens.svg)](https://pypi.org/project/spanlens/)
54
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
55
+ [![Python](https://img.shields.io/pypi/pyversions/spanlens.svg)](https://pypi.org/project/spanlens/)
56
+
57
+ > **Spanlens** is the open-source LLM observability platform. This is the
58
+ > official Python SDK — for the dashboard, signup, and proxy docs, head to
59
+ > [spanlens.io](https://spanlens.io).
60
+
61
+ ---
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install spanlens
67
+
68
+ # Or with provider integrations:
69
+ pip install "spanlens[openai]"
70
+ pip install "spanlens[anthropic]"
71
+ pip install "spanlens[gemini]"
72
+ pip install "spanlens[all]"
73
+ ```
74
+
75
+ ## Two ways to use it
76
+
77
+ | Mode | Best for | Setup |
78
+ | --- | --- | --- |
79
+ | **Proxy** | Single-call observability — drop-in for the OpenAI/Anthropic SDK | Replace `base_url` |
80
+ | **SDK tracing** | Multi-step agents, RAG, tool calls, manual spans | `SpanlensClient(...)` |
81
+
82
+ You can mix both. The proxy logs the raw request; the SDK groups multiple
83
+ requests into a single trace with parent / child spans.
84
+
85
+ ---
86
+
87
+ ## Mode 1 — Proxy (zero-code)
88
+
89
+ Get a Spanlens API key from your dashboard, then point your provider SDK at
90
+ the Spanlens proxy:
91
+
92
+ ```python
93
+ import os
94
+ from spanlens.integrations.openai import create_openai
95
+
96
+ # Reads SPANLENS_API_KEY from the environment
97
+ client = create_openai()
98
+
99
+ response = client.chat.completions.create(
100
+ model="gpt-4o-mini",
101
+ messages=[{"role": "user", "content": "Hello!"}],
102
+ )
103
+ ```
104
+
105
+ Spanlens automatically logs the request, response, latency, token counts,
106
+ and cost — viewable in the dashboard under **Requests**.
107
+
108
+ ### Tagging requests with a prompt version
109
+
110
+ ```python
111
+ from spanlens.integrations.openai import create_openai, with_prompt_version
112
+
113
+ client = create_openai()
114
+ res = client.chat.completions.create(
115
+ model="gpt-4o-mini",
116
+ messages=[...],
117
+ **with_prompt_version("chatbot-system@3"),
118
+ )
119
+ ```
120
+
121
+ The same pattern works for Anthropic — see
122
+ [`spanlens.integrations.anthropic`](./spanlens/integrations/anthropic.py).
123
+
124
+ ---
125
+
126
+ ## Mode 2 — SDK tracing (multi-step agents)
127
+
128
+ Use the SDK when one user request spans multiple LLM calls, retrieval, tool
129
+ use, etc. Spans appear nested under a single trace in the dashboard.
130
+
131
+ ```python
132
+ from spanlens import SpanlensClient
133
+
134
+ client = SpanlensClient(api_key="sl_live_...")
135
+
136
+ with client.start_trace("rag_pipeline", metadata={"user_id": "u_42"}) as trace:
137
+ with trace.span("retrieve", span_type="retrieval") as span:
138
+ docs = vector_store.similarity_search(query, k=5)
139
+ span.end(output={"doc_count": len(docs)})
140
+
141
+ with trace.span("generate", span_type="llm") as span:
142
+ response = openai_client.chat.completions.create(
143
+ model="gpt-4o-mini",
144
+ messages=build_prompt(query, docs),
145
+ extra_headers=span.trace_headers(), # links proxy log to this span
146
+ )
147
+ usage = response.usage
148
+ span.end(
149
+ output=response.choices[0].message.content,
150
+ prompt_tokens=usage.prompt_tokens,
151
+ completion_tokens=usage.completion_tokens,
152
+ total_tokens=usage.total_tokens,
153
+ )
154
+ ```
155
+
156
+ When a span / trace context manager exits with an exception, the span is
157
+ automatically marked `error` with the exception message.
158
+
159
+ ### Helper: `observe_openai`
160
+
161
+ Boilerplate-free version of the LLM span — auto-injects trace headers,
162
+ auto-parses `usage`, and auto-ends the span:
163
+
164
+ ```python
165
+ from spanlens import observe_openai
166
+
167
+ result = observe_openai(trace, "answer", lambda headers:
168
+ openai_client.chat.completions.create(
169
+ model="gpt-4o-mini",
170
+ messages=messages,
171
+ extra_headers=headers,
172
+ )
173
+ )
174
+ ```
175
+
176
+ The same shape exists for Anthropic (`observe_anthropic`) and Gemini
177
+ (`observe_gemini`).
178
+
179
+ ### Async support
180
+
181
+ `observe()` and `observe_*()` detect coroutines automatically. Pass an async
182
+ callable and `await` the result:
183
+
184
+ ```python
185
+ async def go():
186
+ result = await observe_openai(trace, "answer", lambda h:
187
+ async_openai.chat.completions.create(..., extra_headers=h),
188
+ )
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Configuration reference
194
+
195
+ ```python
196
+ SpanlensClient(
197
+ api_key="sl_live_...", # required
198
+ base_url=None, # default: https://spanlens-server.vercel.app
199
+ timeout_ms=3000, # ingest timeout per call
200
+ silent=True, # swallow errors so observability never crashes user code
201
+ on_error=None, # callback (err, context) for non-silent monitoring
202
+ )
203
+ ```
204
+
205
+ Environment variables:
206
+
207
+ * `SPANLENS_API_KEY` — picked up by `create_openai()`, `create_anthropic()`,
208
+ `create_gemini()` when `api_key=` is omitted.
209
+
210
+ ---
211
+
212
+ ## Why the SDK is non-blocking
213
+
214
+ Every `trace.end()` / `span.end()` call returns immediately. Network I/O
215
+ runs on a background thread pool with a configurable timeout, so:
216
+
217
+ * Your hot path (the LLM call itself) is never slowed down.
218
+ * The Spanlens server being slow / down does not crash your app.
219
+ * Order is still preserved: a span POST always waits for its parent trace
220
+ POST to finish — the server's ownership check would otherwise 404 and the
221
+ span would be silently lost.
222
+
223
+ For short-lived scripts, call `client.close()` before exit (or use
224
+ `with SpanlensClient(...) as client:`) to drain the queue.
225
+
226
+ ---
227
+
228
+ ## Compatibility
229
+
230
+ * Python 3.9, 3.10, 3.11, 3.12, 3.13
231
+ * `openai` >= 1.0
232
+ * `anthropic` >= 0.18
233
+ * `google-generativeai` >= 0.5
234
+
235
+ ---
236
+
237
+ ## Self-hosting
238
+
239
+ Point the SDK and proxy helpers at your own deployment:
240
+
241
+ ```python
242
+ client = SpanlensClient(
243
+ api_key="...",
244
+ base_url="https://spanlens.mycompany.com",
245
+ )
246
+
247
+ openai = create_openai(base_url="https://spanlens.mycompany.com/proxy/openai/v1")
248
+ ```
249
+
250
+ ---
251
+
252
+ ## License
253
+
254
+ MIT — see [LICENSE](./LICENSE).
255
+
256
+ ## Links
257
+
258
+ * [Spanlens dashboard](https://spanlens.io)
259
+ * [Proxy docs](https://spanlens.io/docs/proxy)
260
+ * [TypeScript SDK](https://www.npmjs.com/package/@spanlens/sdk)
261
+ * [GitHub](https://github.com/sunes26/Spanlens)
@@ -0,0 +1,214 @@
1
+ # Spanlens Python SDK
2
+
3
+ LLM observability for Python. Trace agent runs, capture token usage and cost,
4
+ and link calls back to your Spanlens dashboard with one line of code.
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/spanlens.svg)](https://pypi.org/project/spanlens/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![Python](https://img.shields.io/pypi/pyversions/spanlens.svg)](https://pypi.org/project/spanlens/)
9
+
10
+ > **Spanlens** is the open-source LLM observability platform. This is the
11
+ > official Python SDK — for the dashboard, signup, and proxy docs, head to
12
+ > [spanlens.io](https://spanlens.io).
13
+
14
+ ---
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install spanlens
20
+
21
+ # Or with provider integrations:
22
+ pip install "spanlens[openai]"
23
+ pip install "spanlens[anthropic]"
24
+ pip install "spanlens[gemini]"
25
+ pip install "spanlens[all]"
26
+ ```
27
+
28
+ ## Two ways to use it
29
+
30
+ | Mode | Best for | Setup |
31
+ | --- | --- | --- |
32
+ | **Proxy** | Single-call observability — drop-in for the OpenAI/Anthropic SDK | Replace `base_url` |
33
+ | **SDK tracing** | Multi-step agents, RAG, tool calls, manual spans | `SpanlensClient(...)` |
34
+
35
+ You can mix both. The proxy logs the raw request; the SDK groups multiple
36
+ requests into a single trace with parent / child spans.
37
+
38
+ ---
39
+
40
+ ## Mode 1 — Proxy (zero-code)
41
+
42
+ Get a Spanlens API key from your dashboard, then point your provider SDK at
43
+ the Spanlens proxy:
44
+
45
+ ```python
46
+ import os
47
+ from spanlens.integrations.openai import create_openai
48
+
49
+ # Reads SPANLENS_API_KEY from the environment
50
+ client = create_openai()
51
+
52
+ response = client.chat.completions.create(
53
+ model="gpt-4o-mini",
54
+ messages=[{"role": "user", "content": "Hello!"}],
55
+ )
56
+ ```
57
+
58
+ Spanlens automatically logs the request, response, latency, token counts,
59
+ and cost — viewable in the dashboard under **Requests**.
60
+
61
+ ### Tagging requests with a prompt version
62
+
63
+ ```python
64
+ from spanlens.integrations.openai import create_openai, with_prompt_version
65
+
66
+ client = create_openai()
67
+ res = client.chat.completions.create(
68
+ model="gpt-4o-mini",
69
+ messages=[...],
70
+ **with_prompt_version("chatbot-system@3"),
71
+ )
72
+ ```
73
+
74
+ The same pattern works for Anthropic — see
75
+ [`spanlens.integrations.anthropic`](./spanlens/integrations/anthropic.py).
76
+
77
+ ---
78
+
79
+ ## Mode 2 — SDK tracing (multi-step agents)
80
+
81
+ Use the SDK when one user request spans multiple LLM calls, retrieval, tool
82
+ use, etc. Spans appear nested under a single trace in the dashboard.
83
+
84
+ ```python
85
+ from spanlens import SpanlensClient
86
+
87
+ client = SpanlensClient(api_key="sl_live_...")
88
+
89
+ with client.start_trace("rag_pipeline", metadata={"user_id": "u_42"}) as trace:
90
+ with trace.span("retrieve", span_type="retrieval") as span:
91
+ docs = vector_store.similarity_search(query, k=5)
92
+ span.end(output={"doc_count": len(docs)})
93
+
94
+ with trace.span("generate", span_type="llm") as span:
95
+ response = openai_client.chat.completions.create(
96
+ model="gpt-4o-mini",
97
+ messages=build_prompt(query, docs),
98
+ extra_headers=span.trace_headers(), # links proxy log to this span
99
+ )
100
+ usage = response.usage
101
+ span.end(
102
+ output=response.choices[0].message.content,
103
+ prompt_tokens=usage.prompt_tokens,
104
+ completion_tokens=usage.completion_tokens,
105
+ total_tokens=usage.total_tokens,
106
+ )
107
+ ```
108
+
109
+ When a span / trace context manager exits with an exception, the span is
110
+ automatically marked `error` with the exception message.
111
+
112
+ ### Helper: `observe_openai`
113
+
114
+ Boilerplate-free version of the LLM span — auto-injects trace headers,
115
+ auto-parses `usage`, and auto-ends the span:
116
+
117
+ ```python
118
+ from spanlens import observe_openai
119
+
120
+ result = observe_openai(trace, "answer", lambda headers:
121
+ openai_client.chat.completions.create(
122
+ model="gpt-4o-mini",
123
+ messages=messages,
124
+ extra_headers=headers,
125
+ )
126
+ )
127
+ ```
128
+
129
+ The same shape exists for Anthropic (`observe_anthropic`) and Gemini
130
+ (`observe_gemini`).
131
+
132
+ ### Async support
133
+
134
+ `observe()` and `observe_*()` detect coroutines automatically. Pass an async
135
+ callable and `await` the result:
136
+
137
+ ```python
138
+ async def go():
139
+ result = await observe_openai(trace, "answer", lambda h:
140
+ async_openai.chat.completions.create(..., extra_headers=h),
141
+ )
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Configuration reference
147
+
148
+ ```python
149
+ SpanlensClient(
150
+ api_key="sl_live_...", # required
151
+ base_url=None, # default: https://spanlens-server.vercel.app
152
+ timeout_ms=3000, # ingest timeout per call
153
+ silent=True, # swallow errors so observability never crashes user code
154
+ on_error=None, # callback (err, context) for non-silent monitoring
155
+ )
156
+ ```
157
+
158
+ Environment variables:
159
+
160
+ * `SPANLENS_API_KEY` — picked up by `create_openai()`, `create_anthropic()`,
161
+ `create_gemini()` when `api_key=` is omitted.
162
+
163
+ ---
164
+
165
+ ## Why the SDK is non-blocking
166
+
167
+ Every `trace.end()` / `span.end()` call returns immediately. Network I/O
168
+ runs on a background thread pool with a configurable timeout, so:
169
+
170
+ * Your hot path (the LLM call itself) is never slowed down.
171
+ * The Spanlens server being slow / down does not crash your app.
172
+ * Order is still preserved: a span POST always waits for its parent trace
173
+ POST to finish — the server's ownership check would otherwise 404 and the
174
+ span would be silently lost.
175
+
176
+ For short-lived scripts, call `client.close()` before exit (or use
177
+ `with SpanlensClient(...) as client:`) to drain the queue.
178
+
179
+ ---
180
+
181
+ ## Compatibility
182
+
183
+ * Python 3.9, 3.10, 3.11, 3.12, 3.13
184
+ * `openai` >= 1.0
185
+ * `anthropic` >= 0.18
186
+ * `google-generativeai` >= 0.5
187
+
188
+ ---
189
+
190
+ ## Self-hosting
191
+
192
+ Point the SDK and proxy helpers at your own deployment:
193
+
194
+ ```python
195
+ client = SpanlensClient(
196
+ api_key="...",
197
+ base_url="https://spanlens.mycompany.com",
198
+ )
199
+
200
+ openai = create_openai(base_url="https://spanlens.mycompany.com/proxy/openai/v1")
201
+ ```
202
+
203
+ ---
204
+
205
+ ## License
206
+
207
+ MIT — see [LICENSE](./LICENSE).
208
+
209
+ ## Links
210
+
211
+ * [Spanlens dashboard](https://spanlens.io)
212
+ * [Proxy docs](https://spanlens.io/docs/proxy)
213
+ * [TypeScript SDK](https://www.npmjs.com/package/@spanlens/sdk)
214
+ * [GitHub](https://github.com/sunes26/Spanlens)
@@ -0,0 +1,113 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "spanlens"
7
+ version = "0.1.0"
8
+ description = "Spanlens SDK — agent tracing, LLM usage capture, and cost observability for Python."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Spanlens", email = "support@spanlens.io" }]
13
+ keywords = [
14
+ "llm",
15
+ "llm-observability",
16
+ "llm-monitoring",
17
+ "llm-tracing",
18
+ "openai",
19
+ "anthropic",
20
+ "claude",
21
+ "gemini",
22
+ "observability",
23
+ "tracing",
24
+ "agent-tracing",
25
+ "ai-agents",
26
+ "cost-tracking",
27
+ "token-tracking",
28
+ "langchain",
29
+ "llamaindex",
30
+ "helicone-alternative",
31
+ "langfuse-alternative",
32
+ "langsmith-alternative",
33
+ "proxy",
34
+ "spanlens",
35
+ ]
36
+ classifiers = [
37
+ "Development Status :: 4 - Beta",
38
+ "Intended Audience :: Developers",
39
+ "License :: OSI Approved :: MIT License",
40
+ "Operating System :: OS Independent",
41
+ "Programming Language :: Python",
42
+ "Programming Language :: Python :: 3",
43
+ "Programming Language :: Python :: 3.9",
44
+ "Programming Language :: Python :: 3.10",
45
+ "Programming Language :: Python :: 3.11",
46
+ "Programming Language :: Python :: 3.12",
47
+ "Programming Language :: Python :: 3.13",
48
+ "Topic :: Software Development :: Libraries :: Python Modules",
49
+ "Topic :: System :: Monitoring",
50
+ "Typing :: Typed",
51
+ ]
52
+ dependencies = [
53
+ "httpx>=0.24.0",
54
+ ]
55
+
56
+ [project.optional-dependencies]
57
+ openai = ["openai>=1.0.0"]
58
+ anthropic = ["anthropic>=0.18.0"]
59
+ gemini = ["google-generativeai>=0.5.0"]
60
+ all = [
61
+ "openai>=1.0.0",
62
+ "anthropic>=0.18.0",
63
+ "google-generativeai>=0.5.0",
64
+ ]
65
+ dev = [
66
+ "pytest>=7.4.0",
67
+ "pytest-asyncio>=0.23.0",
68
+ "pytest-mock>=3.12.0",
69
+ "respx>=0.20.0",
70
+ "ruff>=0.4.0",
71
+ "mypy>=1.8.0",
72
+ ]
73
+
74
+ [project.urls]
75
+ Homepage = "https://spanlens.io"
76
+ Documentation = "https://spanlens.io/docs/sdk"
77
+ Repository = "https://github.com/sunes26/Spanlens"
78
+ Issues = "https://github.com/sunes26/Spanlens/issues"
79
+
80
+ [tool.hatch.build.targets.wheel]
81
+ packages = ["spanlens"]
82
+
83
+ [tool.hatch.build.targets.sdist]
84
+ include = [
85
+ "spanlens",
86
+ "README.md",
87
+ "LICENSE",
88
+ "CHANGELOG.md",
89
+ ]
90
+
91
+ [tool.pytest.ini_options]
92
+ testpaths = ["tests"]
93
+ asyncio_mode = "auto"
94
+ filterwarnings = ["error::DeprecationWarning"]
95
+
96
+ [tool.ruff]
97
+ line-length = 100
98
+ target-version = "py39"
99
+
100
+ [tool.ruff.lint]
101
+ select = ["E", "F", "I", "B", "UP", "N"]
102
+ # UP006/UP007/UP035/UP045 push PEP 604 / PEP 585 syntax that we deliberately
103
+ # avoid: while ``from __future__ import annotations`` makes ``list[int]`` and
104
+ # ``X | None`` legal at parse time on Python 3.9, runtime introspection
105
+ # (``typing.get_type_hints``) still breaks. Stick with ``Optional[X]`` /
106
+ # ``List[X]`` until the minimum supported version is 3.10.
107
+ ignore = ["E501", "UP006", "UP007", "UP035", "UP045"]
108
+
109
+ [tool.mypy]
110
+ python_version = "3.9"
111
+ strict = true
112
+ warn_return_any = true
113
+ warn_unused_configs = true