poelis-sdk 0.2.2__py3-none-any.whl → 0.3.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/_transport.py CHANGED
@@ -1,12 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import Any, Dict, Mapping, Optional
4
+ import os
4
5
  import time
5
6
  import random
6
7
  import httpx
7
8
 
8
9
  from .exceptions import ClientError, HTTPError, NotFoundError, RateLimitError, ServerError, UnauthorizedError
9
- from .auth0 import Auth0TokenManager
10
10
 
11
11
  """HTTP transport abstraction for the Poelis SDK.
12
12
 
@@ -29,7 +29,7 @@ class Transport:
29
29
 
30
30
  Args:
31
31
  base_url: Base API URL.
32
- api_key: API key for Auth0 authentication.
32
+ api_key: API key provided by backend to authenticate requests.
33
33
  org_id: Organization id for tenant scoping.
34
34
  timeout_seconds: Request timeout in seconds.
35
35
  """
@@ -38,18 +38,32 @@ class Transport:
38
38
  self._api_key = api_key
39
39
  self._org_id = org_id
40
40
  self._timeout = timeout_seconds
41
+ # Auth mode is intentionally read from environment to keep the public
42
+ # constructor signature stable for tests and backwards-compatibility.
43
+ # Supported values:
44
+ # - "bearer" (default): Authorization: Bearer <api_key>
45
+ # - "api_key": X-API-Key/X-Poelis-Api-Key headers with no Authorization
46
+ self._auth_mode = os.environ.get("POELIS_AUTH_MODE", "api_key").strip().lower()
41
47
 
42
- # Initialize Auth0 token manager
43
- self._auth0_manager = Auth0TokenManager(api_key, org_id, base_url)
44
48
 
45
49
  def _headers(self, extra: Optional[Mapping[str, str]] = None) -> Dict[str, str]:
46
50
  headers: Dict[str, str] = {
47
51
  "Accept": "application/json",
48
52
  "Content-Type": "application/json",
49
53
  }
50
- # Get fresh JWT token from Auth0
51
- token = self._auth0_manager.get_token()
52
- headers["Authorization"] = f"Bearer {token}"
54
+ # Resolve auth mode with a safe fallback in case tests monkeypatch __init__
55
+ auth_mode = getattr(self, "_auth_mode", None) or os.environ.get("POELIS_AUTH_MODE", "api_key").strip().lower()
56
+ if auth_mode == "api_key":
57
+ # Some staging environments expect an API key header and reject Bearer
58
+ # Include common header variants for compatibility; backend may accept either.
59
+ headers["X-API-Key"] = self._api_key
60
+ headers["X-Poelis-Api-Key"] = self._api_key
61
+ # Additionally include Authorization with Api-Key scheme for services
62
+ # that only read credentials from Authorization.
63
+ headers["Authorization"] = f"Api-Key {self._api_key}"
64
+ else:
65
+ # Default: send API key as Bearer token
66
+ headers["Authorization"] = f"Bearer {self._api_key}"
53
67
  headers["X-Poelis-Org"] = self._org_id
54
68
  if extra:
55
69
  headers.update(dict(extra))
poelis_sdk/browser.py CHANGED
@@ -56,6 +56,10 @@ class _Node:
56
56
  self._load_children()
57
57
  return [child._name or "" for child in self._children_cache.values()]
58
58
 
59
+ def names(self) -> List[str]:
60
+ """Public: return display names of children at this level."""
61
+ return self._names()
62
+
59
63
  def _suggest(self) -> List[str]:
60
64
  """Return suggested attribute names for interactive usage.
61
65
 
@@ -249,10 +253,22 @@ class Browser:
249
253
  """Return display names of root-level children (workspaces)."""
250
254
  return self._root._names()
251
255
 
256
+ def names(self) -> List[str]:
257
+ """Public: return display names of root-level children (workspaces)."""
258
+ return self._root._names()
259
+
252
260
  # keep suggest internal so it doesn't appear in help/dir
253
261
  def _suggest(self) -> List[str]:
254
262
  return self._root._suggest()
255
263
 
264
+ def suggest(self) -> List[str]:
265
+ """Return curated attribute suggestions at the current root level.
266
+
267
+ This mirrors the internal `_suggest` used for interactive completion,
268
+ but is exposed publicly for tests and programmatic usage.
269
+ """
270
+ return self._root._suggest()
271
+
256
272
 
257
273
  def _safe_key(name: str) -> str:
258
274
  """Convert arbitrary display name to a safe attribute key (letters/digits/_)."""
poelis_sdk/client.py CHANGED
@@ -31,7 +31,7 @@ class ClientConfig(BaseModel):
31
31
  timeout_seconds: Request timeout in seconds.
32
32
  """
33
33
 
34
- base_url: HttpUrl = Field(default="https://poelis-be-py-753618215333.europe-west1.run.app")
34
+ base_url: HttpUrl = Field(default="https://api.poelis.ai")
35
35
  api_key: str = Field(min_length=1)
36
36
  org_id: str = Field(min_length=1)
37
37
  timeout_seconds: float = 30.0
@@ -45,7 +45,7 @@ class PoelisClient:
45
45
  resource accessors to unblock incremental development.
46
46
  """
47
47
 
48
- def __init__(self, api_key: str, org_id: str, base_url: str = "https://poelis-be-py-753618215333.europe-west1.run.app", timeout_seconds: float = 30.0) -> None:
48
+ def __init__(self, api_key: str, org_id: str, base_url: str = "https://api.poelis.ai", timeout_seconds: float = 30.0) -> None:
49
49
  """Initialize the client with API endpoint and credentials.
50
50
 
51
51
  Args:
@@ -90,7 +90,7 @@ class PoelisClient:
90
90
  - POELIS_ORG_ID
91
91
  """
92
92
 
93
- base_url = os.environ.get("POELIS_BASE_URL", "https://poelis-be-py-753618215333.europe-west1.run.app")
93
+ base_url = os.environ.get("POELIS_BASE_URL", "https://api.poelis.ai")
94
94
  api_key = os.environ.get("POELIS_API_KEY")
95
95
  org_id = os.environ.get("POELIS_ORG_ID")
96
96
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: poelis-sdk
3
- Version: 0.2.2
3
+ Version: 0.3.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
@@ -60,6 +60,24 @@ export POELIS_ORG_ID=tenant_id_001
60
60
  ```
61
61
 
62
62
 
63
+ ### How authentication works
64
+
65
+ The SDK does not talk to Auth0. It sends your API key directly to the Poelis backend for validation on every request.
66
+
67
+ - Default headers sent by the SDK:
68
+
69
+ - `X-API-Key: <api_key>` (and `X-Poelis-Api-Key` as a compatibility alias)
70
+ - `Authorization: Api-Key <api_key>` (compatibility for gateways expecting Authorization-only)
71
+ - `X-Poelis-Org: <org_id>`
72
+
73
+ You can opt into Bearer mode (legacy) by setting `POELIS_AUTH_MODE=bearer`, which will send:
74
+
75
+ - `Authorization: Bearer <api_key>`
76
+ - `X-Poelis-Org: <org_id>`
77
+
78
+ The backend validates the API key against your organization, applies authorization and filtering, and returns data.
79
+
80
+
63
81
  ## Dot-path browser (Notebook UX)
64
82
 
65
83
  The SDK exposes a dot-path browser for easy exploration:
@@ -69,7 +87,7 @@ client.browser # then use TAB to explore
69
87
  # client.browser.<workspace>.<product>.<item>.<child>.properties
70
88
  ```
71
89
 
72
- See the example notebook in `notebooks/try_poelis_sdk.ipynb` for an end-to-end walkthrough (authentication, listing workspaces/products/items, and simple search queries).
90
+ See the example notebook in `notebooks/try_poelis_sdk.ipynb` for an end-to-end walkthrough (authentication, listing workspaces/products/items, and simple search queries). The client defaults to `https://api.poelis.ai` unless `POELIS_BASE_URL` is set.
73
91
 
74
92
  ## Requirements
75
93
 
@@ -1,8 +1,7 @@
1
1
  poelis_sdk/__init__.py,sha256=vRKuvnMGtq2_6SYDPNpckSPYXTgMDD1vBAfZ1bXlHL0,924
2
- poelis_sdk/_transport.py,sha256=F5EX0EJFHJPAE638nKzlX5zLSU6FIMzMemqgh05_V6U,6061
3
- poelis_sdk/auth0.py,sha256=VDZHCv9YpsW55H-PLINKMq74UhevP6OWyBHQyEFIpvw,3163
4
- poelis_sdk/browser.py,sha256=D-9Xgj1SBy4DQa6Ow8gQ1DOWhy373WqYJMYIiXFXlgM,19803
5
- poelis_sdk/client.py,sha256=10__5po-foX36ZCCduQmzdoh9NNS320kyaqztUNtPvo,3872
2
+ poelis_sdk/_transport.py,sha256=Na1neuS9JyLwHqhWkqwpQiGdbRtS0EJlWd587kQ88-s,7069
3
+ poelis_sdk/browser.py,sha256=mmtG9WUCYmBLFCiElXPjXnKm_kWvPw6nvyakGr05heI,20393
4
+ poelis_sdk/client.py,sha256=kb5JvdEdIDF0XqLjjBbE7AiEz1Xx2SLl8DGdzsyu7_k,3773
6
5
  poelis_sdk/exceptions.py,sha256=qX5kpAr8ozJUOW-CNhmspWVIE-bvUZT_PUnimYuBxNY,1101
7
6
  poelis_sdk/items.py,sha256=vomXn43gcUlX2iUro3mpb8Qicmmt4sWFB2vXXxIfLsM,2575
8
7
  poelis_sdk/logging.py,sha256=zmg8Us-7qjDl0n_NfOSvDolLopy7Dc_hQ-pcrC63dY8,2442
@@ -11,7 +10,7 @@ poelis_sdk/org_validation.py,sha256=c4fB6ySTvcovWxG4F1wU_OBlP-FyuIaAUzCwqgJKzBE,
11
10
  poelis_sdk/products.py,sha256=bwV2mOPvBriy83F3BxWww1oSsyLZFQvh4XOiIE9fI1s,3240
12
11
  poelis_sdk/search.py,sha256=KQbdnu2khQDewddSJHR7JWysH1f2M7swK6MR-ZwrLAE,4101
13
12
  poelis_sdk/workspaces.py,sha256=hpmRl-Hswr4YDvObQdyVpegIYjUWno7A_BiVBz-AQGc,2383
14
- poelis_sdk-0.2.2.dist-info/METADATA,sha256=cMTXDB8V-RCcTq0dffqeSuPCYM4pb67nz6Fx0nGl3yc,2219
15
- poelis_sdk-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- poelis_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=EEmE_r8wk_pdXB8CWp1LG6sBOl7--hNSS2kV94cI6co,1075
17
- poelis_sdk-0.2.2.dist-info/RECORD,,
13
+ poelis_sdk-0.3.0.dist-info/METADATA,sha256=L_MKl7iwVcGp3nLN3cn_bvH1oota-GyYs-zUlbU6t9A,2968
14
+ poelis_sdk-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ poelis_sdk-0.3.0.dist-info/licenses/LICENSE,sha256=EEmE_r8wk_pdXB8CWp1LG6sBOl7--hNSS2kV94cI6co,1075
16
+ poelis_sdk-0.3.0.dist-info/RECORD,,
poelis_sdk/auth0.py DELETED
@@ -1,87 +0,0 @@
1
- """Auth0 Client Credentials flow for SDK authentication.
2
-
3
- This module handles automatic JWT token acquisition using Auth0 Client Credentials
4
- flow when users provide API keys. The API key serves as the client_id, and we
5
- derive the client_secret and audience from the API key format.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import time
11
- from typing import Optional
12
-
13
- import httpx
14
-
15
-
16
- class Auth0TokenManager:
17
- """Manages Auth0 JWT tokens using Client Credentials flow.
18
-
19
- Automatically acquires and refreshes tokens based on API keys.
20
- """
21
-
22
- def __init__(self, api_key: str, org_id: str, base_url: str) -> None:
23
- """Initialize token manager.
24
-
25
- Args:
26
- api_key: API key from webapp (used as client_id)
27
- org_id: Organization ID for audience
28
- base_url: Base URL to derive Auth0 domain and audience
29
- """
30
- self.api_key = api_key
31
- self.org_id = org_id
32
- self.base_url = base_url
33
-
34
- # Extract Auth0 domain from API key format
35
- # Format: poelis_live_org_dev_<client_id>
36
- parts = api_key.split('_')
37
- if len(parts) >= 4 and parts[0] == 'poelis' and parts[1] == 'live' and parts[2] == 'org' and parts[3] == 'dev':
38
- # For now, use the Machine to Machine application credentials
39
- # TODO: Parse client_id from API key when webapp generates proper format
40
- self.client_id = "XcSLURURuQNEVvX2PF5DplNhTY6YCT4C" # Machine to Machine app
41
- self.client_secret = "TM_Fv8FsfAaqvODf7ayyE_LrZM2KbbpdtLIIMqkIZwFXfKYLdOFcO2qmyO0v970-"
42
- else:
43
- raise ValueError("Invalid API key format. Expected: poelis_live_org_dev_<client_id>")
44
-
45
- # Derive Auth0 domain and audience
46
- self.auth0_domain = "poelis-prod.eu.auth0.com"
47
- self.audience = "poelis-auth-api" # Use the API identifier, not the GCP URL
48
-
49
- self._token: Optional[str] = None
50
- self._expires_at: float = 0
51
-
52
- def get_token(self) -> str:
53
- """Get valid JWT token, refreshing if needed.
54
-
55
- Returns:
56
- str: JWT token for Authorization header
57
- """
58
- if self._token and time.time() < self._expires_at - 60: # Refresh 1min early
59
- return self._token
60
-
61
- return self._refresh_token()
62
-
63
- def _refresh_token(self) -> str:
64
- """Acquire new JWT token from Auth0.
65
-
66
- Returns:
67
- str: Fresh JWT token
68
- """
69
- token_url = f"https://{self.auth0_domain}/oauth/token"
70
-
71
- data = {
72
- "client_id": self.client_id,
73
- "client_secret": self.client_secret,
74
- "audience": self.audience,
75
- "grant_type": "client_credentials"
76
- }
77
-
78
- with httpx.Client() as client:
79
- response = client.post(token_url, data=data)
80
- response.raise_for_status()
81
-
82
- token_data = response.json()
83
- self._token = token_data["access_token"]
84
- expires_in = token_data.get("expires_in", 3600)
85
- self._expires_at = time.time() + expires_in
86
-
87
- return self._token