poelis-sdk 0.1.8__py3-none-any.whl → 0.2.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.
Potentially problematic release.
This version of poelis-sdk might be problematic. Click here for more details.
- poelis_sdk/browser.py +202 -37
- poelis_sdk/items.py +3 -12
- poelis_sdk/search.py +3 -3
- {poelis_sdk-0.1.8.dist-info → poelis_sdk-0.2.0.dist-info}/METADATA +1 -18
- {poelis_sdk-0.1.8.dist-info → poelis_sdk-0.2.0.dist-info}/RECORD +7 -7
- {poelis_sdk-0.1.8.dist-info → poelis_sdk-0.2.0.dist-info}/WHEEL +0 -0
- {poelis_sdk-0.1.8.dist-info → poelis_sdk-0.2.0.dist-info}/licenses/LICENSE +0 -0
poelis_sdk/browser.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import Any, Dict, List, Optional
|
|
4
|
+
from types import MethodType
|
|
4
5
|
import re
|
|
5
6
|
|
|
6
7
|
from .org_validation import get_organization_context_message
|
|
@@ -12,6 +13,10 @@ with optional property listing on items. Designed for notebook UX.
|
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
# Internal guard to avoid repeated completer installation
|
|
17
|
+
_AUTO_COMPLETER_INSTALLED: bool = False
|
|
18
|
+
|
|
19
|
+
|
|
15
20
|
class _Node:
|
|
16
21
|
def __init__(self, client: Any, level: str, parent: Optional["_Node"], node_id: Optional[str], name: Optional[str]) -> None:
|
|
17
22
|
self._client = client
|
|
@@ -33,34 +38,37 @@ class _Node:
|
|
|
33
38
|
def __dir__(self) -> List[str]: # pragma: no cover - notebook UX
|
|
34
39
|
# Ensure children are loaded so TAB shows options immediately
|
|
35
40
|
self._load_children()
|
|
36
|
-
keys = list(self._children_cache.keys())
|
|
41
|
+
keys = list(self._children_cache.keys())
|
|
37
42
|
if self._level == "item":
|
|
38
43
|
# Include property names directly on item for suggestions
|
|
39
44
|
prop_keys = list(self._props_key_map().keys())
|
|
40
45
|
keys.extend(prop_keys)
|
|
41
|
-
return sorted(keys)
|
|
46
|
+
return sorted(set(keys))
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
def
|
|
45
|
-
return self._id
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def name(self) -> Optional[str]:
|
|
49
|
-
return self._name
|
|
50
|
-
|
|
51
|
-
def refresh(self) -> "_Node":
|
|
48
|
+
# Intentionally no public id/name/refresh to keep suggestions minimal
|
|
49
|
+
def _refresh(self) -> "_Node":
|
|
52
50
|
self._children_cache.clear()
|
|
53
51
|
self._props_cache = None
|
|
54
52
|
return self
|
|
55
53
|
|
|
56
|
-
def
|
|
57
|
-
"""Return display names of children at this level (
|
|
54
|
+
def _names(self) -> List[str]:
|
|
55
|
+
"""Return display names of children at this level (internal)."""
|
|
58
56
|
self._load_children()
|
|
59
57
|
return [child._name or "" for child in self._children_cache.values()]
|
|
60
58
|
|
|
59
|
+
def _suggest(self) -> List[str]:
|
|
60
|
+
"""Return suggested attribute names for interactive usage.
|
|
61
|
+
|
|
62
|
+
Only child keys are returned; for item level, property keys are also included.
|
|
63
|
+
"""
|
|
64
|
+
self._load_children()
|
|
65
|
+
suggestions: List[str] = list(self._children_cache.keys())
|
|
66
|
+
if self._level == "item":
|
|
67
|
+
suggestions.extend(list(self._props_key_map().keys()))
|
|
68
|
+
return sorted(set(suggestions))
|
|
69
|
+
|
|
61
70
|
def __getattr__(self, attr: str) -> Any:
|
|
62
|
-
|
|
63
|
-
return object.__getattribute__(self, attr)
|
|
71
|
+
# No public properties/id/name/refresh
|
|
64
72
|
if attr == "props": # item-level properties pseudo-node
|
|
65
73
|
if self._level != "item":
|
|
66
74
|
raise AttributeError("props")
|
|
@@ -92,18 +100,17 @@ class _Node:
|
|
|
92
100
|
return self._children_cache[safe]
|
|
93
101
|
raise KeyError(key)
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
def properties(self) -> List[Dict[str, Any]]:
|
|
103
|
+
def _properties(self) -> List[Dict[str, Any]]:
|
|
97
104
|
if self._props_cache is not None:
|
|
98
105
|
return self._props_cache
|
|
99
106
|
if self._level != "item":
|
|
100
107
|
self._props_cache = []
|
|
101
108
|
return self._props_cache
|
|
102
|
-
# Try direct properties(
|
|
109
|
+
# Try direct properties(itemId: ...) first; fallback to searchProperties
|
|
103
110
|
q = (
|
|
104
111
|
"query($iid: ID!) {\n"
|
|
105
|
-
" properties(
|
|
106
|
-
" __typename
|
|
112
|
+
" properties(itemId: $iid) {\n"
|
|
113
|
+
" __typename\n"
|
|
107
114
|
" ... on NumericProperty { integerPart exponent category }\n"
|
|
108
115
|
" ... on TextProperty { value }\n"
|
|
109
116
|
" ... on DateProperty { value }\n"
|
|
@@ -121,7 +128,7 @@ class _Node:
|
|
|
121
128
|
q2 = (
|
|
122
129
|
"query($iid: ID!, $limit: Int!, $offset: Int!) {\n"
|
|
123
130
|
" searchProperties(q: \"*\", itemId: $iid, limit: $limit, offset: $offset) {\n"
|
|
124
|
-
" hits { id
|
|
131
|
+
" hits { id workspaceId productId itemId propertyType name category value owner }\n"
|
|
125
132
|
" }\n"
|
|
126
133
|
"}"
|
|
127
134
|
)
|
|
@@ -138,10 +145,20 @@ class _Node:
|
|
|
138
145
|
out: Dict[str, Dict[str, Any]] = {}
|
|
139
146
|
if self._level != "item":
|
|
140
147
|
return out
|
|
141
|
-
props = self.
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
props = self._properties()
|
|
149
|
+
used_names: Dict[str, int] = {}
|
|
150
|
+
for i, pr in enumerate(props):
|
|
151
|
+
# Try to get name from various possible fields
|
|
152
|
+
display = pr.get("name") or pr.get("id") or pr.get("category") or f"property_{i}"
|
|
144
153
|
safe = _safe_key(str(display))
|
|
154
|
+
|
|
155
|
+
# Handle duplicate names by adding a suffix
|
|
156
|
+
if safe in used_names:
|
|
157
|
+
used_names[safe] += 1
|
|
158
|
+
safe = f"{safe}_{used_names[safe]}"
|
|
159
|
+
else:
|
|
160
|
+
used_names[safe] = 0
|
|
161
|
+
|
|
145
162
|
out[safe] = _PropWrapper(pr)
|
|
146
163
|
return out
|
|
147
164
|
|
|
@@ -178,7 +195,7 @@ class _Node:
|
|
|
178
195
|
return
|
|
179
196
|
q = (
|
|
180
197
|
"query($pid: ID!, $parent: ID!, $limit: Int!, $offset: Int!) {\n"
|
|
181
|
-
" items(productId: $pid, parentItemId: $parent, limit: $limit, offset: $offset) { id name code description productId parentId owner }\n"
|
|
198
|
+
" items(productId: $pid, parentItemId: $parent, limit: $limit, offset: $offset) { id name code description productId parentId owner position }\n"
|
|
182
199
|
"}"
|
|
183
200
|
)
|
|
184
201
|
r = self._client._transport.graphql(q, {"pid": pid, "parent": self._id, "limit": 1000, "offset": 0})
|
|
@@ -201,6 +218,15 @@ class Browser:
|
|
|
201
218
|
|
|
202
219
|
def __init__(self, client: Any) -> None:
|
|
203
220
|
self._root = _Node(client, "root", None, None, None)
|
|
221
|
+
# Best-effort: auto-enable curated completion in interactive shells
|
|
222
|
+
global _AUTO_COMPLETER_INSTALLED
|
|
223
|
+
if not _AUTO_COMPLETER_INSTALLED:
|
|
224
|
+
try:
|
|
225
|
+
if enable_dynamic_completion():
|
|
226
|
+
_AUTO_COMPLETER_INSTALLED = True
|
|
227
|
+
except Exception:
|
|
228
|
+
# Non-interactive or IPython not available; ignore silently
|
|
229
|
+
pass
|
|
204
230
|
|
|
205
231
|
def __getattr__(self, attr: str) -> Any: # pragma: no cover - notebook UX
|
|
206
232
|
return getattr(self._root, attr)
|
|
@@ -217,7 +243,15 @@ class Browser:
|
|
|
217
243
|
def __dir__(self) -> list[str]: # pragma: no cover - notebook UX
|
|
218
244
|
# Ensure children are loaded so TAB shows options
|
|
219
245
|
self._root._load_children()
|
|
220
|
-
return sorted([*self._root._children_cache.keys()
|
|
246
|
+
return sorted([*self._root._children_cache.keys()])
|
|
247
|
+
|
|
248
|
+
def _names(self) -> List[str]:
|
|
249
|
+
"""Return display names of root-level children (workspaces)."""
|
|
250
|
+
return self._root._names()
|
|
251
|
+
|
|
252
|
+
# keep suggest internal so it doesn't appear in help/dir
|
|
253
|
+
def _suggest(self) -> List[str]:
|
|
254
|
+
return self._root._suggest()
|
|
221
255
|
|
|
222
256
|
|
|
223
257
|
def _safe_key(name: str) -> str:
|
|
@@ -245,16 +279,28 @@ class _PropsNode:
|
|
|
245
279
|
def _ensure_loaded(self) -> None:
|
|
246
280
|
if self._children_cache:
|
|
247
281
|
return
|
|
248
|
-
props = self._item.
|
|
249
|
-
|
|
250
|
-
|
|
282
|
+
props = self._item._properties()
|
|
283
|
+
used_names: Dict[str, int] = {}
|
|
284
|
+
names_list = []
|
|
285
|
+
for i, pr in enumerate(props):
|
|
286
|
+
# Try to get name from various possible fields
|
|
287
|
+
display = pr.get("name") or pr.get("id") or pr.get("category") or f"property_{i}"
|
|
251
288
|
safe = _safe_key(str(display))
|
|
289
|
+
|
|
290
|
+
# Handle duplicate names by adding a suffix
|
|
291
|
+
if safe in used_names:
|
|
292
|
+
used_names[safe] += 1
|
|
293
|
+
safe = f"{safe}_{used_names[safe]}"
|
|
294
|
+
else:
|
|
295
|
+
used_names[safe] = 0
|
|
296
|
+
|
|
252
297
|
self._children_cache[safe] = _PropWrapper(pr)
|
|
253
|
-
|
|
298
|
+
names_list.append(display)
|
|
299
|
+
self._names = names_list
|
|
254
300
|
|
|
255
301
|
def __dir__(self) -> List[str]: # pragma: no cover - notebook UX
|
|
256
302
|
self._ensure_loaded()
|
|
257
|
-
return sorted(list(self._children_cache.keys())
|
|
303
|
+
return sorted(list(self._children_cache.keys()))
|
|
258
304
|
|
|
259
305
|
def names(self) -> List[str]:
|
|
260
306
|
self._ensure_loaded()
|
|
@@ -272,13 +318,21 @@ class _PropsNode:
|
|
|
272
318
|
return self._children_cache[key]
|
|
273
319
|
# match by display name
|
|
274
320
|
for safe, data in self._children_cache.items():
|
|
275
|
-
|
|
276
|
-
|
|
321
|
+
try:
|
|
322
|
+
if getattr(data, "_raw", {}).get("name") == key: # type: ignore[arg-type]
|
|
323
|
+
return data
|
|
324
|
+
except Exception:
|
|
325
|
+
continue
|
|
277
326
|
safe = _safe_key(key)
|
|
278
327
|
if safe in self._children_cache:
|
|
279
328
|
return self._children_cache[safe]
|
|
280
329
|
raise KeyError(key)
|
|
281
330
|
|
|
331
|
+
# keep suggest internal so it doesn't appear in help/dir
|
|
332
|
+
def _suggest(self) -> List[str]:
|
|
333
|
+
self._ensure_loaded()
|
|
334
|
+
return sorted(list(self._children_cache.keys()))
|
|
335
|
+
|
|
282
336
|
|
|
283
337
|
class _PropWrapper:
|
|
284
338
|
"""Lightweight accessor for a property dict, exposing `.value` and `.raw`.
|
|
@@ -289,10 +343,6 @@ class _PropWrapper:
|
|
|
289
343
|
def __init__(self, prop: Dict[str, Any]) -> None:
|
|
290
344
|
self._raw = prop
|
|
291
345
|
|
|
292
|
-
@property
|
|
293
|
-
def raw(self) -> Dict[str, Any]:
|
|
294
|
-
return self._raw
|
|
295
|
-
|
|
296
346
|
@property
|
|
297
347
|
def value(self) -> Any: # type: ignore[override]
|
|
298
348
|
p = self._raw
|
|
@@ -315,8 +365,123 @@ class _PropWrapper:
|
|
|
315
365
|
return p.get("value")
|
|
316
366
|
return None
|
|
317
367
|
|
|
368
|
+
@property
|
|
369
|
+
def category(self) -> Optional[str]:
|
|
370
|
+
p = self._raw
|
|
371
|
+
cat = p.get("category")
|
|
372
|
+
return str(cat) if cat is not None else None
|
|
373
|
+
|
|
374
|
+
def __dir__(self) -> List[str]: # pragma: no cover - notebook UX
|
|
375
|
+
# Expose only the minimal attributes for browsing
|
|
376
|
+
return ["value", "category"]
|
|
377
|
+
|
|
318
378
|
def __repr__(self) -> str: # pragma: no cover - notebook UX
|
|
319
379
|
name = self._raw.get("name") or self._raw.get("id")
|
|
320
380
|
return f"<property {name}: {self.value}>"
|
|
321
381
|
|
|
322
382
|
|
|
383
|
+
|
|
384
|
+
def enable_dynamic_completion() -> bool:
|
|
385
|
+
"""Enable dynamic attribute completion in IPython/Jupyter environments.
|
|
386
|
+
|
|
387
|
+
This helper attempts to configure IPython to use runtime-based completion
|
|
388
|
+
(disabling Jedi) so that our dynamic `__dir__` and `suggest()` methods are
|
|
389
|
+
respected by TAB completion. Returns True if an interactive shell was found
|
|
390
|
+
and configured, False otherwise.
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
try:
|
|
394
|
+
# Deferred import to avoid hard dependency
|
|
395
|
+
from IPython import get_ipython # type: ignore
|
|
396
|
+
except Exception:
|
|
397
|
+
return False
|
|
398
|
+
|
|
399
|
+
ip = None
|
|
400
|
+
try:
|
|
401
|
+
ip = get_ipython() # type: ignore[assignment]
|
|
402
|
+
except Exception:
|
|
403
|
+
ip = None
|
|
404
|
+
if ip is None:
|
|
405
|
+
return False
|
|
406
|
+
|
|
407
|
+
enabled = False
|
|
408
|
+
# Best-effort configuration: rely on IPython's fallback (non-Jedi) completer
|
|
409
|
+
try:
|
|
410
|
+
if hasattr(ip, "Completer") and hasattr(ip.Completer, "use_jedi"):
|
|
411
|
+
# Disable Jedi to let IPython consult __dir__ dynamically
|
|
412
|
+
ip.Completer.use_jedi = False # type: ignore[assignment]
|
|
413
|
+
# Greedy completion improves attribute completion depth
|
|
414
|
+
if hasattr(ip.Completer, "greedy"):
|
|
415
|
+
ip.Completer.greedy = True # type: ignore[assignment]
|
|
416
|
+
enabled = True
|
|
417
|
+
except Exception:
|
|
418
|
+
pass
|
|
419
|
+
|
|
420
|
+
# Additionally, install a lightweight attribute completer that uses suggest()
|
|
421
|
+
try:
|
|
422
|
+
comp = getattr(ip, "Completer", None)
|
|
423
|
+
if comp is not None and hasattr(comp, "attr_matches"):
|
|
424
|
+
orig_attr_matches = comp.attr_matches # type: ignore[attr-defined]
|
|
425
|
+
|
|
426
|
+
def _poelis_attr_matches(self: Any, text: str) -> List[str]: # pragma: no cover - interactive behavior
|
|
427
|
+
try:
|
|
428
|
+
# text is like "client.browser.uh2.pr" → split at last dot
|
|
429
|
+
obj_expr, _, prefix = text.rpartition(".")
|
|
430
|
+
if not obj_expr:
|
|
431
|
+
return orig_attr_matches(text) # type: ignore[operator]
|
|
432
|
+
# Evaluate the object in the user namespace
|
|
433
|
+
ns = getattr(self, "namespace", {})
|
|
434
|
+
obj_val = eval(obj_expr, ns, ns)
|
|
435
|
+
|
|
436
|
+
# For Poelis browser objects, show ONLY our curated suggestions
|
|
437
|
+
from_types = (Browser, _Node, _PropsNode, _PropWrapper)
|
|
438
|
+
if isinstance(obj_val, from_types):
|
|
439
|
+
# Build suggestion list
|
|
440
|
+
if isinstance(obj_val, _PropWrapper):
|
|
441
|
+
sugg: List[str] = ["value", "category"]
|
|
442
|
+
elif hasattr(obj_val, "_suggest"):
|
|
443
|
+
sugg = list(getattr(obj_val, "_suggest")()) # type: ignore[no-untyped-call]
|
|
444
|
+
else:
|
|
445
|
+
sugg = list(dir(obj_val))
|
|
446
|
+
# Filter by prefix and format matches as full attribute paths
|
|
447
|
+
out: List[str] = []
|
|
448
|
+
for s in sugg:
|
|
449
|
+
if not prefix or str(s).startswith(prefix):
|
|
450
|
+
out.append(f"{obj_expr}.{s}")
|
|
451
|
+
return out
|
|
452
|
+
|
|
453
|
+
# Otherwise, fall back to default behavior
|
|
454
|
+
return orig_attr_matches(text) # type: ignore[operator]
|
|
455
|
+
except Exception:
|
|
456
|
+
# fall back to original on any error
|
|
457
|
+
return orig_attr_matches(text) # type: ignore[operator]
|
|
458
|
+
|
|
459
|
+
comp.attr_matches = MethodType(_poelis_attr_matches, comp) # type: ignore[assignment]
|
|
460
|
+
enabled = True
|
|
461
|
+
except Exception:
|
|
462
|
+
pass
|
|
463
|
+
|
|
464
|
+
# Also register as a high-priority matcher in IPCompleter.matchers
|
|
465
|
+
try:
|
|
466
|
+
comp = getattr(ip, "Completer", None)
|
|
467
|
+
if comp is not None and hasattr(comp, "matchers") and not getattr(comp, "_poelis_matcher_installed", False):
|
|
468
|
+
orig_attr_matches = comp.attr_matches # type: ignore[attr-defined]
|
|
469
|
+
|
|
470
|
+
def _poelis_matcher(self: Any, text: str) -> List[str]: # pragma: no cover - interactive behavior
|
|
471
|
+
# Delegate to our attribute logic for dotted expressions; otherwise empty
|
|
472
|
+
if "." in text:
|
|
473
|
+
try:
|
|
474
|
+
return self.attr_matches(text) # type: ignore[operator]
|
|
475
|
+
except Exception:
|
|
476
|
+
return orig_attr_matches(text) # type: ignore[operator]
|
|
477
|
+
return []
|
|
478
|
+
|
|
479
|
+
# Prepend our matcher so it's consulted early
|
|
480
|
+
comp.matchers.insert(0, MethodType(_poelis_matcher, comp)) # type: ignore[arg-type]
|
|
481
|
+
setattr(comp, "_poelis_matcher_installed", True)
|
|
482
|
+
enabled = True
|
|
483
|
+
except Exception:
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
return bool(enabled)
|
|
487
|
+
|
poelis_sdk/items.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Generator, Any, Optional, Dict, List
|
|
4
4
|
|
|
5
5
|
from ._transport import Transport
|
|
6
|
-
from .org_validation import validate_item_organization, filter_by_organization
|
|
7
6
|
|
|
8
7
|
"""Items resource client."""
|
|
9
8
|
|
|
@@ -22,7 +21,7 @@ class ItemsClient:
|
|
|
22
21
|
|
|
23
22
|
query = (
|
|
24
23
|
"query($pid: ID!, $q: String, $limit: Int!, $offset: Int!) {\n"
|
|
25
|
-
" items(productId: $pid, q: $q, limit: $limit, offset: $offset) { id name code description productId parentId owner
|
|
24
|
+
" items(productId: $pid, q: $q, limit: $limit, offset: $offset) { id name code description productId parentId owner position }\n"
|
|
26
25
|
"}"
|
|
27
26
|
)
|
|
28
27
|
variables = {"pid": product_id, "q": q, "limit": int(limit), "offset": int(offset)}
|
|
@@ -34,11 +33,7 @@ class ItemsClient:
|
|
|
34
33
|
|
|
35
34
|
items = payload.get("data", {}).get("items", [])
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
expected_org_id = self._t._org_id
|
|
39
|
-
filtered_items = filter_by_organization(items, expected_org_id, "items")
|
|
40
|
-
|
|
41
|
-
return filtered_items
|
|
36
|
+
return items
|
|
42
37
|
|
|
43
38
|
def get(self, item_id: str) -> Dict[str, Any]:
|
|
44
39
|
"""Get a single item by id via GraphQL.
|
|
@@ -48,7 +43,7 @@ class ItemsClient:
|
|
|
48
43
|
|
|
49
44
|
query = (
|
|
50
45
|
"query($id: ID!) {\n"
|
|
51
|
-
" item(id: $id) { id name code description productId parentId owner
|
|
46
|
+
" item(id: $id) { id name code description productId parentId owner position }\n"
|
|
52
47
|
"}"
|
|
53
48
|
)
|
|
54
49
|
resp = self._t.graphql(query=query, variables={"id": item_id})
|
|
@@ -61,10 +56,6 @@ class ItemsClient:
|
|
|
61
56
|
if item is None:
|
|
62
57
|
raise RuntimeError(f"Item with id '{item_id}' not found")
|
|
63
58
|
|
|
64
|
-
# Validate that the item belongs to the configured organization
|
|
65
|
-
expected_org_id = self._t._org_id
|
|
66
|
-
validate_item_organization(item, expected_org_id)
|
|
67
|
-
|
|
68
59
|
return item
|
|
69
60
|
|
|
70
61
|
def iter_all_by_product(self, *, product_id: str, q: Optional[str] = None, page_size: int = 100) -> Generator[dict, None, None]:
|
poelis_sdk/search.py
CHANGED
|
@@ -14,11 +14,11 @@ class SearchClient:
|
|
|
14
14
|
self._t = transport
|
|
15
15
|
|
|
16
16
|
def products(self, *, q: str, workspace_id: str, limit: int = 20, offset: int = 0) -> Dict[str, Any]:
|
|
17
|
-
"""Search/list products via GraphQL products(
|
|
17
|
+
"""Search/list products via GraphQL products(workspaceId, q)."""
|
|
18
18
|
|
|
19
19
|
query = (
|
|
20
20
|
"query($ws: ID!, $q: String, $limit: Int!, $offset: Int!) {\n"
|
|
21
|
-
" products(
|
|
21
|
+
" products(workspaceId: $ws, q: $q, limit: $limit, offset: $offset) { id name code description workspaceId }\n"
|
|
22
22
|
"}"
|
|
23
23
|
)
|
|
24
24
|
variables = {"ws": workspace_id, "q": q, "limit": int(limit), "offset": int(offset)}
|
|
@@ -35,7 +35,7 @@ class SearchClient:
|
|
|
35
35
|
|
|
36
36
|
query = (
|
|
37
37
|
"query($pid: ID!, $q: String, $parent: ID, $limit: Int!, $offset: Int!) {\n"
|
|
38
|
-
" items(productId: $pid, q: $q, parentItemId: $parent, limit: $limit, offset: $offset) { id name code description productId parentId owner }\n"
|
|
38
|
+
" items(productId: $pid, q: $q, parentItemId: $parent, limit: $limit, offset: $offset) { id name code description productId parentId owner position }\n"
|
|
39
39
|
"}"
|
|
40
40
|
)
|
|
41
41
|
variables = {"pid": product_id, "q": q, "parent": parent_item_id, "limit": int(limit), "offset": int(offset)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: poelis-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Official Python SDK for Poelis
|
|
5
5
|
Project-URL: Homepage, https://poelis.com
|
|
6
6
|
Project-URL: Source, https://github.com/PoelisTechnologies/poelis-python-sdk
|
|
@@ -42,22 +42,6 @@ client = PoelisClient(
|
|
|
42
42
|
api_key="poelis_live_A1B2C3...", # Organization Settings → API Keys
|
|
43
43
|
org_id="tenant_uci_001", # same section
|
|
44
44
|
)
|
|
45
|
-
|
|
46
|
-
# Workspaces → Products
|
|
47
|
-
workspaces = client.workspaces.list(limit=10, offset=0)
|
|
48
|
-
ws_id = workspaces[0]["id"]
|
|
49
|
-
|
|
50
|
-
page = client.products.list_by_workspace(workspace_id=ws_id, limit=10, offset=0)
|
|
51
|
-
print([p.name for p in page.data])
|
|
52
|
-
|
|
53
|
-
# Items for a product
|
|
54
|
-
pid = page.data[0].id
|
|
55
|
-
items = client.items.list_by_product(product_id=pid, limit=10, offset=0)
|
|
56
|
-
print([i.get("name") for i in items])
|
|
57
|
-
|
|
58
|
-
# Property search
|
|
59
|
-
props = client.search.properties(q="*", workspace_id=ws_id, limit=10, offset=0)
|
|
60
|
-
print(props["total"], len(props["hits"]))
|
|
61
45
|
```
|
|
62
46
|
|
|
63
47
|
## Configuration
|
|
@@ -73,7 +57,6 @@ print(props["total"], len(props["hits"]))
|
|
|
73
57
|
```bash
|
|
74
58
|
export POELIS_API_KEY=poelis_live_A1B2C3...
|
|
75
59
|
export POELIS_ORG_ID=tenant_id_001
|
|
76
|
-
# POELIS_BASE_URL is optional - defaults to the managed GCP endpoint
|
|
77
60
|
```
|
|
78
61
|
|
|
79
62
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
poelis_sdk/__init__.py,sha256=vRKuvnMGtq2_6SYDPNpckSPYXTgMDD1vBAfZ1bXlHL0,924
|
|
2
2
|
poelis_sdk/_transport.py,sha256=F5EX0EJFHJPAE638nKzlX5zLSU6FIMzMemqgh05_V6U,6061
|
|
3
3
|
poelis_sdk/auth0.py,sha256=VDZHCv9YpsW55H-PLINKMq74UhevP6OWyBHQyEFIpvw,3163
|
|
4
|
-
poelis_sdk/browser.py,sha256=
|
|
4
|
+
poelis_sdk/browser.py,sha256=D-9Xgj1SBy4DQa6Ow8gQ1DOWhy373WqYJMYIiXFXlgM,19803
|
|
5
5
|
poelis_sdk/client.py,sha256=10__5po-foX36ZCCduQmzdoh9NNS320kyaqztUNtPvo,3872
|
|
6
6
|
poelis_sdk/exceptions.py,sha256=qX5kpAr8ozJUOW-CNhmspWVIE-bvUZT_PUnimYuBxNY,1101
|
|
7
|
-
poelis_sdk/items.py,sha256=
|
|
7
|
+
poelis_sdk/items.py,sha256=vomXn43gcUlX2iUro3mpb8Qicmmt4sWFB2vXXxIfLsM,2575
|
|
8
8
|
poelis_sdk/logging.py,sha256=zmg8Us-7qjDl0n_NfOSvDolLopy7Dc_hQ-pcrC63dY8,2442
|
|
9
9
|
poelis_sdk/models.py,sha256=tpL7f66dsCobapKp3_rt-w6oiyYvWtoehLvCfjTDLl4,538
|
|
10
10
|
poelis_sdk/org_validation.py,sha256=c4fB6ySTvcovWxG4F1wU_OBlP-FyuIaAUzCwqgJKzBE,5607
|
|
11
11
|
poelis_sdk/products.py,sha256=bwV2mOPvBriy83F3BxWww1oSsyLZFQvh4XOiIE9fI1s,3240
|
|
12
|
-
poelis_sdk/search.py,sha256=
|
|
12
|
+
poelis_sdk/search.py,sha256=KQbdnu2khQDewddSJHR7JWysH1f2M7swK6MR-ZwrLAE,4101
|
|
13
13
|
poelis_sdk/workspaces.py,sha256=hpmRl-Hswr4YDvObQdyVpegIYjUWno7A_BiVBz-AQGc,2383
|
|
14
|
-
poelis_sdk-0.
|
|
15
|
-
poelis_sdk-0.
|
|
16
|
-
poelis_sdk-0.
|
|
17
|
-
poelis_sdk-0.
|
|
14
|
+
poelis_sdk-0.2.0.dist-info/METADATA,sha256=zV8xPL_4dDRJ8QfzuX2zMswzDi_huEg2DdsFaB32kZ0,2219
|
|
15
|
+
poelis_sdk-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
poelis_sdk-0.2.0.dist-info/licenses/LICENSE,sha256=EEmE_r8wk_pdXB8CWp1LG6sBOl7--hNSS2kV94cI6co,1075
|
|
17
|
+
poelis_sdk-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|