vortex-python-sdk 0.0.2__tar.gz → 0.0.5__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.
@@ -7,9 +7,32 @@ 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
+
16
+ ## [0.0.3] - 2025-01-31
17
+
18
+ ### Fixed
19
+
20
+ - **CRITICAL FIX**: JWT generation now matches Node.js SDK implementation exactly
21
+ - Improved JWT signing algorithm to match Node.js SDK
22
+ - Added `iat` (issued at) and `expires` timestamp fields to JWT
23
+ - Added `attributes` field support in JwtPayload for custom user attributes
24
+ - Fixed base64url encoding
25
+ - Added comprehensive tests verifying JWT output matches Node.js SDK byte-for-byte
26
+
27
+ ### Breaking Changes
28
+
29
+ - JWT structure changed significantly - tokens from 0.0.2 are incompatible with 0.0.3
30
+ - JWTs now include `iat` and `expires` fields for proper token lifecycle management
31
+
10
32
  ## [0.0.2] - 2025-01-31
11
33
 
12
34
  ### Fixed
35
+
13
36
  - **BREAKING FIX**: JWT payload format now matches TypeScript SDK
14
37
  - `identifiers` changed from `Dict[str, str]` to `List[Dict]` with `type` and `value` fields
15
38
  - `groups` structure now properly includes `type`, `id`/`groupId`, and `name` fields
@@ -18,9 +41,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
41
  - Updated documentation with correct JWT generation examples
19
42
 
20
43
  ### Migration Guide
44
+
21
45
  If you're upgrading from 0.0.1, update your JWT generation code:
22
46
 
23
47
  **Before (0.0.1):**
48
+
24
49
  ```python
25
50
  jwt = vortex.generate_jwt({
26
51
  "user_id": "user-123",
@@ -30,6 +55,7 @@ jwt = vortex.generate_jwt({
30
55
  ```
31
56
 
32
57
  **After (0.0.2):**
58
+
33
59
  ```python
34
60
  jwt = vortex.generate_jwt({
35
61
  "user_id": "user-123",
@@ -41,6 +67,7 @@ jwt = vortex.generate_jwt({
41
67
  ## [0.0.1] - 2024-10-10
42
68
 
43
69
  ### Added
70
+
44
71
  - Initial release of Vortex Python SDK
45
72
  - JWT generation with HMAC-SHA256 signing
46
73
  - Complete invitation management API
@@ -51,6 +78,7 @@ jwt = vortex.generate_jwt({
51
78
  - Full compatibility with Node.js SDK API
52
79
 
53
80
  ### Features
81
+
54
82
  - `generate_jwt()` - Generate Vortex JWT tokens
55
83
  - `get_invitations_by_target()` - Get invitations by email/username/phone
56
84
  - `accept_invitations()` - Accept multiple invitations
@@ -60,4 +88,4 @@ jwt = vortex.generate_jwt({
60
88
  - `delete_invitations_by_group()` - Delete all group invitations
61
89
  - `reinvite()` - Reinvite functionality
62
90
  - Both async and sync versions of all methods
63
- - Python 3.8+ support
91
+ - Python 3.8+ support
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.0.2
3
+ Version: 0.0.5
4
4
  Summary: Vortex Python SDK for invitation management and JWT generation
5
5
  Author-email: TeamVortexSoftware <support@vortexsoftware.com>
6
6
  License-Expression: MIT
@@ -55,11 +55,11 @@ pip install vortex-python-sdk
55
55
  ```python
56
56
  from vortex_sdk import Vortex
57
57
 
58
- # Initialize the client
59
- vortex = Vortex(api_key="your-api-key")
58
+ # Initialize the client with your Vortex API key
59
+ vortex = Vortex(api_key="your-vortex-api-key")
60
60
 
61
61
  # Or with custom base URL
62
- vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com")
62
+ vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.example.com")
63
63
  ```
64
64
 
65
65
  ### JWT Generation
@@ -17,11 +17,11 @@ pip install vortex-python-sdk
17
17
  ```python
18
18
  from vortex_sdk import Vortex
19
19
 
20
- # Initialize the client
21
- vortex = Vortex(api_key="your-api-key")
20
+ # Initialize the client with your Vortex API key
21
+ vortex = Vortex(api_key="your-vortex-api-key")
22
22
 
23
23
  # Or with custom base URL
24
- vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com")
24
+ vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.example.com")
25
25
  ```
26
26
 
27
27
  ### JWT Generation
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortex-python-sdk"
7
- version = "0.0.2"
7
+ version = "0.0.5"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.0.2
3
+ Version: 0.0.5
4
4
  Summary: Vortex Python SDK for invitation management and JWT generation
5
5
  Author-email: TeamVortexSoftware <support@vortexsoftware.com>
6
6
  License-Expression: MIT
@@ -55,11 +55,11 @@ pip install vortex-python-sdk
55
55
  ```python
56
56
  from vortex_sdk import Vortex
57
57
 
58
- # Initialize the client
59
- vortex = Vortex(api_key="your-api-key")
58
+ # Initialize the client with your Vortex API key
59
+ vortex = Vortex(api_key="your-vortex-api-key")
60
60
 
61
61
  # Or with custom base URL
62
- vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com")
62
+ vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.example.com")
63
63
  ```
64
64
 
65
65
  ### JWT Generation
@@ -4,21 +4,21 @@ 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
+ AcceptInvitationsRequest,
9
+ ApiResponse,
9
10
  AuthenticatedUser,
10
- JwtPayload,
11
- IdentifierInput,
11
+ CreateInvitationRequest,
12
12
  GroupInput,
13
- InvitationTarget,
13
+ IdentifierInput,
14
14
  Invitation,
15
- CreateInvitationRequest,
16
- AcceptInvitationsRequest,
17
- ApiResponse,
18
- VortexApiError
15
+ InvitationTarget,
16
+ JwtPayload,
17
+ VortexApiError,
19
18
  )
19
+ from .vortex import Vortex
20
20
 
21
- __version__ = "0.0.2"
21
+ __version__ = "0.0.5"
22
22
  __author__ = "TeamVortexSoftware"
23
23
  __email__ = "support@vortexsoftware.com"
24
24
 
@@ -34,4 +34,4 @@ __all__ = [
34
34
  "AcceptInvitationsRequest",
35
35
  "ApiResponse",
36
36
  "VortexApiError",
37
- ]
37
+ ]
@@ -1,18 +1,23 @@
1
- from typing import Dict, List, Optional, Union, Literal
1
+ from typing import Any, Dict, List, Literal, Optional, Union
2
+
2
3
  from pydantic import BaseModel, Field
3
4
 
4
5
 
5
6
  class IdentifierInput(BaseModel):
6
7
  """Identifier structure for JWT generation"""
8
+
7
9
  type: Literal["email", "sms"]
8
10
  value: str
9
11
 
10
12
 
11
13
  class GroupInput(BaseModel):
12
14
  """Group structure for JWT generation (input)"""
15
+
13
16
  type: str
14
17
  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
18
+ groupId: Optional[str] = Field(
19
+ None, alias="group_id", serialization_alias="groupId"
20
+ ) # Preferred: Customer's group ID
16
21
  name: str
17
22
 
18
23
  class Config:
@@ -24,12 +29,13 @@ class InvitationGroup(BaseModel):
24
29
  Invitation group from API responses
25
30
  This matches the MemberGroups table structure from the API
26
31
  """
32
+
27
33
  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)
34
+ account_id: str = Field(alias="accountId") # Vortex account ID
35
+ group_id: str = Field(alias="groupId") # Customer's group ID
30
36
  type: str # Group type (e.g., "workspace", "team")
31
37
  name: str # Group name
32
- created_at: str # ISO 8601 timestamp (camelCase in JSON: createdAt)
38
+ created_at: str = Field(alias="createdAt") # ISO 8601 timestamp
33
39
 
34
40
  class Config:
35
41
  # Allow both snake_case (Python) and camelCase (JSON) field names
@@ -41,7 +47,7 @@ class InvitationGroup(BaseModel):
41
47
  "groupId": "workspace-123",
42
48
  "type": "workspace",
43
49
  "name": "My Workspace",
44
- "createdAt": "2025-01-27T12:00:00.000Z"
50
+ "createdAt": "2025-01-27T12:00:00.000Z",
45
51
  }
46
52
  }
47
53
 
@@ -58,6 +64,7 @@ class JwtPayload(BaseModel):
58
64
  identifiers: List[IdentifierInput]
59
65
  groups: Optional[List[GroupInput]] = None
60
66
  role: Optional[str] = None
67
+ attributes: Optional[Dict[str, Any]] = None
61
68
 
62
69
 
63
70
  class InvitationTarget(BaseModel):
@@ -67,14 +74,17 @@ class InvitationTarget(BaseModel):
67
74
 
68
75
  class Invitation(BaseModel):
69
76
  id: str
70
- target: InvitationTarget
71
- groups: Optional[List[InvitationGroup]] = None # Full group information
77
+ target: Union[InvitationTarget, List[InvitationTarget]] # API returns list or single
78
+ groups: Optional[List[Optional[InvitationGroup]]] = None # Full group information, can contain None
72
79
  status: str
73
- created_at: str
74
- updated_at: Optional[str] = None
75
- expires_at: Optional[str] = None
80
+ created_at: Optional[str] = Field(None, alias="createdAt") # API uses camelCase
81
+ updated_at: Optional[str] = Field(None, alias="updatedAt")
82
+ expires_at: Optional[str] = Field(None, alias="expiresAt")
76
83
  metadata: Optional[Dict[str, Union[str, int, bool]]] = None
77
84
 
85
+ class Config:
86
+ populate_by_name = True
87
+
78
88
 
79
89
  class CreateInvitationRequest(BaseModel):
80
90
  target: InvitationTarget
@@ -99,4 +109,4 @@ class VortexApiError(Exception):
99
109
  def __init__(self, message: str, status_code: int = 500):
100
110
  self.message = message
101
111
  self.status_code = status_code
102
- super().__init__(message)
112
+ super().__init__(message)
@@ -1,96 +1,155 @@
1
- import json
2
- import hmac
3
- import hashlib
4
1
  import base64
5
- from typing import Dict, List, Optional, Union, Literal
2
+ import hashlib
3
+ import hmac
4
+ import json
5
+ import time
6
+ import uuid
7
+ from typing import Dict, List, Literal, Optional, Union
6
8
  from urllib.parse import urlencode
9
+
7
10
  import httpx
11
+
8
12
  from .types import (
9
- JwtPayload,
10
- InvitationTarget,
11
- Invitation,
12
- CreateInvitationRequest,
13
13
  AcceptInvitationsRequest,
14
14
  ApiResponse,
15
- VortexApiError
15
+ CreateInvitationRequest,
16
+ Invitation,
17
+ InvitationTarget,
18
+ JwtPayload,
19
+ VortexApiError,
16
20
  )
17
21
 
18
22
 
23
+ def _get_version():
24
+ """Lazy import of version to avoid circular import"""
25
+ from . import __version__
26
+ return __version__
27
+
28
+
19
29
  class Vortex:
20
- def __init__(self, api_key: str, base_url: str = "https://api.vortexsoftware.com"):
30
+ def __init__(
31
+ self, api_key: str, base_url: str = "https://api.vortexsoftware.com/api/v1"
32
+ ):
21
33
  """
22
34
  Initialize Vortex client
23
35
 
24
36
  Args:
25
37
  api_key: Your Vortex API key
26
- base_url: Base URL for Vortex API (default: https://api.vortexsoftware.com)
38
+ base_url: Base URL for Vortex API (default: https://api.vortexsoftware.com/api/v1)
27
39
  """
28
40
  self.api_key = api_key
29
- self.base_url = base_url.rstrip('/')
41
+ self.base_url = base_url.rstrip("/")
30
42
  self._client = httpx.AsyncClient()
31
43
  self._sync_client = httpx.Client()
32
44
 
33
45
  def generate_jwt(self, payload: Union[JwtPayload, Dict]) -> str:
34
46
  """
35
- Generate a JWT token for the given payload
47
+ Generate a JWT token for the given payload matching Node.js SDK implementation
36
48
 
37
49
  Args:
38
50
  payload: JWT payload containing user_id, identifiers, groups, and role
39
51
 
40
52
  Returns:
41
53
  JWT token string
54
+
55
+ Raises:
56
+ ValueError: If API key format is invalid
42
57
  """
43
58
  if isinstance(payload, dict):
44
59
  payload = JwtPayload(**payload)
45
60
 
46
- # JWT Header
61
+ # Parse API key (format: VRTX.base64url(uuid).key)
62
+ parts = self.api_key.split(".")
63
+ if len(parts) != 3:
64
+ raise ValueError("Invalid API key format. Expected: VRTX.{encodedId}.{key}")
65
+
66
+ prefix, encoded_id, key = parts
67
+
68
+ if prefix != "VRTX":
69
+ raise ValueError("Invalid API key prefix. Expected: VRTX")
70
+
71
+ # Decode UUID from base64url
72
+ # Add padding if needed
73
+ padding = 4 - len(encoded_id) % 4
74
+ if padding != 4:
75
+ encoded_id_padded = encoded_id + ("=" * padding)
76
+ else:
77
+ encoded_id_padded = encoded_id
78
+
79
+ try:
80
+ uuid_bytes = base64.urlsafe_b64decode(encoded_id_padded)
81
+ kid = str(uuid.UUID(bytes=uuid_bytes))
82
+ except Exception as e:
83
+ raise ValueError(f"Invalid UUID in API key: {e}")
84
+
85
+ # Generate timestamps
86
+ iat = int(time.time())
87
+ expires = iat + 3600
88
+
89
+ # Step 1: Derive signing key from API key + UUID
90
+ signing_key = hmac.new(key.encode(), kid.encode(), hashlib.sha256).digest()
91
+
92
+ # Step 2: Build header + payload
47
93
  header = {
94
+ "iat": iat,
48
95
  "alg": "HS256",
49
- "typ": "JWT"
96
+ "typ": "JWT",
97
+ "kid": kid,
50
98
  }
51
99
 
52
- # JWT Payload - serialize identifiers and groups to dicts
53
- jwt_payload = {
54
- "userId": payload.user_id,
55
- "identifiers": [{"type": id.type, "value": id.value} for id in payload.identifiers],
56
- }
100
+ # Serialize identifiers
101
+ identifiers_list = [
102
+ {"type": id.type, "value": id.value} for id in payload.identifiers
103
+ ]
57
104
 
105
+ # Serialize groups
106
+ groups_list = None
58
107
  if payload.groups is not None:
59
- # Serialize groups, using model_dump to handle camelCase conversion
60
- jwt_payload["groups"] = [
61
- {k: v for k, v in group.model_dump(by_alias=True, exclude_none=True).items()}
108
+ groups_list = [
109
+ {
110
+ k: v
111
+ for k, v in group.model_dump(
112
+ by_alias=True, exclude_none=True
113
+ ).items()
114
+ }
62
115
  for group in payload.groups
63
116
  ]
64
- if payload.role is not None:
65
- jwt_payload["role"] = payload.role
66
117
 
67
- # Encode header and payload
68
- header_encoded = base64.urlsafe_b64encode(
69
- json.dumps(header, separators=(',', ':')).encode()
70
- ).decode().rstrip('=')
118
+ jwt_payload = {
119
+ "userId": payload.user_id,
120
+ "groups": groups_list,
121
+ "role": payload.role,
122
+ "expires": expires,
123
+ "identifiers": identifiers_list,
124
+ }
125
+
126
+ # Add attributes if provided
127
+ if hasattr(payload, "attributes") and payload.attributes:
128
+ jwt_payload["attributes"] = payload.attributes
129
+
130
+ # Step 3: Base64URL encode (without padding)
131
+ header_json = json.dumps(header, separators=(",", ":"))
132
+ payload_json = json.dumps(jwt_payload, separators=(",", ":"))
71
133
 
72
- payload_encoded = base64.urlsafe_b64encode(
73
- json.dumps(jwt_payload, separators=(',', ':')).encode()
74
- ).decode().rstrip('=')
134
+ header_b64 = base64.urlsafe_b64encode(header_json.encode()).decode().rstrip("=")
135
+ payload_b64 = (
136
+ base64.urlsafe_b64encode(payload_json.encode()).decode().rstrip("=")
137
+ )
75
138
 
76
- # Create signature
77
- message = f"{header_encoded}.{payload_encoded}"
78
- signature = hmac.new(
79
- self.api_key.encode(),
80
- message.encode(),
81
- hashlib.sha256
82
- ).digest()
139
+ # Step 4: Sign
140
+ to_sign = f"{header_b64}.{payload_b64}"
141
+ signature = hmac.new(signing_key, to_sign.encode(), hashlib.sha256).digest()
83
142
 
84
- signature_encoded = base64.urlsafe_b64encode(signature).decode().rstrip('=')
143
+ signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip("=")
85
144
 
86
- return f"{message}.{signature_encoded}"
145
+ return f"{to_sign}.{signature_b64}"
87
146
 
88
147
  async def _vortex_api_request(
89
148
  self,
90
149
  method: str,
91
150
  endpoint: str,
92
151
  data: Optional[Dict] = None,
93
- params: Optional[Dict] = None
152
+ params: Optional[Dict] = None,
94
153
  ) -> Dict:
95
154
  """
96
155
  Make an API request to Vortex
@@ -109,26 +168,27 @@ class Vortex:
109
168
  """
110
169
  url = f"{self.base_url}{endpoint}"
111
170
  headers = {
112
- "Authorization": f"Bearer {self.api_key}",
171
+ "x-api-key": f"{self.api_key}",
113
172
  "Content-Type": "application/json",
114
- "User-Agent": "vortex-python-sdk/0.0.1"
173
+ "User-Agent": f"vortex-python-sdk/{_get_version()}",
115
174
  }
116
175
 
117
176
  try:
118
177
  response = await self._client.request(
119
- method=method,
120
- url=url,
121
- json=data,
122
- params=params,
123
- headers=headers
178
+ method=method, url=url, json=data, params=params, headers=headers
124
179
  )
125
180
 
126
181
  if response.status_code >= 400:
127
182
  try:
128
183
  error_data = response.json()
129
- error_message = error_data.get('error', f'API request failed with status {response.status_code}')
184
+ error_message = error_data.get(
185
+ "error",
186
+ f"API request failed with status {response.status_code}",
187
+ )
130
188
  except:
131
- error_message = f'API request failed with status {response.status_code}'
189
+ error_message = (
190
+ f"API request failed with status {response.status_code}"
191
+ )
132
192
 
133
193
  raise VortexApiError(error_message, response.status_code)
134
194
 
@@ -142,7 +202,7 @@ class Vortex:
142
202
  method: str,
143
203
  endpoint: str,
144
204
  data: Optional[Dict] = None,
145
- params: Optional[Dict] = None
205
+ params: Optional[Dict] = None,
146
206
  ) -> Dict:
147
207
  """
148
208
  Make a synchronous API request to Vortex
@@ -161,26 +221,27 @@ class Vortex:
161
221
  """
162
222
  url = f"{self.base_url}{endpoint}"
163
223
  headers = {
164
- "Authorization": f"Bearer {self.api_key}",
224
+ "x-api-key": f"{self.api_key}",
165
225
  "Content-Type": "application/json",
166
- "User-Agent": "vortex-python-sdk/0.0.1"
226
+ "User-Agent": f"vortex-python-sdk/{_get_version()}",
167
227
  }
168
228
 
169
229
  try:
170
230
  response = self._sync_client.request(
171
- method=method,
172
- url=url,
173
- json=data,
174
- params=params,
175
- headers=headers
231
+ method=method, url=url, json=data, params=params, headers=headers
176
232
  )
177
233
 
178
234
  if response.status_code >= 400:
179
235
  try:
180
236
  error_data = response.json()
181
- error_message = error_data.get('error', f'API request failed with status {response.status_code}')
237
+ error_message = error_data.get(
238
+ "error",
239
+ f"API request failed with status {response.status_code}",
240
+ )
182
241
  except:
183
- error_message = f'API request failed with status {response.status_code}'
242
+ error_message = (
243
+ f"API request failed with status {response.status_code}"
244
+ )
184
245
 
185
246
  raise VortexApiError(error_message, response.status_code)
186
247
 
@@ -192,7 +253,7 @@ class Vortex:
192
253
  async def get_invitations_by_target(
193
254
  self,
194
255
  target_type: Literal["email", "username", "phoneNumber"],
195
- target_value: str
256
+ target_value: str,
196
257
  ) -> List[Invitation]:
197
258
  """
198
259
  Get invitations for a specific target
@@ -204,18 +265,17 @@ class Vortex:
204
265
  Returns:
205
266
  List of invitations
206
267
  """
207
- params = {
208
- "targetType": target_type,
209
- "targetValue": target_value
210
- }
268
+ params = {"targetType": target_type, "targetValue": target_value}
211
269
 
212
- response = await self._vortex_api_request("GET", "/invitations/by-target", params=params)
270
+ response = await self._vortex_api_request(
271
+ "GET", "/invitations/by-target", params=params
272
+ )
213
273
  return [Invitation(**inv) for inv in response.get("invitations", [])]
214
274
 
215
275
  def get_invitations_by_target_sync(
216
276
  self,
217
277
  target_type: Literal["email", "username", "phoneNumber"],
218
- target_value: str
278
+ target_value: str,
219
279
  ) -> List[Invitation]:
220
280
  """
221
281
  Get invitations for a specific target (synchronous)
@@ -227,12 +287,11 @@ class Vortex:
227
287
  Returns:
228
288
  List of invitations
229
289
  """
230
- params = {
231
- "targetType": target_type,
232
- "targetValue": target_value
233
- }
290
+ params = {"targetType": target_type, "targetValue": target_value}
234
291
 
235
- response = self._vortex_api_request_sync("GET", "/invitations/by-target", params=params)
292
+ response = self._vortex_api_request_sync(
293
+ "GET", "/invitations/by-target", params=params
294
+ )
236
295
  return [Invitation(**inv) for inv in response.get("invitations", [])]
237
296
 
238
297
  async def get_invitation(self, invitation_id: str) -> Invitation:
@@ -245,7 +304,9 @@ class Vortex:
245
304
  Returns:
246
305
  Invitation object
247
306
  """
248
- response = await self._vortex_api_request("GET", f"/invitations/{invitation_id}")
307
+ response = await self._vortex_api_request(
308
+ "GET", f"/invitations/{invitation_id}"
309
+ )
249
310
  return Invitation(**response)
250
311
 
251
312
  def get_invitation_sync(self, invitation_id: str) -> Invitation:
@@ -262,9 +323,7 @@ class Vortex:
262
323
  return Invitation(**response)
263
324
 
264
325
  async def accept_invitations(
265
- self,
266
- invitation_ids: List[str],
267
- target: Union[InvitationTarget, Dict[str, str]]
326
+ self, invitation_ids: List[str], target: Union[InvitationTarget, Dict[str, str]]
268
327
  ) -> Dict:
269
328
  """
270
329
  Accept multiple invitations
@@ -279,17 +338,12 @@ class Vortex:
279
338
  if isinstance(target, dict):
280
339
  target = InvitationTarget(**target)
281
340
 
282
- data = {
283
- "invitationIds": invitation_ids,
284
- "target": target.model_dump()
285
- }
341
+ data = {"invitationIds": invitation_ids, "target": target.model_dump()}
286
342
 
287
343
  return await self._vortex_api_request("POST", "/invitations/accept", data=data)
288
344
 
289
345
  def accept_invitations_sync(
290
- self,
291
- invitation_ids: List[str],
292
- target: Union[InvitationTarget, Dict[str, str]]
346
+ self, invitation_ids: List[str], target: Union[InvitationTarget, Dict[str, str]]
293
347
  ) -> Dict:
294
348
  """
295
349
  Accept multiple invitations (synchronous)
@@ -304,10 +358,7 @@ class Vortex:
304
358
  if isinstance(target, dict):
305
359
  target = InvitationTarget(**target)
306
360
 
307
- data = {
308
- "invitationIds": invitation_ids,
309
- "target": target.model_dump()
310
- }
361
+ data = {"invitationIds": invitation_ids, "target": target.model_dump()}
311
362
 
312
363
  return self._vortex_api_request_sync("POST", "/invitations/accept", data=data)
313
364
 
@@ -336,9 +387,7 @@ class Vortex:
336
387
  return self._vortex_api_request_sync("DELETE", f"/invitations/{invitation_id}")
337
388
 
338
389
  async def get_invitations_by_group(
339
- self,
340
- group_type: str,
341
- group_id: str
390
+ self, group_type: str, group_id: str
342
391
  ) -> List[Invitation]:
343
392
  """
344
393
  Get invitations for a specific group
@@ -350,13 +399,13 @@ class Vortex:
350
399
  Returns:
351
400
  List of invitations
352
401
  """
353
- response = await self._vortex_api_request("GET", f"/invitations/by-group/{group_type}/{group_id}")
402
+ response = await self._vortex_api_request(
403
+ "GET", f"/invitations/by-group/{group_type}/{group_id}"
404
+ )
354
405
  return [Invitation(**inv) for inv in response.get("invitations", [])]
355
406
 
356
407
  def get_invitations_by_group_sync(
357
- self,
358
- group_type: str,
359
- group_id: str
408
+ self, group_type: str, group_id: str
360
409
  ) -> List[Invitation]:
361
410
  """
362
411
  Get invitations for a specific group (synchronous)
@@ -368,14 +417,12 @@ class Vortex:
368
417
  Returns:
369
418
  List of invitations
370
419
  """
371
- response = self._vortex_api_request_sync("GET", f"/invitations/by-group/{group_type}/{group_id}")
420
+ response = self._vortex_api_request_sync(
421
+ "GET", f"/invitations/by-group/{group_type}/{group_id}"
422
+ )
372
423
  return [Invitation(**inv) for inv in response.get("invitations", [])]
373
424
 
374
- async def delete_invitations_by_group(
375
- self,
376
- group_type: str,
377
- group_id: str
378
- ) -> Dict:
425
+ async def delete_invitations_by_group(self, group_type: str, group_id: str) -> Dict:
379
426
  """
380
427
  Delete all invitations for a specific group
381
428
 
@@ -386,13 +433,11 @@ class Vortex:
386
433
  Returns:
387
434
  API response
388
435
  """
389
- return await self._vortex_api_request("DELETE", f"/invitations/by-group/{group_type}/{group_id}")
436
+ return await self._vortex_api_request(
437
+ "DELETE", f"/invitations/by-group/{group_type}/{group_id}"
438
+ )
390
439
 
391
- def delete_invitations_by_group_sync(
392
- self,
393
- group_type: str,
394
- group_id: str
395
- ) -> Dict:
440
+ def delete_invitations_by_group_sync(self, group_type: str, group_id: str) -> Dict:
396
441
  """
397
442
  Delete all invitations for a specific group (synchronous)
398
443
 
@@ -403,7 +448,9 @@ class Vortex:
403
448
  Returns:
404
449
  API response
405
450
  """
406
- return self._vortex_api_request_sync("DELETE", f"/invitations/by-group/{group_type}/{group_id}")
451
+ return self._vortex_api_request_sync(
452
+ "DELETE", f"/invitations/by-group/{group_type}/{group_id}"
453
+ )
407
454
 
408
455
  async def reinvite(self, invitation_id: str) -> Invitation:
409
456
  """
@@ -415,7 +462,9 @@ class Vortex:
415
462
  Returns:
416
463
  Updated invitation object
417
464
  """
418
- response = await self._vortex_api_request("POST", f"/invitations/{invitation_id}/reinvite")
465
+ response = await self._vortex_api_request(
466
+ "POST", f"/invitations/{invitation_id}/reinvite"
467
+ )
419
468
  return Invitation(**response)
420
469
 
421
470
  def reinvite_sync(self, invitation_id: str) -> Invitation:
@@ -428,7 +477,9 @@ class Vortex:
428
477
  Returns:
429
478
  Updated invitation object
430
479
  """
431
- response = self._vortex_api_request_sync("POST", f"/invitations/{invitation_id}/reinvite")
480
+ response = self._vortex_api_request_sync(
481
+ "POST", f"/invitations/{invitation_id}/reinvite"
482
+ )
432
483
  return Invitation(**response)
433
484
 
434
485
  async def close(self):
@@ -453,4 +504,4 @@ class Vortex:
453
504
 
454
505
  def __exit__(self, exc_type, exc_val, exc_tb):
455
506
  """Context manager exit"""
456
- self.close_sync()
507
+ self.close_sync()