Skip to content

Commit d11257a

Browse files
authored
Merge pull request #49 from Infisical/feature/folder-api-methods
feature: folder api methods
2 parents d391144 + be36d47 commit d11257a

File tree

4 files changed

+178
-1
lines changed

4 files changed

+178
-1
lines changed

infisical_sdk/api_types.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,102 @@ class KmsKeyEncryptDataResponse(BaseModel):
194194
class KmsKeyDecryptDataResponse(BaseModel):
195195
"""Response model for decrypt data API"""
196196
plaintext: str
197+
198+
@dataclass
199+
class CreateFolderResponseItem(BaseModel):
200+
"""Folder model with path for create response"""
201+
id: str
202+
name: str
203+
createdAt: str
204+
updatedAt: str
205+
envId: str
206+
path: str
207+
version: Optional[int] = 1
208+
parentId: Optional[str] = None
209+
isReserved: Optional[bool] = False
210+
description: Optional[str] = None
211+
lastSecretModified: Optional[str] = None
212+
213+
@dataclass
214+
class CreateFolderResponse(BaseModel):
215+
"""Response model for create folder API"""
216+
folder: CreateFolderResponseItem
217+
218+
@classmethod
219+
def from_dict(cls, data: Dict) -> 'CreateFolderResponse':
220+
return cls(
221+
folder=CreateFolderResponseItem.from_dict(data['folder']),
222+
)
223+
224+
225+
@dataclass
226+
class ListFoldersResponseItem(BaseModel):
227+
"""Response model for list folders API"""
228+
id: str
229+
name: str
230+
createdAt: str
231+
updatedAt: str
232+
envId: str
233+
version: Optional[int] = 1
234+
parentId: Optional[str] = None
235+
isReserved: Optional[bool] = False
236+
description: Optional[str] = None
237+
lastSecretModified: Optional[str] = None
238+
relativePath: Optional[str] = None
239+
240+
241+
@dataclass
242+
class ListFoldersResponse(BaseModel):
243+
"""Complete response model for folders API"""
244+
folders: List[ListFoldersResponseItem]
245+
246+
@classmethod
247+
def from_dict(cls, data: Dict) -> 'ListFoldersResponse':
248+
"""Create model from dictionary with camelCase keys, handling nested objects"""
249+
return cls(
250+
folders=[ListFoldersResponseItem.from_dict(folder) for folder in data['folders']]
251+
)
252+
253+
254+
@dataclass
255+
class Environment(BaseModel):
256+
"""Environment model"""
257+
envId: str
258+
envName: str
259+
envSlug: str
260+
261+
@dataclass
262+
class SingleFolderResponseItem(BaseModel):
263+
"""Response model for get folder API"""
264+
id: str
265+
name: str
266+
createdAt: str
267+
updatedAt: str
268+
envId: str
269+
path: str
270+
projectId: str
271+
environment: Environment
272+
version: Optional[int] = 1
273+
parentId: Optional[str] = None
274+
isReserved: Optional[bool] = False
275+
description: Optional[str] = None
276+
lastSecretModified: Optional[str] = None
277+
278+
@classmethod
279+
def from_dict(cls, data: Dict) -> 'SingleFolderResponseItem':
280+
"""Create model from dictionary with nested Environment"""
281+
folder_data = data.copy()
282+
folder_data['environment'] = Environment.from_dict(data['environment'])
283+
284+
return super().from_dict(folder_data)
285+
286+
@dataclass
287+
class SingleFolderResponse(BaseModel):
288+
"""Response model for get/create folder API"""
289+
folder: SingleFolderResponseItem
290+
291+
@classmethod
292+
def from_dict(cls, data: Dict) -> 'SingleFolderResponse':
293+
return cls(
294+
folder=SingleFolderResponseItem.from_dict(data['folder']),
295+
)

infisical_sdk/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from infisical_sdk.resources import Auth
44
from infisical_sdk.resources import V3RawSecrets
55
from infisical_sdk.resources import KMS
6+
from infisical_sdk.resources import V2Folders
67

78
from infisical_sdk.util import SecretsCache
89

@@ -24,6 +25,7 @@ def __init__(self, host: str, token: str = None, cache_ttl: int = 60):
2425
self.auth = Auth(self.api, self.set_token)
2526
self.secrets = V3RawSecrets(self.api, self.cache)
2627
self.kms = KMS(self.api)
28+
self.folders = V2Folders(self.api)
2729

2830
def set_token(self, token: str):
2931
"""
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .secrets import V3RawSecrets
22
from .kms import KMS
3-
from .auth import Auth
3+
from .auth import Auth
4+
from .folders import V2Folders

infisical_sdk/resources/folders.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from typing import Optional
2+
from datetime import datetime, timezone
3+
4+
from infisical_sdk.infisical_requests import InfisicalRequests
5+
from infisical_sdk.api_types import ListFoldersResponse, SingleFolderResponse, SingleFolderResponseItem, CreateFolderResponse, CreateFolderResponseItem
6+
7+
8+
class V2Folders:
9+
def __init__(self, requests: InfisicalRequests) -> None:
10+
self.requests = requests
11+
12+
def create_folder(
13+
self,
14+
name: str,
15+
environment_slug: str,
16+
project_id: str,
17+
path: str = "/",
18+
description: Optional[str] = None) -> CreateFolderResponseItem:
19+
20+
request_body = {
21+
"projectId": project_id,
22+
"environment": environment_slug,
23+
"name": name,
24+
"path": path,
25+
"description": description,
26+
}
27+
28+
result = self.requests.post(
29+
path="/api/v2/folders",
30+
json=request_body,
31+
model=CreateFolderResponse
32+
)
33+
34+
return result.data.folder
35+
36+
def list_folders(
37+
self,
38+
project_id: str,
39+
environment_slug: str,
40+
path: str,
41+
last_secret_modified: Optional[datetime] = None,
42+
recursive: bool = False) -> ListFoldersResponse:
43+
44+
params = {
45+
"projectId": project_id,
46+
"environment": environment_slug,
47+
"path": path,
48+
"recursive": recursive,
49+
}
50+
51+
if last_secret_modified is not None:
52+
# Convert to UTC and format as RFC 3339 with 'Z' suffix
53+
# The API expects UTC times in 'Z' format (e.g., 2023-11-07T05:31:56Z)
54+
utc_datetime = last_secret_modified.astimezone(timezone.utc) if last_secret_modified.tzinfo else last_secret_modified.replace(tzinfo=timezone.utc)
55+
params["lastSecretModified"] = utc_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')
56+
57+
result = self.requests.get(
58+
path="/api/v2/folders",
59+
params=params,
60+
model=ListFoldersResponse
61+
)
62+
63+
return result.data
64+
65+
def get_folder_by_id(
66+
self,
67+
id: str) -> SingleFolderResponseItem:
68+
69+
result = self.requests.get(
70+
path=f"/api/v2/folders/{id}",
71+
model=SingleFolderResponse
72+
)
73+
74+
return result.data.folder
75+

0 commit comments

Comments
 (0)