vortex-python-sdk 0.0.3__tar.gz → 0.0.6__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.
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/CHANGELOG.md +15 -1
- {vortex_python_sdk-0.0.3/src/vortex_python_sdk.egg-info → vortex_python_sdk-0.0.6}/PKG-INFO +1 -1
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/pyproject.toml +4 -2
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6/src/vortex_python_sdk.egg-info}/PKG-INFO +1 -1
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_sdk/__init__.py +24 -12
- vortex_python_sdk-0.0.6/src/vortex_sdk/types.py +173 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_sdk/vortex.py +136 -135
- vortex_python_sdk-0.0.3/src/vortex_sdk/types.py +0 -103
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/LICENSE +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/MANIFEST.in +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/README.md +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/setup.cfg +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/SOURCES.txt +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/dependency_links.txt +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/requires.txt +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/top_level.txt +0 -0
- {vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_sdk/py.typed +0 -0
|
@@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.5] - 2025-11-06
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **CRITICAL FIX**: Updated api url & auth headers
|
|
15
|
+
|
|
10
16
|
## [0.0.3] - 2025-01-31
|
|
11
17
|
|
|
12
18
|
### Fixed
|
|
19
|
+
|
|
13
20
|
- **CRITICAL FIX**: JWT generation now matches Node.js SDK implementation exactly
|
|
14
21
|
- Improved JWT signing algorithm to match Node.js SDK
|
|
15
22
|
- Added `iat` (issued at) and `expires` timestamp fields to JWT
|
|
@@ -18,12 +25,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
18
25
|
- Added comprehensive tests verifying JWT output matches Node.js SDK byte-for-byte
|
|
19
26
|
|
|
20
27
|
### Breaking Changes
|
|
28
|
+
|
|
21
29
|
- JWT structure changed significantly - tokens from 0.0.2 are incompatible with 0.0.3
|
|
22
30
|
- JWTs now include `iat` and `expires` fields for proper token lifecycle management
|
|
23
31
|
|
|
24
32
|
## [0.0.2] - 2025-01-31
|
|
25
33
|
|
|
26
34
|
### Fixed
|
|
35
|
+
|
|
27
36
|
- **BREAKING FIX**: JWT payload format now matches TypeScript SDK
|
|
28
37
|
- `identifiers` changed from `Dict[str, str]` to `List[Dict]` with `type` and `value` fields
|
|
29
38
|
- `groups` structure now properly includes `type`, `id`/`groupId`, and `name` fields
|
|
@@ -32,9 +41,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
41
|
- Updated documentation with correct JWT generation examples
|
|
33
42
|
|
|
34
43
|
### Migration Guide
|
|
44
|
+
|
|
35
45
|
If you're upgrading from 0.0.1, update your JWT generation code:
|
|
36
46
|
|
|
37
47
|
**Before (0.0.1):**
|
|
48
|
+
|
|
38
49
|
```python
|
|
39
50
|
jwt = vortex.generate_jwt({
|
|
40
51
|
"user_id": "user-123",
|
|
@@ -44,6 +55,7 @@ jwt = vortex.generate_jwt({
|
|
|
44
55
|
```
|
|
45
56
|
|
|
46
57
|
**After (0.0.2):**
|
|
58
|
+
|
|
47
59
|
```python
|
|
48
60
|
jwt = vortex.generate_jwt({
|
|
49
61
|
"user_id": "user-123",
|
|
@@ -55,6 +67,7 @@ jwt = vortex.generate_jwt({
|
|
|
55
67
|
## [0.0.1] - 2024-10-10
|
|
56
68
|
|
|
57
69
|
### Added
|
|
70
|
+
|
|
58
71
|
- Initial release of Vortex Python SDK
|
|
59
72
|
- JWT generation with HMAC-SHA256 signing
|
|
60
73
|
- Complete invitation management API
|
|
@@ -65,6 +78,7 @@ jwt = vortex.generate_jwt({
|
|
|
65
78
|
- Full compatibility with Node.js SDK API
|
|
66
79
|
|
|
67
80
|
### Features
|
|
81
|
+
|
|
68
82
|
- `generate_jwt()` - Generate Vortex JWT tokens
|
|
69
83
|
- `get_invitations_by_target()` - Get invitations by email/username/phone
|
|
70
84
|
- `accept_invitations()` - Accept multiple invitations
|
|
@@ -74,4 +88,4 @@ jwt = vortex.generate_jwt({
|
|
|
74
88
|
- `delete_invitations_by_group()` - Delete all group invitations
|
|
75
89
|
- `reinvite()` - Reinvite functionality
|
|
76
90
|
- Both async and sync versions of all methods
|
|
77
|
-
- Python 3.8+ support
|
|
91
|
+
- Python 3.8+ support
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vortex-python-sdk"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.6"
|
|
8
8
|
description = "Vortex Python SDK for invitation management and JWT generation"
|
|
9
9
|
authors = [{name = "TeamVortexSoftware", email = "support@vortexsoftware.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -68,7 +68,7 @@ profile = "black"
|
|
|
68
68
|
line_length = 88
|
|
69
69
|
|
|
70
70
|
[tool.mypy]
|
|
71
|
-
python_version = "3.
|
|
71
|
+
python_version = "3.9"
|
|
72
72
|
warn_return_any = true
|
|
73
73
|
warn_unused_configs = true
|
|
74
74
|
disallow_untyped_defs = true
|
|
@@ -83,6 +83,8 @@ warn_unreachable = true
|
|
|
83
83
|
[tool.ruff]
|
|
84
84
|
target-version = "py38"
|
|
85
85
|
line-length = 88
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint]
|
|
86
88
|
select = [
|
|
87
89
|
"E", # pycodestyle errors
|
|
88
90
|
"W", # pycodestyle warnings
|
|
@@ -4,21 +4,27 @@ Vortex Python SDK
|
|
|
4
4
|
A Python SDK for Vortex invitation management and JWT generation.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from .vortex import Vortex
|
|
8
7
|
from .types import (
|
|
8
|
+
AcceptInvitationRequest,
|
|
9
|
+
AcceptInvitationsRequest,
|
|
10
|
+
ApiRequestBody,
|
|
11
|
+
ApiResponse,
|
|
12
|
+
ApiResponseJson,
|
|
9
13
|
AuthenticatedUser,
|
|
10
|
-
|
|
11
|
-
IdentifierInput,
|
|
14
|
+
CreateInvitationRequest,
|
|
12
15
|
GroupInput,
|
|
13
|
-
|
|
16
|
+
IdentifierInput,
|
|
14
17
|
Invitation,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
InvitationAcceptance,
|
|
19
|
+
InvitationGroup,
|
|
20
|
+
InvitationResult,
|
|
21
|
+
InvitationTarget,
|
|
22
|
+
JwtPayload,
|
|
23
|
+
VortexApiError,
|
|
19
24
|
)
|
|
25
|
+
from .vortex import Vortex
|
|
20
26
|
|
|
21
|
-
__version__ = "0.0.
|
|
27
|
+
__version__ = "0.0.6"
|
|
22
28
|
__author__ = "TeamVortexSoftware"
|
|
23
29
|
__email__ = "support@vortexsoftware.com"
|
|
24
30
|
|
|
@@ -29,9 +35,15 @@ __all__ = [
|
|
|
29
35
|
"IdentifierInput",
|
|
30
36
|
"GroupInput",
|
|
31
37
|
"InvitationTarget",
|
|
32
|
-
"
|
|
38
|
+
"InvitationGroup",
|
|
39
|
+
"InvitationAcceptance",
|
|
40
|
+
"InvitationResult",
|
|
41
|
+
"Invitation", # Alias for InvitationResult
|
|
33
42
|
"CreateInvitationRequest",
|
|
34
|
-
"
|
|
43
|
+
"AcceptInvitationRequest",
|
|
44
|
+
"AcceptInvitationsRequest", # Alias for AcceptInvitationRequest
|
|
35
45
|
"ApiResponse",
|
|
46
|
+
"ApiResponseJson",
|
|
47
|
+
"ApiRequestBody",
|
|
36
48
|
"VortexApiError",
|
|
37
|
-
]
|
|
49
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IdentifierInput(BaseModel):
|
|
7
|
+
"""Identifier structure for JWT generation"""
|
|
8
|
+
|
|
9
|
+
type: Literal["email", "sms"]
|
|
10
|
+
value: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GroupInput(BaseModel):
|
|
14
|
+
"""Group structure for JWT generation (input)"""
|
|
15
|
+
|
|
16
|
+
type: str
|
|
17
|
+
id: Optional[str] = None # Legacy field (deprecated, use groupId)
|
|
18
|
+
groupId: Optional[str] = Field(
|
|
19
|
+
None, alias="group_id", serialization_alias="groupId"
|
|
20
|
+
) # Preferred: Customer's group ID
|
|
21
|
+
name: str
|
|
22
|
+
|
|
23
|
+
class Config:
|
|
24
|
+
populate_by_name = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InvitationGroup(BaseModel):
|
|
28
|
+
"""
|
|
29
|
+
Invitation group from API responses
|
|
30
|
+
This matches the MemberGroups table structure from the API
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
id: str # Vortex internal UUID
|
|
34
|
+
account_id: str = Field(alias="accountId") # Vortex account ID
|
|
35
|
+
group_id: str = Field(alias="groupId") # Customer's group ID
|
|
36
|
+
type: str # Group type (e.g., "workspace", "team")
|
|
37
|
+
name: str # Group name
|
|
38
|
+
created_at: str = Field(alias="createdAt") # ISO 8601 timestamp
|
|
39
|
+
|
|
40
|
+
class Config:
|
|
41
|
+
# Allow both snake_case (Python) and camelCase (JSON) field names
|
|
42
|
+
populate_by_name = True
|
|
43
|
+
json_schema_extra = {
|
|
44
|
+
"example": {
|
|
45
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
46
|
+
"accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
47
|
+
"groupId": "workspace-123",
|
|
48
|
+
"type": "workspace",
|
|
49
|
+
"name": "My Workspace",
|
|
50
|
+
"createdAt": "2025-01-27T12:00:00.000Z",
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AuthenticatedUser(BaseModel):
|
|
56
|
+
user_id: str
|
|
57
|
+
identifiers: List[IdentifierInput]
|
|
58
|
+
groups: Optional[List[GroupInput]] = None
|
|
59
|
+
role: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class JwtPayload(BaseModel):
|
|
63
|
+
user_id: str
|
|
64
|
+
identifiers: List[IdentifierInput]
|
|
65
|
+
groups: Optional[List[GroupInput]] = None
|
|
66
|
+
role: Optional[str] = None
|
|
67
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class InvitationTarget(BaseModel):
|
|
71
|
+
type: Literal["email", "sms"]
|
|
72
|
+
value: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class InvitationAcceptance(BaseModel):
|
|
76
|
+
"""Represents an acceptance of an invitation"""
|
|
77
|
+
|
|
78
|
+
id: str
|
|
79
|
+
account_id: str = Field(alias="accountId")
|
|
80
|
+
project_id: str = Field(alias="projectId")
|
|
81
|
+
accepted_at: str = Field(alias="acceptedAt")
|
|
82
|
+
target: InvitationTarget
|
|
83
|
+
|
|
84
|
+
class Config:
|
|
85
|
+
populate_by_name = True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class InvitationResult(BaseModel):
|
|
89
|
+
"""
|
|
90
|
+
Complete invitation result from API responses.
|
|
91
|
+
This is the exact port of the Node.js SDK's InvitationResult type.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
id: str
|
|
95
|
+
account_id: str = Field(alias="accountId")
|
|
96
|
+
click_throughs: int = Field(alias="clickThroughs")
|
|
97
|
+
configuration_attributes: Optional[Dict[str, Any]] = Field(
|
|
98
|
+
None, alias="configurationAttributes"
|
|
99
|
+
)
|
|
100
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
101
|
+
created_at: str = Field(alias="createdAt")
|
|
102
|
+
deactivated: bool
|
|
103
|
+
delivery_count: int = Field(alias="deliveryCount")
|
|
104
|
+
delivery_types: List[Literal["email", "sms", "share"]] = Field(
|
|
105
|
+
alias="deliveryTypes"
|
|
106
|
+
)
|
|
107
|
+
foreign_creator_id: str = Field(alias="foreignCreatorId")
|
|
108
|
+
invitation_type: Literal["single_use", "multi_use"] = Field(alias="invitationType")
|
|
109
|
+
modified_at: Optional[str] = Field(None, alias="modifiedAt")
|
|
110
|
+
status: Literal[
|
|
111
|
+
"queued",
|
|
112
|
+
"sending",
|
|
113
|
+
"delivered",
|
|
114
|
+
"accepted",
|
|
115
|
+
"shared",
|
|
116
|
+
"unfurled",
|
|
117
|
+
"accepted_elsewhere",
|
|
118
|
+
]
|
|
119
|
+
target: List[InvitationTarget]
|
|
120
|
+
views: int
|
|
121
|
+
widget_configuration_id: str = Field(alias="widgetConfigurationId")
|
|
122
|
+
project_id: str = Field(alias="projectId")
|
|
123
|
+
groups: List[Optional[InvitationGroup]] = Field(default_factory=list)
|
|
124
|
+
accepts: List[InvitationAcceptance] = Field(default_factory=list)
|
|
125
|
+
|
|
126
|
+
class Config:
|
|
127
|
+
populate_by_name = True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Alias for backward compatibility
|
|
131
|
+
Invitation = InvitationResult
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CreateInvitationRequest(BaseModel):
|
|
135
|
+
target: InvitationTarget
|
|
136
|
+
group_type: Optional[str] = None
|
|
137
|
+
group_id: Optional[str] = None
|
|
138
|
+
expires_at: Optional[str] = None
|
|
139
|
+
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class AcceptInvitationRequest(BaseModel):
|
|
143
|
+
"""Request to accept one or more invitations"""
|
|
144
|
+
|
|
145
|
+
invitation_ids: List[str] = Field(alias="invitationIds")
|
|
146
|
+
target: InvitationTarget
|
|
147
|
+
|
|
148
|
+
class Config:
|
|
149
|
+
populate_by_name = True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Alias for backward compatibility
|
|
153
|
+
AcceptInvitationsRequest = AcceptInvitationRequest
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class ApiResponse(BaseModel):
|
|
157
|
+
data: Optional[Dict] = None
|
|
158
|
+
error: Optional[str] = None
|
|
159
|
+
status_code: int = 200
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class VortexApiError(Exception):
|
|
163
|
+
def __init__(self, message: str, status_code: int = 500):
|
|
164
|
+
self.message = message
|
|
165
|
+
self.status_code = status_code
|
|
166
|
+
super().__init__(message)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Type aliases to match Node.js SDK
|
|
170
|
+
ApiResponseJson = Union[
|
|
171
|
+
InvitationResult, Dict[str, List[InvitationResult]], Dict[str, Any]
|
|
172
|
+
]
|
|
173
|
+
ApiRequestBody = Union[AcceptInvitationRequest, None]
|
|
@@ -1,34 +1,41 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import hmac
|
|
3
|
-
import hashlib
|
|
4
1
|
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
import json
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
|
-
from typing import Dict, List, Optional, Union
|
|
8
|
-
|
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
8
|
+
|
|
9
9
|
import httpx
|
|
10
|
+
|
|
10
11
|
from .types import (
|
|
11
|
-
JwtPayload,
|
|
12
|
-
InvitationTarget,
|
|
13
12
|
Invitation,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
VortexApiError
|
|
13
|
+
InvitationTarget,
|
|
14
|
+
JwtPayload,
|
|
15
|
+
VortexApiError,
|
|
18
16
|
)
|
|
19
17
|
|
|
20
18
|
|
|
19
|
+
def _get_version() -> str:
|
|
20
|
+
"""Lazy import of version to avoid circular import"""
|
|
21
|
+
from . import __version__
|
|
22
|
+
|
|
23
|
+
return __version__
|
|
24
|
+
|
|
25
|
+
|
|
21
26
|
class Vortex:
|
|
22
|
-
def __init__(
|
|
27
|
+
def __init__(
|
|
28
|
+
self, api_key: str, base_url: str = "https://api.vortexsoftware.com/api/v1"
|
|
29
|
+
):
|
|
23
30
|
"""
|
|
24
31
|
Initialize Vortex client
|
|
25
32
|
|
|
26
33
|
Args:
|
|
27
34
|
api_key: Your Vortex API key
|
|
28
|
-
base_url: Base URL for Vortex API (default: https://api.vortexsoftware.com)
|
|
35
|
+
base_url: Base URL for Vortex API (default: https://api.vortexsoftware.com/api/v1)
|
|
29
36
|
"""
|
|
30
37
|
self.api_key = api_key
|
|
31
|
-
self.base_url = base_url.rstrip(
|
|
38
|
+
self.base_url = base_url.rstrip("/")
|
|
32
39
|
self._client = httpx.AsyncClient()
|
|
33
40
|
self._sync_client = httpx.Client()
|
|
34
41
|
|
|
@@ -49,20 +56,20 @@ class Vortex:
|
|
|
49
56
|
payload = JwtPayload(**payload)
|
|
50
57
|
|
|
51
58
|
# Parse API key (format: VRTX.base64url(uuid).key)
|
|
52
|
-
parts = self.api_key.split(
|
|
59
|
+
parts = self.api_key.split(".")
|
|
53
60
|
if len(parts) != 3:
|
|
54
|
-
raise ValueError(
|
|
61
|
+
raise ValueError("Invalid API key format. Expected: VRTX.{encodedId}.{key}")
|
|
55
62
|
|
|
56
63
|
prefix, encoded_id, key = parts
|
|
57
64
|
|
|
58
|
-
if prefix !=
|
|
59
|
-
raise ValueError(
|
|
65
|
+
if prefix != "VRTX":
|
|
66
|
+
raise ValueError("Invalid API key prefix. Expected: VRTX")
|
|
60
67
|
|
|
61
68
|
# Decode UUID from base64url
|
|
62
69
|
# Add padding if needed
|
|
63
70
|
padding = 4 - len(encoded_id) % 4
|
|
64
71
|
if padding != 4:
|
|
65
|
-
encoded_id_padded = encoded_id + (
|
|
72
|
+
encoded_id_padded = encoded_id + ("=" * padding)
|
|
66
73
|
else:
|
|
67
74
|
encoded_id_padded = encoded_id
|
|
68
75
|
|
|
@@ -70,75 +77,71 @@ class Vortex:
|
|
|
70
77
|
uuid_bytes = base64.urlsafe_b64decode(encoded_id_padded)
|
|
71
78
|
kid = str(uuid.UUID(bytes=uuid_bytes))
|
|
72
79
|
except Exception as e:
|
|
73
|
-
raise ValueError(f
|
|
80
|
+
raise ValueError(f"Invalid UUID in API key: {e}") from e
|
|
74
81
|
|
|
75
82
|
# Generate timestamps
|
|
76
83
|
iat = int(time.time())
|
|
77
84
|
expires = iat + 3600
|
|
78
85
|
|
|
79
86
|
# Step 1: Derive signing key from API key + UUID
|
|
80
|
-
signing_key = hmac.new(
|
|
81
|
-
key.encode(),
|
|
82
|
-
kid.encode(),
|
|
83
|
-
hashlib.sha256
|
|
84
|
-
).digest()
|
|
87
|
+
signing_key = hmac.new(key.encode(), kid.encode(), hashlib.sha256).digest()
|
|
85
88
|
|
|
86
89
|
# Step 2: Build header + payload
|
|
87
90
|
header = {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
"iat": iat,
|
|
92
|
+
"alg": "HS256",
|
|
93
|
+
"typ": "JWT",
|
|
94
|
+
"kid": kid,
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
# Serialize identifiers
|
|
95
|
-
identifiers_list = [
|
|
98
|
+
identifiers_list = [
|
|
99
|
+
{"type": id.type, "value": id.value} for id in payload.identifiers
|
|
100
|
+
]
|
|
96
101
|
|
|
97
102
|
# Serialize groups
|
|
98
103
|
groups_list = None
|
|
99
104
|
if payload.groups is not None:
|
|
100
105
|
groups_list = [
|
|
101
|
-
|
|
106
|
+
group.model_dump(by_alias=True, exclude_none=True)
|
|
102
107
|
for group in payload.groups
|
|
103
108
|
]
|
|
104
109
|
|
|
105
|
-
jwt_payload = {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
jwt_payload: Dict[str, Any] = {
|
|
111
|
+
"userId": payload.user_id,
|
|
112
|
+
"groups": groups_list,
|
|
113
|
+
"role": payload.role,
|
|
114
|
+
"expires": expires,
|
|
115
|
+
"identifiers": identifiers_list,
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
# Add attributes if provided
|
|
114
|
-
if hasattr(payload,
|
|
115
|
-
jwt_payload[
|
|
119
|
+
if hasattr(payload, "attributes") and payload.attributes:
|
|
120
|
+
jwt_payload["attributes"] = payload.attributes
|
|
116
121
|
|
|
117
122
|
# Step 3: Base64URL encode (without padding)
|
|
118
|
-
header_json = json.dumps(header, separators=(
|
|
119
|
-
payload_json = json.dumps(jwt_payload, separators=(
|
|
123
|
+
header_json = json.dumps(header, separators=(",", ":"))
|
|
124
|
+
payload_json = json.dumps(jwt_payload, separators=(",", ":"))
|
|
120
125
|
|
|
121
|
-
header_b64 = base64.urlsafe_b64encode(header_json.encode()).decode().rstrip(
|
|
122
|
-
payload_b64 =
|
|
126
|
+
header_b64 = base64.urlsafe_b64encode(header_json.encode()).decode().rstrip("=")
|
|
127
|
+
payload_b64 = (
|
|
128
|
+
base64.urlsafe_b64encode(payload_json.encode()).decode().rstrip("=")
|
|
129
|
+
)
|
|
123
130
|
|
|
124
131
|
# Step 4: Sign
|
|
125
|
-
to_sign = f
|
|
126
|
-
signature = hmac.new(
|
|
127
|
-
signing_key,
|
|
128
|
-
to_sign.encode(),
|
|
129
|
-
hashlib.sha256
|
|
130
|
-
).digest()
|
|
132
|
+
to_sign = f"{header_b64}.{payload_b64}"
|
|
133
|
+
signature = hmac.new(signing_key, to_sign.encode(), hashlib.sha256).digest()
|
|
131
134
|
|
|
132
|
-
signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip(
|
|
135
|
+
signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip("=")
|
|
133
136
|
|
|
134
|
-
return f
|
|
137
|
+
return f"{to_sign}.{signature_b64}"
|
|
135
138
|
|
|
136
139
|
async def _vortex_api_request(
|
|
137
140
|
self,
|
|
138
141
|
method: str,
|
|
139
142
|
endpoint: str,
|
|
140
143
|
data: Optional[Dict] = None,
|
|
141
|
-
params: Optional[Dict] = None
|
|
144
|
+
params: Optional[Dict] = None,
|
|
142
145
|
) -> Dict:
|
|
143
146
|
"""
|
|
144
147
|
Make an API request to Vortex
|
|
@@ -157,40 +160,41 @@ class Vortex:
|
|
|
157
160
|
"""
|
|
158
161
|
url = f"{self.base_url}{endpoint}"
|
|
159
162
|
headers = {
|
|
160
|
-
"
|
|
163
|
+
"x-api-key": f"{self.api_key}",
|
|
161
164
|
"Content-Type": "application/json",
|
|
162
|
-
"User-Agent": "vortex-python-sdk/
|
|
165
|
+
"User-Agent": f"vortex-python-sdk/{_get_version()}",
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
try:
|
|
166
169
|
response = await self._client.request(
|
|
167
|
-
method=method,
|
|
168
|
-
url=url,
|
|
169
|
-
json=data,
|
|
170
|
-
params=params,
|
|
171
|
-
headers=headers
|
|
170
|
+
method=method, url=url, json=data, params=params, headers=headers
|
|
172
171
|
)
|
|
173
172
|
|
|
174
173
|
if response.status_code >= 400:
|
|
175
174
|
try:
|
|
176
175
|
error_data = response.json()
|
|
177
|
-
error_message = error_data.get(
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
error_message = error_data.get(
|
|
177
|
+
"error",
|
|
178
|
+
f"API request failed with status {response.status_code}",
|
|
179
|
+
)
|
|
180
|
+
except Exception:
|
|
181
|
+
error_message = (
|
|
182
|
+
f"API request failed with status {response.status_code}"
|
|
183
|
+
)
|
|
180
184
|
|
|
181
185
|
raise VortexApiError(error_message, response.status_code)
|
|
182
186
|
|
|
183
|
-
return response.json()
|
|
187
|
+
return response.json() # type: ignore[no-any-return]
|
|
184
188
|
|
|
185
189
|
except httpx.RequestError as e:
|
|
186
|
-
raise VortexApiError(f"Request failed: {str(e)}")
|
|
190
|
+
raise VortexApiError(f"Request failed: {str(e)}") from e
|
|
187
191
|
|
|
188
192
|
def _vortex_api_request_sync(
|
|
189
193
|
self,
|
|
190
194
|
method: str,
|
|
191
195
|
endpoint: str,
|
|
192
196
|
data: Optional[Dict] = None,
|
|
193
|
-
params: Optional[Dict] = None
|
|
197
|
+
params: Optional[Dict] = None,
|
|
194
198
|
) -> Dict:
|
|
195
199
|
"""
|
|
196
200
|
Make a synchronous API request to Vortex
|
|
@@ -209,38 +213,39 @@ class Vortex:
|
|
|
209
213
|
"""
|
|
210
214
|
url = f"{self.base_url}{endpoint}"
|
|
211
215
|
headers = {
|
|
212
|
-
"
|
|
216
|
+
"x-api-key": f"{self.api_key}",
|
|
213
217
|
"Content-Type": "application/json",
|
|
214
|
-
"User-Agent": "vortex-python-sdk/
|
|
218
|
+
"User-Agent": f"vortex-python-sdk/{_get_version()}",
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
try:
|
|
218
222
|
response = self._sync_client.request(
|
|
219
|
-
method=method,
|
|
220
|
-
url=url,
|
|
221
|
-
json=data,
|
|
222
|
-
params=params,
|
|
223
|
-
headers=headers
|
|
223
|
+
method=method, url=url, json=data, params=params, headers=headers
|
|
224
224
|
)
|
|
225
225
|
|
|
226
226
|
if response.status_code >= 400:
|
|
227
227
|
try:
|
|
228
228
|
error_data = response.json()
|
|
229
|
-
error_message = error_data.get(
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
error_message = error_data.get(
|
|
230
|
+
"error",
|
|
231
|
+
f"API request failed with status {response.status_code}",
|
|
232
|
+
)
|
|
233
|
+
except Exception:
|
|
234
|
+
error_message = (
|
|
235
|
+
f"API request failed with status {response.status_code}"
|
|
236
|
+
)
|
|
232
237
|
|
|
233
238
|
raise VortexApiError(error_message, response.status_code)
|
|
234
239
|
|
|
235
|
-
return response.json()
|
|
240
|
+
return response.json() # type: ignore[no-any-return]
|
|
236
241
|
|
|
237
242
|
except httpx.RequestError as e:
|
|
238
|
-
raise VortexApiError(f"Request failed: {str(e)}")
|
|
243
|
+
raise VortexApiError(f"Request failed: {str(e)}") from e
|
|
239
244
|
|
|
240
245
|
async def get_invitations_by_target(
|
|
241
246
|
self,
|
|
242
247
|
target_type: Literal["email", "username", "phoneNumber"],
|
|
243
|
-
target_value: str
|
|
248
|
+
target_value: str,
|
|
244
249
|
) -> List[Invitation]:
|
|
245
250
|
"""
|
|
246
251
|
Get invitations for a specific target
|
|
@@ -252,18 +257,17 @@ class Vortex:
|
|
|
252
257
|
Returns:
|
|
253
258
|
List of invitations
|
|
254
259
|
"""
|
|
255
|
-
params = {
|
|
256
|
-
"targetType": target_type,
|
|
257
|
-
"targetValue": target_value
|
|
258
|
-
}
|
|
260
|
+
params = {"targetType": target_type, "targetValue": target_value}
|
|
259
261
|
|
|
260
|
-
response = await self._vortex_api_request(
|
|
262
|
+
response = await self._vortex_api_request(
|
|
263
|
+
"GET", "/invitations/by-target", params=params
|
|
264
|
+
)
|
|
261
265
|
return [Invitation(**inv) for inv in response.get("invitations", [])]
|
|
262
266
|
|
|
263
267
|
def get_invitations_by_target_sync(
|
|
264
268
|
self,
|
|
265
269
|
target_type: Literal["email", "username", "phoneNumber"],
|
|
266
|
-
target_value: str
|
|
270
|
+
target_value: str,
|
|
267
271
|
) -> List[Invitation]:
|
|
268
272
|
"""
|
|
269
273
|
Get invitations for a specific target (synchronous)
|
|
@@ -275,12 +279,11 @@ class Vortex:
|
|
|
275
279
|
Returns:
|
|
276
280
|
List of invitations
|
|
277
281
|
"""
|
|
278
|
-
params = {
|
|
279
|
-
"targetType": target_type,
|
|
280
|
-
"targetValue": target_value
|
|
281
|
-
}
|
|
282
|
+
params = {"targetType": target_type, "targetValue": target_value}
|
|
282
283
|
|
|
283
|
-
response = self._vortex_api_request_sync(
|
|
284
|
+
response = self._vortex_api_request_sync(
|
|
285
|
+
"GET", "/invitations/by-target", params=params
|
|
286
|
+
)
|
|
284
287
|
return [Invitation(**inv) for inv in response.get("invitations", [])]
|
|
285
288
|
|
|
286
289
|
async def get_invitation(self, invitation_id: str) -> Invitation:
|
|
@@ -293,7 +296,9 @@ class Vortex:
|
|
|
293
296
|
Returns:
|
|
294
297
|
Invitation object
|
|
295
298
|
"""
|
|
296
|
-
response = await self._vortex_api_request(
|
|
299
|
+
response = await self._vortex_api_request(
|
|
300
|
+
"GET", f"/invitations/{invitation_id}"
|
|
301
|
+
)
|
|
297
302
|
return Invitation(**response)
|
|
298
303
|
|
|
299
304
|
def get_invitation_sync(self, invitation_id: str) -> Invitation:
|
|
@@ -310,9 +315,7 @@ class Vortex:
|
|
|
310
315
|
return Invitation(**response)
|
|
311
316
|
|
|
312
317
|
async def accept_invitations(
|
|
313
|
-
self,
|
|
314
|
-
invitation_ids: List[str],
|
|
315
|
-
target: Union[InvitationTarget, Dict[str, str]]
|
|
318
|
+
self, invitation_ids: List[str], target: Union[InvitationTarget, Dict[str, str]]
|
|
316
319
|
) -> Dict:
|
|
317
320
|
"""
|
|
318
321
|
Accept multiple invitations
|
|
@@ -324,20 +327,18 @@ class Vortex:
|
|
|
324
327
|
Returns:
|
|
325
328
|
API response
|
|
326
329
|
"""
|
|
330
|
+
target_obj: InvitationTarget
|
|
327
331
|
if isinstance(target, dict):
|
|
328
|
-
|
|
332
|
+
target_obj = InvitationTarget(**target) # type: ignore[arg-type]
|
|
333
|
+
else:
|
|
334
|
+
target_obj = target
|
|
329
335
|
|
|
330
|
-
data = {
|
|
331
|
-
"invitationIds": invitation_ids,
|
|
332
|
-
"target": target.model_dump()
|
|
333
|
-
}
|
|
336
|
+
data = {"invitationIds": invitation_ids, "target": target_obj.model_dump()}
|
|
334
337
|
|
|
335
338
|
return await self._vortex_api_request("POST", "/invitations/accept", data=data)
|
|
336
339
|
|
|
337
340
|
def accept_invitations_sync(
|
|
338
|
-
self,
|
|
339
|
-
invitation_ids: List[str],
|
|
340
|
-
target: Union[InvitationTarget, Dict[str, str]]
|
|
341
|
+
self, invitation_ids: List[str], target: Union[InvitationTarget, Dict[str, str]]
|
|
341
342
|
) -> Dict:
|
|
342
343
|
"""
|
|
343
344
|
Accept multiple invitations (synchronous)
|
|
@@ -349,13 +350,13 @@ class Vortex:
|
|
|
349
350
|
Returns:
|
|
350
351
|
API response
|
|
351
352
|
"""
|
|
353
|
+
target_obj: InvitationTarget
|
|
352
354
|
if isinstance(target, dict):
|
|
353
|
-
|
|
355
|
+
target_obj = InvitationTarget(**target) # type: ignore[arg-type]
|
|
356
|
+
else:
|
|
357
|
+
target_obj = target
|
|
354
358
|
|
|
355
|
-
data = {
|
|
356
|
-
"invitationIds": invitation_ids,
|
|
357
|
-
"target": target.model_dump()
|
|
358
|
-
}
|
|
359
|
+
data = {"invitationIds": invitation_ids, "target": target_obj.model_dump()}
|
|
359
360
|
|
|
360
361
|
return self._vortex_api_request_sync("POST", "/invitations/accept", data=data)
|
|
361
362
|
|
|
@@ -384,9 +385,7 @@ class Vortex:
|
|
|
384
385
|
return self._vortex_api_request_sync("DELETE", f"/invitations/{invitation_id}")
|
|
385
386
|
|
|
386
387
|
async def get_invitations_by_group(
|
|
387
|
-
self,
|
|
388
|
-
group_type: str,
|
|
389
|
-
group_id: str
|
|
388
|
+
self, group_type: str, group_id: str
|
|
390
389
|
) -> List[Invitation]:
|
|
391
390
|
"""
|
|
392
391
|
Get invitations for a specific group
|
|
@@ -398,13 +397,13 @@ class Vortex:
|
|
|
398
397
|
Returns:
|
|
399
398
|
List of invitations
|
|
400
399
|
"""
|
|
401
|
-
response = await self._vortex_api_request(
|
|
400
|
+
response = await self._vortex_api_request(
|
|
401
|
+
"GET", f"/invitations/by-group/{group_type}/{group_id}"
|
|
402
|
+
)
|
|
402
403
|
return [Invitation(**inv) for inv in response.get("invitations", [])]
|
|
403
404
|
|
|
404
405
|
def get_invitations_by_group_sync(
|
|
405
|
-
self,
|
|
406
|
-
group_type: str,
|
|
407
|
-
group_id: str
|
|
406
|
+
self, group_type: str, group_id: str
|
|
408
407
|
) -> List[Invitation]:
|
|
409
408
|
"""
|
|
410
409
|
Get invitations for a specific group (synchronous)
|
|
@@ -416,14 +415,12 @@ class Vortex:
|
|
|
416
415
|
Returns:
|
|
417
416
|
List of invitations
|
|
418
417
|
"""
|
|
419
|
-
response = self._vortex_api_request_sync(
|
|
418
|
+
response = self._vortex_api_request_sync(
|
|
419
|
+
"GET", f"/invitations/by-group/{group_type}/{group_id}"
|
|
420
|
+
)
|
|
420
421
|
return [Invitation(**inv) for inv in response.get("invitations", [])]
|
|
421
422
|
|
|
422
|
-
async def delete_invitations_by_group(
|
|
423
|
-
self,
|
|
424
|
-
group_type: str,
|
|
425
|
-
group_id: str
|
|
426
|
-
) -> Dict:
|
|
423
|
+
async def delete_invitations_by_group(self, group_type: str, group_id: str) -> Dict:
|
|
427
424
|
"""
|
|
428
425
|
Delete all invitations for a specific group
|
|
429
426
|
|
|
@@ -434,13 +431,11 @@ class Vortex:
|
|
|
434
431
|
Returns:
|
|
435
432
|
API response
|
|
436
433
|
"""
|
|
437
|
-
return await self._vortex_api_request(
|
|
434
|
+
return await self._vortex_api_request(
|
|
435
|
+
"DELETE", f"/invitations/by-group/{group_type}/{group_id}"
|
|
436
|
+
)
|
|
438
437
|
|
|
439
|
-
def delete_invitations_by_group_sync(
|
|
440
|
-
self,
|
|
441
|
-
group_type: str,
|
|
442
|
-
group_id: str
|
|
443
|
-
) -> Dict:
|
|
438
|
+
def delete_invitations_by_group_sync(self, group_type: str, group_id: str) -> Dict:
|
|
444
439
|
"""
|
|
445
440
|
Delete all invitations for a specific group (synchronous)
|
|
446
441
|
|
|
@@ -451,7 +446,9 @@ class Vortex:
|
|
|
451
446
|
Returns:
|
|
452
447
|
API response
|
|
453
448
|
"""
|
|
454
|
-
return self._vortex_api_request_sync(
|
|
449
|
+
return self._vortex_api_request_sync(
|
|
450
|
+
"DELETE", f"/invitations/by-group/{group_type}/{group_id}"
|
|
451
|
+
)
|
|
455
452
|
|
|
456
453
|
async def reinvite(self, invitation_id: str) -> Invitation:
|
|
457
454
|
"""
|
|
@@ -463,7 +460,9 @@ class Vortex:
|
|
|
463
460
|
Returns:
|
|
464
461
|
Updated invitation object
|
|
465
462
|
"""
|
|
466
|
-
response = await self._vortex_api_request(
|
|
463
|
+
response = await self._vortex_api_request(
|
|
464
|
+
"POST", f"/invitations/{invitation_id}/reinvite"
|
|
465
|
+
)
|
|
467
466
|
return Invitation(**response)
|
|
468
467
|
|
|
469
468
|
def reinvite_sync(self, invitation_id: str) -> Invitation:
|
|
@@ -476,29 +475,31 @@ class Vortex:
|
|
|
476
475
|
Returns:
|
|
477
476
|
Updated invitation object
|
|
478
477
|
"""
|
|
479
|
-
response = self._vortex_api_request_sync(
|
|
478
|
+
response = self._vortex_api_request_sync(
|
|
479
|
+
"POST", f"/invitations/{invitation_id}/reinvite"
|
|
480
|
+
)
|
|
480
481
|
return Invitation(**response)
|
|
481
482
|
|
|
482
|
-
async def close(self):
|
|
483
|
+
async def close(self) -> None:
|
|
483
484
|
"""Close the HTTP client"""
|
|
484
485
|
await self._client.aclose()
|
|
485
486
|
|
|
486
|
-
def close_sync(self):
|
|
487
|
+
def close_sync(self) -> None:
|
|
487
488
|
"""Close the synchronous HTTP client"""
|
|
488
489
|
self._sync_client.close()
|
|
489
490
|
|
|
490
|
-
async def __aenter__(self):
|
|
491
|
+
async def __aenter__(self) -> "Vortex":
|
|
491
492
|
"""Async context manager entry"""
|
|
492
493
|
return self
|
|
493
494
|
|
|
494
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
495
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
495
496
|
"""Async context manager exit"""
|
|
496
497
|
await self.close()
|
|
497
498
|
|
|
498
|
-
def __enter__(self):
|
|
499
|
+
def __enter__(self) -> "Vortex":
|
|
499
500
|
"""Context manager entry"""
|
|
500
501
|
return self
|
|
501
502
|
|
|
502
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
503
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
503
504
|
"""Context manager exit"""
|
|
504
|
-
self.close_sync()
|
|
505
|
+
self.close_sync()
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List, Optional, Union, Literal, Any
|
|
2
|
-
from pydantic import BaseModel, Field
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class IdentifierInput(BaseModel):
|
|
6
|
-
"""Identifier structure for JWT generation"""
|
|
7
|
-
type: Literal["email", "sms"]
|
|
8
|
-
value: str
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class GroupInput(BaseModel):
|
|
12
|
-
"""Group structure for JWT generation (input)"""
|
|
13
|
-
type: str
|
|
14
|
-
id: Optional[str] = None # Legacy field (deprecated, use groupId)
|
|
15
|
-
groupId: Optional[str] = Field(None, alias="group_id", serialization_alias="groupId") # Preferred: Customer's group ID
|
|
16
|
-
name: str
|
|
17
|
-
|
|
18
|
-
class Config:
|
|
19
|
-
populate_by_name = True
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class InvitationGroup(BaseModel):
|
|
23
|
-
"""
|
|
24
|
-
Invitation group from API responses
|
|
25
|
-
This matches the MemberGroups table structure from the API
|
|
26
|
-
"""
|
|
27
|
-
id: str # Vortex internal UUID
|
|
28
|
-
account_id: str # Vortex account ID (camelCase in JSON: accountId)
|
|
29
|
-
group_id: str # Customer's group ID (camelCase in JSON: groupId)
|
|
30
|
-
type: str # Group type (e.g., "workspace", "team")
|
|
31
|
-
name: str # Group name
|
|
32
|
-
created_at: str # ISO 8601 timestamp (camelCase in JSON: createdAt)
|
|
33
|
-
|
|
34
|
-
class Config:
|
|
35
|
-
# Allow both snake_case (Python) and camelCase (JSON) field names
|
|
36
|
-
populate_by_name = True
|
|
37
|
-
json_schema_extra = {
|
|
38
|
-
"example": {
|
|
39
|
-
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
40
|
-
"accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
41
|
-
"groupId": "workspace-123",
|
|
42
|
-
"type": "workspace",
|
|
43
|
-
"name": "My Workspace",
|
|
44
|
-
"createdAt": "2025-01-27T12:00:00.000Z"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class AuthenticatedUser(BaseModel):
|
|
50
|
-
user_id: str
|
|
51
|
-
identifiers: List[IdentifierInput]
|
|
52
|
-
groups: Optional[List[GroupInput]] = None
|
|
53
|
-
role: Optional[str] = None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class JwtPayload(BaseModel):
|
|
57
|
-
user_id: str
|
|
58
|
-
identifiers: List[IdentifierInput]
|
|
59
|
-
groups: Optional[List[GroupInput]] = None
|
|
60
|
-
role: Optional[str] = None
|
|
61
|
-
attributes: Optional[Dict[str, Any]] = None
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class InvitationTarget(BaseModel):
|
|
65
|
-
type: Literal["email", "username", "phoneNumber"]
|
|
66
|
-
value: str
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class Invitation(BaseModel):
|
|
70
|
-
id: str
|
|
71
|
-
target: InvitationTarget
|
|
72
|
-
groups: Optional[List[InvitationGroup]] = None # Full group information
|
|
73
|
-
status: str
|
|
74
|
-
created_at: str
|
|
75
|
-
updated_at: Optional[str] = None
|
|
76
|
-
expires_at: Optional[str] = None
|
|
77
|
-
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class CreateInvitationRequest(BaseModel):
|
|
81
|
-
target: InvitationTarget
|
|
82
|
-
group_type: Optional[str] = None
|
|
83
|
-
group_id: Optional[str] = None
|
|
84
|
-
expires_at: Optional[str] = None
|
|
85
|
-
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class AcceptInvitationsRequest(BaseModel):
|
|
89
|
-
invitation_ids: List[str]
|
|
90
|
-
target: InvitationTarget
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class ApiResponse(BaseModel):
|
|
94
|
-
data: Optional[Dict] = None
|
|
95
|
-
error: Optional[str] = None
|
|
96
|
-
status_code: int = 200
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class VortexApiError(Exception):
|
|
100
|
-
def __init__(self, message: str, status_code: int = 500):
|
|
101
|
-
self.message = message
|
|
102
|
-
self.status_code = status_code
|
|
103
|
-
super().__init__(message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/requires.txt
RENAMED
|
File without changes
|
{vortex_python_sdk-0.0.3 → vortex_python_sdk-0.0.6}/src/vortex_python_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|