From 2ca5380f85bfdbf6c8f1d22fd5c24c15d6e2229c Mon Sep 17 00:00:00 2001 From: adilson Date: Thu, 13 Aug 2026 15:16:02 +0100 Subject: [PATCH] feat: add support for folders --- infisical_sdk/api_types.py | 53 ++++++++++++++++++++++++++ infisical_sdk/resources/folders.py | 60 +++++++++++++++++++++++++++++- sink/example.py | 53 ++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) diff --git a/infisical_sdk/api_types.py b/infisical_sdk/api_types.py index 425a512..73f1084 100644 --- a/infisical_sdk/api_types.py +++ b/infisical_sdk/api_types.py @@ -222,6 +222,59 @@ def from_dict(cls, data: Dict) -> 'CreateFolderResponse': ) +@dataclass +class UpdateFolderResponseItem(BaseModel): + """Folder model with path for update response""" + id: str + name: str + createdAt: str + updatedAt: str + envId: str + path: str + version: Optional[int] = 1 + parentId: Optional[str] = None + isReserved: Optional[bool] = False + description: Optional[str] = None + lastSecretModified: Optional[str] = None + +@dataclass +class UpdateFolderResponse(BaseModel): + """Response model for update folder API""" + folder: UpdateFolderResponseItem + + @classmethod + def from_dict(cls, data: Dict) -> 'UpdateFolderResponse': + return cls( + folder=UpdateFolderResponseItem.from_dict(data['folder']), + ) + + +@dataclass +class DeleteFolderResponseItem(BaseModel): + """Folder model for delete response, which does not return a path""" + id: str + name: str + createdAt: str + updatedAt: str + envId: str + version: Optional[int] = 1 + parentId: Optional[str] = None + isReserved: Optional[bool] = False + description: Optional[str] = None + lastSecretModified: Optional[str] = None + +@dataclass +class DeleteFolderResponse(BaseModel): + """Response model for delete folder API""" + folder: DeleteFolderResponseItem + + @classmethod + def from_dict(cls, data: Dict) -> 'DeleteFolderResponse': + return cls( + folder=DeleteFolderResponseItem.from_dict(data['folder']), + ) + + @dataclass class ListFoldersResponseItem(BaseModel): """Response model for list folders API""" diff --git a/infisical_sdk/resources/folders.py b/infisical_sdk/resources/folders.py index 519bf8b..7cabcb0 100644 --- a/infisical_sdk/resources/folders.py +++ b/infisical_sdk/resources/folders.py @@ -2,7 +2,17 @@ from datetime import datetime, timezone from infisical_sdk.infisical_requests import InfisicalRequests -from infisical_sdk.api_types import ListFoldersResponse, SingleFolderResponse, SingleFolderResponseItem, CreateFolderResponse, CreateFolderResponseItem +from infisical_sdk.api_types import ( + ListFoldersResponse, + SingleFolderResponse, + SingleFolderResponseItem, + CreateFolderResponse, + CreateFolderResponseItem, + UpdateFolderResponse, + UpdateFolderResponseItem, + DeleteFolderResponse, + DeleteFolderResponseItem, +) class V2Folders: @@ -73,3 +83,51 @@ def get_folder_by_id( return result.data.folder + def update_folder( + self, + folder_id: str, + name: str, + environment_slug: str, + project_id: str, + path: str = "/", + description: Optional[str] = None) -> UpdateFolderResponseItem: + + request_body = { + "projectId": project_id, + "environment": environment_slug, + "name": name, + "path": path, + "description": description, + } + + result = self.requests.patch( + path=f"/api/v2/folders/{folder_id}", + json=request_body, + model=UpdateFolderResponse + ) + + return result.data.folder + + def delete_folder( + self, + folder_id_or_name: str, + environment_slug: str, + project_id: str, + path: str = "/", + force_delete: bool = False) -> DeleteFolderResponseItem: + + request_body = { + "projectId": project_id, + "environment": environment_slug, + "path": path, + "forceDelete": force_delete, + } + + result = self.requests.delete( + path=f"/api/v2/folders/{folder_id_or_name}", + json=request_body, + model=DeleteFolderResponse + ) + + return result.data.folder + diff --git a/sink/example.py b/sink/example.py index ae252e4..286d8cf 100644 --- a/sink/example.py +++ b/sink/example.py @@ -112,6 +112,59 @@ def random_string(length: int = 10) -> str: print(f"Deleted secret: [key={deleted_secret.secretKey}] | [value={deleted_secret.secretValue}]") +################################################# FOLDER TESTS ################################################# + +new_folder = sdkInstance.folders.create_folder( + name=f"test-folder-{random_string()}", + project_id=SECRETS_PROJECT_ID, + environment_slug=SECRETS_ENVIRONMENT_SLUG, + path="/", + description=f"Optional description_{random_string()}" # Optional +) + +print(f"Created folder: [id={new_folder.id}] | [name={new_folder.name}]") + +new_folder_name = f"renamed-folder-{random_string()}" + +updated_folder = sdkInstance.folders.update_folder( + folder_id=new_folder.id, + name=new_folder_name, + project_id=SECRETS_PROJECT_ID, + environment_slug=SECRETS_ENVIRONMENT_SLUG, + path="/", + description=f"Updated description_{random_string()}" # Optional +) + +if updated_folder.name != new_folder_name: + raise Exception("Expected folder name {}, got {}".format(new_folder_name, updated_folder.name)) + +print(f"Updated folder: [id={updated_folder.id}] | [name={updated_folder.name}]") + +folder = sdkInstance.folders.get_folder_by_id( + id=updated_folder.id +) + +print(f"Retrieved folder: [id={folder.id}] | [name={folder.name}] | [path={folder.path}]") + +all_folders = sdkInstance.folders.list_folders( + project_id=SECRETS_PROJECT_ID, + environment_slug=SECRETS_ENVIRONMENT_SLUG, + path="/" +) + +for idx, listed_folder in enumerate(all_folders.folders): + print(f"Listed folders name {idx}: [{listed_folder.name}] | [id={listed_folder.id}]") + +deleted_folder = sdkInstance.folders.delete_folder( + folder_id_or_name=updated_folder.id, + project_id=SECRETS_PROJECT_ID, + environment_slug=SECRETS_ENVIRONMENT_SLUG, + path="/", + force_delete=True +) + +print(f"Deleted folder: [id={deleted_folder.id}] | [name={deleted_folder.name}]") + ################################################# KMS TESTS ################################################# kms_key = sdkInstance.kms.create_key(