poelis-sdk 0.1.1__tar.gz → 0.1.3__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.1 → poelis_sdk-0.1.3}/PKG-INFO +1 -1
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/pyproject.toml +1 -1
- poelis_sdk-0.1.3/src/poelis_sdk/__init__.py +29 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/_transport.py +9 -3
- poelis_sdk-0.1.3/src/poelis_sdk/auth0.py +85 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/client.py +4 -4
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/uv.lock +1 -1
- poelis_sdk-0.1.1/.github/workflows/publish-pypi.yml +0 -42
- poelis_sdk-0.1.1/.github/workflows/publish-testpypi.yml +0 -11
- poelis_sdk-0.1.1/src/poelis_sdk/__init__.py +0 -8
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/.github/workflows/ci.yml +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/.github/workflows/codeql.yml +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/.github/workflows/publish-on-push.yml +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/.gitignore +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/LICENSE +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/README.md +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/notebooks/try_poelis_sdk.ipynb +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/.github/workflows/sdk-ci.yml +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/.github/workflows/sdk-docs.yml +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/.github/workflows/sdk-publish-testpypi.yml +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/browser.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/exceptions.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/items.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/models.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/products.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/search.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/workspaces.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/tests/test_client_basic.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/tests/test_errors_and_backoff.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/tests/test_items_client.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/tests/test_search_client.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/tests/test_transport_and_products.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/tests/__init__.py +0 -0
- {poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/tests/test_integration_smoke.py +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Poelis Python SDK public exports.
|
|
2
|
+
|
|
3
|
+
Exposes the primary client and resolves the package version from installed
|
|
4
|
+
metadata so it stays in sync with ``pyproject.toml`` without manual edits.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib import metadata
|
|
8
|
+
|
|
9
|
+
from .client import PoelisClient
|
|
10
|
+
|
|
11
|
+
__all__ = ["PoelisClient", "__version__"]
|
|
12
|
+
|
|
13
|
+
def _resolve_version() -> str:
|
|
14
|
+
"""Return installed package version or a dev fallback.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
str: Version string from package metadata, or ``"0.0.0-dev"`` when
|
|
18
|
+
metadata is unavailable (e.g., editable installs without built metadata).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
return metadata.version("poelis-sdk")
|
|
23
|
+
except metadata.PackageNotFoundError:
|
|
24
|
+
return "0.0.0-dev"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__version__: str = _resolve_version()
|
|
28
|
+
|
|
29
|
+
|
|
@@ -6,6 +6,7 @@ import random
|
|
|
6
6
|
import httpx
|
|
7
7
|
|
|
8
8
|
from .exceptions import ClientError, HTTPError, NotFoundError, RateLimitError, ServerError, UnauthorizedError
|
|
9
|
+
from .auth0 import Auth0TokenManager
|
|
9
10
|
|
|
10
11
|
"""HTTP transport abstraction for the Poelis SDK.
|
|
11
12
|
|
|
@@ -28,8 +29,8 @@ class Transport:
|
|
|
28
29
|
|
|
29
30
|
Args:
|
|
30
31
|
base_url: Base API URL.
|
|
31
|
-
|
|
32
|
-
org_id:
|
|
32
|
+
api_key: API key for Auth0 authentication.
|
|
33
|
+
org_id: Organization id for tenant scoping.
|
|
33
34
|
timeout_seconds: Request timeout in seconds.
|
|
34
35
|
"""
|
|
35
36
|
|
|
@@ -37,13 +38,18 @@ class Transport:
|
|
|
37
38
|
self._api_key = api_key
|
|
38
39
|
self._org_id = org_id
|
|
39
40
|
self._timeout = timeout_seconds
|
|
41
|
+
|
|
42
|
+
# Initialize Auth0 token manager
|
|
43
|
+
self._auth0_manager = Auth0TokenManager(api_key, org_id, base_url)
|
|
40
44
|
|
|
41
45
|
def _headers(self, extra: Optional[Mapping[str, str]] = None) -> Dict[str, str]:
|
|
42
46
|
headers: Dict[str, str] = {
|
|
43
47
|
"Accept": "application/json",
|
|
44
48
|
"Content-Type": "application/json",
|
|
45
49
|
}
|
|
46
|
-
|
|
50
|
+
# Get fresh JWT token from Auth0
|
|
51
|
+
token = self._auth0_manager.get_token()
|
|
52
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
47
53
|
headers["X-Poelis-Org"] = self._org_id
|
|
48
54
|
if extra:
|
|
49
55
|
headers.update(dict(extra))
|
|
@@ -0,0 +1,85 @@
|
|
|
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>_<secret>
|
|
36
|
+
parts = api_key.split('_')
|
|
37
|
+
if len(parts) >= 4 and parts[0] == 'poelis' and parts[1] == 'live':
|
|
38
|
+
self.client_id = parts[3] # Extract client_id from API key
|
|
39
|
+
self.client_secret = '_'.join(parts[4:]) # Rest is secret
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError("Invalid API key format. Expected: poelis_live_org_dev_<client_id>_<secret>")
|
|
42
|
+
|
|
43
|
+
# Derive Auth0 domain and audience
|
|
44
|
+
self.auth0_domain = "poelis-prod.eu.auth0.com"
|
|
45
|
+
self.audience = base_url
|
|
46
|
+
|
|
47
|
+
self._token: Optional[str] = None
|
|
48
|
+
self._expires_at: float = 0
|
|
49
|
+
|
|
50
|
+
def get_token(self) -> str:
|
|
51
|
+
"""Get valid JWT token, refreshing if needed.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str: JWT token for Authorization header
|
|
55
|
+
"""
|
|
56
|
+
if self._token and time.time() < self._expires_at - 60: # Refresh 1min early
|
|
57
|
+
return self._token
|
|
58
|
+
|
|
59
|
+
return self._refresh_token()
|
|
60
|
+
|
|
61
|
+
def _refresh_token(self) -> str:
|
|
62
|
+
"""Acquire new JWT token from Auth0.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
str: Fresh JWT token
|
|
66
|
+
"""
|
|
67
|
+
token_url = f"https://{self.auth0_domain}/oauth/token"
|
|
68
|
+
|
|
69
|
+
data = {
|
|
70
|
+
"client_id": self.client_id,
|
|
71
|
+
"client_secret": self.client_secret,
|
|
72
|
+
"audience": self.audience,
|
|
73
|
+
"grant_type": "client_credentials"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
with httpx.Client() as client:
|
|
77
|
+
response = client.post(token_url, data=data)
|
|
78
|
+
response.raise_for_status()
|
|
79
|
+
|
|
80
|
+
token_data = response.json()
|
|
81
|
+
self._token = token_data["access_token"]
|
|
82
|
+
expires_in = token_data.get("expires_in", 3600)
|
|
83
|
+
self._expires_at = time.time() + expires_in
|
|
84
|
+
|
|
85
|
+
return self._token
|
|
@@ -30,7 +30,7 @@ class ClientConfig(BaseModel):
|
|
|
30
30
|
timeout_seconds: Request timeout in seconds.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
base_url: HttpUrl = Field(default="https://
|
|
33
|
+
base_url: HttpUrl = Field(default="https://poelis-be-py-753618215333.europe-west1.run.app")
|
|
34
34
|
api_key: str = Field(min_length=1)
|
|
35
35
|
org_id: str = Field(min_length=1)
|
|
36
36
|
timeout_seconds: float = 30.0
|
|
@@ -44,7 +44,7 @@ class PoelisClient:
|
|
|
44
44
|
resource accessors to unblock incremental development.
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
|
-
def __init__(self, api_key: str, org_id: str, base_url: str = "https://
|
|
47
|
+
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
48
|
"""Initialize the client with API endpoint and credentials.
|
|
49
49
|
|
|
50
50
|
Args:
|
|
@@ -81,12 +81,12 @@ class PoelisClient:
|
|
|
81
81
|
"""Construct a client using environment variables.
|
|
82
82
|
|
|
83
83
|
Expected variables:
|
|
84
|
-
- POELIS_BASE_URL (optional, defaults to
|
|
84
|
+
- POELIS_BASE_URL (optional, defaults to managed GCP endpoint)
|
|
85
85
|
- POELIS_API_KEY
|
|
86
86
|
- POELIS_ORG_ID
|
|
87
87
|
"""
|
|
88
88
|
|
|
89
|
-
base_url = os.environ.get("POELIS_BASE_URL", "https://
|
|
89
|
+
base_url = os.environ.get("POELIS_BASE_URL", "https://poelis-be-py-753618215333.europe-west1.run.app")
|
|
90
90
|
api_key = os.environ.get("POELIS_API_KEY")
|
|
91
91
|
org_id = os.environ.get("POELIS_ORG_ID")
|
|
92
92
|
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: Publish (PyPI)
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
build-and-publish:
|
|
10
|
-
if: ${{ !contains(github.ref_name, 'rc') }}
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- name: Checkout
|
|
17
|
-
uses: actions/checkout@v4
|
|
18
|
-
|
|
19
|
-
- name: Set up Python
|
|
20
|
-
uses: actions/setup-python@v5
|
|
21
|
-
with:
|
|
22
|
-
python-version: '3.12'
|
|
23
|
-
|
|
24
|
-
- name: Install uv
|
|
25
|
-
uses: astral-sh/setup-uv@v3
|
|
26
|
-
with:
|
|
27
|
-
version: 'latest'
|
|
28
|
-
|
|
29
|
-
- name: Install build tooling
|
|
30
|
-
run: |
|
|
31
|
-
uv sync --dev
|
|
32
|
-
|
|
33
|
-
- name: Build sdist and wheel
|
|
34
|
-
run: |
|
|
35
|
-
uv run python -m build
|
|
36
|
-
|
|
37
|
-
- name: Publish to PyPI
|
|
38
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
39
|
-
with:
|
|
40
|
-
user: __token__
|
|
41
|
-
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
42
|
-
|
|
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
|
{poelis_sdk-0.1.1 → poelis_sdk-0.1.3}/src/poelis_sdk/.github/workflows/sdk-publish-testpypi.yml
RENAMED
|
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
|