konecty-sdk-python 2.0.5__tar.gz → 2.1.0__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.
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/client.py +57 -1
- konecty_sdk_python-2.1.0/KonectySdkPython/lib/services/auth.py +105 -0
- konecty_sdk_python-2.1.0/PKG-INFO +135 -0
- konecty_sdk_python-2.1.0/README.md +105 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/pyproject.toml +11 -1
- konecty_sdk_python-2.0.5/PKG-INFO +0 -59
- konecty_sdk_python-2.0.5/README.md +0 -32
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/.gitignore +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/__init__.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/cli/__init__.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/cli/apply.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/cli/backup.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/cli/pull.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/__init__.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/exceptions.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/feature_types/__init__.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/feature_types/cross_module_query.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/feature_types/kpi.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/feature_types/query_json.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/file_manager.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/filters.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/http.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/model.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/serialization.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/__init__.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/aggregation.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/base.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/change_user.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/comments.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/export.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/files.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/notifications.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/query.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/stream.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/subscriptions.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/settings.py +0 -0
- {konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/types.py +0 -0
|
@@ -15,6 +15,7 @@ from .http import StreamResponse
|
|
|
15
15
|
from .http import request as _http_request
|
|
16
16
|
from .serialization import json_serial
|
|
17
17
|
from .services.aggregation import AggregationService
|
|
18
|
+
from .services.auth import AuthService
|
|
18
19
|
from .services.change_user import ChangeUserService
|
|
19
20
|
from .services.comments import CommentsService
|
|
20
21
|
from .services.export import ExportService
|
|
@@ -52,11 +53,21 @@ def get_first_dict(items: List[Any]) -> Optional[KonectyDict]:
|
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
class KonectyClient:
|
|
55
|
-
def __init__(self, base_url: str, token: str) -> None:
|
|
56
|
+
def __init__(self, base_url: str, token: str = "") -> None:
|
|
56
57
|
self.base_url = base_url
|
|
57
58
|
self.headers = {"Authorization": f"{token}"}
|
|
58
59
|
self.file_manager = FileManager(base_url=base_url, headers=self.headers)
|
|
59
60
|
|
|
61
|
+
@property
|
|
62
|
+
def auth_id(self) -> Optional[str]:
|
|
63
|
+
"""Current session token (authId) sent in the Authorization header, or None."""
|
|
64
|
+
return self.headers.get("Authorization") or None
|
|
65
|
+
|
|
66
|
+
def set_auth_id(self, auth_id: str) -> None:
|
|
67
|
+
"""Adopt a session token so following requests are authenticated."""
|
|
68
|
+
# headers is shared with file_manager: mutate in place so both see the token.
|
|
69
|
+
self.headers["Authorization"] = auth_id
|
|
70
|
+
|
|
60
71
|
async def _request(
|
|
61
72
|
self,
|
|
62
73
|
method: str,
|
|
@@ -361,6 +372,51 @@ class KonectyClient:
|
|
|
361
372
|
"""Set queue on records."""
|
|
362
373
|
return await self._change_user.set_queue(module, ids, queue)
|
|
363
374
|
|
|
375
|
+
@property
|
|
376
|
+
def _auth(self) -> AuthService:
|
|
377
|
+
if not hasattr(self, "_auth_service"):
|
|
378
|
+
self._auth_service = AuthService(self)
|
|
379
|
+
return self._auth_service
|
|
380
|
+
|
|
381
|
+
def google_login_url(
|
|
382
|
+
self,
|
|
383
|
+
client_id: str,
|
|
384
|
+
redirect_uri: Optional[str] = None,
|
|
385
|
+
state: Optional[str] = None,
|
|
386
|
+
) -> str:
|
|
387
|
+
"""Build the URL of GET /api/auth/google/start. Pure, performs no request."""
|
|
388
|
+
return self._auth.google_login_url(
|
|
389
|
+
client_id, redirect_uri=redirect_uri, state=state
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
async def exchange_google_code(
|
|
393
|
+
self,
|
|
394
|
+
code: str,
|
|
395
|
+
*,
|
|
396
|
+
geolocation: Optional[Union[Dict[str, Any], str]] = None,
|
|
397
|
+
resolution: Optional[Union[Dict[str, Any], str]] = None,
|
|
398
|
+
source: Optional[str] = None,
|
|
399
|
+
fingerprint: Optional[str] = None,
|
|
400
|
+
) -> Dict[str, Any]:
|
|
401
|
+
"""
|
|
402
|
+
Exchange the single-use code from the callback for a session
|
|
403
|
+
(POST /api/auth/google/session).
|
|
404
|
+
|
|
405
|
+
On success adopts the returned authId in this client. On error raises
|
|
406
|
+
KonectyAPIError with errors[0].message and keeps the client unauthenticated.
|
|
407
|
+
"""
|
|
408
|
+
return await self._auth.exchange_google_code(
|
|
409
|
+
code,
|
|
410
|
+
geolocation=geolocation,
|
|
411
|
+
resolution=resolution,
|
|
412
|
+
source=source,
|
|
413
|
+
fingerprint=fingerprint,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
async def get_login_options(self) -> Dict[str, Any]:
|
|
417
|
+
"""Get login method flags for the namespace (GET /api/auth/login-options)."""
|
|
418
|
+
return await self._auth.get_login_options()
|
|
419
|
+
|
|
364
420
|
@property
|
|
365
421
|
def _query(self) -> QueryService:
|
|
366
422
|
if not hasattr(self, "_query_service"):
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Auth API: Google hosted authorization code flow and login options."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional, Union
|
|
4
|
+
from urllib.parse import quote, urlencode
|
|
5
|
+
|
|
6
|
+
from ..exceptions import KonectyAPIError
|
|
7
|
+
from .base import BaseService
|
|
8
|
+
|
|
9
|
+
GOOGLE_START_PATH = "/api/auth/google/start"
|
|
10
|
+
# Conjunto que `encodeURIComponent` deixa passar além de [A-Za-z0-9_.-~], que o
|
|
11
|
+
# `quote` já preserva por padrão. Usado para que a URL montada aqui seja
|
|
12
|
+
# byte a byte igual à do SDK TypeScript — ver `_encode_query` abaixo.
|
|
13
|
+
_ENCODE_URI_COMPONENT_SAFE = "!*'()"
|
|
14
|
+
GOOGLE_SESSION_PATH = "/api/auth/google/session"
|
|
15
|
+
LOGIN_OPTIONS_PATH = "/api/auth/login-options"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _first_error_message(error: KonectyAPIError) -> str:
|
|
19
|
+
"""Extract errors[0].message from a KonectyAPIError raised by the HTTP layer."""
|
|
20
|
+
errors = error.args[0] if error.args else None
|
|
21
|
+
if isinstance(errors, list) and errors:
|
|
22
|
+
first = errors[0]
|
|
23
|
+
if isinstance(first, dict):
|
|
24
|
+
message = first.get("message")
|
|
25
|
+
if isinstance(message, str) and message:
|
|
26
|
+
return message
|
|
27
|
+
return str(error)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _encode_query(params: list) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Codifica o query string como `encodeURIComponent` do SDK TypeScript.
|
|
33
|
+
|
|
34
|
+
O default do `urlencode` é `quote_plus`, que transforma espaço em `+`;
|
|
35
|
+
`encodeURIComponent` produz `%20`. Os dois decodificam para o mesmo valor no
|
|
36
|
+
servidor, mas a spec pede URLs idênticas entre os SDKs.
|
|
37
|
+
"""
|
|
38
|
+
return urlencode(params, quote_via=quote, safe=_ENCODE_URI_COMPONENT_SAFE)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AuthService(BaseService):
|
|
42
|
+
"""Service for the Google login flow (authorization code hosted by Konecty)."""
|
|
43
|
+
|
|
44
|
+
def google_login_url(
|
|
45
|
+
self,
|
|
46
|
+
client_id: str,
|
|
47
|
+
redirect_uri: Optional[str] = None,
|
|
48
|
+
state: Optional[str] = None,
|
|
49
|
+
) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Build the absolute URL of GET /api/auth/google/start. Does not perform any request.
|
|
52
|
+
|
|
53
|
+
The browser follows this URL; Konecty redirects to accounts.google.com and,
|
|
54
|
+
after consent, back to redirect_uri with a single-use code (and the original state).
|
|
55
|
+
"""
|
|
56
|
+
params = [("client_id", client_id)]
|
|
57
|
+
if redirect_uri is not None:
|
|
58
|
+
params.append(("redirect_uri", redirect_uri))
|
|
59
|
+
if state is not None:
|
|
60
|
+
params.append(("state", state))
|
|
61
|
+
base_url = self._client.base_url.rstrip("/")
|
|
62
|
+
return f"{base_url}{GOOGLE_START_PATH}?{_encode_query(params)}"
|
|
63
|
+
|
|
64
|
+
async def exchange_google_code(
|
|
65
|
+
self,
|
|
66
|
+
code: str,
|
|
67
|
+
*,
|
|
68
|
+
geolocation: Optional[Union[Dict[str, Any], str]] = None,
|
|
69
|
+
resolution: Optional[Union[Dict[str, Any], str]] = None,
|
|
70
|
+
source: Optional[str] = None,
|
|
71
|
+
fingerprint: Optional[str] = None,
|
|
72
|
+
) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
POST /api/auth/google/session. Exchanges the single-use code (TTL 60s) for a session.
|
|
75
|
+
|
|
76
|
+
On success the returned authId is adopted by the client, so following requests
|
|
77
|
+
are authenticated. On error raises KonectyAPIError with errors[0].message and
|
|
78
|
+
leaves the client authentication state untouched.
|
|
79
|
+
"""
|
|
80
|
+
payload: Dict[str, Any] = {"code": code}
|
|
81
|
+
if geolocation is not None:
|
|
82
|
+
payload["geolocation"] = geolocation
|
|
83
|
+
if resolution is not None:
|
|
84
|
+
payload["resolution"] = resolution
|
|
85
|
+
if source is not None:
|
|
86
|
+
payload["source"] = source
|
|
87
|
+
if fingerprint is not None:
|
|
88
|
+
payload["fingerprint"] = fingerprint
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
response = await self._post(GOOGLE_SESSION_PATH, json=payload)
|
|
92
|
+
except KonectyAPIError as error:
|
|
93
|
+
raise KonectyAPIError(_first_error_message(error)) from error
|
|
94
|
+
|
|
95
|
+
auth_id = response.get("authId") if isinstance(response, dict) else None
|
|
96
|
+
if not isinstance(auth_id, str) or not auth_id:
|
|
97
|
+
raise KonectyAPIError("Google session response has no authId")
|
|
98
|
+
|
|
99
|
+
self._client.set_auth_id(auth_id)
|
|
100
|
+
return response
|
|
101
|
+
|
|
102
|
+
async def get_login_options(self) -> Dict[str, Any]:
|
|
103
|
+
"""GET /api/auth/login-options. Flags: passwordEnabled, emailOtpEnabled,
|
|
104
|
+
whatsAppOtpEnabled, webauthnEnabled, webauthnRequired, googleEnabled."""
|
|
105
|
+
return await self._get(LOGIN_OPTIONS_PATH)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: konecty_sdk_python
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Konecty SDK Python
|
|
5
|
+
Author-email: Leonardo Leal <leonardo.leal@konecty.com>, Derotino Silveira <derotino.silveira@konecty.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: api,konecty,sdk
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: aiohttp>=3.11.11
|
|
17
|
+
Requires-Dist: asyncio>=3.4.3
|
|
18
|
+
Requires-Dist: black>=24.10.0
|
|
19
|
+
Requires-Dist: email-validator>=2.2.0
|
|
20
|
+
Requires-Dist: inquirer>=3.4.0
|
|
21
|
+
Requires-Dist: pydantic>=2.11.4
|
|
22
|
+
Requires-Dist: pymongo>=4.10.1
|
|
23
|
+
Requires-Dist: requests>=2.32.3
|
|
24
|
+
Requires-Dist: rich>=13.9.4
|
|
25
|
+
Requires-Dist: typing-extensions>=4.12.2
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
## Konecty Python SDK
|
|
32
|
+
|
|
33
|
+
> 🛠️ Work in progress
|
|
34
|
+
|
|
35
|
+
This project exposes both:
|
|
36
|
+
|
|
37
|
+
- a cli for interacting with the database
|
|
38
|
+
- the Konecty client sdk for interacting with Konecty's api
|
|
39
|
+
|
|
40
|
+
#### Usage
|
|
41
|
+
|
|
42
|
+
##### Installing on a project
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
uv pip install konecty-sdk-python
|
|
46
|
+
konecty-cli apply --mongo-url="..." --database my-db
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
##### Running on uvx
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
uvx --from konecty-sdk-python konecty-cli pull --all --mongo-url="..." --database my-db
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Google login
|
|
56
|
+
|
|
57
|
+
The Konecty core hosts the whole authorization code flow: your app never talks to Google
|
|
58
|
+
and never sees `client_secret`. The `authId` never travels in a URL — the callback returns
|
|
59
|
+
a single-use code (TTL 60s) and the `authId` only exists in the body of `POST /api/auth/google/session`.
|
|
60
|
+
|
|
61
|
+
Three steps, three methods:
|
|
62
|
+
|
|
63
|
+
1. `client.google_login_url(...)` — build the URL to send the browser to (no request is made).
|
|
64
|
+
2. the browser comes back to your `redirect_uri` with `?code=...&state=...` (or `?error=...&state=...`).
|
|
65
|
+
3. `await client.exchange_google_code(code)` — turn the code into a session; the returned
|
|
66
|
+
`authId` is adopted by the client, so the next calls are authenticated.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
import os
|
|
71
|
+
import secrets
|
|
72
|
+
|
|
73
|
+
from KonectySdkPython import KonectyClient
|
|
74
|
+
from KonectySdkPython.lib.exceptions import KonectyAPIError
|
|
75
|
+
|
|
76
|
+
KONECTY_URL = os.environ["KONECTY_URL"]
|
|
77
|
+
CLIENT_ID = os.environ["KONECTY_CLIENT_ID"]
|
|
78
|
+
REDIRECT_URI = os.environ["KONECTY_REDIRECT_URI"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def main() -> None:
|
|
82
|
+
client = KonectyClient(KONECTY_URL)
|
|
83
|
+
|
|
84
|
+
# Is Google login enabled for this namespace?
|
|
85
|
+
options = await client.get_login_options()
|
|
86
|
+
if not options["googleEnabled"]:
|
|
87
|
+
raise SystemExit("Google login is disabled for this namespace")
|
|
88
|
+
|
|
89
|
+
# Step 1 — send the browser here (state is opaque to Konecty; use it against CSRF).
|
|
90
|
+
state = secrets.token_urlsafe(16)
|
|
91
|
+
print(client.google_login_url(client_id=CLIENT_ID, redirect_uri=REDIRECT_URI, state=state))
|
|
92
|
+
|
|
93
|
+
# Step 2 — Konecty redirects the browser back to REDIRECT_URI with code + state.
|
|
94
|
+
# On refusal it comes back as ?error=<access_denied|provider_error|
|
|
95
|
+
# email_not_verified|user_not_found|user_inactive|ambiguous_user>&state=...
|
|
96
|
+
returned_code = input("code from the callback: ")
|
|
97
|
+
|
|
98
|
+
# Step 3 — exchange the single-use code for a session.
|
|
99
|
+
try:
|
|
100
|
+
session = await client.exchange_google_code(
|
|
101
|
+
returned_code,
|
|
102
|
+
source="my-app", # optional telemetry: geolocation, resolution, source, fingerprint
|
|
103
|
+
)
|
|
104
|
+
except KonectyAPIError as error:
|
|
105
|
+
raise SystemExit(f"Google login failed: {error}")
|
|
106
|
+
|
|
107
|
+
print("logged as", session["user"]["login"], "authId:", client.auth_id)
|
|
108
|
+
|
|
109
|
+
# The client is now authenticated: no token needed on the constructor.
|
|
110
|
+
contacts = await client.find_by_id("User", session["user"]["_id"])
|
|
111
|
+
print(contacts)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
asyncio.run(main())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`exchange_google_code` raises `KonectyAPIError` with the message of `errors[0]`
|
|
118
|
+
(`invalid_code`, `expired_code`, `user_not_found`, `user_inactive`) and leaves the client's
|
|
119
|
+
authentication state untouched.
|
|
120
|
+
|
|
121
|
+
#### Tests
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
uv pip install -e ".[dev]"
|
|
125
|
+
.venv/bin/python -m pytest
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Build & Publish
|
|
129
|
+
|
|
130
|
+
It is needed to increase the version number on the [pyproject](./pyproject.toml) file.
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
uv build
|
|
134
|
+
uvx twine upload --config-file .pypirc --skip-existing dist/*
|
|
135
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
## Konecty Python SDK
|
|
2
|
+
|
|
3
|
+
> 🛠️ Work in progress
|
|
4
|
+
|
|
5
|
+
This project exposes both:
|
|
6
|
+
|
|
7
|
+
- a cli for interacting with the database
|
|
8
|
+
- the Konecty client sdk for interacting with Konecty's api
|
|
9
|
+
|
|
10
|
+
#### Usage
|
|
11
|
+
|
|
12
|
+
##### Installing on a project
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
uv pip install konecty-sdk-python
|
|
16
|
+
konecty-cli apply --mongo-url="..." --database my-db
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
##### Running on uvx
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
uvx --from konecty-sdk-python konecty-cli pull --all --mongo-url="..." --database my-db
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### Google login
|
|
26
|
+
|
|
27
|
+
The Konecty core hosts the whole authorization code flow: your app never talks to Google
|
|
28
|
+
and never sees `client_secret`. The `authId` never travels in a URL — the callback returns
|
|
29
|
+
a single-use code (TTL 60s) and the `authId` only exists in the body of `POST /api/auth/google/session`.
|
|
30
|
+
|
|
31
|
+
Three steps, three methods:
|
|
32
|
+
|
|
33
|
+
1. `client.google_login_url(...)` — build the URL to send the browser to (no request is made).
|
|
34
|
+
2. the browser comes back to your `redirect_uri` with `?code=...&state=...` (or `?error=...&state=...`).
|
|
35
|
+
3. `await client.exchange_google_code(code)` — turn the code into a session; the returned
|
|
36
|
+
`authId` is adopted by the client, so the next calls are authenticated.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
import os
|
|
41
|
+
import secrets
|
|
42
|
+
|
|
43
|
+
from KonectySdkPython import KonectyClient
|
|
44
|
+
from KonectySdkPython.lib.exceptions import KonectyAPIError
|
|
45
|
+
|
|
46
|
+
KONECTY_URL = os.environ["KONECTY_URL"]
|
|
47
|
+
CLIENT_ID = os.environ["KONECTY_CLIENT_ID"]
|
|
48
|
+
REDIRECT_URI = os.environ["KONECTY_REDIRECT_URI"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def main() -> None:
|
|
52
|
+
client = KonectyClient(KONECTY_URL)
|
|
53
|
+
|
|
54
|
+
# Is Google login enabled for this namespace?
|
|
55
|
+
options = await client.get_login_options()
|
|
56
|
+
if not options["googleEnabled"]:
|
|
57
|
+
raise SystemExit("Google login is disabled for this namespace")
|
|
58
|
+
|
|
59
|
+
# Step 1 — send the browser here (state is opaque to Konecty; use it against CSRF).
|
|
60
|
+
state = secrets.token_urlsafe(16)
|
|
61
|
+
print(client.google_login_url(client_id=CLIENT_ID, redirect_uri=REDIRECT_URI, state=state))
|
|
62
|
+
|
|
63
|
+
# Step 2 — Konecty redirects the browser back to REDIRECT_URI with code + state.
|
|
64
|
+
# On refusal it comes back as ?error=<access_denied|provider_error|
|
|
65
|
+
# email_not_verified|user_not_found|user_inactive|ambiguous_user>&state=...
|
|
66
|
+
returned_code = input("code from the callback: ")
|
|
67
|
+
|
|
68
|
+
# Step 3 — exchange the single-use code for a session.
|
|
69
|
+
try:
|
|
70
|
+
session = await client.exchange_google_code(
|
|
71
|
+
returned_code,
|
|
72
|
+
source="my-app", # optional telemetry: geolocation, resolution, source, fingerprint
|
|
73
|
+
)
|
|
74
|
+
except KonectyAPIError as error:
|
|
75
|
+
raise SystemExit(f"Google login failed: {error}")
|
|
76
|
+
|
|
77
|
+
print("logged as", session["user"]["login"], "authId:", client.auth_id)
|
|
78
|
+
|
|
79
|
+
# The client is now authenticated: no token needed on the constructor.
|
|
80
|
+
contacts = await client.find_by_id("User", session["user"]["_id"])
|
|
81
|
+
print(contacts)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
asyncio.run(main())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`exchange_google_code` raises `KonectyAPIError` with the message of `errors[0]`
|
|
88
|
+
(`invalid_code`, `expired_code`, `user_not_found`, `user_inactive`) and leaves the client's
|
|
89
|
+
authentication state untouched.
|
|
90
|
+
|
|
91
|
+
#### Tests
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
uv pip install -e ".[dev]"
|
|
95
|
+
.venv/bin/python -m pytest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Build & Publish
|
|
99
|
+
|
|
100
|
+
It is needed to increase the version number on the [pyproject](./pyproject.toml) file.
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
uv build
|
|
104
|
+
uvx twine upload --config-file .pypirc --skip-existing dist/*
|
|
105
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "konecty_sdk_python"
|
|
3
|
-
version = "2.0
|
|
3
|
+
version = "2.1.0"
|
|
4
4
|
description = "Konecty SDK Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -32,6 +32,16 @@ dependencies = [
|
|
|
32
32
|
"pydantic>=2.11.4",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.0.0",
|
|
38
|
+
"pytest-asyncio>=0.24.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
testpaths = ["tests"]
|
|
43
|
+
asyncio_mode = "strict"
|
|
44
|
+
|
|
35
45
|
[project.scripts]
|
|
36
46
|
konecty-cli = "KonectySdkPython.cli:main"
|
|
37
47
|
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: konecty_sdk_python
|
|
3
|
-
Version: 2.0.5
|
|
4
|
-
Summary: Konecty SDK Python
|
|
5
|
-
Author-email: Leonardo Leal <leonardo.leal@konecty.com>, Derotino Silveira <derotino.silveira@konecty.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Keywords: api,konecty,sdk
|
|
8
|
-
Classifier: Development Status :: 4 - Beta
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
-
Requires-Python: >=3.11
|
|
16
|
-
Requires-Dist: aiohttp>=3.11.11
|
|
17
|
-
Requires-Dist: asyncio>=3.4.3
|
|
18
|
-
Requires-Dist: black>=24.10.0
|
|
19
|
-
Requires-Dist: email-validator>=2.2.0
|
|
20
|
-
Requires-Dist: inquirer>=3.4.0
|
|
21
|
-
Requires-Dist: pydantic>=2.11.4
|
|
22
|
-
Requires-Dist: pymongo>=4.10.1
|
|
23
|
-
Requires-Dist: requests>=2.32.3
|
|
24
|
-
Requires-Dist: rich>=13.9.4
|
|
25
|
-
Requires-Dist: typing-extensions>=4.12.2
|
|
26
|
-
Description-Content-Type: text/markdown
|
|
27
|
-
|
|
28
|
-
## Konecty Python SDK
|
|
29
|
-
|
|
30
|
-
> 🛠️ Work in progress
|
|
31
|
-
|
|
32
|
-
This project exposes both:
|
|
33
|
-
|
|
34
|
-
- a cli for interacting with the database
|
|
35
|
-
- the Konecty client sdk for interacting with Konecty's api
|
|
36
|
-
|
|
37
|
-
#### Usage
|
|
38
|
-
|
|
39
|
-
##### Installing on a project
|
|
40
|
-
|
|
41
|
-
```sh
|
|
42
|
-
uv pip install konecty-sdk-python
|
|
43
|
-
konecty-cli apply --mongo-url="..." --database my-db
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
##### Running on uvx
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
uvx --from konecty-sdk-python konecty-cli pull --all --mongo-url="..." --database my-db
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
#### Build & Publish
|
|
53
|
-
|
|
54
|
-
It is needed to increase the version number on the [pyproject](./pyproject.toml) file.
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
uv build
|
|
58
|
-
uvx twine upload --config-file .pypirc --skip-existing dist/*
|
|
59
|
-
```
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
## Konecty Python SDK
|
|
2
|
-
|
|
3
|
-
> 🛠️ Work in progress
|
|
4
|
-
|
|
5
|
-
This project exposes both:
|
|
6
|
-
|
|
7
|
-
- a cli for interacting with the database
|
|
8
|
-
- the Konecty client sdk for interacting with Konecty's api
|
|
9
|
-
|
|
10
|
-
#### Usage
|
|
11
|
-
|
|
12
|
-
##### Installing on a project
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
uv pip install konecty-sdk-python
|
|
16
|
-
konecty-cli apply --mongo-url="..." --database my-db
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
##### Running on uvx
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
uvx --from konecty-sdk-python konecty-cli pull --all --mongo-url="..." --database my-db
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
#### Build & Publish
|
|
26
|
-
|
|
27
|
-
It is needed to increase the version number on the [pyproject](./pyproject.toml) file.
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
uv build
|
|
31
|
-
uvx twine upload --config-file .pypirc --skip-existing dist/*
|
|
32
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/feature_types/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/feature_types/kpi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/__init__.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/aggregation.py
RENAMED
|
File without changes
|
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/change_user.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/comments.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/export.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/files.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/notifications.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/query.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/stream.py
RENAMED
|
File without changes
|
{konecty_sdk_python-2.0.5 → konecty_sdk_python-2.1.0}/KonectySdkPython/lib/services/subscriptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|