upp-python 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.
upp/rpc/codec.py ADDED
@@ -0,0 +1,112 @@
1
+ """UPP JSON-RPC 2.0 Codec.
2
+
3
+ Provides functions for encoding and decoding JSON-RPC 2.0 messages as
4
+ JSON strings. The codec handles serialization/deserialization between
5
+ :class:`JsonRpcRequest`/:class:`JsonRpcResponse` Pydantic models and
6
+ their JSON wire format.
7
+
8
+ Usage::
9
+
10
+ from upp.rpc.codec import encode_request, decode_response
11
+
12
+ wire = encode_request("upp/ingest", {"entity_key": "u1", "text": "..."}, request_id=1)
13
+ response = decode_response(wire_response)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from typing import Any
20
+
21
+ from upp.rpc.messages import JsonRpcError, JsonRpcRequest, JsonRpcResponse
22
+
23
+ __all__ = [
24
+ "decode_request",
25
+ "decode_response",
26
+ "encode_request",
27
+ "encode_response",
28
+ ]
29
+
30
+
31
+ def encode_request(
32
+ method: str,
33
+ params: dict[str, Any],
34
+ request_id: int | str,
35
+ ) -> str:
36
+ """Encode a JSON-RPC 2.0 request as a JSON string.
37
+
38
+ Args:
39
+ method: The method name (e.g., ``"upp/ingest"``).
40
+ params: The method parameters as a dictionary.
41
+ request_id: Unique request identifier.
42
+
43
+ Returns:
44
+ A JSON string representing the full JSON-RPC 2.0 request.
45
+ """
46
+ request = JsonRpcRequest(
47
+ id=request_id,
48
+ method=method,
49
+ params=params,
50
+ )
51
+ return request.model_dump_json()
52
+
53
+
54
+ def decode_request(data: str) -> JsonRpcRequest:
55
+ """Decode a JSON string into a :class:`JsonRpcRequest`.
56
+
57
+ Args:
58
+ data: A JSON string representing a JSON-RPC 2.0 request.
59
+
60
+ Returns:
61
+ A validated :class:`JsonRpcRequest` instance.
62
+
63
+ Raises:
64
+ pydantic.ValidationError: If the JSON does not conform to the
65
+ JSON-RPC 2.0 request schema.
66
+ json.JSONDecodeError: If the string is not valid JSON.
67
+ """
68
+ parsed = json.loads(data)
69
+ return JsonRpcRequest.model_validate(parsed)
70
+
71
+
72
+ def encode_response(
73
+ request_id: int | str | None,
74
+ result: dict[str, Any] | None = None,
75
+ error: JsonRpcError | None = None,
76
+ ) -> str:
77
+ """Encode a JSON-RPC 2.0 response as a JSON string.
78
+
79
+ Either ``result`` or ``error`` should be provided, but not both.
80
+
81
+ Args:
82
+ request_id: The matching request identifier.
83
+ result: The result value on success.
84
+ error: The error object on failure.
85
+
86
+ Returns:
87
+ A JSON string representing the full JSON-RPC 2.0 response.
88
+ """
89
+ response = JsonRpcResponse(
90
+ id=request_id,
91
+ result=result,
92
+ error=error,
93
+ )
94
+ return response.model_dump_json()
95
+
96
+
97
+ def decode_response(data: str) -> JsonRpcResponse:
98
+ """Decode a JSON string into a :class:`JsonRpcResponse`.
99
+
100
+ Args:
101
+ data: A JSON string representing a JSON-RPC 2.0 response.
102
+
103
+ Returns:
104
+ A validated :class:`JsonRpcResponse` instance.
105
+
106
+ Raises:
107
+ pydantic.ValidationError: If the JSON does not conform to the
108
+ JSON-RPC 2.0 response schema.
109
+ json.JSONDecodeError: If the string is not valid JSON.
110
+ """
111
+ parsed = json.loads(data)
112
+ return JsonRpcResponse.model_validate(parsed)
upp/rpc/errors.py ADDED
@@ -0,0 +1,127 @@
1
+ """UPP Error Codes and Exception Class.
2
+
3
+ Defines all standard JSON-RPC 2.0 error codes, UPP-specific error codes,
4
+ and the :class:`UppError` exception class used throughout the protocol.
5
+
6
+ Standard JSON-RPC Error Codes:
7
+ -32700 Parse error
8
+ -32600 Invalid Request
9
+ -32601 Method not found
10
+ -32602 Invalid params
11
+ -32603 Internal error
12
+
13
+ UPP-Specific Error Codes (-32001 to -32099):
14
+ -32001 User not found
15
+ -32002 Ontology not found
16
+ -32003 Ingest failed
17
+ -32004 Extraction failed
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from upp.rpc.messages import JsonRpcError
23
+
24
+ __all__ = [
25
+ # Standard JSON-RPC error codes
26
+ "INTERNAL_ERROR",
27
+ "INVALID_PARAMS",
28
+ "INVALID_REQUEST",
29
+ "METHOD_NOT_FOUND",
30
+ "PARSE_ERROR",
31
+ # UPP-specific error codes
32
+ "EXTRACTION_FAILED",
33
+ "ONTOLOGY_NOT_FOUND",
34
+ "INGEST_FAILED",
35
+ "USER_NOT_FOUND",
36
+ # Exception class
37
+ "UppError",
38
+ ]
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Standard JSON-RPC 2.0 Error Codes
42
+ # ---------------------------------------------------------------------------
43
+
44
+ #: Invalid JSON received by the server.
45
+ PARSE_ERROR: int = -32700
46
+
47
+ #: The JSON is valid but not a valid JSON-RPC 2.0 request.
48
+ INVALID_REQUEST: int = -32600
49
+
50
+ #: The requested method does not exist or is not available.
51
+ METHOD_NOT_FOUND: int = -32601
52
+
53
+ #: Invalid method parameter(s).
54
+ INVALID_PARAMS: int = -32602
55
+
56
+ #: Internal JSON-RPC error.
57
+ INTERNAL_ERROR: int = -32603
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # UPP-Specific Error Codes (-32001 to -32099)
61
+ # ---------------------------------------------------------------------------
62
+
63
+ #: The specified ``entity_key`` does not exist.
64
+ USER_NOT_FOUND: int = -32001
65
+
66
+ #: The requested ontology does not exist.
67
+ ONTOLOGY_NOT_FOUND: int = -32002
68
+
69
+ #: Error persisting events during ingestion.
70
+ INGEST_FAILED: int = -32003
71
+
72
+ #: Error extracting events from text.
73
+ EXTRACTION_FAILED: int = -32004
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Exception Class
78
+ # ---------------------------------------------------------------------------
79
+
80
+
81
+ class UppError(Exception):
82
+ """Exception representing a UPP protocol error.
83
+
84
+ Can be raised by any backend or the client when a protocol-level
85
+ error occurs. Provides a :meth:`to_jsonrpc_error` method for
86
+ converting to a :class:`JsonRpcError`.
87
+
88
+ Attributes:
89
+ code: The numeric error code.
90
+ message: A human-readable description of the error.
91
+ data: Optional additional structured error data.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ code: int,
97
+ message: str,
98
+ data: dict[str, object] | None = None,
99
+ ) -> None:
100
+ """Initialize a UPP error.
101
+
102
+ Args:
103
+ code: The numeric error code.
104
+ message: A human-readable description.
105
+ data: Optional structured data providing additional context.
106
+ """
107
+ super().__init__(message)
108
+ self.code = code
109
+ self.message = message
110
+ self.data = data
111
+
112
+ def to_jsonrpc_error(self) -> JsonRpcError:
113
+ """Convert this exception to a :class:`JsonRpcError` object.
114
+
115
+ Returns:
116
+ A :class:`JsonRpcError` suitable for inclusion in a
117
+ :class:`JsonRpcResponse`.
118
+ """
119
+ return JsonRpcError(
120
+ code=self.code,
121
+ message=self.message,
122
+ data=self.data,
123
+ )
124
+
125
+ def __repr__(self) -> str:
126
+ """Return a developer-friendly string representation."""
127
+ return f"UppError(code={self.code}, message={self.message!r})"
upp/rpc/messages.py ADDED
@@ -0,0 +1,354 @@
1
+ """UPP JSON-RPC 2.0 Message Types and Operation Models.
2
+
3
+ Defines Pydantic models for the four JSON-RPC 2.0 message types and
4
+ typed request/response models for all ten UPP operations.
5
+
6
+ JSON-RPC Message Types:
7
+ JsonRpcRequest, JsonRpcResponse, JsonRpcError, JsonRpcNotification
8
+
9
+ Operation Request/Response Models:
10
+ IngestRequest/IngestResponse, RetrieveRequest/RetrieveResponse,
11
+ EventsRequest/EventsResponse, DeleteRequest/DeleteResponse,
12
+ InfoRequest/InfoResponse, LabelsRequest/LabelsResponse,
13
+ ExportRequest/ExportResponse, ImportRequest/ImportResponse
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from datetime import datetime
19
+ from typing import Any, Literal
20
+
21
+ from pydantic import BaseModel, Field
22
+
23
+ from upp.models.events import Event, StoredEvent, TaskResult
24
+ from upp.models.labels import LabelDefinition
25
+
26
+ __all__ = [
27
+ # JSON-RPC base types
28
+ "JsonRpcError",
29
+ "JsonRpcNotification",
30
+ "JsonRpcRequest",
31
+ "JsonRpcResponse",
32
+ # Operation request/response models
33
+ "ContextualizeRequest",
34
+ "ContextualizeResponse",
35
+ "DeleteRequest",
36
+ "DeleteResponse",
37
+ "EventsRequest",
38
+ "EventsResponse",
39
+ "ExportRequest",
40
+ "ExportResponse",
41
+ "GetTasksRequest",
42
+ "GetTasksResponse",
43
+ "ImportRequest",
44
+ "ImportResponse",
45
+ "InfoRequest",
46
+ "InfoResponse",
47
+ "LabelsRequest",
48
+ "LabelsResponse",
49
+ "RetrieveRequest",
50
+ "RetrieveResponse",
51
+ "IngestRequest",
52
+ "IngestResponse",
53
+ ]
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # JSON-RPC 2.0 Base Types
58
+ # ---------------------------------------------------------------------------
59
+
60
+
61
+ class JsonRpcError(BaseModel):
62
+ """JSON-RPC 2.0 error object.
63
+
64
+ Attributes:
65
+ code: A number indicating the error type.
66
+ message: A short, human-readable description of the error.
67
+ data: Additional structured data about the error (optional).
68
+ """
69
+
70
+ code: int = Field(description="A number indicating the error type.")
71
+ message: str = Field(description="A short, human-readable description of the error.")
72
+ data: dict[str, Any] | None = Field(
73
+ default=None,
74
+ description="Additional structured data about the error.",
75
+ )
76
+
77
+
78
+ class JsonRpcRequest(BaseModel):
79
+ """JSON-RPC 2.0 request message.
80
+
81
+ Attributes:
82
+ jsonrpc: Protocol version, always ``"2.0"``.
83
+ id: Unique request identifier.
84
+ method: The name of the method to invoke.
85
+ params: Method parameters as a dictionary.
86
+ """
87
+
88
+ jsonrpc: Literal["2.0"] = Field(
89
+ default="2.0",
90
+ description='JSON-RPC protocol version. Always "2.0".',
91
+ )
92
+ id: int | str = Field(description="Unique request identifier.")
93
+ method: str = Field(description="The name of the method to invoke.")
94
+ params: dict[str, Any] = Field(
95
+ default_factory=dict,
96
+ description="Method parameters as a dictionary.",
97
+ )
98
+
99
+
100
+ class JsonRpcResponse(BaseModel):
101
+ """JSON-RPC 2.0 response message.
102
+
103
+ Contains either ``result`` (success) or ``error`` (failure), not both.
104
+
105
+ Attributes:
106
+ jsonrpc: Protocol version, always ``"2.0"``.
107
+ id: Matching request identifier.
108
+ result: The result value on success.
109
+ error: The error object on failure.
110
+ """
111
+
112
+ jsonrpc: Literal["2.0"] = Field(
113
+ default="2.0",
114
+ description='JSON-RPC protocol version. Always "2.0".',
115
+ )
116
+ id: int | str | None = Field(default=None, description="Matching request identifier.")
117
+ result: dict[str, Any] | None = Field(
118
+ default=None,
119
+ description="The result value on success.",
120
+ )
121
+ error: JsonRpcError | None = Field(
122
+ default=None,
123
+ description="The error object on failure.",
124
+ )
125
+
126
+
127
+ class JsonRpcNotification(BaseModel):
128
+ """JSON-RPC 2.0 notification message (fire-and-forget, no ``id``).
129
+
130
+ Attributes:
131
+ jsonrpc: Protocol version, always ``"2.0"``.
132
+ method: The name of the method to invoke.
133
+ params: Method parameters as a dictionary.
134
+ """
135
+
136
+ jsonrpc: Literal["2.0"] = Field(
137
+ default="2.0",
138
+ description='JSON-RPC protocol version. Always "2.0".',
139
+ )
140
+ method: str = Field(description="The name of the method to invoke.")
141
+ params: dict[str, Any] | None = Field(
142
+ default=None,
143
+ description="Method parameters as a dictionary.",
144
+ )
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # UPP Operation Request/Response Models
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ class IngestRequest(BaseModel):
153
+ """Request model for ``upp/ingest``.
154
+
155
+ Attributes:
156
+ entity_key: Identifier of the user.
157
+ text: Text from which to extract events.
158
+ """
159
+
160
+ entity_key: str = Field(description="Identifier of the user.")
161
+ text: str = Field(description="Text from which to extract events.")
162
+
163
+
164
+ class IngestResponse(BaseModel):
165
+ """Response model for ``upp/ingest``.
166
+
167
+ Attributes:
168
+ events: The stored events with server-assigned metadata.
169
+ """
170
+
171
+ events: list[StoredEvent] = Field(description="The stored events.")
172
+
173
+
174
+ class RetrieveRequest(BaseModel):
175
+ """Request model for ``upp/retrieve``.
176
+
177
+ Attributes:
178
+ entity_key: Identifier of the user.
179
+ query: Free-text query for relevance matching.
180
+ """
181
+
182
+ entity_key: str = Field(description="Identifier of the user.")
183
+ query: str = Field(description="Free-text query for relevance matching.")
184
+
185
+
186
+ class RetrieveResponse(BaseModel):
187
+ """Response model for ``upp/retrieve``.
188
+
189
+ Attributes:
190
+ events: Relevant events ranked by the server.
191
+ """
192
+
193
+ events: list[Event] = Field(description="Relevant events ranked by the server.")
194
+
195
+
196
+ class EventsRequest(BaseModel):
197
+ """Request model for ``upp/events``.
198
+
199
+ Attributes:
200
+ entity_key: Identifier of the user.
201
+ """
202
+
203
+ entity_key: str = Field(description="Identifier of the user.")
204
+
205
+
206
+ class EventsResponse(BaseModel):
207
+ """Response model for ``upp/events``.
208
+
209
+ Attributes:
210
+ events: All stored events for the user.
211
+ """
212
+
213
+ events: list[StoredEvent] = Field(description="All stored events for the user.")
214
+
215
+
216
+ class DeleteRequest(BaseModel):
217
+ """Request model for ``upp/delete``.
218
+
219
+ Attributes:
220
+ entity_key: Identifier of the user.
221
+ event_ids: Specific event IDs to delete. If not provided, all events are deleted.
222
+ """
223
+
224
+ entity_key: str = Field(description="Identifier of the user.")
225
+ event_ids: list[str] | None = Field(
226
+ default=None,
227
+ description="Specific event IDs to delete. If omitted, all events are deleted.",
228
+ )
229
+
230
+
231
+ class DeleteResponse(BaseModel):
232
+ """Response model for ``upp/delete``.
233
+
234
+ Attributes:
235
+ deleted_count: Number of events deleted.
236
+ """
237
+
238
+ deleted_count: int = Field(description="Number of events deleted.")
239
+
240
+
241
+ class InfoRequest(BaseModel):
242
+ """Request model for ``upp/info``. No parameters required."""
243
+
244
+ pass
245
+
246
+
247
+ class InfoResponse(BaseModel):
248
+ """Response model for ``upp/info``.
249
+
250
+ Attributes:
251
+ protocol_version: Semantic version of the UPP protocol.
252
+ ontology: The ontology identifier for this server instance.
253
+ operations: List of supported operations.
254
+ conformance_level: Server conformance level (1=Minimal, 2=Full, 3=Portable).
255
+ """
256
+
257
+ protocol_version: str = Field(description="Semantic version of the UPP protocol.")
258
+ ontology: str = Field(description="The ontology identifier for this server instance.")
259
+ operations: list[str] = Field(description="Supported operations.")
260
+ conformance_level: int = Field(
261
+ ge=1,
262
+ le=3,
263
+ description=("Server conformance level. 1: Minimal (ingest, retrieve, info). 2: Full (+ events, delete, labels). 3: Portable (+ export, import)."),
264
+ )
265
+
266
+
267
+ class LabelsRequest(BaseModel):
268
+ """Request model for ``upp/labels``. No parameters required."""
269
+
270
+ pass
271
+
272
+
273
+ class LabelsResponse(BaseModel):
274
+ """Response model for ``upp/labels``.
275
+
276
+ Attributes:
277
+ labels: Label definitions from the ontology.
278
+ """
279
+
280
+ labels: list[LabelDefinition] = Field(description="Label definitions from the ontology.")
281
+
282
+
283
+ class ExportRequest(BaseModel):
284
+ """Request model for ``upp/export``.
285
+
286
+ Attributes:
287
+ entity_key: Identifier of the user.
288
+ """
289
+
290
+ entity_key: str = Field(description="Identifier of the user.")
291
+
292
+
293
+ class ExportResponse(BaseModel):
294
+ """Response model for ``upp/export``.
295
+
296
+ Attributes:
297
+ file: Path to the exported JSON file.
298
+ event_count: Number of events exported.
299
+ exported_at: Timestamp of the export.
300
+ """
301
+
302
+ file: str = Field(description="Path to the exported JSON file.")
303
+ event_count: int = Field(description="Number of events exported.")
304
+ exported_at: datetime = Field(description="Timestamp of the export.")
305
+
306
+
307
+ class ImportRequest(BaseModel):
308
+ """Request model for ``upp/import``.
309
+
310
+ Attributes:
311
+ entity_key: Identifier of the destination user.
312
+ file: Path to the JSON file containing events to import.
313
+ """
314
+
315
+ entity_key: str = Field(description="Identifier of the destination user.")
316
+ file: str = Field(description="Path to the JSON file containing events to import.")
317
+
318
+
319
+ class ImportResponse(BaseModel):
320
+ """Response model for ``upp/import``.
321
+
322
+ Attributes:
323
+ imported_count: Number of events successfully imported.
324
+ skipped_count: Number of events skipped during import.
325
+ """
326
+
327
+ imported_count: int = Field(description="Number of events successfully imported.")
328
+ skipped_count: int = Field(description="Number of events skipped during import.")
329
+
330
+
331
+ class ContextualizeRequest(BaseModel):
332
+ """Request model for ``upp/contextualize``."""
333
+
334
+ entity_key: str = Field(description="Identifier of the user.")
335
+ text: str = Field(description="Text to retrieve context for and extract events from.")
336
+
337
+
338
+ class ContextualizeResponse(BaseModel):
339
+ """Response model for ``upp/contextualize``."""
340
+
341
+ events: list[StoredEvent] = Field(description="Relevant existing events.")
342
+ task_id: str = Field(description="Reference to the background ingest task.")
343
+
344
+
345
+ class GetTasksRequest(BaseModel):
346
+ """Request model for ``upp/get_tasks``."""
347
+
348
+ task_ids: list[str] = Field(description="One or more task IDs to check.")
349
+
350
+
351
+ class GetTasksResponse(BaseModel):
352
+ """Response model for ``upp/get_tasks``."""
353
+
354
+ tasks: list[TaskResult] = Field(description="Status of each requested task.")
upp/rpc/methods.py ADDED
@@ -0,0 +1,73 @@
1
+ """UPP JSON-RPC Method Constants.
2
+
3
+ Defines the method name constants for all ten UPP operations.
4
+ All method names use the ``upp/`` prefix as specified by the protocol.
5
+
6
+ Operations:
7
+ 5 Core: ingest, retrieve, events, delete, contextualize
8
+ 3 Discovery: info, labels, get_tasks
9
+ 2 Portability: export, import
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ __all__ = [
15
+ "ALL_METHODS",
16
+ "UPP_CONTEXTUALIZE",
17
+ "UPP_DELETE",
18
+ "UPP_EVENTS",
19
+ "UPP_EXPORT",
20
+ "UPP_GET_TASKS",
21
+ "UPP_IMPORT",
22
+ "UPP_INFO",
23
+ "UPP_LABELS",
24
+ "UPP_RETRIEVE",
25
+ "UPP_INGEST",
26
+ ]
27
+
28
+ # Core operations
29
+ UPP_INGEST: str = "upp/ingest"
30
+ """Extract and ingest events from text."""
31
+
32
+ UPP_RETRIEVE: str = "upp/retrieve"
33
+ """Retrieve relevant events given a query."""
34
+
35
+ UPP_EVENTS: str = "upp/events"
36
+ """List all stored events for a user."""
37
+
38
+ UPP_DELETE: str = "upp/delete"
39
+ """Delete events for compliance (GDPR, CCPA)."""
40
+
41
+ # Discovery operations
42
+ UPP_INFO: str = "upp/info"
43
+ """Return server metadata."""
44
+
45
+ UPP_LABELS: str = "upp/labels"
46
+ """List label definitions from an ontology."""
47
+
48
+ # Portability operations
49
+ UPP_EXPORT: str = "upp/export"
50
+ """Export events for migration."""
51
+
52
+ UPP_IMPORT: str = "upp/import"
53
+ """Import events from another server."""
54
+
55
+ UPP_CONTEXTUALIZE: str = "upp/contextualize"
56
+ """Retrieve context and ingest events in the background."""
57
+
58
+ UPP_GET_TASKS: str = "upp/get_tasks"
59
+ """Check status of background tasks."""
60
+
61
+ #: All UPP method names.
62
+ ALL_METHODS: list[str] = [
63
+ UPP_INGEST,
64
+ UPP_RETRIEVE,
65
+ UPP_EVENTS,
66
+ UPP_DELETE,
67
+ UPP_INFO,
68
+ UPP_LABELS,
69
+ UPP_EXPORT,
70
+ UPP_IMPORT,
71
+ UPP_CONTEXTUALIZE,
72
+ UPP_GET_TASKS,
73
+ ]