confamnode 0.2.2__tar.gz → 0.2.4__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.
Files changed (27) hide show
  1. {confamnode-0.2.2 → confamnode-0.2.4}/PKG-INFO +41 -15
  2. {confamnode-0.2.2 → confamnode-0.2.4}/README.md +40 -14
  3. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/__init__.py +1 -1
  4. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/client.py +25 -3
  5. confamnode-0.2.4/confamnode/utils.py +26 -0
  6. {confamnode-0.2.2 → confamnode-0.2.4}/pyproject.toml +1 -1
  7. confamnode-0.2.4/tests/test_cache_flag.py +74 -0
  8. confamnode-0.2.4/tests/test_client_errors.py +82 -0
  9. confamnode-0.2.4/tests/test_utils.py +46 -0
  10. {confamnode-0.2.2 → confamnode-0.2.4}/uv.lock +1 -1
  11. {confamnode-0.2.2 → confamnode-0.2.4}/.gitignore +0 -0
  12. {confamnode-0.2.2 → confamnode-0.2.4}/.python-version +0 -0
  13. {confamnode-0.2.2 → confamnode-0.2.4}/LICENSE +0 -0
  14. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/ansa.py +0 -0
  15. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/builders.py +0 -0
  16. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/config.py +0 -0
  17. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/exceptions.py +0 -0
  18. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/models.py +0 -0
  19. {confamnode-0.2.2 → confamnode-0.2.4}/confamnode/registry.py +0 -0
  20. {confamnode-0.2.2 → confamnode-0.2.4}/tests/__init__.py +0 -0
  21. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_ansa.py +0 -0
  22. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_client.py +0 -0
  23. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_exceptions.py +0 -0
  24. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_gist.py +0 -0
  25. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_init.py +0 -0
  26. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_models.py +0 -0
  27. {confamnode-0.2.2 → confamnode-0.2.4}/tests/test_stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: confamnode
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: The Nigerian AI inference gateway
5
5
  Project-URL: Repository, https://github.com/confamnodeai/confamnode
6
6
  Project-URL: Bug Tracker, https://github.com/confamnodeai/confamnode/issues
@@ -174,25 +174,25 @@ ansa.raw["usage"] # prompt and completion token counts
174
174
 
175
175
  ### Free Tier
176
176
 
177
- | Model | Description | Price |
178
- |---|---|---|
179
- | `confam-lite` | Light text and general chat | Free |
180
- | `confam-speed` | Fast, high quality responses | Free |
181
- | `confam-reasoning` | Standard reasoning and analysis | Free |
177
+ | Model | Description | Modality | Price |
178
+ |---|---|---|---|
179
+ | `confam-lite` | Light text and general chat | Text-to-Text | Free |
180
+ | `confam-speed` | Fast, high quality responses | Image-Text-to-Text | Free |
181
+ | `confam-reasoning` | Standard reasoning and analysis | Text-to-Text | Free |
182
182
 
183
183
  ### Paid Tier
184
184
 
185
- | Model | Description | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
186
- |---|---|---|---|---|---|
187
- | `confam-intelligence` | General smart tasks, 1M context | ₦596 | ₦3,571 | ₦0.596 | ₦3.571 |
188
- | `confam-deep-reasoning` | Complex thinking, multi-step analysis | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
189
- | `confam-code` | Coding assistance, 1M context | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
185
+ | Model | Description | Modality | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
186
+ |---|---|---|---|---|---|---|
187
+ | `confam-intelligence` | General smart tasks, 1M context | Image-Text-to-Text | ₦596 | ₦3,571 | ₦0.596 | ₦3.571 |
188
+ | `confam-deep-reasoning` | Complex thinking, multi-step analysis | Image-Text-to-Text | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
189
+ | `confam-code` | Coding assistance, 1M context | Image-Text-to-Text | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
190
190
 
191
191
  ### Local Models — Nigerian Data Residency
192
192
 
193
- | Model | Description | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
194
- |---|---|---|---|---|---|
195
- | `confam-nano` | Local model — data stays in Nigeria | ₦500 | ₦1,500 | ₦0.500 | ₦1.500 |
193
+ | Model | Description | Modality | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
194
+ |---|---|---|---|---|---|---|
195
+ | `confam-nano` | Local model — data stays in Nigeria | Image-Text-to-Text | ₦500 | ₦1,500 | ₦0.500 | ₦1.500 |
196
196
 
197
197
  Runs entirely on Nigerian hardware. Data never transmitted abroad.
198
198
  Ideal for banks, fintechs, hospitals, law firms, and government agencies.
@@ -254,6 +254,32 @@ ansa = client.gist(
254
254
 
255
255
  ---
256
256
 
257
+ ## Caching
258
+
259
+ Caching is controlled **per request** and is **off by default** — every call returns a fresh response, even when the request is identical. This keeps data-generation loops and any workflow that resends the same prompt from getting the same cached answer back each time.
260
+
261
+ Pass `cache=True` to read from and write to the cache — useful for idempotent lookups or to save cost on repeated queries:
262
+
263
+ ```python
264
+ # Default — caching off, fresh response every call
265
+ ansa = client.gist(
266
+ model="confam-speed",
267
+ messages="How you dey?"
268
+ )
269
+
270
+ # Enable caching — use a stored response when the request matches,
271
+ # and store this response for next time
272
+ ansa = client.gist(
273
+ model="confam-speed",
274
+ messages="How you dey?",
275
+ cache=True
276
+ )
277
+ ```
278
+
279
+ A cache hit is typically returned near-instantly and at little or no token cost — a quick way to confirm caching is active.
280
+
281
+ ---
282
+
257
283
  ## Reasoning Models
258
284
 
259
285
  Enable extended thinking for complex problems:
@@ -404,4 +430,4 @@ Contact: [hello@confamnode.com](mailto:hello@confamnode.com)
404
430
 
405
431
  Apache 2.0
406
432
 
407
- ---
433
+ ---
@@ -150,25 +150,25 @@ ansa.raw["usage"] # prompt and completion token counts
150
150
 
151
151
  ### Free Tier
152
152
 
153
- | Model | Description | Price |
154
- |---|---|---|
155
- | `confam-lite` | Light text and general chat | Free |
156
- | `confam-speed` | Fast, high quality responses | Free |
157
- | `confam-reasoning` | Standard reasoning and analysis | Free |
153
+ | Model | Description | Modality | Price |
154
+ |---|---|---|---|
155
+ | `confam-lite` | Light text and general chat | Text-to-Text | Free |
156
+ | `confam-speed` | Fast, high quality responses | Image-Text-to-Text | Free |
157
+ | `confam-reasoning` | Standard reasoning and analysis | Text-to-Text | Free |
158
158
 
159
159
  ### Paid Tier
160
160
 
161
- | Model | Description | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
162
- |---|---|---|---|---|---|
163
- | `confam-intelligence` | General smart tasks, 1M context | ₦596 | ₦3,571 | ₦0.596 | ₦3.571 |
164
- | `confam-deep-reasoning` | Complex thinking, multi-step analysis | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
165
- | `confam-code` | Coding assistance, 1M context | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
161
+ | Model | Description | Modality | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
162
+ |---|---|---|---|---|---|---|
163
+ | `confam-intelligence` | General smart tasks, 1M context | Image-Text-to-Text | ₦596 | ₦3,571 | ₦0.596 | ₦3.571 |
164
+ | `confam-deep-reasoning` | Complex thinking, multi-step analysis | Image-Text-to-Text | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
165
+ | `confam-code` | Coding assistance, 1M context | Image-Text-to-Text | ₦234 | ₦468 | ₦0.234 | ₦0.468 |
166
166
 
167
167
  ### Local Models — Nigerian Data Residency
168
168
 
169
- | Model | Description | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
170
- |---|---|---|---|---|---|
171
- | `confam-nano` | Local model — data stays in Nigeria | ₦500 | ₦1,500 | ₦0.500 | ₦1.500 |
169
+ | Model | Description | Modality | Input ₦/1M | Output ₦/1M | Input ₦/1K | Output ₦/1K |
170
+ |---|---|---|---|---|---|---|
171
+ | `confam-nano` | Local model — data stays in Nigeria | Image-Text-to-Text | ₦500 | ₦1,500 | ₦0.500 | ₦1.500 |
172
172
 
173
173
  Runs entirely on Nigerian hardware. Data never transmitted abroad.
174
174
  Ideal for banks, fintechs, hospitals, law firms, and government agencies.
@@ -230,6 +230,32 @@ ansa = client.gist(
230
230
 
231
231
  ---
232
232
 
233
+ ## Caching
234
+
235
+ Caching is controlled **per request** and is **off by default** — every call returns a fresh response, even when the request is identical. This keeps data-generation loops and any workflow that resends the same prompt from getting the same cached answer back each time.
236
+
237
+ Pass `cache=True` to read from and write to the cache — useful for idempotent lookups or to save cost on repeated queries:
238
+
239
+ ```python
240
+ # Default — caching off, fresh response every call
241
+ ansa = client.gist(
242
+ model="confam-speed",
243
+ messages="How you dey?"
244
+ )
245
+
246
+ # Enable caching — use a stored response when the request matches,
247
+ # and store this response for next time
248
+ ansa = client.gist(
249
+ model="confam-speed",
250
+ messages="How you dey?",
251
+ cache=True
252
+ )
253
+ ```
254
+
255
+ A cache hit is typically returned near-instantly and at little or no token cost — a quick way to confirm caching is active.
256
+
257
+ ---
258
+
233
259
  ## Reasoning Models
234
260
 
235
261
  Enable extended thinking for complex problems:
@@ -380,4 +406,4 @@ Contact: [hello@confamnode.com](mailto:hello@confamnode.com)
380
406
 
381
407
  Apache 2.0
382
408
 
383
- ---
409
+ ---
@@ -8,7 +8,7 @@ from confamnode.exceptions import (
8
8
  from confamnode.ansa import Ansa, Usage, Cost
9
9
  from confamnode import models
10
10
 
11
- __version__ = "0.2.2"
11
+ __version__ = "0.2.4"
12
12
 
13
13
  __all__ = [
14
14
  "ConfamNode",
@@ -4,6 +4,7 @@ import httpx
4
4
 
5
5
  from typing import Union, List, Dict
6
6
 
7
+ from confamnode.utils import extract_error
7
8
  from confamnode.builders import parse_chunk
8
9
  from confamnode.ansa import Ansa, Usage, Cost
9
10
  from confamnode.registry import VALID_MODELS
@@ -34,6 +35,7 @@ class ConfamNode:
34
35
  model: str,
35
36
  messages: Union[str, List[Dict[str, str]]],
36
37
  system: str | None = "default",
38
+ cache: bool = False,
37
39
  **kwargs
38
40
  ) -> "Ansa | ConfamStream":
39
41
  if model not in VALID_MODELS:
@@ -59,6 +61,25 @@ class ConfamNode:
59
61
  else:
60
62
  body["system"] = system # None or custom string
61
63
 
64
+ # Caching is controlled per request. By default the SDK opts OUT, so
65
+ # identical requests (same model + messages + system) each return a
66
+ # FRESH response -- important for data-generation loops that resend the
67
+ # same prompt expecting varied output. Pass cache=True to read from and
68
+ # write to the cache (idempotent lookups, cost savings on repeats).
69
+ #
70
+ # These flags only tell the gateway whether to use a cache for THIS
71
+ # request; whether one exists at all is a gateway-side capability.
72
+ if cache:
73
+ body["cache"] = {
74
+ "no-cache": False, # Cache the response
75
+ "no-store": False # Store the response
76
+ }
77
+ else:
78
+ body["cache"] = {
79
+ "no-cache": True, # Skip cache check, get fresh response
80
+ "no-store": True # Don't cache this response
81
+ }
82
+
62
83
  if kwargs.get("stream", False):
63
84
  http_client = httpx.Client(
64
85
  timeout=httpx.Timeout(DEFAULT_TIMEOUT, connect=DEFAULT_CONNECT_TIMEOUT)
@@ -76,10 +97,11 @@ class ConfamNode:
76
97
 
77
98
  if stream_response.status_code >= 400:
78
99
  stream_response.read()
100
+ status = stream_response.status_code
101
+ error = extract_error(stream_response)
79
102
  stream_response.close()
80
103
  http_client.close()
81
- error = stream_response.json().get("detail", "Requeest failed")
82
- raise Exception(f"ConfamNode error {stream_response.status_code}: {error}")
104
+ raise Exception(f"ConfamNode error {status}: {error}")
83
105
 
84
106
  return ConfamStream(stream_response, http_client, model)
85
107
 
@@ -94,7 +116,7 @@ class ConfamNode:
94
116
  )
95
117
 
96
118
  if response.status_code >= 400:
97
- error = response.json().get("detail", "Request failed")
119
+ error = extract_error(response)
98
120
  raise Exception(f"ConfamNode error {response.status_code}: {error}")
99
121
 
100
122
  data = response.json()
@@ -0,0 +1,26 @@
1
+ """
2
+ Small internal helpers shared across the confamnode client.
3
+ """
4
+
5
+
6
+ def extract_error(response) -> str:
7
+ """
8
+ Best-effort error detail from a non-2xx response, WITHOUT raising.
9
+
10
+ The server's normal error shape is {"detail": "..."}, but gateway-level
11
+ errors (e.g. a 429 throttle, a 502/504 from a proxy) often return an
12
+ empty or non-JSON body. Calling response.json() directly on those bodies
13
+ raises JSONDecodeError and swallows the status code -- the one thing the
14
+ caller actually needs. So we parse defensively and always fall back to
15
+ the raw text, then to a status-only message.
16
+ """
17
+ try:
18
+ payload = response.json()
19
+ if isinstance(payload, dict):
20
+ detail = payload.get("detail") or payload.get("error") or payload.get("message")
21
+ if detail:
22
+ return str(detail)
23
+ except Exception:
24
+ pass
25
+ text = (getattr(response, "text", "") or "").strip()
26
+ return text or "no response body"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "confamnode"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "The Nigerian AI inference gateway"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -0,0 +1,74 @@
1
+ """
2
+ Tests for the per-request cache flag on ConfamNode.gist().
3
+
4
+ Caching is opt-OUT by default: identical requests must each return a fresh
5
+ response (critical for data-generation loops). cache=True flips the request
6
+ to read-from + write-to the cache. These tests pin down the exact payload
7
+ sent in each case so a future refactor can't silently flip the default.
8
+
9
+ No network: httpx.post is monkeypatched to capture the request body.
10
+ """
11
+ import httpx
12
+ import pytest
13
+
14
+ from confamnode.client import ConfamNode
15
+ from confamnode.registry import VALID_MODELS
16
+
17
+ API_KEY = "confam-test"
18
+ MODEL = next(iter(VALID_MODELS))
19
+
20
+ CACHE_OFF = {"no-cache": True, "no-store": True} # skip read + skip store
21
+ CACHE_ON = {"no-cache": False, "no-store": False} # use + store
22
+
23
+
24
+ @pytest.fixture
25
+ def captured_body(monkeypatch):
26
+ """Patch httpx.post to record the JSON body and return a valid 200."""
27
+ box = {}
28
+
29
+ def fake_post(url, headers=None, json=None, timeout=None):
30
+ box["body"] = json
31
+
32
+ class _Resp:
33
+ status_code = 200
34
+ text = ""
35
+
36
+ def json(self):
37
+ return {
38
+ "choices": [{"message": {"content": "hi"}, "finish_reason": "stop"}],
39
+ "usage": {},
40
+ "confam": {"cost": {"naira": 0.0}},
41
+ }
42
+
43
+ return _Resp()
44
+
45
+ monkeypatch.setattr(httpx, "post", fake_post)
46
+ return box
47
+
48
+
49
+ def _gist(**kwargs):
50
+ ConfamNode(api_key=API_KEY).gist(model=MODEL, messages="x", **kwargs)
51
+
52
+
53
+ def test_cache_off_by_default(captured_body):
54
+ _gist()
55
+ assert captured_body["body"]["cache"] == CACHE_OFF
56
+
57
+
58
+ def test_cache_true_uses_and_stores(captured_body):
59
+ _gist(cache=True)
60
+ assert captured_body["body"]["cache"] == CACHE_ON
61
+
62
+
63
+ def test_cache_false_is_explicit_off(captured_body):
64
+ _gist(cache=False)
65
+ assert captured_body["body"]["cache"] == CACHE_OFF
66
+
67
+
68
+ def test_cache_field_always_present(captured_body):
69
+ # The request should always state its caching intent in both directions,
70
+ # never leave it implied by omission.
71
+ _gist()
72
+ assert "cache" in captured_body["body"]
73
+ _gist(cache=True)
74
+ assert "cache" in captured_body["body"]
@@ -0,0 +1,82 @@
1
+ """
2
+ Tests for ConfamNode.gist() error handling.
3
+
4
+ The original bug: a non-2xx response with an empty / non-JSON body made the
5
+ SDK call response.json() in its error branch, which raised JSONDecodeError
6
+ and hid the HTTP status. These tests pin down that:
7
+ - an empty error body raises a clean "ConfamNode error <status>: ..." with
8
+ the status code intact (NOT a JSONDecodeError), and
9
+ - the normal success path still works after the refactor.
10
+
11
+ No network: httpx.post is monkeypatched to return a fake response.
12
+ """
13
+ import httpx
14
+ import pytest
15
+
16
+ from confamnode.client import ConfamNode
17
+ from confamnode.registry import VALID_MODELS
18
+
19
+ API_KEY = "confam-test"
20
+ MODEL = next(iter(VALID_MODELS)) # any valid model; we never really call out
21
+
22
+
23
+ class FakeResponse:
24
+ def __init__(self, status_code, payload=None, *, json_raises=False, text=""):
25
+ self.status_code = status_code
26
+ self._payload = payload
27
+ self._json_raises = json_raises
28
+ self.text = text
29
+
30
+ def json(self):
31
+ if self._json_raises:
32
+ raise ValueError("Expecting value: line 1 column 1 (char 0)")
33
+ return self._payload
34
+
35
+
36
+ def _patch_post(monkeypatch, response):
37
+ monkeypatch.setattr(httpx, "post", lambda *a, **k: response)
38
+
39
+
40
+ def test_empty_error_body_raises_with_status_not_jsondecodeerror(monkeypatch):
41
+ # The reproduction of the reported crash: 429, empty body.
42
+ _patch_post(monkeypatch, FakeResponse(429, json_raises=True, text=""))
43
+ client = ConfamNode(api_key=API_KEY)
44
+ with pytest.raises(Exception) as exc:
45
+ client.gist(model=MODEL, messages="hi")
46
+ msg = str(exc.value)
47
+ assert "429" in msg
48
+ assert "no response body" in msg
49
+ assert "JSONDecode" not in type(exc.value).__name__
50
+
51
+
52
+ def test_json_detail_error_surfaces_detail(monkeypatch):
53
+ _patch_post(monkeypatch, FakeResponse(400, payload={"detail": "invalid model"}))
54
+ client = ConfamNode(api_key=API_KEY)
55
+ with pytest.raises(Exception) as exc:
56
+ client.gist(model=MODEL, messages="hi")
57
+ assert "400" in str(exc.value)
58
+ assert "invalid model" in str(exc.value)
59
+
60
+
61
+ def test_html_gateway_error_surfaces_text(monkeypatch):
62
+ _patch_post(monkeypatch, FakeResponse(502, json_raises=True,
63
+ text="<html>502 Bad Gateway</html>"))
64
+ client = ConfamNode(api_key=API_KEY)
65
+ with pytest.raises(Exception) as exc:
66
+ client.gist(model=MODEL, messages="hi")
67
+ assert "502" in str(exc.value)
68
+ assert "Bad Gateway" in str(exc.value)
69
+
70
+
71
+ def test_success_path_still_returns_ansa(monkeypatch):
72
+ payload = {
73
+ "id": "abc",
74
+ "choices": [{"message": {"content": "hello"}, "finish_reason": "stop"}],
75
+ "usage": {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3},
76
+ "confam": {"request_id": "r1", "cost": {"naira": 0.0}},
77
+ }
78
+ _patch_post(monkeypatch, FakeResponse(200, payload=payload))
79
+ client = ConfamNode(api_key=API_KEY)
80
+ ansa = client.gist(model=MODEL, messages="hi")
81
+ assert ansa.text == "hello"
82
+ assert ansa.cost.naira == 0.0
@@ -0,0 +1,46 @@
1
+ """
2
+ Unit tests for confamnode.utils.extract_error.
3
+
4
+ Pure function, no network: just feed it fake responses and assert it never
5
+ raises and always produces a usable message.
6
+ """
7
+ from confamnode.utils import extract_error
8
+
9
+
10
+ class _Resp:
11
+ """Minimal stand-in for an httpx.Response."""
12
+ def __init__(self, payload=None, *, json_raises=False, text=""):
13
+ self._payload = payload
14
+ self._json_raises = json_raises
15
+ self.text = text
16
+
17
+ def json(self):
18
+ if self._json_raises:
19
+ raise ValueError("Expecting value: line 1 column 1 (char 0)")
20
+ return self._payload
21
+
22
+
23
+ def test_empty_body_returns_placeholder():
24
+ # The exact case that crashed the SDK: empty 429 body.
25
+ assert extract_error(_Resp(json_raises=True, text="")) == "no response body"
26
+
27
+
28
+ def test_non_json_text_body_is_returned_stripped():
29
+ assert extract_error(_Resp(json_raises=True, text=" upstream timeout ")) == "upstream timeout"
30
+
31
+
32
+ def test_detail_key_is_preferred():
33
+ assert extract_error(_Resp(payload={"detail": "invalid model"})) == "invalid model"
34
+
35
+
36
+ def test_error_key_fallback():
37
+ assert extract_error(_Resp(payload={"error": "nope"})) == "nope"
38
+
39
+
40
+ def test_message_key_fallback():
41
+ assert extract_error(_Resp(payload={"message": "hmm"})) == "hmm"
42
+
43
+
44
+ def test_non_dict_json_falls_back_to_text():
45
+ # A JSON array (not a dict) shouldn't blow up; fall back to text.
46
+ assert extract_error(_Resp(payload=["a", "b"], text="listy")) == "listy"
@@ -40,7 +40,7 @@ wheels = [
40
40
 
41
41
  [[package]]
42
42
  name = "confamnode"
43
- version = "0.2.1"
43
+ version = "0.2.3"
44
44
  source = { editable = "." }
45
45
  dependencies = [
46
46
  { name = "httpx" },
File without changes
File without changes
File without changes
File without changes