Skip to content

Commit 46c7c9b

Browse files
committed
refactor: Make commits into one commit
1 parent be76c47 commit 46c7c9b

File tree

12 files changed

+790
-3
lines changed

12 files changed

+790
-3
lines changed

changes/3493.enhance.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add skeleton vFolder handler Interface of manager

python.lock

+63-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
// "prometheus-client~=0.21.1",
7070
// "psutil~=6.0",
7171
// "pycryptodome>=3.20.0",
72-
// "pydantic~=2.9.2",
72+
// "pydantic[email]~=2.9.2",
7373
// "pyhumps~=3.8.0",
7474
// "pyroscope-io~=0.8.8",
7575
// "pytest-aiohttp~=1.0.5",
@@ -1530,6 +1530,46 @@
15301530
"requires_python": "<3.13,>=3.7",
15311531
"version": "0.5.14"
15321532
},
1533+
{
1534+
"artifacts": [
1535+
{
1536+
"algorithm": "sha256",
1537+
"hash": "b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86",
1538+
"url": "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl"
1539+
},
1540+
{
1541+
"algorithm": "sha256",
1542+
"hash": "ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1",
1543+
"url": "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz"
1544+
}
1545+
],
1546+
"project_name": "dnspython",
1547+
"requires_dists": [
1548+
"aioquic>=1.0.0; extra == \"doq\"",
1549+
"black>=23.1.0; extra == \"dev\"",
1550+
"coverage>=7.0; extra == \"dev\"",
1551+
"cryptography>=43; extra == \"dnssec\"",
1552+
"flake8>=7; extra == \"dev\"",
1553+
"h2>=4.1.0; extra == \"doh\"",
1554+
"httpcore>=1.0.0; extra == \"doh\"",
1555+
"httpx>=0.26.0; extra == \"doh\"",
1556+
"hypercorn>=0.16.0; extra == \"dev\"",
1557+
"idna>=3.7; extra == \"idna\"",
1558+
"mypy>=1.8; extra == \"dev\"",
1559+
"pylint>=3; extra == \"dev\"",
1560+
"pytest-cov>=4.1.0; extra == \"dev\"",
1561+
"pytest>=7.4; extra == \"dev\"",
1562+
"quart-trio>=0.11.0; extra == \"dev\"",
1563+
"sphinx-rtd-theme>=2.0.0; extra == \"dev\"",
1564+
"sphinx>=7.2.0; extra == \"dev\"",
1565+
"trio>=0.23; extra == \"trio\"",
1566+
"twine>=4.0.0; extra == \"dev\"",
1567+
"wheel>=0.42.0; extra == \"dev\"",
1568+
"wmi>=1.5.1; extra == \"wmi\""
1569+
],
1570+
"requires_python": ">=3.9",
1571+
"version": "2.7.0"
1572+
},
15331573
{
15341574
"artifacts": [
15351575
{
@@ -1551,6 +1591,27 @@
15511591
"requires_python": ">=3.8",
15521592
"version": "1.6.6"
15531593
},
1594+
{
1595+
"artifacts": [
1596+
{
1597+
"algorithm": "sha256",
1598+
"hash": "561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631",
1599+
"url": "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl"
1600+
},
1601+
{
1602+
"algorithm": "sha256",
1603+
"hash": "cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7",
1604+
"url": "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz"
1605+
}
1606+
],
1607+
"project_name": "email-validator",
1608+
"requires_dists": [
1609+
"dnspython>=2.0.0",
1610+
"idna>=2.0.0"
1611+
],
1612+
"requires_python": ">=3.8",
1613+
"version": "2.2.0"
1614+
},
15541615
{
15551616
"artifacts": [
15561617
{
@@ -5155,7 +5216,7 @@
51555216
"prometheus-client~=0.21.1",
51565217
"psutil~=6.0",
51575218
"pycryptodome>=3.20.0",
5158-
"pydantic~=2.9.2",
5219+
"pydantic[email]~=2.9.2",
51595220
"pyhumps~=3.8.0",
51605221
"pyroscope-io~=0.8.8",
51615222
"pytest-aiohttp~=1.0.5",

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ python-json-logger~=3.2.0
5858
pyzmq~=26.2
5959
PyJWT~=2.0
6060
PyYAML~=6.0
61-
pydantic~=2.9.2
61+
pydantic[email]~=2.9.2
6262
packaging>=24.1
6363
hiredis>=3.0.0
6464
redis[hiredis]==4.5.5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python_sources(name="src")

src/ai/backend/manager/api/vfolders/__init__.py

Whitespace-only changes.
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import uuid
2+
from dataclasses import dataclass
3+
from typing import Any, Mapping, Optional
4+
5+
from ai.backend.common.types import QuotaScopeID, VFolderUsageMode
6+
from ai.backend.manager.models import (
7+
ProjectType,
8+
VFolderOperationStatus,
9+
VFolderOwnershipType,
10+
VFolderPermission,
11+
)
12+
13+
14+
@dataclass
15+
class UserIdentity:
16+
user_uuid: uuid.UUID
17+
user_role: str
18+
user_email: str
19+
domain_name: str
20+
21+
22+
@dataclass
23+
class Keypair:
24+
access_key: str
25+
resource_policy: Mapping[str, Any]
26+
27+
28+
@dataclass
29+
class UserScopeInput:
30+
requester_id: uuid.UUID
31+
is_authorized: bool
32+
is_superadmin: bool
33+
delegate_email: Optional[str] = None
34+
35+
36+
@dataclass
37+
class VFolderCreateRequirements:
38+
name: str
39+
folder_host: Optional[str]
40+
usage_mode: VFolderUsageMode
41+
permission: VFolderPermission
42+
group_id: Optional[uuid.UUID]
43+
cloneable: bool
44+
unmanaged_path: Optional[str]
45+
46+
47+
@dataclass
48+
class VFolderMetadata:
49+
id: str
50+
name: str
51+
quota_scope_id: QuotaScopeID
52+
host: str
53+
usage_mode: VFolderUsageMode
54+
created_at: str
55+
permission: VFolderPermission
56+
max_size: int # migrated to quota scopes, no longer valid
57+
creator: str
58+
ownership_type: VFolderOwnershipType
59+
user: Optional[str]
60+
group: Optional[str]
61+
cloneable: bool
62+
status: VFolderOperationStatus
63+
64+
65+
@dataclass
66+
class VFolderListItem:
67+
id: str
68+
name: str
69+
quota_scope_id: str
70+
host: str
71+
usage_mode: VFolderUsageMode
72+
created_at: str
73+
permission: VFolderPermission
74+
max_size: int
75+
creator: str
76+
ownership_type: VFolderOwnershipType
77+
user: Optional[str]
78+
group: Optional[str]
79+
cloneable: bool
80+
status: VFolderOperationStatus
81+
is_owner: bool
82+
user_email: str
83+
group_name: str
84+
type: str # legacy
85+
max_files: int
86+
cur_size: int
87+
88+
89+
@dataclass
90+
class VFolderList:
91+
entries: list[VFolderListItem]
92+
93+
94+
@dataclass
95+
class VFolderCapabilityInfo:
96+
max_vfolder_count: int
97+
max_quota_scope_size: int
98+
ownership_type: str
99+
quota_scope_id: QuotaScopeID
100+
group_uuid: Optional[uuid.UUID] = None
101+
group_type: Optional[ProjectType] = None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import uuid
2+
from typing import Optional, Protocol
3+
4+
from ai.backend.common.api_handlers import (
5+
APIResponse,
6+
BodyParam,
7+
PathParam,
8+
QueryParam,
9+
api_handler,
10+
)
11+
from ai.backend.manager.api.vfolders.dtos import (
12+
Keypair,
13+
UserIdentity,
14+
VFolderCreateRequirements,
15+
VFolderList,
16+
VFolderMetadata,
17+
)
18+
from ai.backend.manager.api.vfolders.request import (
19+
DeleteVFolderId,
20+
KeypairModel,
21+
RenameVFolderId,
22+
UserIdentityModel,
23+
VFolderCreateData,
24+
VFolderListRequestedGroupId,
25+
VFolderNewName,
26+
)
27+
from ai.backend.manager.api.vfolders.response import VFolderCreateResponse, VFolderListResponse
28+
29+
30+
class VFolderServiceProtocol(Protocol):
31+
async def create_vfolder_in_personal(
32+
self,
33+
user_identity: UserIdentity,
34+
keypair: Keypair,
35+
vfolder_create_requirements: VFolderCreateRequirements,
36+
) -> VFolderMetadata: ...
37+
38+
async def create_vfolder_in_group(
39+
self,
40+
user_identity: UserIdentity,
41+
keypair: Keypair,
42+
vfolder_create_requirements: VFolderCreateRequirements,
43+
) -> VFolderMetadata: ...
44+
45+
async def get_vfolders(
46+
self, user_identity: UserIdentity, group_id: Optional[uuid.UUID]
47+
) -> VFolderList: ...
48+
49+
async def rename_vfolder(
50+
self, user_identity: UserIdentity, keypair: Keypair, vfolder_id: uuid.UUID, new_name: str
51+
) -> None: ...
52+
53+
async def delete_vfolder(
54+
self,
55+
vfolder_id: str,
56+
user_identity: UserIdentity,
57+
keypair: Keypair,
58+
) -> None: ...
59+
60+
61+
class VFolderHandler:
62+
def __init__(self, vfolder_service: VFolderServiceProtocol):
63+
self.vfolder_service = vfolder_service
64+
65+
@api_handler
66+
async def create_vfolder(
67+
self,
68+
keypair: KeypairModel,
69+
user_identity: UserIdentityModel,
70+
body: BodyParam[VFolderCreateData],
71+
) -> APIResponse:
72+
parsed_body = body.parsed
73+
create_requirements: VFolderCreateRequirements = parsed_body.to_dto()
74+
75+
vfolder_metadata: VFolderMetadata
76+
if create_requirements.group_id:
77+
vfolder_metadata = await self.vfolder_service.create_vfolder_in_group(
78+
user_identity=user_identity.to_dto(),
79+
keypair=keypair.to_dto(),
80+
vfolder_create_requirements=create_requirements,
81+
)
82+
else:
83+
vfolder_metadata = await self.vfolder_service.create_vfolder_in_personal(
84+
user_identity=user_identity.to_dto(),
85+
keypair=keypair.to_dto(),
86+
vfolder_create_requirements=create_requirements,
87+
)
88+
89+
return APIResponse.build(
90+
status_code=200,
91+
response_model=VFolderCreateResponse.from_vfolder_metadata(vfolder_metadata),
92+
)
93+
94+
@api_handler
95+
async def list_vfolders(
96+
self, user_identity: UserIdentityModel, query: QueryParam[VFolderListRequestedGroupId]
97+
) -> APIResponse:
98+
parsed_query = query.parsed
99+
100+
vfolder_list: VFolderList = await self.vfolder_service.get_vfolders(
101+
user_identity=user_identity.to_dto(), group_id=parsed_query.group_id
102+
)
103+
104+
return APIResponse.build(
105+
status_code=200,
106+
response_model=VFolderListResponse.from_dataclass(vfolder_list=vfolder_list),
107+
)
108+
109+
@api_handler
110+
async def rename_vfolder(
111+
self,
112+
keypair: KeypairModel,
113+
user_identity: UserIdentityModel,
114+
path: PathParam[RenameVFolderId],
115+
body: BodyParam[VFolderNewName],
116+
) -> APIResponse:
117+
parsed_path = path.parsed
118+
parsed_body = body.parsed
119+
120+
vfolder_id: uuid.UUID = parsed_path.vfolder_id
121+
new_name: str = parsed_body.new_name
122+
123+
await self.vfolder_service.rename_vfolder(
124+
user_identity=user_identity.to_dto(),
125+
keypair=keypair.to_dto(),
126+
vfolder_id=vfolder_id,
127+
new_name=new_name,
128+
)
129+
130+
return APIResponse.no_content(status_code=201)
131+
132+
@api_handler
133+
async def delete_vfolder(
134+
self,
135+
keypair: KeypairModel,
136+
user_identity: UserIdentityModel,
137+
path: PathParam[DeleteVFolderId],
138+
) -> APIResponse:
139+
parsed_path = path.parsed
140+
141+
await self.vfolder_service.delete_vfolder(
142+
user_identity=user_identity.to_dto(),
143+
keypair=keypair.to_dto(),
144+
vfolder_id=str(parsed_path.vfolder_id),
145+
)
146+
147+
return APIResponse.no_content(status_code=204)

0 commit comments

Comments
 (0)