vortex-python-sdk 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: vortex-python-sdk
3
+ Version: 0.0.1
4
+ Summary: Vortex Python SDK for invitation management and JWT generation
5
+ Author-email: TeamVortexSoftware <support@vortexsoftware.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/teamvortexsoftware/vortex-python-sdk
8
+ Project-URL: Repository, https://github.com/teamvortexsoftware/vortex-python-sdk.git
9
+ Project-URL: Documentation, https://docs.vortexsoftware.com/python-sdk
10
+ Project-URL: Changelog, https://github.com/teamvortexsoftware/vortex-python-sdk/blob/main/CHANGELOG.md
11
+ Keywords: vortex,invitations,jwt,api,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: httpx>=0.27.0
27
+ Requires-Dist: pydantic>=2.8.0
28
+ Requires-Dist: typing-extensions>=4.8.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: black>=23.0.0; extra == "dev"
34
+ Requires-Dist: isort>=5.12.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # Vortex Python SDK
40
+
41
+ A Python SDK for Vortex invitation management and JWT generation.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install vortex-python-sdk
47
+ ```
48
+
49
+ > **Note**: The package will be available on PyPI once published. See [PUBLISHING.md](PUBLISHING.md) for publishing instructions.
50
+
51
+ ## Usage
52
+
53
+ ### Basic Setup
54
+
55
+ ```python
56
+ from vortex_sdk import Vortex
57
+
58
+ # Initialize the client
59
+ vortex = Vortex(api_key="your-api-key")
60
+
61
+ # Or with custom base URL
62
+ vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com")
63
+ ```
64
+
65
+ ### JWT Generation
66
+
67
+ ```python
68
+ # Generate JWT for a user
69
+ jwt = vortex.generate_jwt({
70
+ "user_id": "user123",
71
+ "identifiers": {
72
+ "email": "user@example.com",
73
+ "username": "johndoe"
74
+ },
75
+ "groups": ["admin", "users"],
76
+ "role": "admin"
77
+ })
78
+
79
+ print(f"JWT: {jwt}")
80
+ ```
81
+
82
+ ### Invitation Management
83
+
84
+ #### Get Invitations by Target
85
+
86
+ ```python
87
+ import asyncio
88
+
89
+ async def get_user_invitations():
90
+ # Async version
91
+ invitations = await vortex.get_invitations_by_target("email", "user@example.com")
92
+ for invitation in invitations:
93
+ print(f"Invitation ID: {invitation.id}, Status: {invitation.status}")
94
+
95
+ # Sync version
96
+ invitations = vortex.get_invitations_by_target_sync("email", "user@example.com")
97
+ ```
98
+
99
+ #### Accept Invitations
100
+
101
+ ```python
102
+ async def accept_user_invitations():
103
+ # Async version
104
+ result = await vortex.accept_invitations(
105
+ invitation_ids=["inv1", "inv2"],
106
+ target={"type": "email", "value": "user@example.com"}
107
+ )
108
+ print(f"Result: {result}")
109
+
110
+ # Sync version
111
+ result = vortex.accept_invitations_sync(
112
+ invitation_ids=["inv1", "inv2"],
113
+ target={"type": "email", "value": "user@example.com"}
114
+ )
115
+ ```
116
+
117
+ #### Get Specific Invitation
118
+
119
+ ```python
120
+ async def get_invitation():
121
+ # Async version
122
+ invitation = await vortex.get_invitation("invitation-id")
123
+ print(f"Invitation: {invitation.id}")
124
+
125
+ # Sync version
126
+ invitation = vortex.get_invitation_sync("invitation-id")
127
+ ```
128
+
129
+ #### Revoke Invitation
130
+
131
+ ```python
132
+ async def revoke_invitation():
133
+ # Async version
134
+ result = await vortex.revoke_invitation("invitation-id")
135
+ print(f"Revoked: {result}")
136
+
137
+ # Sync version
138
+ result = vortex.revoke_invitation_sync("invitation-id")
139
+ ```
140
+
141
+ ### Group Operations
142
+
143
+ #### Get Invitations by Group
144
+
145
+ ```python
146
+ async def get_group_invitations():
147
+ # Async version
148
+ invitations = await vortex.get_invitations_by_group("organization", "org123")
149
+ print(f"Found {len(invitations)} invitations")
150
+
151
+ # Sync version
152
+ invitations = vortex.get_invitations_by_group_sync("organization", "org123")
153
+ ```
154
+
155
+ #### Delete Invitations by Group
156
+
157
+ ```python
158
+ async def delete_group_invitations():
159
+ # Async version
160
+ result = await vortex.delete_invitations_by_group("organization", "org123")
161
+ print(f"Deleted: {result}")
162
+
163
+ # Sync version
164
+ result = vortex.delete_invitations_by_group_sync("organization", "org123")
165
+ ```
166
+
167
+ #### Reinvite
168
+
169
+ ```python
170
+ async def reinvite_user():
171
+ # Async version
172
+ invitation = await vortex.reinvite("invitation-id")
173
+ print(f"Reinvited: {invitation.id}")
174
+
175
+ # Sync version
176
+ invitation = vortex.reinvite_sync("invitation-id")
177
+ ```
178
+
179
+ ### Context Manager Usage
180
+
181
+ ```python
182
+ # Async context manager
183
+ async with Vortex(api_key="your-api-key") as vortex:
184
+ invitations = await vortex.get_invitations_by_target("email", "user@example.com")
185
+
186
+ # Sync context manager
187
+ with Vortex(api_key="your-api-key") as vortex:
188
+ invitations = vortex.get_invitations_by_target_sync("email", "user@example.com")
189
+ ```
190
+
191
+ ### Error Handling
192
+
193
+ ```python
194
+ from vortex_sdk import VortexApiError
195
+
196
+ try:
197
+ invitation = vortex.get_invitation_sync("invalid-id")
198
+ except VortexApiError as e:
199
+ print(f"API Error: {e.message} (Status: {e.status_code})")
200
+ except Exception as e:
201
+ print(f"Unexpected error: {e}")
202
+ ```
203
+
204
+ ## Development
205
+
206
+ ### Installation
207
+
208
+ ```bash
209
+ # Install development dependencies
210
+ pip install -e ".[dev]"
211
+ ```
212
+
213
+ ### Running Tests
214
+
215
+ ```bash
216
+ pytest
217
+ ```
218
+
219
+ ### Code Formatting
220
+
221
+ ```bash
222
+ # Format code
223
+ black src/ tests/
224
+ isort src/ tests/
225
+
226
+ # Lint code
227
+ ruff check src/ tests/
228
+ mypy src/
229
+ ```
230
+
231
+ ## License
232
+
233
+ MIT
@@ -0,0 +1,9 @@
1
+ vortex_python_sdk-0.0.1.dist-info/licenses/LICENSE,sha256=VndlWxbL4-w3YDf2yE5gJscj4zVXF0qlSq0LDtay3lo,1074
2
+ vortex_sdk/__init__.py,sha256=mFLcyV2i5W7xuj25cThFWYBm6S0HpeOGtUIHDenzhBc,631
3
+ vortex_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ vortex_sdk/types.py,sha256=whBfV7yVcrmk5K7ex1s4FCs92V9_bj-7KUsCRi5fmgk,1491
5
+ vortex_sdk/vortex.py,sha256=_Ov62rzGdLecbID6LVQLMZgDc96xw7jY5qNqsfUjV3o,12963
6
+ vortex_python_sdk-0.0.1.dist-info/METADATA,sha256=BH1AkI5SMi_mhRtttqLa5w1o2gk8ymwdzX3emM6U5iQ,5765
7
+ vortex_python_sdk-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ vortex_python_sdk-0.0.1.dist-info/top_level.txt,sha256=xFDlEcXIIi_sBhkse0YfMnSdg2IlaYUd0oP2UCDc_Y0,11
9
+ vortex_python_sdk-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 TeamVortexSoftware
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ vortex_sdk
vortex_sdk/__init__.py ADDED
@@ -0,0 +1,33 @@
1
+ """
2
+ Vortex Python SDK
3
+
4
+ A Python SDK for Vortex invitation management and JWT generation.
5
+ """
6
+
7
+ from .vortex import Vortex
8
+ from .types import (
9
+ AuthenticatedUser,
10
+ JwtPayload,
11
+ InvitationTarget,
12
+ Invitation,
13
+ CreateInvitationRequest,
14
+ AcceptInvitationsRequest,
15
+ ApiResponse,
16
+ VortexApiError
17
+ )
18
+
19
+ __version__ = "0.0.1"
20
+ __author__ = "TeamVortexSoftware"
21
+ __email__ = "support@vortexsoftware.com"
22
+
23
+ __all__ = [
24
+ "Vortex",
25
+ "AuthenticatedUser",
26
+ "JwtPayload",
27
+ "InvitationTarget",
28
+ "Invitation",
29
+ "CreateInvitationRequest",
30
+ "AcceptInvitationsRequest",
31
+ "ApiResponse",
32
+ "VortexApiError",
33
+ ]
vortex_sdk/py.typed ADDED
File without changes
vortex_sdk/types.py ADDED
@@ -0,0 +1,59 @@
1
+ from typing import Dict, List, Optional, Union, Literal
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class AuthenticatedUser(BaseModel):
6
+ user_id: str
7
+ identifiers: Dict[str, str]
8
+ groups: Optional[List[str]] = None
9
+ role: Optional[str] = None
10
+
11
+
12
+ class JwtPayload(BaseModel):
13
+ user_id: str
14
+ identifiers: Dict[str, str]
15
+ groups: Optional[List[str]] = None
16
+ role: Optional[str] = None
17
+
18
+
19
+ class InvitationTarget(BaseModel):
20
+ type: Literal["email", "username", "phoneNumber"]
21
+ value: str
22
+
23
+
24
+ class Invitation(BaseModel):
25
+ id: str
26
+ target: InvitationTarget
27
+ group_type: Optional[str] = None
28
+ group_id: Optional[str] = None
29
+ status: str
30
+ created_at: str
31
+ updated_at: Optional[str] = None
32
+ expires_at: Optional[str] = None
33
+ metadata: Optional[Dict[str, Union[str, int, bool]]] = None
34
+
35
+
36
+ class CreateInvitationRequest(BaseModel):
37
+ target: InvitationTarget
38
+ group_type: Optional[str] = None
39
+ group_id: Optional[str] = None
40
+ expires_at: Optional[str] = None
41
+ metadata: Optional[Dict[str, Union[str, int, bool]]] = None
42
+
43
+
44
+ class AcceptInvitationsRequest(BaseModel):
45
+ invitation_ids: List[str]
46
+ target: InvitationTarget
47
+
48
+
49
+ class ApiResponse(BaseModel):
50
+ data: Optional[Dict] = None
51
+ error: Optional[str] = None
52
+ status_code: int = 200
53
+
54
+
55
+ class VortexApiError(Exception):
56
+ def __init__(self, message: str, status_code: int = 500):
57
+ self.message = message
58
+ self.status_code = status_code
59
+ super().__init__(message)
vortex_sdk/vortex.py ADDED
@@ -0,0 +1,452 @@
1
+ import json
2
+ import hmac
3
+ import hashlib
4
+ import base64
5
+ from typing import Dict, List, Optional, Union, Literal
6
+ from urllib.parse import urlencode
7
+ import httpx
8
+ from .types import (
9
+ JwtPayload,
10
+ InvitationTarget,
11
+ Invitation,
12
+ CreateInvitationRequest,
13
+ AcceptInvitationsRequest,
14
+ ApiResponse,
15
+ VortexApiError
16
+ )
17
+
18
+
19
+ class Vortex:
20
+ def __init__(self, api_key: str, base_url: str = "https://api.vortexsoftware.com"):
21
+ """
22
+ Initialize Vortex client
23
+
24
+ Args:
25
+ api_key: Your Vortex API key
26
+ base_url: Base URL for Vortex API (default: https://api.vortexsoftware.com)
27
+ """
28
+ self.api_key = api_key
29
+ self.base_url = base_url.rstrip('/')
30
+ self._client = httpx.AsyncClient()
31
+ self._sync_client = httpx.Client()
32
+
33
+ def generate_jwt(self, payload: Union[JwtPayload, Dict]) -> str:
34
+ """
35
+ Generate a JWT token for the given payload
36
+
37
+ Args:
38
+ payload: JWT payload containing user_id, identifiers, groups, and role
39
+
40
+ Returns:
41
+ JWT token string
42
+ """
43
+ if isinstance(payload, dict):
44
+ payload = JwtPayload(**payload)
45
+
46
+ # JWT Header
47
+ header = {
48
+ "alg": "HS256",
49
+ "typ": "JWT"
50
+ }
51
+
52
+ # JWT Payload
53
+ jwt_payload = {
54
+ "userId": payload.user_id,
55
+ "identifiers": payload.identifiers,
56
+ }
57
+
58
+ if payload.groups is not None:
59
+ jwt_payload["groups"] = payload.groups
60
+ if payload.role is not None:
61
+ jwt_payload["role"] = payload.role
62
+
63
+ # Encode header and payload
64
+ header_encoded = base64.urlsafe_b64encode(
65
+ json.dumps(header, separators=(',', ':')).encode()
66
+ ).decode().rstrip('=')
67
+
68
+ payload_encoded = base64.urlsafe_b64encode(
69
+ json.dumps(jwt_payload, separators=(',', ':')).encode()
70
+ ).decode().rstrip('=')
71
+
72
+ # Create signature
73
+ message = f"{header_encoded}.{payload_encoded}"
74
+ signature = hmac.new(
75
+ self.api_key.encode(),
76
+ message.encode(),
77
+ hashlib.sha256
78
+ ).digest()
79
+
80
+ signature_encoded = base64.urlsafe_b64encode(signature).decode().rstrip('=')
81
+
82
+ return f"{message}.{signature_encoded}"
83
+
84
+ async def _vortex_api_request(
85
+ self,
86
+ method: str,
87
+ endpoint: str,
88
+ data: Optional[Dict] = None,
89
+ params: Optional[Dict] = None
90
+ ) -> Dict:
91
+ """
92
+ Make an API request to Vortex
93
+
94
+ Args:
95
+ method: HTTP method (GET, POST, DELETE, etc.)
96
+ endpoint: API endpoint path
97
+ data: Request body data
98
+ params: Query parameters
99
+
100
+ Returns:
101
+ API response data
102
+
103
+ Raises:
104
+ VortexApiError: If the API request fails
105
+ """
106
+ url = f"{self.base_url}{endpoint}"
107
+ headers = {
108
+ "Authorization": f"Bearer {self.api_key}",
109
+ "Content-Type": "application/json",
110
+ "User-Agent": "vortex-python-sdk/0.0.1"
111
+ }
112
+
113
+ try:
114
+ response = await self._client.request(
115
+ method=method,
116
+ url=url,
117
+ json=data,
118
+ params=params,
119
+ headers=headers
120
+ )
121
+
122
+ if response.status_code >= 400:
123
+ try:
124
+ error_data = response.json()
125
+ error_message = error_data.get('error', f'API request failed with status {response.status_code}')
126
+ except:
127
+ error_message = f'API request failed with status {response.status_code}'
128
+
129
+ raise VortexApiError(error_message, response.status_code)
130
+
131
+ return response.json()
132
+
133
+ except httpx.RequestError as e:
134
+ raise VortexApiError(f"Request failed: {str(e)}")
135
+
136
+ def _vortex_api_request_sync(
137
+ self,
138
+ method: str,
139
+ endpoint: str,
140
+ data: Optional[Dict] = None,
141
+ params: Optional[Dict] = None
142
+ ) -> Dict:
143
+ """
144
+ Make a synchronous API request to Vortex
145
+
146
+ Args:
147
+ method: HTTP method (GET, POST, DELETE, etc.)
148
+ endpoint: API endpoint path
149
+ data: Request body data
150
+ params: Query parameters
151
+
152
+ Returns:
153
+ API response data
154
+
155
+ Raises:
156
+ VortexApiError: If the API request fails
157
+ """
158
+ url = f"{self.base_url}{endpoint}"
159
+ headers = {
160
+ "Authorization": f"Bearer {self.api_key}",
161
+ "Content-Type": "application/json",
162
+ "User-Agent": "vortex-python-sdk/0.0.1"
163
+ }
164
+
165
+ try:
166
+ response = self._sync_client.request(
167
+ method=method,
168
+ url=url,
169
+ json=data,
170
+ params=params,
171
+ headers=headers
172
+ )
173
+
174
+ if response.status_code >= 400:
175
+ try:
176
+ error_data = response.json()
177
+ error_message = error_data.get('error', f'API request failed with status {response.status_code}')
178
+ except:
179
+ error_message = f'API request failed with status {response.status_code}'
180
+
181
+ raise VortexApiError(error_message, response.status_code)
182
+
183
+ return response.json()
184
+
185
+ except httpx.RequestError as e:
186
+ raise VortexApiError(f"Request failed: {str(e)}")
187
+
188
+ async def get_invitations_by_target(
189
+ self,
190
+ target_type: Literal["email", "username", "phoneNumber"],
191
+ target_value: str
192
+ ) -> List[Invitation]:
193
+ """
194
+ Get invitations for a specific target
195
+
196
+ Args:
197
+ target_type: Type of target (email, username, or phoneNumber)
198
+ target_value: Target value
199
+
200
+ Returns:
201
+ List of invitations
202
+ """
203
+ params = {
204
+ "targetType": target_type,
205
+ "targetValue": target_value
206
+ }
207
+
208
+ response = await self._vortex_api_request("GET", "/invitations/by-target", params=params)
209
+ return [Invitation(**inv) for inv in response.get("invitations", [])]
210
+
211
+ def get_invitations_by_target_sync(
212
+ self,
213
+ target_type: Literal["email", "username", "phoneNumber"],
214
+ target_value: str
215
+ ) -> List[Invitation]:
216
+ """
217
+ Get invitations for a specific target (synchronous)
218
+
219
+ Args:
220
+ target_type: Type of target (email, username, or phoneNumber)
221
+ target_value: Target value
222
+
223
+ Returns:
224
+ List of invitations
225
+ """
226
+ params = {
227
+ "targetType": target_type,
228
+ "targetValue": target_value
229
+ }
230
+
231
+ response = self._vortex_api_request_sync("GET", "/invitations/by-target", params=params)
232
+ return [Invitation(**inv) for inv in response.get("invitations", [])]
233
+
234
+ async def get_invitation(self, invitation_id: str) -> Invitation:
235
+ """
236
+ Get a specific invitation by ID
237
+
238
+ Args:
239
+ invitation_id: Invitation ID
240
+
241
+ Returns:
242
+ Invitation object
243
+ """
244
+ response = await self._vortex_api_request("GET", f"/invitations/{invitation_id}")
245
+ return Invitation(**response)
246
+
247
+ def get_invitation_sync(self, invitation_id: str) -> Invitation:
248
+ """
249
+ Get a specific invitation by ID (synchronous)
250
+
251
+ Args:
252
+ invitation_id: Invitation ID
253
+
254
+ Returns:
255
+ Invitation object
256
+ """
257
+ response = self._vortex_api_request_sync("GET", f"/invitations/{invitation_id}")
258
+ return Invitation(**response)
259
+
260
+ async def accept_invitations(
261
+ self,
262
+ invitation_ids: List[str],
263
+ target: Union[InvitationTarget, Dict[str, str]]
264
+ ) -> Dict:
265
+ """
266
+ Accept multiple invitations
267
+
268
+ Args:
269
+ invitation_ids: List of invitation IDs to accept
270
+ target: Target information (type and value)
271
+
272
+ Returns:
273
+ API response
274
+ """
275
+ if isinstance(target, dict):
276
+ target = InvitationTarget(**target)
277
+
278
+ data = {
279
+ "invitationIds": invitation_ids,
280
+ "target": target.model_dump()
281
+ }
282
+
283
+ return await self._vortex_api_request("POST", "/invitations/accept", data=data)
284
+
285
+ def accept_invitations_sync(
286
+ self,
287
+ invitation_ids: List[str],
288
+ target: Union[InvitationTarget, Dict[str, str]]
289
+ ) -> Dict:
290
+ """
291
+ Accept multiple invitations (synchronous)
292
+
293
+ Args:
294
+ invitation_ids: List of invitation IDs to accept
295
+ target: Target information (type and value)
296
+
297
+ Returns:
298
+ API response
299
+ """
300
+ if isinstance(target, dict):
301
+ target = InvitationTarget(**target)
302
+
303
+ data = {
304
+ "invitationIds": invitation_ids,
305
+ "target": target.model_dump()
306
+ }
307
+
308
+ return self._vortex_api_request_sync("POST", "/invitations/accept", data=data)
309
+
310
+ async def revoke_invitation(self, invitation_id: str) -> Dict:
311
+ """
312
+ Revoke an invitation
313
+
314
+ Args:
315
+ invitation_id: Invitation ID to revoke
316
+
317
+ Returns:
318
+ API response
319
+ """
320
+ return await self._vortex_api_request("DELETE", f"/invitations/{invitation_id}")
321
+
322
+ def revoke_invitation_sync(self, invitation_id: str) -> Dict:
323
+ """
324
+ Revoke an invitation (synchronous)
325
+
326
+ Args:
327
+ invitation_id: Invitation ID to revoke
328
+
329
+ Returns:
330
+ API response
331
+ """
332
+ return self._vortex_api_request_sync("DELETE", f"/invitations/{invitation_id}")
333
+
334
+ async def get_invitations_by_group(
335
+ self,
336
+ group_type: str,
337
+ group_id: str
338
+ ) -> List[Invitation]:
339
+ """
340
+ Get invitations for a specific group
341
+
342
+ Args:
343
+ group_type: Type of group
344
+ group_id: Group ID
345
+
346
+ Returns:
347
+ List of invitations
348
+ """
349
+ response = await self._vortex_api_request("GET", f"/invitations/by-group/{group_type}/{group_id}")
350
+ return [Invitation(**inv) for inv in response.get("invitations", [])]
351
+
352
+ def get_invitations_by_group_sync(
353
+ self,
354
+ group_type: str,
355
+ group_id: str
356
+ ) -> List[Invitation]:
357
+ """
358
+ Get invitations for a specific group (synchronous)
359
+
360
+ Args:
361
+ group_type: Type of group
362
+ group_id: Group ID
363
+
364
+ Returns:
365
+ List of invitations
366
+ """
367
+ response = self._vortex_api_request_sync("GET", f"/invitations/by-group/{group_type}/{group_id}")
368
+ return [Invitation(**inv) for inv in response.get("invitations", [])]
369
+
370
+ async def delete_invitations_by_group(
371
+ self,
372
+ group_type: str,
373
+ group_id: str
374
+ ) -> Dict:
375
+ """
376
+ Delete all invitations for a specific group
377
+
378
+ Args:
379
+ group_type: Type of group
380
+ group_id: Group ID
381
+
382
+ Returns:
383
+ API response
384
+ """
385
+ return await self._vortex_api_request("DELETE", f"/invitations/by-group/{group_type}/{group_id}")
386
+
387
+ def delete_invitations_by_group_sync(
388
+ self,
389
+ group_type: str,
390
+ group_id: str
391
+ ) -> Dict:
392
+ """
393
+ Delete all invitations for a specific group (synchronous)
394
+
395
+ Args:
396
+ group_type: Type of group
397
+ group_id: Group ID
398
+
399
+ Returns:
400
+ API response
401
+ """
402
+ return self._vortex_api_request_sync("DELETE", f"/invitations/by-group/{group_type}/{group_id}")
403
+
404
+ async def reinvite(self, invitation_id: str) -> Invitation:
405
+ """
406
+ Reinvite for a specific invitation
407
+
408
+ Args:
409
+ invitation_id: Invitation ID to reinvite
410
+
411
+ Returns:
412
+ Updated invitation object
413
+ """
414
+ response = await self._vortex_api_request("POST", f"/invitations/{invitation_id}/reinvite")
415
+ return Invitation(**response)
416
+
417
+ def reinvite_sync(self, invitation_id: str) -> Invitation:
418
+ """
419
+ Reinvite for a specific invitation (synchronous)
420
+
421
+ Args:
422
+ invitation_id: Invitation ID to reinvite
423
+
424
+ Returns:
425
+ Updated invitation object
426
+ """
427
+ response = self._vortex_api_request_sync("POST", f"/invitations/{invitation_id}/reinvite")
428
+ return Invitation(**response)
429
+
430
+ async def close(self):
431
+ """Close the HTTP client"""
432
+ await self._client.aclose()
433
+
434
+ def close_sync(self):
435
+ """Close the synchronous HTTP client"""
436
+ self._sync_client.close()
437
+
438
+ async def __aenter__(self):
439
+ """Async context manager entry"""
440
+ return self
441
+
442
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
443
+ """Async context manager exit"""
444
+ await self.close()
445
+
446
+ def __enter__(self):
447
+ """Context manager entry"""
448
+ return self
449
+
450
+ def __exit__(self, exc_type, exc_val, exc_tb):
451
+ """Context manager exit"""
452
+ self.close_sync()