poelis-sdk 0.1.7__tar.gz → 0.1.9__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.
Potentially problematic release.
This version of poelis-sdk might be problematic. Click here for more details.
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/PKG-INFO +1 -1
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/notebooks/try_poelis_sdk.ipynb +1 -9
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/pyproject.toml +1 -1
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/browser.py +31 -9
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/items.py +3 -12
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/models.py +3 -2
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/products.py +3 -8
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/uv.lock +1 -1
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/.github/workflows/ci.yml +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/.github/workflows/codeql.yml +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/.github/workflows/publish-on-push.yml +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/.gitignore +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/LICENSE +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/README.md +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/__init__.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/_transport.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/auth0.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/client.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/exceptions.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/logging.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/org_validation.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/search.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/poelis_sdk/workspaces.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/tests/test_client_basic.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/tests/test_errors_and_backoff.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/tests/test_items_client.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/tests/test_search_client.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/src/tests/test_transport_and_products.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/tests/__init__.py +0 -0
- {poelis_sdk-0.1.7 → poelis_sdk-0.1.9}/tests/test_integration_smoke.py +0 -0
|
@@ -47,15 +47,7 @@
|
|
|
47
47
|
"cell_type": "code",
|
|
48
48
|
"execution_count": null,
|
|
49
49
|
"metadata": {},
|
|
50
|
-
"outputs": [
|
|
51
|
-
{
|
|
52
|
-
"name": "stdout",
|
|
53
|
-
"output_type": "stream",
|
|
54
|
-
"text": [
|
|
55
|
-
"The folder you are executing pip from can no longer be found.\n"
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
],
|
|
50
|
+
"outputs": [],
|
|
59
51
|
"source": [
|
|
60
52
|
"!pip install -U poelis-sdk"
|
|
61
53
|
]
|
|
@@ -99,11 +99,11 @@ class _Node:
|
|
|
99
99
|
if self._level != "item":
|
|
100
100
|
self._props_cache = []
|
|
101
101
|
return self._props_cache
|
|
102
|
-
# Try direct properties(
|
|
102
|
+
# Try direct properties(itemId: ...) first; fallback to searchProperties
|
|
103
103
|
q = (
|
|
104
104
|
"query($iid: ID!) {\n"
|
|
105
|
-
" properties(
|
|
106
|
-
" __typename
|
|
105
|
+
" properties(itemId: $iid) {\n"
|
|
106
|
+
" __typename\n"
|
|
107
107
|
" ... on NumericProperty { integerPart exponent category }\n"
|
|
108
108
|
" ... on TextProperty { value }\n"
|
|
109
109
|
" ... on DateProperty { value }\n"
|
|
@@ -121,7 +121,7 @@ class _Node:
|
|
|
121
121
|
q2 = (
|
|
122
122
|
"query($iid: ID!, $limit: Int!, $offset: Int!) {\n"
|
|
123
123
|
" searchProperties(q: \"*\", itemId: $iid, limit: $limit, offset: $offset) {\n"
|
|
124
|
-
" hits { id
|
|
124
|
+
" hits { id workspaceId productId itemId propertyType name category value owner }\n"
|
|
125
125
|
" }\n"
|
|
126
126
|
"}"
|
|
127
127
|
)
|
|
@@ -139,9 +139,19 @@ class _Node:
|
|
|
139
139
|
if self._level != "item":
|
|
140
140
|
return out
|
|
141
141
|
props = self.properties
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
used_names: Dict[str, int] = {}
|
|
143
|
+
for i, pr in enumerate(props):
|
|
144
|
+
# Try to get name from various possible fields
|
|
145
|
+
display = pr.get("name") or pr.get("id") or pr.get("category") or f"property_{i}"
|
|
144
146
|
safe = _safe_key(str(display))
|
|
147
|
+
|
|
148
|
+
# Handle duplicate names by adding a suffix
|
|
149
|
+
if safe in used_names:
|
|
150
|
+
used_names[safe] += 1
|
|
151
|
+
safe = f"{safe}_{used_names[safe]}"
|
|
152
|
+
else:
|
|
153
|
+
used_names[safe] = 0
|
|
154
|
+
|
|
145
155
|
out[safe] = _PropWrapper(pr)
|
|
146
156
|
return out
|
|
147
157
|
|
|
@@ -246,11 +256,23 @@ class _PropsNode:
|
|
|
246
256
|
if self._children_cache:
|
|
247
257
|
return
|
|
248
258
|
props = self._item.properties
|
|
249
|
-
|
|
250
|
-
|
|
259
|
+
used_names: Dict[str, int] = {}
|
|
260
|
+
names_list = []
|
|
261
|
+
for i, pr in enumerate(props):
|
|
262
|
+
# Try to get name from various possible fields
|
|
263
|
+
display = pr.get("name") or pr.get("id") or pr.get("category") or f"property_{i}"
|
|
251
264
|
safe = _safe_key(str(display))
|
|
265
|
+
|
|
266
|
+
# Handle duplicate names by adding a suffix
|
|
267
|
+
if safe in used_names:
|
|
268
|
+
used_names[safe] += 1
|
|
269
|
+
safe = f"{safe}_{used_names[safe]}"
|
|
270
|
+
else:
|
|
271
|
+
used_names[safe] = 0
|
|
272
|
+
|
|
252
273
|
self._children_cache[safe] = _PropWrapper(pr)
|
|
253
|
-
|
|
274
|
+
names_list.append(display)
|
|
275
|
+
self._names = names_list
|
|
254
276
|
|
|
255
277
|
def __dir__(self) -> List[str]: # pragma: no cover - notebook UX
|
|
256
278
|
self._ensure_loaded()
|
|
@@ -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 }\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 }\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]:
|
|
@@ -12,8 +12,9 @@ class Product(BaseModel):
|
|
|
12
12
|
|
|
13
13
|
id: str = Field(min_length=1)
|
|
14
14
|
name: str = Field(min_length=1)
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
workspaceId: Optional[str] = None
|
|
16
|
+
code: Optional[str] = None
|
|
17
|
+
description: Optional[str] = None
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class PaginatedProducts(BaseModel):
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Generator, Optional,
|
|
3
|
+
from typing import Generator, Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from ._transport import Transport
|
|
6
6
|
from .models import PaginatedProducts, Product
|
|
7
|
-
from .org_validation import filter_by_organization
|
|
8
7
|
|
|
9
8
|
if TYPE_CHECKING:
|
|
10
9
|
from .workspaces import WorkspacesClient
|
|
@@ -33,7 +32,7 @@ class ProductsClient:
|
|
|
33
32
|
|
|
34
33
|
query = (
|
|
35
34
|
"query($ws: ID!, $q: String, $limit: Int!, $offset: Int!) {\n"
|
|
36
|
-
" products(workspaceId: $ws, q: $q, limit: $limit, offset: $offset) { id name code description
|
|
35
|
+
" products(workspaceId: $ws, q: $q, limit: $limit, offset: $offset) { id name workspaceId code description }\n"
|
|
37
36
|
"}"
|
|
38
37
|
)
|
|
39
38
|
variables = {"ws": workspace_id, "q": q, "limit": int(limit), "offset": int(offset)}
|
|
@@ -45,11 +44,7 @@ class ProductsClient:
|
|
|
45
44
|
|
|
46
45
|
products = payload.get("data", {}).get("products", [])
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
expected_org_id = self._t._org_id
|
|
50
|
-
filtered_products = filter_by_organization(products, expected_org_id, "products")
|
|
51
|
-
|
|
52
|
-
return PaginatedProducts(data=[Product(**r) for r in filtered_products], limit=limit, offset=offset)
|
|
47
|
+
return PaginatedProducts(data=[Product(**r) for r in products], limit=limit, offset=offset)
|
|
53
48
|
|
|
54
49
|
def iter_all_by_workspace(self, *, workspace_id: str, q: Optional[str] = None, page_size: int = 100, start_offset: int = 0) -> Generator[Product, None, None]:
|
|
55
50
|
"""Iterate products via GraphQL with offset pagination for a workspace."""
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|