polygres 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.
polygres/errors.py ADDED
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class PolygresError(Exception):
7
+ """Base Polygres SDK exception."""
8
+
9
+
10
+ class PolygresValidationError(PolygresError, ValueError):
11
+ """Raised before a request is sent when local validation fails."""
12
+
13
+
14
+ class PolygresAPIError(PolygresError):
15
+ def __init__(
16
+ self,
17
+ message: str,
18
+ *,
19
+ status_code: int | None = None,
20
+ request_id: str | None = None,
21
+ code: str | None = None,
22
+ details: dict[str, Any] | None = None,
23
+ ) -> None:
24
+ super().__init__(message)
25
+ self.status_code = status_code
26
+ self.request_id = request_id
27
+ self.code = code
28
+ self.details = details or {}
29
+
30
+
31
+ class PolygresAuthError(PolygresAPIError):
32
+ """Raised for authentication failures."""
33
+
34
+
35
+ class PolygresPermissionError(PolygresAPIError):
36
+ """Raised for authorization failures."""
37
+
38
+
39
+ class PolygresNotFoundError(PolygresAPIError):
40
+ """Raised when a requested resource is not found."""
41
+
42
+
43
+ class PolygresRateLimitError(PolygresAPIError):
44
+ """Raised when the API rate limits a request."""
45
+
46
+
47
+ class PolygresRuntimeError(PolygresAPIError):
48
+ """Raised for transient API/runtime failures."""
polygres/models.py ADDED
@@ -0,0 +1,336 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Iterator
4
+ from dataclasses import asdict, dataclass, field
5
+ from typing import Any, Generic, TypeVar
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ def _to_dict(value: Any) -> Any:
11
+ if hasattr(value, "to_dict"):
12
+ return value.to_dict()
13
+ if isinstance(value, list):
14
+ return [_to_dict(item) for item in value]
15
+ if isinstance(value, dict):
16
+ return {key: _to_dict(item) for key, item in value.items()}
17
+ return value
18
+
19
+
20
+ @dataclass
21
+ class Page(Generic[T]):
22
+ results: list[T]
23
+ next_cursor: str | None
24
+ has_more: bool
25
+ request_id: str | None = None
26
+ metadata: dict[str, Any] = field(default_factory=dict)
27
+ _fetch_next: Callable[[str], Page[T]] | None = field(
28
+ default=None, repr=False, compare=False
29
+ )
30
+
31
+ @classmethod
32
+ def from_api(
33
+ cls,
34
+ payload: dict[str, Any],
35
+ parser: Callable[[dict[str, Any]], T],
36
+ fetch_next: Callable[[str], Page[T]] | None = None,
37
+ ) -> Page[T]:
38
+ return cls(
39
+ results=[parser(item) for item in payload.get("results", [])],
40
+ next_cursor=payload.get("next_cursor"),
41
+ has_more=bool(payload.get("has_more", False)),
42
+ request_id=payload.get("request_id"),
43
+ metadata={
44
+ key: value
45
+ for key, value in payload.items()
46
+ if key not in {"results", "next_cursor", "has_more", "request_id"}
47
+ },
48
+ _fetch_next=fetch_next,
49
+ )
50
+
51
+ def auto_paging_iter(self) -> Iterator[T]:
52
+ page: Page[T] = self
53
+ while True:
54
+ yield from page.results
55
+ if not page.has_more or not page.next_cursor or page._fetch_next is None:
56
+ return
57
+ page = page._fetch_next(page.next_cursor)
58
+
59
+ def to_dict(self) -> dict[str, Any]:
60
+ return {
61
+ "results": [_to_dict(item) for item in self.results],
62
+ "next_cursor": self.next_cursor,
63
+ "has_more": self.has_more,
64
+ "request_id": self.request_id,
65
+ "metadata": _to_dict(self.metadata),
66
+ }
67
+
68
+
69
+ @dataclass
70
+ class ConnectionInfo:
71
+ project_id: str
72
+ database: str
73
+ username: str
74
+ port: int
75
+ direct_host: str
76
+ pooled_host: str
77
+ direct_url_without_password: str
78
+ pooled_url_without_password: str
79
+ request_id: str | None = None
80
+ metadata: dict[str, Any] = field(default_factory=dict)
81
+
82
+ @classmethod
83
+ def from_api(cls, payload: dict[str, Any]) -> ConnectionInfo:
84
+ direct = payload.get("direct", {})
85
+ pooled = payload.get("pooled", {})
86
+ return cls(
87
+ project_id=payload["project_id"],
88
+ database=payload["database"],
89
+ username=payload["username"],
90
+ port=int(payload["port"]),
91
+ direct_host=direct["host"],
92
+ pooled_host=pooled["host"],
93
+ direct_url_without_password=direct["connection_string_without_password"],
94
+ pooled_url_without_password=pooled["connection_string_without_password"],
95
+ request_id=payload.get("request_id"),
96
+ metadata=dict(payload.get("metadata", {})),
97
+ )
98
+
99
+ def to_dict(self) -> dict[str, Any]:
100
+ return asdict(self)
101
+
102
+
103
+ @dataclass
104
+ class RetrievalReadiness:
105
+ project_id: str
106
+ graph: dict[str, Any]
107
+ vector: dict[str, Any]
108
+ hybrid: dict[str, Any]
109
+ request_id: str | None = None
110
+ metadata: dict[str, Any] = field(default_factory=dict)
111
+
112
+ @classmethod
113
+ def from_api(cls, payload: dict[str, Any]) -> RetrievalReadiness:
114
+ return cls(
115
+ project_id=payload["project_id"],
116
+ graph=dict(payload.get("graph", {})),
117
+ vector=dict(payload.get("vector", {})),
118
+ hybrid=dict(payload.get("hybrid", {})),
119
+ request_id=payload.get("request_id"),
120
+ metadata=dict(payload.get("metadata", {})),
121
+ )
122
+
123
+ def to_dict(self) -> dict[str, Any]:
124
+ return asdict(self)
125
+
126
+
127
+ @dataclass
128
+ class GraphNode:
129
+ schema: str
130
+ table: str
131
+ id: str
132
+ properties: dict[str, Any] = field(default_factory=dict)
133
+
134
+ @classmethod
135
+ def from_api(cls, payload: dict[str, Any]) -> GraphNode:
136
+ return cls(
137
+ schema=payload["schema"],
138
+ table=payload["table"],
139
+ id=str(payload["id"]),
140
+ properties=dict(payload.get("properties", {})),
141
+ )
142
+
143
+ def to_dict(self) -> dict[str, Any]:
144
+ return asdict(self)
145
+
146
+
147
+ @dataclass
148
+ class GraphPathStep:
149
+ step: int
150
+ node: GraphNode
151
+ edge_label: str | None = None
152
+ readable_path: str | None = None
153
+
154
+ @classmethod
155
+ def from_api(cls, payload: dict[str, Any]) -> GraphPathStep:
156
+ node = payload.get("node", payload)
157
+ return cls(
158
+ step=int(payload.get("step", 0)),
159
+ node=GraphNode.from_api(node),
160
+ edge_label=payload.get("edge_label"),
161
+ readable_path=payload.get("readable_path"),
162
+ )
163
+
164
+ def to_dict(self) -> dict[str, Any]:
165
+ return {
166
+ "step": self.step,
167
+ "node": self.node.to_dict(),
168
+ "edge_label": self.edge_label,
169
+ "readable_path": self.readable_path,
170
+ }
171
+
172
+
173
+ @dataclass
174
+ class GraphResult:
175
+ node: GraphNode
176
+ depth: int
177
+ rank: int | None
178
+ graph_score: float | None
179
+ path: list[Any] | None
180
+ edge_path: list[Any] | None
181
+ readable_path: str | None
182
+ relationships: list[Any] = field(default_factory=list)
183
+
184
+ @classmethod
185
+ def from_api(cls, payload: dict[str, Any]) -> GraphResult:
186
+ node_payload = dict(payload["node"])
187
+ if "properties" not in node_payload:
188
+ node_payload["properties"] = payload.get("properties", {})
189
+ return cls(
190
+ node=GraphNode.from_api(node_payload),
191
+ depth=int(payload.get("depth", 0)),
192
+ rank=payload.get("rank"),
193
+ graph_score=payload.get("graph_score"),
194
+ path=payload.get("path"),
195
+ edge_path=payload.get("edge_path"),
196
+ readable_path=payload.get("readable_path"),
197
+ relationships=list(payload.get("relationships", [])),
198
+ )
199
+
200
+ def to_dict(self) -> dict[str, Any]:
201
+ return {
202
+ "node": self.node.to_dict(),
203
+ "depth": self.depth,
204
+ "rank": self.rank,
205
+ "graph_score": self.graph_score,
206
+ "path": self.path,
207
+ "edge_path": self.edge_path,
208
+ "readable_path": self.readable_path,
209
+ "relationships": self.relationships,
210
+ }
211
+
212
+
213
+ @dataclass
214
+ class VectorResult:
215
+ schema: str
216
+ table: str
217
+ id: str
218
+ properties: dict[str, Any]
219
+ distance: float
220
+ similarity: float | None
221
+ score: float
222
+
223
+ @classmethod
224
+ def from_api(cls, payload: dict[str, Any]) -> VectorResult:
225
+ return cls(
226
+ schema=payload["schema"],
227
+ table=payload["table"],
228
+ id=str(payload["id"]),
229
+ properties=dict(payload.get("properties", {})),
230
+ distance=float(payload["distance"]),
231
+ similarity=payload.get("similarity"),
232
+ score=float(payload["score"]),
233
+ )
234
+
235
+ def to_dict(self) -> dict[str, Any]:
236
+ return asdict(self)
237
+
238
+
239
+ @dataclass
240
+ class TextResult:
241
+ schema: str
242
+ table: str
243
+ id: str
244
+ properties: dict[str, Any]
245
+ score: float
246
+ similarity: float | None = None
247
+
248
+ @classmethod
249
+ def from_api(cls, payload: dict[str, Any]) -> TextResult:
250
+ return cls(
251
+ schema=payload["schema"],
252
+ table=payload["table"],
253
+ id=str(payload["id"]),
254
+ properties=dict(payload.get("properties", {})),
255
+ score=float(payload["score"]),
256
+ similarity=payload.get("similarity"),
257
+ )
258
+
259
+ def to_dict(self) -> dict[str, Any]:
260
+ return asdict(self)
261
+
262
+
263
+ @dataclass
264
+ class HybridResult:
265
+ schema: str
266
+ table: str
267
+ id: str
268
+ properties: dict[str, Any]
269
+ score: float
270
+ vector_score: float | None
271
+ graph_score: float | None
272
+ distance: float | None
273
+ similarity: float | None
274
+ relationships: list[Any]
275
+
276
+ @classmethod
277
+ def from_api(cls, payload: dict[str, Any]) -> HybridResult:
278
+ node = payload.get("node", payload)
279
+ score = payload.get("score", payload.get("rrf_score", 0.0))
280
+ return cls(
281
+ schema=node["schema"],
282
+ table=node["table"],
283
+ id=str(node["id"]),
284
+ properties=dict(payload.get("properties", node.get("properties", {}))),
285
+ score=float(score),
286
+ vector_score=payload.get("vector_score"),
287
+ graph_score=payload.get("graph_score"),
288
+ distance=payload.get("distance"),
289
+ similarity=payload.get("similarity"),
290
+ relationships=list(payload.get("relationships", [])),
291
+ )
292
+
293
+ def to_dict(self) -> dict[str, Any]:
294
+ return asdict(self)
295
+
296
+
297
+ @dataclass
298
+ class GraphPathResponse:
299
+ paths: list[dict[str, Any]]
300
+ request_id: str | None = None
301
+ metadata: dict[str, Any] = field(default_factory=dict)
302
+
303
+ @classmethod
304
+ def from_api(cls, payload: dict[str, Any]) -> GraphPathResponse:
305
+ return cls(
306
+ paths=list(payload.get("paths", [])),
307
+ request_id=payload.get("request_id"),
308
+ metadata={
309
+ key: value for key, value in payload.items() if key not in {"paths", "request_id"}
310
+ },
311
+ )
312
+
313
+ def to_dict(self) -> dict[str, Any]:
314
+ return asdict(self)
315
+
316
+
317
+ @dataclass
318
+ class GraphConnectionResponse:
319
+ connections: list[dict[str, Any]]
320
+ request_id: str | None = None
321
+ metadata: dict[str, Any] = field(default_factory=dict)
322
+
323
+ @classmethod
324
+ def from_api(cls, payload: dict[str, Any]) -> GraphConnectionResponse:
325
+ return cls(
326
+ connections=list(payload.get("connections", [])),
327
+ request_id=payload.get("request_id"),
328
+ metadata={
329
+ key: value
330
+ for key, value in payload.items()
331
+ if key not in {"connections", "request_id"}
332
+ },
333
+ )
334
+
335
+ def to_dict(self) -> dict[str, Any]:
336
+ return asdict(self)
polygres/py.typed ADDED
@@ -0,0 +1 @@
1
+