vortex-python-sdk 0.0.5__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/CHANGELOG.md +37 -0
- {vortex_python_sdk-0.0.5/src/vortex_python_sdk.egg-info → vortex_python_sdk-0.1.0}/PKG-INFO +21 -23
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/README.md +21 -23
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/pyproject.toml +5 -3
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0/src/vortex_python_sdk.egg-info}/PKG-INFO +21 -23
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_sdk/__init__.py +15 -3
- vortex_python_sdk-0.1.0/src/vortex_sdk/types.py +232 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_sdk/vortex.py +67 -58
- vortex_python_sdk-0.0.5/src/vortex_sdk/types.py +0 -112
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/LICENSE +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/MANIFEST.in +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/setup.cfg +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/SOURCES.txt +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/dependency_links.txt +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/requires.txt +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/top_level.txt +0 -0
- {vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_sdk/py.typed +0 -0
|
@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **JWT Payload Simplification**: Updated JWT structure to use simplified fields (backward compatible)
|
|
13
|
+
- **NEW (Preferred)**: Use `User` object with `id`, `email`, and `admin_scopes` for cleaner, more maintainable code
|
|
14
|
+
- `id` (string): User's ID in your system
|
|
15
|
+
- `email` (string): User's email address - replaces the `identifiers` array
|
|
16
|
+
- `admin_scopes` (list): List of admin scopes (e.g., `['autojoin']` for autojoin admin privileges)
|
|
17
|
+
- **DEPRECATED (Still Supported)**: `identifiers`, `groups`, and `role` fields maintained for backward compatibility
|
|
18
|
+
- Supports additional properties via Pydantic's `extra="allow"` configuration
|
|
19
|
+
- All existing integrations will continue to work without changes
|
|
20
|
+
|
|
21
|
+
### Migration Guide (Optional - Backward Compatible)
|
|
22
|
+
|
|
23
|
+
The old format still works, but we recommend migrating to the simpler structure:
|
|
24
|
+
|
|
25
|
+
**New simplified format (recommended):**
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
user = {
|
|
29
|
+
"id": "user-123",
|
|
30
|
+
"email": "user@example.com",
|
|
31
|
+
"admin_scopes": ["autojoin"] # optional: grants autojoin admin privileges
|
|
32
|
+
}
|
|
33
|
+
jwt = vortex.generate_jwt(user=user)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Old format (still supported):**
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
jwt = vortex.generate_jwt({
|
|
40
|
+
"user_id": "user-123",
|
|
41
|
+
"identifiers": [{"type": "email", "value": "user@example.com"}],
|
|
42
|
+
"groups": [{"type": "team", "id": "team-1", "name": "Engineering"}],
|
|
43
|
+
"role": "admin"
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
10
47
|
## [0.0.5] - 2025-11-06
|
|
11
48
|
|
|
12
49
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vortex-python-sdk
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
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
|
|
@@ -66,34 +66,32 @@ vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.exam
|
|
|
66
66
|
|
|
67
67
|
```python
|
|
68
68
|
# Generate JWT for a user
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"groups": [
|
|
75
|
-
{"type": "team", "id": "team-1", "name": "Engineering"}
|
|
76
|
-
],
|
|
77
|
-
"role": "admin"
|
|
78
|
-
})
|
|
69
|
+
user = {
|
|
70
|
+
"id": "user-123",
|
|
71
|
+
"email": "user@example.com",
|
|
72
|
+
"admin_scopes": ["autojoin"] # Optional - included as adminScopes array in JWT
|
|
73
|
+
}
|
|
79
74
|
|
|
75
|
+
jwt = vortex.generate_jwt(user=user)
|
|
80
76
|
print(f"JWT: {jwt}")
|
|
81
77
|
|
|
78
|
+
# With additional properties
|
|
79
|
+
jwt = vortex.generate_jwt(
|
|
80
|
+
user=user,
|
|
81
|
+
role="admin",
|
|
82
|
+
department="Engineering"
|
|
83
|
+
)
|
|
84
|
+
|
|
82
85
|
# Or using type-safe models
|
|
83
|
-
from vortex_sdk import
|
|
86
|
+
from vortex_sdk import User
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
IdentifierInput(type="email", value="user@example.com")
|
|
90
|
-
],
|
|
91
|
-
groups=[
|
|
92
|
-
GroupInput(type="team", id="team-1", name="Engineering")
|
|
93
|
-
],
|
|
94
|
-
role="admin"
|
|
95
|
-
)
|
|
88
|
+
user = User(
|
|
89
|
+
id="user-123",
|
|
90
|
+
email="user@example.com",
|
|
91
|
+
admin_scopes=["autojoin"]
|
|
96
92
|
)
|
|
93
|
+
|
|
94
|
+
jwt = vortex.generate_jwt(user=user)
|
|
97
95
|
```
|
|
98
96
|
|
|
99
97
|
### Invitation Management
|
|
@@ -28,34 +28,32 @@ vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.exam
|
|
|
28
28
|
|
|
29
29
|
```python
|
|
30
30
|
# Generate JWT for a user
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"groups": [
|
|
37
|
-
{"type": "team", "id": "team-1", "name": "Engineering"}
|
|
38
|
-
],
|
|
39
|
-
"role": "admin"
|
|
40
|
-
})
|
|
31
|
+
user = {
|
|
32
|
+
"id": "user-123",
|
|
33
|
+
"email": "user@example.com",
|
|
34
|
+
"admin_scopes": ["autojoin"] # Optional - included as adminScopes array in JWT
|
|
35
|
+
}
|
|
41
36
|
|
|
37
|
+
jwt = vortex.generate_jwt(user=user)
|
|
42
38
|
print(f"JWT: {jwt}")
|
|
43
39
|
|
|
40
|
+
# With additional properties
|
|
41
|
+
jwt = vortex.generate_jwt(
|
|
42
|
+
user=user,
|
|
43
|
+
role="admin",
|
|
44
|
+
department="Engineering"
|
|
45
|
+
)
|
|
46
|
+
|
|
44
47
|
# Or using type-safe models
|
|
45
|
-
from vortex_sdk import
|
|
48
|
+
from vortex_sdk import User
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
IdentifierInput(type="email", value="user@example.com")
|
|
52
|
-
],
|
|
53
|
-
groups=[
|
|
54
|
-
GroupInput(type="team", id="team-1", name="Engineering")
|
|
55
|
-
],
|
|
56
|
-
role="admin"
|
|
57
|
-
)
|
|
50
|
+
user = User(
|
|
51
|
+
id="user-123",
|
|
52
|
+
email="user@example.com",
|
|
53
|
+
admin_scopes=["autojoin"]
|
|
58
54
|
)
|
|
55
|
+
|
|
56
|
+
jwt = vortex.generate_jwt(user=user)
|
|
59
57
|
```
|
|
60
58
|
|
|
61
59
|
### Invitation Management
|
|
@@ -209,4 +207,4 @@ mypy src/
|
|
|
209
207
|
|
|
210
208
|
## License
|
|
211
209
|
|
|
212
|
-
MIT
|
|
210
|
+
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
|
|
7
|
+
version = "0.1.0"
|
|
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
|
|
@@ -81,8 +81,10 @@ warn_no_return = true
|
|
|
81
81
|
warn_unreachable = true
|
|
82
82
|
|
|
83
83
|
[tool.ruff]
|
|
84
|
-
target-version = "
|
|
84
|
+
target-version = "py39"
|
|
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
|
|
3
|
+
Version: 0.1.0
|
|
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
|
|
@@ -66,34 +66,32 @@ vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.exam
|
|
|
66
66
|
|
|
67
67
|
```python
|
|
68
68
|
# Generate JWT for a user
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"groups": [
|
|
75
|
-
{"type": "team", "id": "team-1", "name": "Engineering"}
|
|
76
|
-
],
|
|
77
|
-
"role": "admin"
|
|
78
|
-
})
|
|
69
|
+
user = {
|
|
70
|
+
"id": "user-123",
|
|
71
|
+
"email": "user@example.com",
|
|
72
|
+
"admin_scopes": ["autojoin"] # Optional - included as adminScopes array in JWT
|
|
73
|
+
}
|
|
79
74
|
|
|
75
|
+
jwt = vortex.generate_jwt(user=user)
|
|
80
76
|
print(f"JWT: {jwt}")
|
|
81
77
|
|
|
78
|
+
# With additional properties
|
|
79
|
+
jwt = vortex.generate_jwt(
|
|
80
|
+
user=user,
|
|
81
|
+
role="admin",
|
|
82
|
+
department="Engineering"
|
|
83
|
+
)
|
|
84
|
+
|
|
82
85
|
# Or using type-safe models
|
|
83
|
-
from vortex_sdk import
|
|
86
|
+
from vortex_sdk import User
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
IdentifierInput(type="email", value="user@example.com")
|
|
90
|
-
],
|
|
91
|
-
groups=[
|
|
92
|
-
GroupInput(type="team", id="team-1", name="Engineering")
|
|
93
|
-
],
|
|
94
|
-
role="admin"
|
|
95
|
-
)
|
|
88
|
+
user = User(
|
|
89
|
+
id="user-123",
|
|
90
|
+
email="user@example.com",
|
|
91
|
+
admin_scopes=["autojoin"]
|
|
96
92
|
)
|
|
93
|
+
|
|
94
|
+
jwt = vortex.generate_jwt(user=user)
|
|
97
95
|
```
|
|
98
96
|
|
|
99
97
|
### Invitation Management
|
|
@@ -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.
|
|
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,232 @@
|
|
|
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 User(BaseModel):
|
|
56
|
+
"""
|
|
57
|
+
User data for JWT generation
|
|
58
|
+
|
|
59
|
+
Required fields:
|
|
60
|
+
- id: User's ID in their system
|
|
61
|
+
- email: User's email address
|
|
62
|
+
|
|
63
|
+
Optional fields:
|
|
64
|
+
- admin_scopes: List of admin scopes (e.g., ['autojoin'])
|
|
65
|
+
|
|
66
|
+
Additional fields are allowed via extra parameter
|
|
67
|
+
"""
|
|
68
|
+
id: str
|
|
69
|
+
email: str
|
|
70
|
+
admin_scopes: Optional[List[str]] = None
|
|
71
|
+
|
|
72
|
+
class Config:
|
|
73
|
+
extra = "allow" # Allow additional fields
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AuthenticatedUser(BaseModel):
|
|
77
|
+
"""
|
|
78
|
+
User data for JWT generation (simplified structure)
|
|
79
|
+
|
|
80
|
+
Note: identifiers, groups, and role are maintained for backward compatibility
|
|
81
|
+
but are deprecated in favor of the User object with admin_scopes.
|
|
82
|
+
"""
|
|
83
|
+
user_id: str
|
|
84
|
+
user_email: Optional[str] = None
|
|
85
|
+
admin_scopes: Optional[List[str]] = None
|
|
86
|
+
|
|
87
|
+
# Deprecated fields (maintained for backward compatibility)
|
|
88
|
+
identifiers: Optional[List[IdentifierInput]] = None
|
|
89
|
+
groups: Optional[List[GroupInput]] = None
|
|
90
|
+
role: Optional[str] = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class JwtPayload(BaseModel):
|
|
94
|
+
"""
|
|
95
|
+
JWT payload structure (simplified)
|
|
96
|
+
|
|
97
|
+
Required fields:
|
|
98
|
+
- user_id: User's ID in their system
|
|
99
|
+
- user_email: User's email address (preferred)
|
|
100
|
+
|
|
101
|
+
Optional fields:
|
|
102
|
+
- admin_scopes: List of admin scopes (e.g., ['autojoin'] for autojoin admin privileges)
|
|
103
|
+
- attributes: Additional custom attributes
|
|
104
|
+
|
|
105
|
+
Deprecated fields (maintained for backward compatibility):
|
|
106
|
+
- identifiers: Use user_email instead
|
|
107
|
+
- groups: No longer required
|
|
108
|
+
- role: No longer required
|
|
109
|
+
|
|
110
|
+
Additional fields are allowed via [key: string]: any pattern
|
|
111
|
+
"""
|
|
112
|
+
user_id: str
|
|
113
|
+
user_email: Optional[str] = None
|
|
114
|
+
admin_scopes: Optional[List[str]] = None
|
|
115
|
+
|
|
116
|
+
# Deprecated fields (maintained for backward compatibility)
|
|
117
|
+
identifiers: Optional[List[IdentifierInput]] = None
|
|
118
|
+
groups: Optional[List[GroupInput]] = None
|
|
119
|
+
role: Optional[str] = None
|
|
120
|
+
|
|
121
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
122
|
+
|
|
123
|
+
class Config:
|
|
124
|
+
extra = "allow" # Allow additional fields [key: string]: any
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class InvitationTarget(BaseModel):
|
|
128
|
+
type: Literal["email", "sms"]
|
|
129
|
+
value: str
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class InvitationAcceptance(BaseModel):
|
|
133
|
+
"""Represents an acceptance of an invitation"""
|
|
134
|
+
|
|
135
|
+
id: str
|
|
136
|
+
account_id: str = Field(alias="accountId")
|
|
137
|
+
project_id: str = Field(alias="projectId")
|
|
138
|
+
accepted_at: str = Field(alias="acceptedAt")
|
|
139
|
+
target: InvitationTarget
|
|
140
|
+
|
|
141
|
+
class Config:
|
|
142
|
+
populate_by_name = True
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class InvitationResult(BaseModel):
|
|
146
|
+
"""
|
|
147
|
+
Complete invitation result from API responses.
|
|
148
|
+
This is the exact port of the Node.js SDK's InvitationResult type.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
id: str
|
|
152
|
+
account_id: str = Field(alias="accountId")
|
|
153
|
+
click_throughs: int = Field(alias="clickThroughs")
|
|
154
|
+
configuration_attributes: Optional[Dict[str, Any]] = Field(
|
|
155
|
+
None, alias="configurationAttributes"
|
|
156
|
+
)
|
|
157
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
158
|
+
created_at: str = Field(alias="createdAt")
|
|
159
|
+
deactivated: bool
|
|
160
|
+
delivery_count: int = Field(alias="deliveryCount")
|
|
161
|
+
delivery_types: List[Literal["email", "sms", "share"]] = Field(
|
|
162
|
+
alias="deliveryTypes"
|
|
163
|
+
)
|
|
164
|
+
foreign_creator_id: str = Field(alias="foreignCreatorId")
|
|
165
|
+
invitation_type: Literal["single_use", "multi_use"] = Field(alias="invitationType")
|
|
166
|
+
modified_at: Optional[str] = Field(None, alias="modifiedAt")
|
|
167
|
+
status: Literal[
|
|
168
|
+
"queued",
|
|
169
|
+
"sending",
|
|
170
|
+
"delivered",
|
|
171
|
+
"accepted",
|
|
172
|
+
"shared",
|
|
173
|
+
"unfurled",
|
|
174
|
+
"accepted_elsewhere",
|
|
175
|
+
]
|
|
176
|
+
target: List[InvitationTarget] = Field(default_factory=list)
|
|
177
|
+
views: int
|
|
178
|
+
widget_configuration_id: str = Field(alias="widgetConfigurationId")
|
|
179
|
+
project_id: str = Field(alias="projectId")
|
|
180
|
+
groups: List[Optional[InvitationGroup]] = Field(default_factory=list)
|
|
181
|
+
accepts: List[InvitationAcceptance] = Field(default_factory=list)
|
|
182
|
+
expired: bool
|
|
183
|
+
expires: Optional[str] = None
|
|
184
|
+
|
|
185
|
+
class Config:
|
|
186
|
+
populate_by_name = True
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# Alias for backward compatibility
|
|
190
|
+
Invitation = InvitationResult
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class CreateInvitationRequest(BaseModel):
|
|
194
|
+
target: InvitationTarget
|
|
195
|
+
group_type: Optional[str] = None
|
|
196
|
+
group_id: Optional[str] = None
|
|
197
|
+
expires_at: Optional[str] = None
|
|
198
|
+
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class AcceptInvitationRequest(BaseModel):
|
|
202
|
+
"""Request to accept one or more invitations"""
|
|
203
|
+
|
|
204
|
+
invitation_ids: List[str] = Field(alias="invitationIds")
|
|
205
|
+
target: InvitationTarget
|
|
206
|
+
|
|
207
|
+
class Config:
|
|
208
|
+
populate_by_name = True
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# Alias for backward compatibility
|
|
212
|
+
AcceptInvitationsRequest = AcceptInvitationRequest
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class ApiResponse(BaseModel):
|
|
216
|
+
data: Optional[Dict] = None
|
|
217
|
+
error: Optional[str] = None
|
|
218
|
+
status_code: int = 200
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class VortexApiError(Exception):
|
|
222
|
+
def __init__(self, message: str, status_code: int = 500):
|
|
223
|
+
self.message = message
|
|
224
|
+
self.status_code = status_code
|
|
225
|
+
super().__init__(message)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Type aliases to match Node.js SDK
|
|
229
|
+
ApiResponseJson = Union[
|
|
230
|
+
InvitationResult, Dict[str, List[InvitationResult]], Dict[str, Any]
|
|
231
|
+
]
|
|
232
|
+
ApiRequestBody = Union[AcceptInvitationRequest, None]
|
|
@@ -4,25 +4,22 @@ 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
|
+
User,
|
|
19
15
|
VortexApiError,
|
|
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
|
|
|
@@ -42,21 +39,30 @@ class Vortex:
|
|
|
42
39
|
self._client = httpx.AsyncClient()
|
|
43
40
|
self._sync_client = httpx.Client()
|
|
44
41
|
|
|
45
|
-
def generate_jwt(self,
|
|
42
|
+
def generate_jwt(self, user: Union[User, Dict], **extra: Any) -> str:
|
|
46
43
|
"""
|
|
47
|
-
Generate a JWT token for
|
|
44
|
+
Generate a JWT token for a user
|
|
48
45
|
|
|
49
46
|
Args:
|
|
50
|
-
|
|
47
|
+
user: User object or dict with 'id', 'email', and optional 'admin_scopes'
|
|
48
|
+
**extra: Additional properties to include in JWT payload
|
|
51
49
|
|
|
52
50
|
Returns:
|
|
53
51
|
JWT token string
|
|
54
52
|
|
|
55
53
|
Raises:
|
|
56
|
-
ValueError: If API key format is invalid
|
|
54
|
+
ValueError: If API key format is invalid or required fields are missing
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
user = {'id': 'user-123', 'email': 'user@example.com', 'admin_scopes': ['autojoin']}
|
|
58
|
+
jwt = vortex.generate_jwt(user=user)
|
|
59
|
+
|
|
60
|
+
# With additional properties
|
|
61
|
+
jwt = vortex.generate_jwt(user=user, role='admin', department='Engineering')
|
|
57
62
|
"""
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
# Convert dict to User if needed
|
|
64
|
+
if isinstance(user, dict):
|
|
65
|
+
user = User(**user)
|
|
60
66
|
|
|
61
67
|
# Parse API key (format: VRTX.base64url(uuid).key)
|
|
62
68
|
parts = self.api_key.split(".")
|
|
@@ -80,7 +86,7 @@ class Vortex:
|
|
|
80
86
|
uuid_bytes = base64.urlsafe_b64decode(encoded_id_padded)
|
|
81
87
|
kid = str(uuid.UUID(bytes=uuid_bytes))
|
|
82
88
|
except Exception as e:
|
|
83
|
-
raise ValueError(f"Invalid UUID in API key: {e}")
|
|
89
|
+
raise ValueError(f"Invalid UUID in API key: {e}") from e
|
|
84
90
|
|
|
85
91
|
# Generate timestamps
|
|
86
92
|
iat = int(time.time())
|
|
@@ -97,35 +103,24 @@ class Vortex:
|
|
|
97
103
|
"kid": kid,
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Serialize groups
|
|
106
|
-
groups_list = None
|
|
107
|
-
if payload.groups is not None:
|
|
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
|
-
}
|
|
115
|
-
for group in payload.groups
|
|
116
|
-
]
|
|
117
|
-
|
|
118
|
-
jwt_payload = {
|
|
119
|
-
"userId": payload.user_id,
|
|
120
|
-
"groups": groups_list,
|
|
121
|
-
"role": payload.role,
|
|
106
|
+
# Build JWT payload
|
|
107
|
+
jwt_payload: Dict[str, Any] = {
|
|
108
|
+
"userId": user.id,
|
|
109
|
+
"userEmail": user.email,
|
|
122
110
|
"expires": expires,
|
|
123
|
-
"identifiers": identifiers_list,
|
|
124
111
|
}
|
|
125
112
|
|
|
126
|
-
# Add
|
|
127
|
-
if
|
|
128
|
-
jwt_payload["
|
|
113
|
+
# Add adminScopes if present
|
|
114
|
+
if user.admin_scopes:
|
|
115
|
+
jwt_payload["adminScopes"] = user.admin_scopes
|
|
116
|
+
|
|
117
|
+
# Add any additional properties from user.model_extra
|
|
118
|
+
if hasattr(user, "model_extra") and user.model_extra:
|
|
119
|
+
jwt_payload.update(user.model_extra)
|
|
120
|
+
|
|
121
|
+
# Add any additional properties from **extra
|
|
122
|
+
if extra:
|
|
123
|
+
jwt_payload.update(extra)
|
|
129
124
|
|
|
130
125
|
# Step 3: Base64URL encode (without padding)
|
|
131
126
|
header_json = json.dumps(header, separators=(",", ":"))
|
|
@@ -185,17 +180,21 @@ class Vortex:
|
|
|
185
180
|
"error",
|
|
186
181
|
f"API request failed with status {response.status_code}",
|
|
187
182
|
)
|
|
188
|
-
except:
|
|
183
|
+
except Exception:
|
|
189
184
|
error_message = (
|
|
190
185
|
f"API request failed with status {response.status_code}"
|
|
191
186
|
)
|
|
192
187
|
|
|
193
188
|
raise VortexApiError(error_message, response.status_code)
|
|
194
189
|
|
|
195
|
-
return
|
|
190
|
+
# Handle empty responses (e.g., DELETE requests may return 204 or empty 200)
|
|
191
|
+
if response.status_code == 204 or not response.content:
|
|
192
|
+
return {} # type: ignore[return-value]
|
|
193
|
+
|
|
194
|
+
return response.json() # type: ignore[no-any-return]
|
|
196
195
|
|
|
197
196
|
except httpx.RequestError as e:
|
|
198
|
-
raise VortexApiError(f"Request failed: {str(e)}")
|
|
197
|
+
raise VortexApiError(f"Request failed: {str(e)}") from e
|
|
199
198
|
|
|
200
199
|
def _vortex_api_request_sync(
|
|
201
200
|
self,
|
|
@@ -238,17 +237,21 @@ class Vortex:
|
|
|
238
237
|
"error",
|
|
239
238
|
f"API request failed with status {response.status_code}",
|
|
240
239
|
)
|
|
241
|
-
except:
|
|
240
|
+
except Exception:
|
|
242
241
|
error_message = (
|
|
243
242
|
f"API request failed with status {response.status_code}"
|
|
244
243
|
)
|
|
245
244
|
|
|
246
245
|
raise VortexApiError(error_message, response.status_code)
|
|
247
246
|
|
|
248
|
-
return
|
|
247
|
+
# Handle empty responses (e.g., DELETE requests may return 204 or empty 200)
|
|
248
|
+
if response.status_code == 204 or not response.content:
|
|
249
|
+
return {} # type: ignore[return-value]
|
|
250
|
+
|
|
251
|
+
return response.json() # type: ignore[no-any-return]
|
|
249
252
|
|
|
250
253
|
except httpx.RequestError as e:
|
|
251
|
-
raise VortexApiError(f"Request failed: {str(e)}")
|
|
254
|
+
raise VortexApiError(f"Request failed: {str(e)}") from e
|
|
252
255
|
|
|
253
256
|
async def get_invitations_by_target(
|
|
254
257
|
self,
|
|
@@ -268,7 +271,7 @@ class Vortex:
|
|
|
268
271
|
params = {"targetType": target_type, "targetValue": target_value}
|
|
269
272
|
|
|
270
273
|
response = await self._vortex_api_request(
|
|
271
|
-
"GET", "/invitations
|
|
274
|
+
"GET", "/invitations", params=params
|
|
272
275
|
)
|
|
273
276
|
return [Invitation(**inv) for inv in response.get("invitations", [])]
|
|
274
277
|
|
|
@@ -290,7 +293,7 @@ class Vortex:
|
|
|
290
293
|
params = {"targetType": target_type, "targetValue": target_value}
|
|
291
294
|
|
|
292
295
|
response = self._vortex_api_request_sync(
|
|
293
|
-
"GET", "/invitations
|
|
296
|
+
"GET", "/invitations", params=params
|
|
294
297
|
)
|
|
295
298
|
return [Invitation(**inv) for inv in response.get("invitations", [])]
|
|
296
299
|
|
|
@@ -335,10 +338,13 @@ class Vortex:
|
|
|
335
338
|
Returns:
|
|
336
339
|
API response
|
|
337
340
|
"""
|
|
341
|
+
target_obj: InvitationTarget
|
|
338
342
|
if isinstance(target, dict):
|
|
339
|
-
|
|
343
|
+
target_obj = InvitationTarget(**target) # type: ignore[arg-type]
|
|
344
|
+
else:
|
|
345
|
+
target_obj = target
|
|
340
346
|
|
|
341
|
-
data = {"invitationIds": invitation_ids, "target":
|
|
347
|
+
data = {"invitationIds": invitation_ids, "target": target_obj.model_dump()}
|
|
342
348
|
|
|
343
349
|
return await self._vortex_api_request("POST", "/invitations/accept", data=data)
|
|
344
350
|
|
|
@@ -355,10 +361,13 @@ class Vortex:
|
|
|
355
361
|
Returns:
|
|
356
362
|
API response
|
|
357
363
|
"""
|
|
364
|
+
target_obj: InvitationTarget
|
|
358
365
|
if isinstance(target, dict):
|
|
359
|
-
|
|
366
|
+
target_obj = InvitationTarget(**target) # type: ignore[arg-type]
|
|
367
|
+
else:
|
|
368
|
+
target_obj = target
|
|
360
369
|
|
|
361
|
-
data = {"invitationIds": invitation_ids, "target":
|
|
370
|
+
data = {"invitationIds": invitation_ids, "target": target_obj.model_dump()}
|
|
362
371
|
|
|
363
372
|
return self._vortex_api_request_sync("POST", "/invitations/accept", data=data)
|
|
364
373
|
|
|
@@ -482,26 +491,26 @@ class Vortex:
|
|
|
482
491
|
)
|
|
483
492
|
return Invitation(**response)
|
|
484
493
|
|
|
485
|
-
async def close(self):
|
|
494
|
+
async def close(self) -> None:
|
|
486
495
|
"""Close the HTTP client"""
|
|
487
496
|
await self._client.aclose()
|
|
488
497
|
|
|
489
|
-
def close_sync(self):
|
|
498
|
+
def close_sync(self) -> None:
|
|
490
499
|
"""Close the synchronous HTTP client"""
|
|
491
500
|
self._sync_client.close()
|
|
492
501
|
|
|
493
|
-
async def __aenter__(self):
|
|
502
|
+
async def __aenter__(self) -> "Vortex":
|
|
494
503
|
"""Async context manager entry"""
|
|
495
504
|
return self
|
|
496
505
|
|
|
497
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
506
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
498
507
|
"""Async context manager exit"""
|
|
499
508
|
await self.close()
|
|
500
509
|
|
|
501
|
-
def __enter__(self):
|
|
510
|
+
def __enter__(self) -> "Vortex":
|
|
502
511
|
"""Context manager entry"""
|
|
503
512
|
return self
|
|
504
513
|
|
|
505
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
514
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
506
515
|
"""Context manager exit"""
|
|
507
516
|
self.close_sync()
|
|
@@ -1,112 +0,0 @@
|
|
|
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", "username", "phoneNumber"]
|
|
72
|
-
value: str
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class Invitation(BaseModel):
|
|
76
|
-
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
|
|
84
|
-
|
|
85
|
-
class Config:
|
|
86
|
-
populate_by_name = True
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class CreateInvitationRequest(BaseModel):
|
|
90
|
-
target: InvitationTarget
|
|
91
|
-
group_type: Optional[str] = None
|
|
92
|
-
group_id: Optional[str] = None
|
|
93
|
-
expires_at: Optional[str] = None
|
|
94
|
-
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class AcceptInvitationsRequest(BaseModel):
|
|
98
|
-
invitation_ids: List[str]
|
|
99
|
-
target: InvitationTarget
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class ApiResponse(BaseModel):
|
|
103
|
-
data: Optional[Dict] = None
|
|
104
|
-
error: Optional[str] = None
|
|
105
|
-
status_code: int = 200
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class VortexApiError(Exception):
|
|
109
|
-
def __init__(self, message: str, status_code: int = 500):
|
|
110
|
-
self.message = message
|
|
111
|
-
self.status_code = status_code
|
|
112
|
-
super().__init__(message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/requires.txt
RENAMED
|
File without changes
|
{vortex_python_sdk-0.0.5 → vortex_python_sdk-0.1.0}/src/vortex_python_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|