pyspiral 0.3.1__cp310-abi3-macosx_11_0_arm64.whl → 0.4.1__cp310-abi3-macosx_11_0_arm64.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.
- {pyspiral-0.3.1.dist-info → pyspiral-0.4.1.dist-info}/METADATA +9 -13
- pyspiral-0.4.1.dist-info/RECORD +98 -0
- {pyspiral-0.3.1.dist-info → pyspiral-0.4.1.dist-info}/WHEEL +1 -1
- spiral/__init__.py +6 -9
- spiral/_lib.abi3.so +0 -0
- spiral/adbc.py +21 -14
- spiral/api/__init__.py +14 -175
- spiral/api/admin.py +12 -26
- spiral/api/client.py +160 -0
- spiral/api/filesystems.py +100 -72
- spiral/api/organizations.py +45 -58
- spiral/api/projects.py +171 -134
- spiral/api/telemetry.py +19 -0
- spiral/api/types.py +20 -0
- spiral/api/workloads.py +32 -25
- spiral/{arrow.py → arrow_.py} +12 -0
- spiral/cli/__init__.py +2 -5
- spiral/cli/admin.py +7 -12
- spiral/cli/app.py +23 -6
- spiral/cli/console.py +1 -1
- spiral/cli/fs.py +82 -17
- spiral/cli/iceberg/__init__.py +7 -0
- spiral/cli/iceberg/namespaces.py +47 -0
- spiral/cli/iceberg/tables.py +60 -0
- spiral/cli/indexes/__init__.py +19 -0
- spiral/cli/login.py +14 -5
- spiral/cli/orgs.py +90 -0
- spiral/cli/printer.py +9 -1
- spiral/cli/projects.py +136 -0
- spiral/cli/state.py +2 -0
- spiral/cli/tables/__init__.py +121 -0
- spiral/cli/telemetry.py +18 -0
- spiral/cli/types.py +8 -10
- spiral/cli/{workload.py → workloads.py} +11 -11
- spiral/{catalog.py → client.py} +23 -37
- spiral/core/client/__init__.pyi +117 -0
- spiral/core/index/__init__.pyi +15 -0
- spiral/core/{core → table}/__init__.pyi +44 -17
- spiral/core/{manifests → table/manifests}/__init__.pyi +5 -23
- spiral/core/table/metastore/__init__.pyi +62 -0
- spiral/core/{spec → table/spec}/__init__.pyi +41 -66
- spiral/datetime_.py +27 -0
- spiral/expressions/__init__.py +26 -18
- spiral/expressions/base.py +5 -5
- spiral/expressions/list_.py +1 -1
- spiral/expressions/mp4.py +2 -9
- spiral/expressions/png.py +1 -1
- spiral/expressions/qoi.py +1 -1
- spiral/expressions/refs.py +3 -9
- spiral/expressions/struct.py +7 -5
- spiral/expressions/text.py +62 -0
- spiral/expressions/udf.py +3 -3
- spiral/iceberg/__init__.py +3 -0
- spiral/iceberg/client.py +33 -0
- spiral/indexes/__init__.py +5 -0
- spiral/indexes/client.py +137 -0
- spiral/indexes/index.py +34 -0
- spiral/indexes/scan.py +22 -0
- spiral/project.py +19 -110
- spiral/{proto → protogen}/_/scandal/__init__.py +23 -135
- spiral/protogen/_/spiral/table/__init__.py +22 -0
- spiral/protogen/substrait/__init__.py +3399 -0
- spiral/protogen/substrait/extensions/__init__.py +115 -0
- spiral/server.py +17 -0
- spiral/settings.py +29 -91
- spiral/substrait_.py +9 -5
- spiral/tables/__init__.py +12 -0
- spiral/tables/client.py +130 -0
- spiral/{dataset.py → tables/dataset.py} +9 -199
- spiral/tables/debug/manifests.py +70 -0
- spiral/tables/debug/metrics.py +56 -0
- spiral/{debug.py → tables/debug/scan.py} +6 -9
- spiral/{maintenance.py → tables/maintenance.py} +1 -1
- spiral/{scan_.py → tables/scan.py} +63 -89
- spiral/tables/snapshot.py +78 -0
- spiral/{table.py → tables/table.py} +59 -73
- spiral/{txn.py → tables/transaction.py} +7 -3
- pyspiral-0.3.1.dist-info/RECORD +0 -85
- spiral/api/tables.py +0 -91
- spiral/api/tokens.py +0 -56
- spiral/authn/authn.py +0 -89
- spiral/authn/device.py +0 -206
- spiral/authn/github_.py +0 -33
- spiral/authn/modal_.py +0 -18
- spiral/cli/org.py +0 -90
- spiral/cli/project.py +0 -109
- spiral/cli/table.py +0 -20
- spiral/cli/token.py +0 -27
- spiral/core/metastore/__init__.pyi +0 -91
- spiral/proto/_/spfs/__init__.py +0 -36
- spiral/proto/_/spiral/table/__init__.py +0 -276
- spiral/proto/_/spiraldb/metastore/__init__.py +0 -499
- spiral/proto/__init__.py +0 -0
- spiral/proto/scandal/__init__.py +0 -45
- spiral/proto/spiral/__init__.py +0 -0
- spiral/proto/spiral/table/__init__.py +0 -96
- {pyspiral-0.3.1.dist-info → pyspiral-0.4.1.dist-info}/entry_points.txt +0 -0
- /spiral/{authn/__init__.py → core/__init__.pyi} +0 -0
- /spiral/{core → protogen/_}/__init__.py +0 -0
- /spiral/{proto/_ → protogen/_/arrow}/__init__.py +0 -0
- /spiral/{proto/_/arrow → protogen/_/arrow/flight}/__init__.py +0 -0
- /spiral/{proto/_/arrow/flight → protogen/_/arrow/flight/protocol}/__init__.py +0 -0
- /spiral/{proto → protogen}/_/arrow/flight/protocol/sql/__init__.py +0 -0
- /spiral/{proto/_/arrow/flight/protocol → protogen/_/spiral}/__init__.py +0 -0
- /spiral/{proto → protogen/_}/substrait/__init__.py +0 -0
- /spiral/{proto → protogen/_}/substrait/extensions/__init__.py +0 -0
- /spiral/{proto/_/spiral → protogen}/__init__.py +0 -0
- /spiral/{proto → protogen}/util.py +0 -0
- /spiral/{proto/_/spiraldb → tables/debug}/__init__.py +0 -0
spiral/api/client.py
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
import logging
|
2
|
+
from collections.abc import Iterable, Iterator, Mapping
|
3
|
+
from typing import Any, Generic, TypeVar
|
4
|
+
|
5
|
+
import httpx
|
6
|
+
from httpx import HTTPStatusError
|
7
|
+
from pydantic import BaseModel, Field, TypeAdapter
|
8
|
+
|
9
|
+
from spiral.core.client import Authn
|
10
|
+
|
11
|
+
log = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
E = TypeVar("E")
|
15
|
+
|
16
|
+
|
17
|
+
class PagedRequest(BaseModel):
|
18
|
+
page_token: str | None = None
|
19
|
+
page_size: int = 50
|
20
|
+
|
21
|
+
|
22
|
+
class PagedResponse(BaseModel, Generic[E]):
|
23
|
+
items: list[E] = Field(default_factory=list)
|
24
|
+
next_page_token: str | None = None
|
25
|
+
|
26
|
+
|
27
|
+
PagedReqT = TypeVar("PagedReqT", bound=PagedRequest)
|
28
|
+
|
29
|
+
|
30
|
+
class Paged(Iterable[E], Generic[E]):
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
client: "_Client",
|
34
|
+
path: str,
|
35
|
+
page_token: str | None,
|
36
|
+
page_size: int,
|
37
|
+
response_cls: type[PagedResponse[E]],
|
38
|
+
params: Mapping[str, str] | None = None,
|
39
|
+
):
|
40
|
+
self._client = client
|
41
|
+
self._path = path
|
42
|
+
self._response_cls = response_cls
|
43
|
+
|
44
|
+
self._page_size = page_size
|
45
|
+
|
46
|
+
self._params = params or {}
|
47
|
+
if page_token is not None:
|
48
|
+
self._params["page_token"] = str(page_token)
|
49
|
+
# TODO(marko): Support paging.
|
50
|
+
# if page_size is not None:
|
51
|
+
# self._params["page_size"] = str(page_size)
|
52
|
+
|
53
|
+
self._response: PagedResponse[E] = client.get(path, response_cls, params=self._params)
|
54
|
+
|
55
|
+
@property
|
56
|
+
def page(self) -> PagedResponse[E]:
|
57
|
+
return self._response
|
58
|
+
|
59
|
+
def __iter__(self) -> Iterator[E]:
|
60
|
+
while True:
|
61
|
+
yield from self._response.items
|
62
|
+
|
63
|
+
if self._response.next_page_token is None:
|
64
|
+
break
|
65
|
+
|
66
|
+
params = self._params.copy()
|
67
|
+
params["page_token"] = self._response.next_page_token
|
68
|
+
self._response = self._client.get(self._path, self._response_cls, params=params)
|
69
|
+
|
70
|
+
|
71
|
+
class ServiceBase:
|
72
|
+
def __init__(self, client: "_Client"):
|
73
|
+
self.client = client
|
74
|
+
|
75
|
+
|
76
|
+
class SpiralHTTPError(Exception):
|
77
|
+
def __init__(self, body: str, code: int):
|
78
|
+
super().__init__(body)
|
79
|
+
self.body = body
|
80
|
+
self.code = code
|
81
|
+
|
82
|
+
|
83
|
+
class _Client:
|
84
|
+
RequestT = TypeVar("RequestT")
|
85
|
+
ResponseT = TypeVar("ResponseT")
|
86
|
+
|
87
|
+
def __init__(self, http: httpx.Client, authn: Authn):
|
88
|
+
self.http = http
|
89
|
+
self.authn = authn
|
90
|
+
|
91
|
+
def get(
|
92
|
+
self, path: str, response_cls: type[ResponseT], *, params: Mapping[str, str | list[str]] | None = None
|
93
|
+
) -> ResponseT:
|
94
|
+
return self.request("GET", path, None, response_cls, params=params)
|
95
|
+
|
96
|
+
def post(
|
97
|
+
self,
|
98
|
+
path: str,
|
99
|
+
req: RequestT,
|
100
|
+
response_cls: type[ResponseT],
|
101
|
+
*,
|
102
|
+
params: Mapping[str, str | list[str]] | None = None,
|
103
|
+
) -> ResponseT:
|
104
|
+
return self.request("POST", path, req, response_cls, params=params)
|
105
|
+
|
106
|
+
def put(
|
107
|
+
self,
|
108
|
+
path: str,
|
109
|
+
req: RequestT,
|
110
|
+
response_cls: type[ResponseT],
|
111
|
+
*,
|
112
|
+
params: Mapping[str, str | list[str]] | None = None,
|
113
|
+
) -> ResponseT:
|
114
|
+
return self.request("PUT", path, req, response_cls, params=params)
|
115
|
+
|
116
|
+
def delete(
|
117
|
+
self, path: str, response_cls: type[ResponseT], *, params: Mapping[str, str | list[str]] | None = None
|
118
|
+
) -> ResponseT:
|
119
|
+
return self.request("DELETE", path, None, response_cls, params=params)
|
120
|
+
|
121
|
+
def request(
|
122
|
+
self,
|
123
|
+
method: str,
|
124
|
+
path: str,
|
125
|
+
req: RequestT | None,
|
126
|
+
response_cls: type[ResponseT],
|
127
|
+
*,
|
128
|
+
params: Mapping[str, str | list[str]] | None = None,
|
129
|
+
) -> ResponseT:
|
130
|
+
req_data: dict[str, Any] = {}
|
131
|
+
if req is not None:
|
132
|
+
req_data = dict(json=TypeAdapter(req.__class__).dump_python(req, mode="json"))
|
133
|
+
|
134
|
+
token = self.authn.token()
|
135
|
+
resp = self.http.request(
|
136
|
+
method,
|
137
|
+
path,
|
138
|
+
params=params or {},
|
139
|
+
headers={"authorization": f"Bearer {token.expose_secret()}"} if token else None,
|
140
|
+
**req_data,
|
141
|
+
)
|
142
|
+
|
143
|
+
try:
|
144
|
+
resp.raise_for_status()
|
145
|
+
except HTTPStatusError as e:
|
146
|
+
# Enrich the exception with the response body
|
147
|
+
raise SpiralHTTPError(body=resp.text, code=resp.status_code) from e
|
148
|
+
|
149
|
+
return TypeAdapter(response_cls).validate_python(resp.json())
|
150
|
+
|
151
|
+
def paged(
|
152
|
+
self,
|
153
|
+
path: str,
|
154
|
+
response_cls: type[PagedResponse[E]],
|
155
|
+
*,
|
156
|
+
page_token: str | None = None,
|
157
|
+
page_size: int = 50,
|
158
|
+
params: Mapping[str, str] | None = None,
|
159
|
+
) -> Paged[E]:
|
160
|
+
return Paged(self, path, page_token, page_size, response_cls, params)
|
spiral/api/filesystems.py
CHANGED
@@ -1,125 +1,153 @@
|
|
1
|
-
from datetime import timedelta
|
2
1
|
from enum import Enum
|
3
2
|
from typing import Annotated, Literal
|
4
3
|
|
5
4
|
from pydantic import AfterValidator, BaseModel, Field
|
6
5
|
|
7
|
-
from . import
|
6
|
+
from .client import Paged, PagedResponse, ServiceBase
|
7
|
+
from .types import ProjectId
|
8
|
+
|
9
|
+
|
10
|
+
def _validate_directory_path(path: str) -> str:
|
11
|
+
if not path.startswith("/"):
|
12
|
+
raise ValueError("Directory path must start with a slash.")
|
13
|
+
if not path.endswith("/"):
|
14
|
+
raise ValueError("Directory path must not end with a slash.")
|
15
|
+
return path
|
16
|
+
|
17
|
+
|
18
|
+
DirectoryPath = Annotated[str, AfterValidator(_validate_directory_path)]
|
19
|
+
FilePath = str # Path or directory
|
8
20
|
|
9
21
|
|
10
22
|
class BuiltinFileSystem(BaseModel):
|
23
|
+
"""Spiral supports several builtin file systems in different cloud provider regions."""
|
24
|
+
|
11
25
|
type: Literal["builtin"] = "builtin"
|
12
26
|
provider: str
|
13
27
|
|
14
28
|
|
15
|
-
|
29
|
+
class UpstreamFileSystem(BaseModel):
|
30
|
+
"""File system that points to another project, usually a "file system" project.
|
16
31
|
|
32
|
+
Upstream project must have an external file system configured,
|
33
|
+
and not a builtin file system or another upstream file system.
|
34
|
+
"""
|
17
35
|
|
18
|
-
|
19
|
-
|
20
|
-
raise ValueError("FilePath must not contain multiple slashes")
|
21
|
-
if not path.startswith("/"):
|
22
|
-
raise ValueError("FilePath must start with /")
|
23
|
-
if path.endswith("/"):
|
24
|
-
raise ValueError("FilePath must not end with /")
|
25
|
-
return path
|
36
|
+
type: Literal["upstream"] = "upstream"
|
37
|
+
project_id: ProjectId
|
26
38
|
|
27
39
|
|
28
|
-
|
29
|
-
|
30
|
-
AfterValidator(_validate_file_path),
|
31
|
-
Field(description="A path to an individual file in the file system. Must not end with a slash."),
|
32
|
-
]
|
40
|
+
class S3FileSystem(BaseModel):
|
41
|
+
"""File system backed by an S3-compatible bucket."""
|
33
42
|
|
43
|
+
type: Literal["s3"] = "s3"
|
44
|
+
endpoint: str = "https://s3.amazonaws.com"
|
45
|
+
region: str = "auto"
|
46
|
+
bucket: str
|
47
|
+
directory: DirectoryPath | None
|
34
48
|
|
35
|
-
def _validate_prefix(path: str) -> str:
|
36
|
-
if "//" in path:
|
37
|
-
raise ValueError("Prefix must not contain multiple slashes")
|
38
|
-
if not path.startswith("/"):
|
39
|
-
raise ValueError("Prefix must start with /")
|
40
|
-
if not path.endswith("/"):
|
41
|
-
raise ValueError("Prefix must end with /")
|
42
|
-
return path
|
43
49
|
|
50
|
+
class GCSFileSystem(BaseModel):
|
51
|
+
"""File system backed by a Google Cloud Storage bucket."""
|
52
|
+
|
53
|
+
type: Literal["gcs"] = "gcs"
|
54
|
+
region: str
|
55
|
+
bucket: str
|
56
|
+
directory: DirectoryPath | None
|
44
57
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Field(description="A prefix in a file system. Must end with a slash."),
|
58
|
+
|
59
|
+
FileSystem = Annotated[
|
60
|
+
BuiltinFileSystem | UpstreamFileSystem | S3FileSystem | GCSFileSystem, Field(discriminator="type")
|
49
61
|
]
|
50
62
|
|
51
63
|
|
52
|
-
class Mode(Enum):
|
64
|
+
class Mode(str, Enum):
|
53
65
|
READ_ONLY = "ro"
|
54
66
|
READ_WRITE = "rw"
|
55
67
|
|
56
68
|
|
57
69
|
class Mount(BaseModel):
|
70
|
+
"""Mount grants permission to a Spiral resource to use a specific directory within the file system."""
|
71
|
+
|
58
72
|
id: str
|
59
73
|
project_id: ProjectId
|
60
|
-
|
74
|
+
directory: DirectoryPath
|
61
75
|
mode: Mode
|
62
76
|
principal: str
|
63
77
|
|
64
78
|
|
65
|
-
class
|
66
|
-
|
67
|
-
|
79
|
+
class AWSSecretAccessKey(BaseModel):
|
80
|
+
"""AWS secret access key credentials to be used with an S3 file system.
|
81
|
+
The access key must have read/write access to the bucket specified in the file system.
|
82
|
+
"""
|
83
|
+
|
84
|
+
access_key_id: str
|
85
|
+
secret_access_key: str
|
86
|
+
|
87
|
+
|
88
|
+
class UpdateS3FileSystem(S3FileSystem):
|
89
|
+
credentials: AWSSecretAccessKey
|
68
90
|
|
69
|
-
class Response(BaseModel):
|
70
|
-
file_system: FileSystem
|
71
91
|
|
92
|
+
class GCPServiceAccount(BaseModel):
|
93
|
+
"""Google Cloud Platform service account credentials to be used with a GCS file system.
|
94
|
+
The service account must have read/write access to the bucket specified in the file system.
|
95
|
+
"""
|
72
96
|
|
73
|
-
|
74
|
-
class Request(BaseModel):
|
75
|
-
project_id: ProjectId
|
76
|
-
file_system: FileSystem
|
97
|
+
service_account: str
|
77
98
|
|
78
|
-
class Response(BaseModel):
|
79
|
-
file_system: FileSystem
|
80
99
|
|
100
|
+
class UpdateGCSFileSystem(GCSFileSystem):
|
101
|
+
credentials: GCPServiceAccount
|
81
102
|
|
82
|
-
|
83
|
-
|
84
|
-
|
103
|
+
|
104
|
+
UpdateFileSystemRequest = Annotated[
|
105
|
+
BuiltinFileSystem | UpstreamFileSystem | UpdateS3FileSystem | UpdateGCSFileSystem, Field(discriminator="type")
|
106
|
+
]
|
85
107
|
|
86
108
|
|
87
|
-
class
|
88
|
-
|
89
|
-
project_id: ProjectId
|
90
|
-
prefix: Prefix
|
91
|
-
mode: Mode
|
92
|
-
principal: str
|
109
|
+
class UpdateFileSystemResponse(BaseModel):
|
110
|
+
file_system: FileSystem
|
93
111
|
|
94
|
-
class Response(BaseModel):
|
95
|
-
mount: Mount
|
96
112
|
|
113
|
+
class CreateMountRequest(BaseModel):
|
114
|
+
directory: DirectoryPath
|
115
|
+
mode: Mode
|
116
|
+
principal: str
|
97
117
|
|
98
|
-
class CreateMountToken:
|
99
|
-
class Request(BaseModel):
|
100
|
-
mount_id: str
|
101
|
-
mode: Mode
|
102
|
-
path: FilePath | Prefix
|
103
|
-
ttl: int = Field(default_factory=lambda: int(timedelta(hours=1).total_seconds()))
|
104
118
|
|
105
|
-
|
106
|
-
|
119
|
+
class CreateMountResponse(BaseModel):
|
120
|
+
mount: Mount
|
107
121
|
|
108
122
|
|
109
123
|
class FileSystemService(ServiceBase):
|
110
|
-
|
111
|
-
|
112
|
-
|
124
|
+
"""Service for file system operations."""
|
125
|
+
|
126
|
+
def list_providers(self) -> list[str]:
|
127
|
+
"""List builtin providers."""
|
128
|
+
response = self.client.get("/v1/file-systems/builtin-providers", dict)
|
129
|
+
return response.get("providers", [])
|
130
|
+
|
131
|
+
def update_file_system(self, project_id: ProjectId, request: UpdateFileSystemRequest) -> UpdateFileSystemResponse:
|
132
|
+
"""Update project's default file system."""
|
133
|
+
return self.client.post(f"/v1/file-systems/{project_id}", request, UpdateFileSystemResponse)
|
134
|
+
|
135
|
+
def get_file_system(self, project_id: ProjectId) -> FileSystem:
|
136
|
+
"""Get project's default file system."""
|
137
|
+
return self.client.get(f"/v1/file-systems/{project_id}", FileSystem)
|
113
138
|
|
114
|
-
def
|
115
|
-
"""
|
116
|
-
return self.client.
|
139
|
+
def create_mount(self, project_id: ProjectId, request: CreateMountRequest) -> CreateMountResponse:
|
140
|
+
"""Create a mount."""
|
141
|
+
return self.client.post(f"/v1/file-systems/{project_id}/mounts", request, CreateMountResponse)
|
117
142
|
|
118
|
-
def
|
119
|
-
|
143
|
+
def list_mounts(self, project_id: ProjectId) -> Paged[Mount]:
|
144
|
+
"""List active mounts in project's file system."""
|
145
|
+
return self.client.paged(f"/v1/file-systems/{project_id}/mounts", PagedResponse[Mount])
|
120
146
|
|
121
|
-
def
|
122
|
-
|
147
|
+
def get_mount(self, mount_id: str) -> Mount:
|
148
|
+
"""Get a mount."""
|
149
|
+
return self.client.get(f"/v1/mounts/{mount_id}", Mount)
|
123
150
|
|
124
|
-
def
|
125
|
-
|
151
|
+
def remove_mount(self, mount_id: str) -> None:
|
152
|
+
"""Remove mount."""
|
153
|
+
return self.client.delete(f"/v1/mounts/{mount_id}", None)
|
spiral/api/organizations.py
CHANGED
@@ -1,90 +1,77 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
|
3
|
-
from pydantic import BaseModel
|
3
|
+
from pydantic import BaseModel
|
4
4
|
|
5
|
-
from . import
|
5
|
+
from .client import Paged, PagedResponse, ServiceBase
|
6
|
+
from .types import OrgId
|
6
7
|
|
7
8
|
|
8
|
-
class
|
9
|
+
class OrgRole(str, Enum):
|
9
10
|
OWNER = "owner"
|
10
11
|
MEMBER = "member"
|
11
12
|
GUEST = "guest"
|
12
13
|
|
13
14
|
|
14
|
-
class
|
15
|
-
id:
|
16
|
-
name: str | None =
|
17
|
-
default=None,
|
18
|
-
description="Optional human-readable name for the organization",
|
19
|
-
)
|
15
|
+
class Org(BaseModel):
|
16
|
+
id: OrgId
|
17
|
+
name: str | None = None
|
20
18
|
|
21
19
|
|
22
|
-
class
|
23
|
-
|
24
|
-
|
20
|
+
class OrgMembership(BaseModel):
|
21
|
+
user_id: str
|
22
|
+
org: Org
|
23
|
+
role: str
|
25
24
|
|
26
25
|
|
27
|
-
class
|
28
|
-
|
29
|
-
name: str | None = Field(
|
30
|
-
default=None,
|
31
|
-
description="Optional human-readable name for the organization",
|
32
|
-
)
|
26
|
+
class CreateOrgRequest(BaseModel):
|
27
|
+
name: str | None = None
|
33
28
|
|
34
|
-
class Response(BaseModel):
|
35
|
-
organization: Organization
|
36
29
|
|
30
|
+
class CreateOrgResponse(BaseModel):
|
31
|
+
org: Org
|
37
32
|
|
38
|
-
class ListUserMemberships:
|
39
|
-
class Request(PagedRequest):
|
40
|
-
"""List the organization memberships of the current user."""
|
41
33
|
|
42
|
-
|
43
|
-
|
34
|
+
class PortalLinkIntent(str, Enum):
|
35
|
+
SSO = "sso"
|
36
|
+
DIRECTORY_SYNC = "directory-sync"
|
37
|
+
AUDIT_LOGS = "audit-logs"
|
38
|
+
LOG_STREAMS = "log-streams"
|
39
|
+
DOMAIN_VERIFICATION = "domain-verification"
|
44
40
|
|
45
41
|
|
46
|
-
class
|
47
|
-
|
48
|
-
SSO = "sso"
|
49
|
-
DIRECTORY = "directory"
|
50
|
-
AUDIT_LOGS = "audit-logs"
|
51
|
-
LOG_STREAMS = "log-streams"
|
52
|
-
DOMAIN_VERIFICATION = "domain-verification"
|
42
|
+
class PortalLinkRequest(BaseModel):
|
43
|
+
intent: PortalLinkIntent
|
53
44
|
|
54
|
-
class Request(BaseModel):
|
55
|
-
intent: "PortalLink.Intent"
|
56
45
|
|
57
|
-
|
58
|
-
|
46
|
+
class PortalLinkResponse(BaseModel):
|
47
|
+
url: str
|
59
48
|
|
60
49
|
|
61
|
-
class
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
expires_in_days: int = 7
|
50
|
+
class InviteUserRequest(BaseModel):
|
51
|
+
email: str
|
52
|
+
role: OrgRole
|
53
|
+
expires_in_days: int | None = 7
|
66
54
|
|
67
|
-
|
68
|
-
|
55
|
+
|
56
|
+
class InviteUserResponse(BaseModel):
|
57
|
+
invite_id: str
|
69
58
|
|
70
59
|
|
71
60
|
class OrganizationService(ServiceBase):
|
72
|
-
|
73
|
-
"""Create a new organization."""
|
74
|
-
return self.client.post("/organization/create", request, CreateOrganization.Response)
|
61
|
+
"""Service for organization operations."""
|
75
62
|
|
76
|
-
def
|
77
|
-
"""
|
78
|
-
return self.client.
|
79
|
-
"/organization/list-user-memberships",
|
80
|
-
ListUserMemberships.Request(),
|
81
|
-
ListUserMemberships.Response,
|
82
|
-
)
|
63
|
+
def create(self, request: CreateOrgRequest) -> CreateOrgResponse:
|
64
|
+
"""Create a new organization."""
|
65
|
+
return self.client.post("/v1/organizations", request, CreateOrgResponse)
|
83
66
|
|
84
|
-
def
|
85
|
-
"""
|
86
|
-
return self.client.
|
67
|
+
def list_memberships(self) -> Paged[OrgMembership]:
|
68
|
+
"""List organization memberships."""
|
69
|
+
return self.client.paged("/v1/organizations", PagedResponse[OrgMembership])
|
87
70
|
|
88
|
-
def invite_user(self, request:
|
71
|
+
def invite_user(self, request: InviteUserRequest) -> InviteUserResponse:
|
89
72
|
"""Invite a user to the organization."""
|
90
|
-
return self.client.post("/
|
73
|
+
return self.client.post("/v1/organizations/invite-user", request, InviteUserResponse)
|
74
|
+
|
75
|
+
def portal_link(self, request: PortalLinkRequest) -> PortalLinkResponse:
|
76
|
+
"""Get configuration portal link for the organization."""
|
77
|
+
return self.client.put("/v1/organizations/portal-link", request, PortalLinkResponse)
|