vortex-python-sdk 0.0.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.0.5
3
+ Version: 0.0.6
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortex-python-sdk"
7
- version = "0.0.5"
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.8"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.0.5
3
+ Version: 0.0.6
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
@@ -5,20 +5,26 @@ A Python SDK for Vortex invitation management and JWT generation.
5
5
  """
6
6
 
7
7
  from .types import (
8
+ AcceptInvitationRequest,
8
9
  AcceptInvitationsRequest,
10
+ ApiRequestBody,
9
11
  ApiResponse,
12
+ ApiResponseJson,
10
13
  AuthenticatedUser,
11
14
  CreateInvitationRequest,
12
15
  GroupInput,
13
16
  IdentifierInput,
14
17
  Invitation,
18
+ InvitationAcceptance,
19
+ InvitationGroup,
20
+ InvitationResult,
15
21
  InvitationTarget,
16
22
  JwtPayload,
17
23
  VortexApiError,
18
24
  )
19
25
  from .vortex import Vortex
20
26
 
21
- __version__ = "0.0.5"
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
- "Invitation",
38
+ "InvitationGroup",
39
+ "InvitationAcceptance",
40
+ "InvitationResult",
41
+ "Invitation", # Alias for InvitationResult
33
42
  "CreateInvitationRequest",
34
- "AcceptInvitationsRequest",
43
+ "AcceptInvitationRequest",
44
+ "AcceptInvitationsRequest", # Alias for AcceptInvitationRequest
35
45
  "ApiResponse",
46
+ "ApiResponseJson",
47
+ "ApiRequestBody",
36
48
  "VortexApiError",
37
49
  ]
@@ -68,24 +68,69 @@ class JwtPayload(BaseModel):
68
68
 
69
69
 
70
70
  class InvitationTarget(BaseModel):
71
- type: Literal["email", "username", "phoneNumber"]
71
+ type: Literal["email", "sms"]
72
72
  value: str
73
73
 
74
74
 
75
- class Invitation(BaseModel):
75
+ class InvitationAcceptance(BaseModel):
76
+ """Represents an acceptance of an invitation"""
77
+
76
78
  id: str
77
- target: Union[InvitationTarget, List[InvitationTarget]] # API returns list or single
78
- groups: Optional[List[Optional[InvitationGroup]]] = None # Full group information, can contain None
79
- status: str
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")
83
- metadata: Optional[Dict[str, Union[str, int, bool]]] = None
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)
84
125
 
85
126
  class Config:
86
127
  populate_by_name = True
87
128
 
88
129
 
130
+ # Alias for backward compatibility
131
+ Invitation = InvitationResult
132
+
133
+
89
134
  class CreateInvitationRequest(BaseModel):
90
135
  target: InvitationTarget
91
136
  group_type: Optional[str] = None
@@ -94,10 +139,19 @@ class CreateInvitationRequest(BaseModel):
94
139
  metadata: Optional[Dict[str, Union[str, int, bool]]] = None
95
140
 
96
141
 
97
- class AcceptInvitationsRequest(BaseModel):
98
- invitation_ids: List[str]
142
+ class AcceptInvitationRequest(BaseModel):
143
+ """Request to accept one or more invitations"""
144
+
145
+ invitation_ids: List[str] = Field(alias="invitationIds")
99
146
  target: InvitationTarget
100
147
 
148
+ class Config:
149
+ populate_by_name = True
150
+
151
+
152
+ # Alias for backward compatibility
153
+ AcceptInvitationsRequest = AcceptInvitationRequest
154
+
101
155
 
102
156
  class ApiResponse(BaseModel):
103
157
  data: Optional[Dict] = None
@@ -110,3 +164,10 @@ class VortexApiError(Exception):
110
164
  self.message = message
111
165
  self.status_code = status_code
112
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]
@@ -4,15 +4,11 @@ import hmac
4
4
  import json
5
5
  import time
6
6
  import uuid
7
- from typing import Dict, List, Literal, Optional, Union
8
- from urllib.parse import urlencode
7
+ from typing import Any, Dict, List, Literal, Optional, Union
9
8
 
10
9
  import httpx
11
10
 
12
11
  from .types import (
13
- AcceptInvitationsRequest,
14
- ApiResponse,
15
- CreateInvitationRequest,
16
12
  Invitation,
17
13
  InvitationTarget,
18
14
  JwtPayload,
@@ -20,9 +16,10 @@ from .types import (
20
16
  )
21
17
 
22
18
 
23
- def _get_version():
19
+ def _get_version() -> str:
24
20
  """Lazy import of version to avoid circular import"""
25
21
  from . import __version__
22
+
26
23
  return __version__
27
24
 
28
25
 
@@ -80,7 +77,7 @@ class Vortex:
80
77
  uuid_bytes = base64.urlsafe_b64decode(encoded_id_padded)
81
78
  kid = str(uuid.UUID(bytes=uuid_bytes))
82
79
  except Exception as e:
83
- raise ValueError(f"Invalid UUID in API key: {e}")
80
+ raise ValueError(f"Invalid UUID in API key: {e}") from e
84
81
 
85
82
  # Generate timestamps
86
83
  iat = int(time.time())
@@ -106,16 +103,11 @@ class Vortex:
106
103
  groups_list = None
107
104
  if payload.groups is not None:
108
105
  groups_list = [
109
- {
110
- k: v
111
- for k, v in group.model_dump(
112
- by_alias=True, exclude_none=True
113
- ).items()
114
- }
106
+ group.model_dump(by_alias=True, exclude_none=True)
115
107
  for group in payload.groups
116
108
  ]
117
109
 
118
- jwt_payload = {
110
+ jwt_payload: Dict[str, Any] = {
119
111
  "userId": payload.user_id,
120
112
  "groups": groups_list,
121
113
  "role": payload.role,
@@ -185,17 +177,17 @@ class Vortex:
185
177
  "error",
186
178
  f"API request failed with status {response.status_code}",
187
179
  )
188
- except:
180
+ except Exception:
189
181
  error_message = (
190
182
  f"API request failed with status {response.status_code}"
191
183
  )
192
184
 
193
185
  raise VortexApiError(error_message, response.status_code)
194
186
 
195
- return response.json()
187
+ return response.json() # type: ignore[no-any-return]
196
188
 
197
189
  except httpx.RequestError as e:
198
- raise VortexApiError(f"Request failed: {str(e)}")
190
+ raise VortexApiError(f"Request failed: {str(e)}") from e
199
191
 
200
192
  def _vortex_api_request_sync(
201
193
  self,
@@ -238,17 +230,17 @@ class Vortex:
238
230
  "error",
239
231
  f"API request failed with status {response.status_code}",
240
232
  )
241
- except:
233
+ except Exception:
242
234
  error_message = (
243
235
  f"API request failed with status {response.status_code}"
244
236
  )
245
237
 
246
238
  raise VortexApiError(error_message, response.status_code)
247
239
 
248
- return response.json()
240
+ return response.json() # type: ignore[no-any-return]
249
241
 
250
242
  except httpx.RequestError as e:
251
- raise VortexApiError(f"Request failed: {str(e)}")
243
+ raise VortexApiError(f"Request failed: {str(e)}") from e
252
244
 
253
245
  async def get_invitations_by_target(
254
246
  self,
@@ -335,10 +327,13 @@ class Vortex:
335
327
  Returns:
336
328
  API response
337
329
  """
330
+ target_obj: InvitationTarget
338
331
  if isinstance(target, dict):
339
- target = InvitationTarget(**target)
332
+ target_obj = InvitationTarget(**target) # type: ignore[arg-type]
333
+ else:
334
+ target_obj = target
340
335
 
341
- data = {"invitationIds": invitation_ids, "target": target.model_dump()}
336
+ data = {"invitationIds": invitation_ids, "target": target_obj.model_dump()}
342
337
 
343
338
  return await self._vortex_api_request("POST", "/invitations/accept", data=data)
344
339
 
@@ -355,10 +350,13 @@ class Vortex:
355
350
  Returns:
356
351
  API response
357
352
  """
353
+ target_obj: InvitationTarget
358
354
  if isinstance(target, dict):
359
- target = InvitationTarget(**target)
355
+ target_obj = InvitationTarget(**target) # type: ignore[arg-type]
356
+ else:
357
+ target_obj = target
360
358
 
361
- data = {"invitationIds": invitation_ids, "target": target.model_dump()}
359
+ data = {"invitationIds": invitation_ids, "target": target_obj.model_dump()}
362
360
 
363
361
  return self._vortex_api_request_sync("POST", "/invitations/accept", data=data)
364
362
 
@@ -482,26 +480,26 @@ class Vortex:
482
480
  )
483
481
  return Invitation(**response)
484
482
 
485
- async def close(self):
483
+ async def close(self) -> None:
486
484
  """Close the HTTP client"""
487
485
  await self._client.aclose()
488
486
 
489
- def close_sync(self):
487
+ def close_sync(self) -> None:
490
488
  """Close the synchronous HTTP client"""
491
489
  self._sync_client.close()
492
490
 
493
- async def __aenter__(self):
491
+ async def __aenter__(self) -> "Vortex":
494
492
  """Async context manager entry"""
495
493
  return self
496
494
 
497
- 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:
498
496
  """Async context manager exit"""
499
497
  await self.close()
500
498
 
501
- def __enter__(self):
499
+ def __enter__(self) -> "Vortex":
502
500
  """Context manager entry"""
503
501
  return self
504
502
 
505
- def __exit__(self, exc_type, exc_val, exc_tb):
503
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
506
504
  """Context manager exit"""
507
505
  self.close_sync()