needlepath 0.1.0__tar.gz → 0.1.1__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.
- {needlepath-0.1.0 → needlepath-0.1.1}/PKG-INFO +2 -2
- {needlepath-0.1.0 → needlepath-0.1.1}/README.md +1 -1
- {needlepath-0.1.0 → needlepath-0.1.1}/pyproject.toml +1 -1
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/client.py +1 -1
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/models.py +14 -4
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_config_and_guards.py +1 -1
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_wire_compatibility.py +21 -1
- {needlepath-0.1.0 → needlepath-0.1.1}/.gitignore +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/LICENSE +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/__init__.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/errors.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/features.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/py.typed +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/result.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/testing.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/transport.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/src/needlepath/version.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_async_parity.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_fail_open.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_live_endpoint.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_no_secret_leak.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_retries.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_shadow_mode.py +0 -0
- {needlepath-0.1.0 → needlepath-0.1.1}/tests/test_urllib_transport.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: needlepath
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Framework-free client for the Needlepath hosted context-selection service.
|
|
5
5
|
Project-URL: Homepage, https://nextmoca.com
|
|
6
6
|
Author: Next Moca Global, Inc.
|
|
@@ -94,7 +94,7 @@ never blocks the event loop either way.
|
|
|
94
94
|
|---|---|---|
|
|
95
95
|
| `api_key` | `NEEDLEPATH_API_KEY` | none (no `Authorization` header) |
|
|
96
96
|
| `operating_point` | `NEEDLEPATH_OPERATING_POINT` | **required** |
|
|
97
|
-
| `base_url` | `NEEDLEPATH_BASE_URL` | `https://
|
|
97
|
+
| `base_url` | `NEEDLEPATH_BASE_URL` | `https://api.nextmoca.com` |
|
|
98
98
|
| `timeout` | — | `10.0` s, total, retries included |
|
|
99
99
|
| `max_retries` | — | `2` |
|
|
100
100
|
| `max_request_bytes` | — | `6 * 1024 * 1024` |
|
|
@@ -65,7 +65,7 @@ never blocks the event loop either way.
|
|
|
65
65
|
|---|---|---|
|
|
66
66
|
| `api_key` | `NEEDLEPATH_API_KEY` | none (no `Authorization` header) |
|
|
67
67
|
| `operating_point` | `NEEDLEPATH_OPERATING_POINT` | **required** |
|
|
68
|
-
| `base_url` | `NEEDLEPATH_BASE_URL` | `https://
|
|
68
|
+
| `base_url` | `NEEDLEPATH_BASE_URL` | `https://api.nextmoca.com` |
|
|
69
69
|
| `timeout` | — | `10.0` s, total, retries included |
|
|
70
70
|
| `max_retries` | — | `2` |
|
|
71
71
|
| `max_request_bytes` | — | `6 * 1024 * 1024` |
|
|
@@ -62,7 +62,7 @@ from .version import __version__
|
|
|
62
62
|
#: follow-through (§5, S-17a); until it exists this is the address published runs
|
|
63
63
|
#: and design partners use, and it stays overridable by argument or by
|
|
64
64
|
#: ``NEEDLEPATH_BASE_URL`` so that swapping it is a config change, not a release.
|
|
65
|
-
DEFAULT_BASE_URL = "https://
|
|
65
|
+
DEFAULT_BASE_URL = "https://api.nextmoca.com"
|
|
66
66
|
|
|
67
67
|
SELECT_PATH = "/v1/context/select"
|
|
68
68
|
HEALTH_PATH = "/v1/health"
|
|
@@ -90,24 +90,34 @@ class ContextRecord:
|
|
|
90
90
|
source: Optional[str] = None
|
|
91
91
|
title: Optional[str] = None
|
|
92
92
|
step_id: Optional[str] = None
|
|
93
|
-
|
|
93
|
+
# NEXPE-437: None means "the caller never annotated importance", and the
|
|
94
|
+
# field is then OMITTED from the wire so the server applies its neutral
|
|
95
|
+
# default. The old `0.0` default was sent explicitly, which the server
|
|
96
|
+
# cannot tell apart from a deliberate 0.0 -- and 0.0 stands the record
|
|
97
|
+
# down. An explicit `importance=0.0` still goes on the wire as 0.0.
|
|
98
|
+
importance: Optional[float] = None
|
|
94
99
|
keywords: List[str] = field(default_factory=list)
|
|
95
100
|
tags: List[str] = field(default_factory=list)
|
|
96
101
|
attributes: Dict[str, Any] = field(default_factory=dict)
|
|
97
102
|
|
|
98
103
|
def to_wire(self) -> Dict[str, Any]:
|
|
99
|
-
|
|
104
|
+
wire = {
|
|
100
105
|
"text": self.text,
|
|
101
106
|
"kind": self.kind,
|
|
102
107
|
"id": self.id,
|
|
103
108
|
"source": self.source,
|
|
104
109
|
"title": self.title,
|
|
105
110
|
"step_id": self.step_id,
|
|
106
|
-
"importance": self.importance,
|
|
107
111
|
"keywords": list(self.keywords),
|
|
108
112
|
"tags": list(self.tags),
|
|
109
113
|
"attributes": dict(self.attributes),
|
|
110
114
|
}
|
|
115
|
+
# NEXPE-437: omitted, not null. The server reads importance with
|
|
116
|
+
# `float(d.get("importance", ...))`, so `null` would be a crash and
|
|
117
|
+
# an always-present key would erase the unset/explicit distinction.
|
|
118
|
+
if self.importance is not None:
|
|
119
|
+
wire["importance"] = self.importance
|
|
120
|
+
return wire
|
|
111
121
|
|
|
112
122
|
@classmethod
|
|
113
123
|
def from_wire(cls, d: Dict[str, Any]) -> ContextRecord:
|
|
@@ -118,7 +128,7 @@ class ContextRecord:
|
|
|
118
128
|
source=d.get("source"),
|
|
119
129
|
title=d.get("title"),
|
|
120
130
|
step_id=d.get("step_id"),
|
|
121
|
-
importance=float(d.get("importance"
|
|
131
|
+
importance=float(d["importance"]) if d.get("importance") is not None else None,
|
|
122
132
|
keywords=list(d.get("keywords", [])),
|
|
123
133
|
tags=list(d.get("tags", [])),
|
|
124
134
|
attributes=dict(d.get("attributes", {})),
|
|
@@ -185,7 +185,7 @@ def test_base_url_comes_from_the_environment(monkeypatch):
|
|
|
185
185
|
def test_the_documented_default_is_what_ships():
|
|
186
186
|
from needlepath import DEFAULT_BASE_URL
|
|
187
187
|
|
|
188
|
-
assert DEFAULT_BASE_URL == "https://
|
|
188
|
+
assert DEFAULT_BASE_URL == "https://api.nextmoca.com"
|
|
189
189
|
|
|
190
190
|
|
|
191
191
|
# --------------------------------------------------------------------------- #
|
|
@@ -182,7 +182,11 @@ def test_to_wire_emits_the_full_shape_including_nones():
|
|
|
182
182
|
)
|
|
183
183
|
wire = request.to_wire()
|
|
184
184
|
assert set(wire) == PUBLISHED["ContextRequest"]
|
|
185
|
-
|
|
185
|
+
# NEXPE-437: `importance` is the one deliberate exception to the
|
|
186
|
+
# full-shape rule. Unset means OMITTED from the wire, so the server can
|
|
187
|
+
# tell "never annotated" (neutral) apart from an explicit 0.0
|
|
188
|
+
# (stand-down). Every other optional stays present-as-null.
|
|
189
|
+
assert set(wire["records"][0]) == PUBLISHED["ContextRecord"] - {"importance"}
|
|
186
190
|
assert set(wire["task"]) == PUBLISHED["TaskSpec"]
|
|
187
191
|
assert set(wire["budget"]) == PUBLISHED["BudgetSpec"]
|
|
188
192
|
assert wire["records"][0]["source"] is None # present, not omitted
|
|
@@ -190,6 +194,22 @@ def test_to_wire_emits_the_full_shape_including_nones():
|
|
|
190
194
|
json.dumps(wire) # and it is serializable
|
|
191
195
|
|
|
192
196
|
|
|
197
|
+
def test_unset_importance_is_omitted_and_explicit_zero_is_sent():
|
|
198
|
+
"""NEXPE-437: the wire distinguishes unannotated from deliberately 0.0."""
|
|
199
|
+
unset = models.ContextRecord(text="t").to_wire()
|
|
200
|
+
assert "importance" not in unset
|
|
201
|
+
|
|
202
|
+
explicit_zero = models.ContextRecord(text="t", importance=0.0).to_wire()
|
|
203
|
+
assert explicit_zero["importance"] == 0.0
|
|
204
|
+
|
|
205
|
+
explicit = models.ContextRecord(text="t", importance=7.5).to_wire()
|
|
206
|
+
assert explicit["importance"] == 7.5
|
|
207
|
+
|
|
208
|
+
# And from_wire preserves the same distinction on the way back in.
|
|
209
|
+
assert models.ContextRecord.from_wire({"text": "t"}).importance is None
|
|
210
|
+
assert models.ContextRecord.from_wire({"text": "t", "importance": 0.0}).importance == 0.0
|
|
211
|
+
|
|
212
|
+
|
|
193
213
|
def test_unknown_response_fields_are_kept_not_rejected():
|
|
194
214
|
"""`/v1` is additive (API_VERSIONING.md). `usage` is already scheduled to
|
|
195
215
|
arrive this way (E2), and a client that 'validates' by rejecting unknown
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|