infisicalsdk 1.0.3__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.
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.2
2
+ Name: infisicalsdk
3
+ Version: 1.0.3
4
+ Summary: Infisical API Client
5
+ Home-page: https://github.com/Infisical/python-sdk-official
6
+ Author: Infisical
7
+ Author-email: support@infisical.com
8
+ Keywords: Infisical,Infisical API,Infisical SDK
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: urllib3<2.1.0,>=1.25.3
11
+ Requires-Dist: python-dateutil
12
+ Requires-Dist: aenum
13
+ Requires-Dist: requests~=2.32
14
+ Requires-Dist: boto3~=1.35
15
+ Requires-Dist: botocore~=1.35
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: keywords
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
24
+
25
+ Infisical SDK client for Python. To view documentation, please visit https://github.com/Infisical/python-sdk-official
26
+
@@ -0,0 +1,204 @@
1
+ # Infisical Python SDK
2
+
3
+ The Infisical SDK provides a convenient way to interact with the Infisical API.
4
+
5
+ ### Migrating to version 1.0.2 or above
6
+
7
+ We have recently rolled out our first stable version of the SDK, version `1.0.2` and above.
8
+
9
+ The 1.0.2 version comes with a few key changes that may change how you're using the SDK.
10
+ 1. **Removal of `rest`**: The SDK no longer exposes the entire Infisical API. This was nessecary as we have moved away from using an OpenAPI generator approach. We aim to add support for more API resources in the near future. If you have any specific requests, please [open an issue](https://github.com/Infisical/python-sdk-official/issues).
11
+
12
+ 2. **New response types**: The 1.0.2 release uses return types that differ from the older versions. The new return types such as `BaseSecret`, are all exported from the Infisical SDK.
13
+
14
+ 3. **Property renaming**: Some properties on the responses have been slightly renamed. An example of this would be that the `secret_key` property on the `get_secret_by_name()` method, that has been renamed to `secretKey`.
15
+
16
+ With this in mind, you're ready to upgrade your SDK version to `1.0.2` or above.
17
+
18
+ You can refer to our [legacy documentation](https://github.com/Infisical/python-sdk-official/tree/9b0403938ee5ae599d42c5f1fdf9158671a15606?tab=readme-ov-file#infisical-python-sdk) if need be.
19
+
20
+ ## Requirements
21
+
22
+ Python 3.7+
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install infisicalsdk
28
+ ```
29
+
30
+ ## Getting Started
31
+
32
+ ```python
33
+ from infisical_sdk import InfisicalSDKClient
34
+
35
+ # Initialize the client
36
+ client = InfisicalSDKClient(host="https://app.infisical.com")
37
+
38
+ # Authenticate (example using Universal Auth)
39
+ client.auth.universal_auth.login(client_id="your_client_id", client_secret="your_client_secret")
40
+
41
+ # Use the SDK to interact with Infisical
42
+ secrets = client.secrets.list_secrets(project_id="your_project_id", environment_slug="dev", secret_path="/")
43
+ ```
44
+
45
+ ## Core Methods
46
+
47
+ The SDK methods are organized into the following high-level categories:
48
+
49
+ 1. `auth`: Handles authentication methods.
50
+ 2. `secrets`: Manages CRUD operations for secrets.
51
+
52
+ ### `auth`
53
+
54
+ The `Auth` component provides methods for authentication:
55
+
56
+ #### Universal Auth
57
+
58
+ ```python
59
+ response = client.auth.universal_auth.login(client_id="your_client_id", client_secret="your_client_secret")
60
+ ```
61
+
62
+ #### AWS Auth
63
+
64
+ ```python
65
+ response = client.auth.aws_auth.login(identity_id="your_identity_id")
66
+ ```
67
+
68
+ ### `secrets`
69
+
70
+ This sub-class handles operations related to secrets:
71
+
72
+ #### List Secrets
73
+
74
+ ```python
75
+ secrets = client.secrets.list_secrets(
76
+ project_id="your_project_id",
77
+ environment_slug="dev",
78
+ secret_path="/",
79
+ expand_secret_references=True,
80
+ recursive=False,
81
+ include_imports=True,
82
+ tag_filters=[]
83
+ )
84
+ ```
85
+
86
+ **Parameters:**
87
+ - `project_id` (str): The ID of your project.
88
+ - `environment_slug` (str): The environment in which to list secrets (e.g., "dev").
89
+ - `secret_path` (str): The path to the secrets.
90
+ - `expand_secret_references` (bool): Whether to expand secret references.
91
+ - `recursive` (bool): Whether to list secrets recursively.
92
+ - `include_imports` (bool): Whether to include imported secrets.
93
+ - `tag_filters` (List[str]): Tags to filter secrets.
94
+
95
+ **Returns:**
96
+ - `ListSecretsResponse`: The response containing the list of secrets.
97
+
98
+ #### Create Secret
99
+
100
+ ```python
101
+ new_secret = client.secrets.create_secret_by_name(
102
+ secret_name="NEW_SECRET",
103
+ project_id="your_project_id",
104
+ secret_path="/",
105
+ environment_slug="dev",
106
+ secret_value="secret_value",
107
+ secret_comment="Optional comment",
108
+ skip_multiline_encoding=False,
109
+ secret_reminder_repeat_days=30, # Optional
110
+ secret_reminder_note="Remember to update this secret" # Optional
111
+ )
112
+ ```
113
+
114
+ **Parameters:**
115
+ - `secret_name` (str): The name of the secret.
116
+ - `project_id` (str): The ID of your project.
117
+ - `secret_path` (str): The path to the secret.
118
+ - `environment_slug` (str): The environment in which to create the secret.
119
+ - `secret_value` (str): The value of the secret.
120
+ - `secret_comment` (str, optional): A comment associated with the secret.
121
+ - `skip_multiline_encoding` (bool, optional): Whether to skip encoding for multiline secrets.
122
+ - `secret_reminder_repeat_days` (Union[float, int], optional): Number of days after which to repeat secret reminders.
123
+ - `secret_reminder_note` (str, optional): A note for the secret reminder.
124
+
125
+ **Returns:**
126
+ - `BaseSecret`: The response after creating the secret.
127
+
128
+ #### Update Secret
129
+
130
+ ```python
131
+ updated_secret = client.secrets.update_secret_by_name(
132
+ current_secret_name="EXISTING_SECRET",
133
+ project_id="your_project_id",
134
+ secret_path="/",
135
+ environment_slug="dev",
136
+ secret_value="new_secret_value",
137
+ secret_comment="Updated comment", # Optional
138
+ skip_multiline_encoding=False,
139
+ secret_reminder_repeat_days=30, # Optional
140
+ secret_reminder_note="Updated reminder note", # Optional
141
+ new_secret_name="NEW_NAME" # Optional
142
+ )
143
+ ```
144
+
145
+ **Parameters:**
146
+ - `current_secret_name` (str): The current name of the secret.
147
+ - `project_id` (str): The ID of your project.
148
+ - `secret_path` (str): The path to the secret.
149
+ - `environment_slug` (str): The environment in which to update the secret.
150
+ - `secret_value` (str, optional): The new value of the secret.
151
+ - `secret_comment` (str, optional): An updated comment associated with the secret.
152
+ - `skip_multiline_encoding` (bool, optional): Whether to skip encoding for multiline secrets.
153
+ - `secret_reminder_repeat_days` (Union[float, int], optional): Updated number of days after which to repeat secret reminders.
154
+ - `secret_reminder_note` (str, optional): An updated note for the secret reminder.
155
+ - `new_secret_name` (str, optional): A new name for the secret.
156
+
157
+ **Returns:**
158
+ - `BaseSecret`: The response after updating the secret.
159
+
160
+ #### Get Secret by Name
161
+
162
+ ```python
163
+ secret = client.secrets.get_secret_by_name(
164
+ secret_name="EXISTING_SECRET",
165
+ project_id="your_project_id",
166
+ environment_slug="dev",
167
+ secret_path="/",
168
+ expand_secret_references=True,
169
+ include_imports=True,
170
+ version=None # Optional
171
+ )
172
+ ```
173
+
174
+ **Parameters:**
175
+ - `secret_name` (str): The name of the secret.
176
+ - `project_id` (str): The ID of your project.
177
+ - `environment_slug` (str): The environment in which to retrieve the secret.
178
+ - `secret_path` (str): The path to the secret.
179
+ - `expand_secret_references` (bool): Whether to expand secret references.
180
+ - `include_imports` (bool): Whether to include imported secrets.
181
+ - `version` (str, optional): The version of the secret to retrieve. Fetches the latest by default.
182
+
183
+ **Returns:**
184
+ - `BaseSecret`: The response containing the secret.
185
+
186
+ #### Delete Secret by Name
187
+
188
+ ```python
189
+ deleted_secret = client.secrets.delete_secret_by_name(
190
+ secret_name="EXISTING_SECRET",
191
+ project_id="your_project_id",
192
+ environment_slug="dev",
193
+ secret_path="/"
194
+ )
195
+ ```
196
+
197
+ **Parameters:**
198
+ - `secret_name` (str): The name of the secret to delete.
199
+ - `project_id` (str): The ID of your project.
200
+ - `environment_slug` (str): The environment in which to delete the secret.
201
+ - `secret_path` (str): The path to the secret.
202
+
203
+ **Returns:**
204
+ - `BaseSecret`: The response after deleting the secret.
@@ -0,0 +1,3 @@
1
+ from .client import InfisicalSDKClient # noqa
2
+ from .infisical_requests import InfisicalError # noqa
3
+ from .api_types import SingleSecretResponse, ListSecretsResponse, BaseSecret # noqa
@@ -0,0 +1,127 @@
1
+ from dataclasses import dataclass, field, fields
2
+ from typing import Optional, List, Any, Dict
3
+ from enum import Enum
4
+ import json
5
+
6
+
7
+ class ApprovalStatus(str, Enum):
8
+ """Enum for approval status"""
9
+ OPEN = "open"
10
+ APPROVED = "approved"
11
+ REJECTED = "rejected"
12
+
13
+
14
+ class BaseModel:
15
+ """Base class for all models"""
16
+ def to_dict(self) -> Dict:
17
+ """Convert model to dictionary"""
18
+ result = {}
19
+ for key, value in self.__dict__.items():
20
+ if value is not None: # Skip None values
21
+ if isinstance(value, BaseModel):
22
+ result[key] = value.to_dict()
23
+ elif isinstance(value, list):
24
+ result[key] = [
25
+ item.to_dict() if isinstance(item, BaseModel) else item
26
+ for item in value
27
+ ]
28
+ elif isinstance(value, Enum):
29
+ result[key] = value.value
30
+ else:
31
+ result[key] = value
32
+ return result
33
+
34
+ @classmethod
35
+ def from_dict(cls, data: Dict) -> 'BaseModel':
36
+ """Create model from dictionary"""
37
+ # Get only the fields that exist in the dataclass
38
+ valid_fields = {f.name for f in fields(cls)}
39
+ filtered_data = {k: v for k, v in data.items() if k in valid_fields}
40
+ return cls(**filtered_data)
41
+
42
+ def to_json(self) -> str:
43
+ """Convert model to JSON string"""
44
+ return json.dumps(self.to_dict())
45
+
46
+ @classmethod
47
+ def from_json(cls, json_str: str) -> 'BaseModel':
48
+ """Create model from JSON string"""
49
+ data = json.loads(json_str)
50
+ return cls.from_dict(data)
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class SecretTag(BaseModel):
55
+ """Model for secret tags"""
56
+ id: str
57
+ slug: str
58
+ name: str
59
+ color: Optional[str] = None
60
+
61
+
62
+ @dataclass
63
+ class BaseSecret(BaseModel):
64
+ """Infisical Secret"""
65
+ id: str
66
+ _id: str
67
+ workspace: str
68
+ environment: str
69
+ version: int
70
+ type: str
71
+ secretKey: str
72
+ secretValue: str
73
+ secretComment: str
74
+ createdAt: str
75
+ updatedAt: str
76
+ secretMetadata: Optional[Dict[str, Any]] = None
77
+ secretReminderNote: Optional[str] = None
78
+ secretReminderRepeatDays: Optional[int] = None
79
+ skipMultilineEncoding: Optional[bool] = False
80
+ metadata: Optional[Any] = None
81
+ secretPath: Optional[str] = None
82
+ tags: List[SecretTag] = field(default_factory=list)
83
+
84
+
85
+ @dataclass
86
+ class Import(BaseModel):
87
+ """Model for imports section"""
88
+ secretPath: str
89
+ environment: str
90
+ folderId: Optional[str] = None
91
+ secrets: List[BaseSecret] = field(default_factory=list)
92
+
93
+
94
+ @dataclass
95
+ class ListSecretsResponse(BaseModel):
96
+ """Complete response model for secrets API"""
97
+ secrets: List[BaseSecret]
98
+ imports: List[Import] = field(default_factory=list)
99
+
100
+ @classmethod
101
+ def from_dict(cls, data: Dict) -> 'ListSecretsResponse':
102
+ """Create model from dictionary with camelCase keys, handling nested objects"""
103
+ return cls(
104
+ secrets=[BaseSecret.from_dict(secret) for secret in data['secrets']],
105
+ imports=[Import.from_dict(imp) for imp in data.get('imports', [])]
106
+ )
107
+
108
+
109
+ @dataclass
110
+ class SingleSecretResponse(BaseModel):
111
+ """Response model for get secret API"""
112
+ secret: BaseSecret
113
+
114
+ @classmethod
115
+ def from_dict(cls, data: Dict) -> 'ListSecretsResponse':
116
+ return cls(
117
+ secret=BaseSecret.from_dict(data['secret']),
118
+ )
119
+
120
+
121
+ @dataclass
122
+ class MachineIdentityLoginResponse(BaseModel):
123
+ """Response model for machine identity login API"""
124
+ accessToken: str
125
+ expiresIn: int
126
+ accessTokenMaxTTL: int
127
+ tokenType: str
@@ -0,0 +1,345 @@
1
+ import base64
2
+ import json
3
+ from typing import List, Union
4
+ import os
5
+ import datetime
6
+ from typing import Dict, Any
7
+
8
+ import requests
9
+ import boto3
10
+ from botocore.auth import SigV4Auth
11
+ from botocore.awsrequest import AWSRequest
12
+ from botocore.exceptions import NoCredentialsError
13
+
14
+ from .infisical_requests import InfisicalRequests
15
+ from .api_types import ListSecretsResponse, MachineIdentityLoginResponse
16
+ from .api_types import SingleSecretResponse, BaseSecret
17
+
18
+
19
+ class InfisicalSDKClient:
20
+ def __init__(self, host: str, token: str = None):
21
+ self.host = host
22
+ self.access_token = token
23
+
24
+ self.api = InfisicalRequests(host=host, token=token)
25
+
26
+ self.auth = Auth(self)
27
+ self.secrets = V3RawSecrets(self)
28
+
29
+ def set_token(self, token: str):
30
+ """
31
+ Set the access token for future requests.
32
+ """
33
+ self.api.set_token(token)
34
+ self.access_token = token
35
+
36
+ def get_token(self):
37
+ """
38
+ Set the access token for future requests.
39
+ """
40
+ return self.access_token
41
+
42
+
43
+ class UniversalAuth:
44
+ def __init__(self, client: InfisicalSDKClient):
45
+ self.client = client
46
+
47
+ def login(self, client_id: str, client_secret: str) -> MachineIdentityLoginResponse:
48
+ """
49
+ Login with Universal Auth.
50
+
51
+ Args:
52
+ client_id (str): Your Machine Identity Client ID.
53
+ client_secret (str): Your Machine Identity Client Secret.
54
+
55
+ Returns:
56
+ Dict: A dictionary containing the access token and related information.
57
+ """
58
+
59
+ requestBody = {
60
+ "clientId": client_id,
61
+ "clientSecret": client_secret
62
+ }
63
+
64
+ result = self.client.api.post(
65
+ path="/api/v1/auth/universal-auth/login",
66
+ json=requestBody,
67
+ model=MachineIdentityLoginResponse
68
+ )
69
+
70
+ self.client.set_token(result.data.accessToken)
71
+
72
+ return result.data
73
+
74
+
75
+ class AWSAuth:
76
+ def __init__(self, client: InfisicalSDKClient) -> None:
77
+ self.client = client
78
+
79
+ def login(self, identity_id: str) -> MachineIdentityLoginResponse:
80
+ """
81
+ Login with AWS Authentication.
82
+
83
+ Args:
84
+ identity_id (str): Your Machine Identity ID that has AWS Auth configured.
85
+
86
+ Returns:
87
+ Dict: A dictionary containing the access token and related information.
88
+ """
89
+
90
+ identity_id = identity_id or os.getenv("INFISICAL_AWS_IAM_AUTH_IDENTITY_ID")
91
+ if not identity_id:
92
+ raise ValueError(
93
+ "Identity ID must be provided or set in the environment variable" +
94
+ "INFISICAL_AWS_IAM_AUTH_IDENTITY_ID."
95
+ )
96
+
97
+ aws_region = self.get_aws_region()
98
+ session = boto3.Session(region_name=aws_region)
99
+
100
+ credentials = self._get_aws_credentials(session)
101
+
102
+ iam_request_url = f"https://sts.{aws_region}.amazonaws.com/"
103
+ iam_request_body = "Action=GetCallerIdentity&Version=2011-06-15"
104
+
105
+ request_headers = self._prepare_aws_request(
106
+ iam_request_url,
107
+ iam_request_body,
108
+ credentials,
109
+ aws_region
110
+ )
111
+
112
+ requestBody = {
113
+ "identityId": identity_id,
114
+ "iamRequestBody": base64.b64encode(iam_request_body.encode()).decode(),
115
+ "iamRequestHeaders": base64.b64encode(json.dumps(request_headers).encode()).decode(),
116
+ "iamHttpRequestMethod": "POST"
117
+ }
118
+
119
+ result = self.client.api.post(
120
+ path="/api/v1/auth/aws-auth/login",
121
+ json=requestBody,
122
+ model=MachineIdentityLoginResponse
123
+ )
124
+
125
+ self.client.set_token(result.data.accessToken)
126
+
127
+ return result.data
128
+
129
+ def _get_aws_credentials(self, session: boto3.Session) -> Any:
130
+ try:
131
+ credentials = session.get_credentials()
132
+ if credentials is None:
133
+ raise NoCredentialsError("AWS credentials not found.")
134
+ return credentials.get_frozen_credentials()
135
+ except NoCredentialsError as e:
136
+ raise RuntimeError(f"AWS IAM Auth Login failed: {str(e)}")
137
+
138
+ def _prepare_aws_request(
139
+ self,
140
+ url: str,
141
+ body: str,
142
+ credentials: Any,
143
+ region: str) -> Dict[str, str]:
144
+
145
+ current_time = datetime.datetime.now(datetime.timezone.utc)
146
+ amz_date = current_time.strftime('%Y%m%dT%H%M%SZ')
147
+
148
+ request = AWSRequest(method="POST", url=url, data=body)
149
+ request.headers["X-Amz-Date"] = amz_date
150
+ request.headers["Host"] = f"sts.{region}.amazonaws.com"
151
+ request.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
152
+ request.headers["Content-Length"] = str(len(body))
153
+
154
+ signer = SigV4Auth(credentials, "sts", region)
155
+ signer.add_auth(request)
156
+
157
+ return {k: v for k, v in request.headers.items() if k.lower() != "content-length"}
158
+
159
+ @staticmethod
160
+ def get_aws_region() -> str:
161
+ region = os.getenv("AWS_REGION") # Typically found in lambda runtime environment
162
+ if region:
163
+ return region
164
+
165
+ try:
166
+ return AWSAuth._get_aws_ec2_identity_document_region()
167
+ except Exception as e:
168
+ raise Exception("Failed to retrieve AWS region") from e
169
+
170
+ @staticmethod
171
+ def _get_aws_ec2_identity_document_region(timeout: int = 5000) -> str:
172
+ session = requests.Session()
173
+ token_response = session.put(
174
+ "http://169.254.169.254/latest/api/token",
175
+ headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"},
176
+ timeout=timeout / 1000
177
+ )
178
+ token_response.raise_for_status()
179
+ metadata_token = token_response.text
180
+
181
+ identity_response = session.get(
182
+ "http://169.254.169.254/latest/dynamic/instance-identity/document",
183
+ headers={"X-aws-ec2-metadata-token": metadata_token, "Accept": "application/json"},
184
+ timeout=timeout / 1000
185
+ )
186
+
187
+ identity_response.raise_for_status()
188
+ return identity_response.json().get("region")
189
+
190
+
191
+ class Auth:
192
+ def __init__(self, client):
193
+ self.client = client
194
+ self.aws_auth = AWSAuth(client)
195
+ self.universal_auth = UniversalAuth(client)
196
+
197
+
198
+ class V3RawSecrets:
199
+ def __init__(self, client: InfisicalSDKClient) -> None:
200
+ self.client = client
201
+
202
+ def list_secrets(
203
+ self,
204
+ project_id: str,
205
+ environment_slug: str,
206
+ secret_path: str,
207
+ expand_secret_references: bool = True,
208
+ recursive: bool = False,
209
+ include_imports: bool = True,
210
+ tag_filters: List[str] = []) -> ListSecretsResponse:
211
+
212
+ params = {
213
+ "workspaceId": project_id,
214
+ "environment": environment_slug,
215
+ "secretPath": secret_path,
216
+ "expandSecretReferences": str(expand_secret_references).lower(),
217
+ "recursive": str(recursive).lower(),
218
+ "include_imports": str(include_imports).lower(),
219
+ }
220
+
221
+ if tag_filters:
222
+ params["tag_slugs"] = ",".join(tag_filters)
223
+
224
+ result = self.client.api.get(
225
+ path="/api/v3/secrets/raw",
226
+ params=params,
227
+ model=ListSecretsResponse
228
+ )
229
+
230
+ return result.data
231
+
232
+ def get_secret_by_name(
233
+ self,
234
+ secret_name: str,
235
+ project_id: str,
236
+ environment_slug: str,
237
+ secret_path: str,
238
+ expand_secret_references: bool = True,
239
+ include_imports: bool = True,
240
+ version: str = None) -> BaseSecret:
241
+
242
+ params = {
243
+ "workspaceId": project_id,
244
+ "environment": environment_slug,
245
+ "secretPath": secret_path,
246
+ "expandSecretReferences": str(expand_secret_references).lower(),
247
+ "include_imports": str(include_imports).lower(),
248
+ "version": version
249
+ }
250
+
251
+ result = self.client.api.get(
252
+ path=f"/api/v3/secrets/raw/{secret_name}",
253
+ params=params,
254
+ model=SingleSecretResponse
255
+ )
256
+
257
+ return result.data.secret
258
+
259
+ def create_secret_by_name(
260
+ self,
261
+ secret_name: str,
262
+ project_id: str,
263
+ secret_path: str,
264
+ environment_slug: str,
265
+ secret_value: str = None,
266
+ secret_comment: str = None,
267
+ skip_multiline_encoding: bool = False,
268
+ secret_reminder_repeat_days: Union[float, int] = None,
269
+ secret_reminder_note: str = None) -> BaseSecret:
270
+
271
+ requestBody = {
272
+ "workspaceId": project_id,
273
+ "environment": environment_slug,
274
+ "secretPath": secret_path,
275
+ "secretValue": secret_value,
276
+ "secretComment": secret_comment,
277
+ "tagIds": None,
278
+ "skipMultilineEncoding": skip_multiline_encoding,
279
+ "type": "shared",
280
+ "secretReminderRepeatDays": secret_reminder_repeat_days,
281
+ "secretReminderNote": secret_reminder_note
282
+ }
283
+ result = self.client.api.post(
284
+ path=f"/api/v3/secrets/raw/{secret_name}",
285
+ json=requestBody,
286
+ model=SingleSecretResponse
287
+ )
288
+
289
+ return result.data.secret
290
+
291
+ def update_secret_by_name(
292
+ self,
293
+ current_secret_name: str,
294
+ project_id: str,
295
+ secret_path: str,
296
+ environment_slug: str,
297
+ secret_value: str = None,
298
+ secret_comment: str = None,
299
+ skip_multiline_encoding: bool = False,
300
+ secret_reminder_repeat_days: Union[float, int] = None,
301
+ secret_reminder_note: str = None,
302
+ new_secret_name: str = None) -> BaseSecret:
303
+
304
+ requestBody = {
305
+ "workspaceId": project_id,
306
+ "environment": environment_slug,
307
+ "secretPath": secret_path,
308
+ "secretValue": secret_value,
309
+ "secretComment": secret_comment,
310
+ "new_secret_name": new_secret_name,
311
+ "tagIds": None,
312
+ "skipMultilineEncoding": skip_multiline_encoding,
313
+ "type": "shared",
314
+ "secretReminderRepeatDays": secret_reminder_repeat_days,
315
+ "secretReminderNote": secret_reminder_note
316
+ }
317
+
318
+ result = self.client.api.patch(
319
+ path=f"/api/v3/secrets/raw/{current_secret_name}",
320
+ json=requestBody,
321
+ model=SingleSecretResponse
322
+ )
323
+ return result.data.secret
324
+
325
+ def delete_secret_by_name(
326
+ self,
327
+ secret_name: str,
328
+ project_id: str,
329
+ secret_path: str,
330
+ environment_slug: str) -> BaseSecret:
331
+
332
+ requestBody = {
333
+ "workspaceId": project_id,
334
+ "environment": environment_slug,
335
+ "secretPath": secret_path,
336
+ "type": "shared",
337
+ }
338
+
339
+ result = self.client.api.delete(
340
+ path=f"/api/v3/secrets/raw/{secret_name}",
341
+ json=requestBody,
342
+ model=SingleSecretResponse
343
+ )
344
+
345
+ return result.data.secret
@@ -0,0 +1,186 @@
1
+ from typing import Any, Dict, Generic, Optional, TypeVar
2
+ from urllib.parse import urljoin
3
+ import requests
4
+ from dataclasses import dataclass
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ class InfisicalError(Exception):
10
+ """Base exception for Infisical client errors"""
11
+ pass
12
+
13
+
14
+ class APIError(InfisicalError):
15
+ """API-specific errors"""
16
+ def __init__(self, message: str, status_code: int, response: Dict[str, Any]):
17
+ self.status_code = status_code
18
+ self.response = response
19
+ super().__init__(f"{message} (Status: {status_code})")
20
+
21
+
22
+ @dataclass
23
+ class APIResponse(Generic[T]):
24
+ """Generic API response wrapper"""
25
+ data: T
26
+ status_code: int
27
+ headers: Dict[str, str]
28
+
29
+ def to_dict(self) -> Dict:
30
+ """Convert to dictionary with camelCase keys"""
31
+ return {
32
+ 'data': self.data.to_dict() if hasattr(self.data, 'to_dict') else self.data,
33
+ 'statusCode': self.status_code,
34
+ 'headers': self.headers
35
+ }
36
+
37
+ @classmethod
38
+ def from_dict(cls, data: Dict) -> 'APIResponse[T]':
39
+ """Create from dictionary with camelCase keys"""
40
+ return cls(
41
+ data=data['data'],
42
+ status_code=data['statusCode'],
43
+ headers=data['headers']
44
+ )
45
+
46
+
47
+ class InfisicalRequests:
48
+ def __init__(self, host: str, token: Optional[str] = None):
49
+ self.host = host.rstrip("/")
50
+ self.session = requests.Session()
51
+
52
+ # Set common headers
53
+ self.session.headers.update({
54
+ "Content-Type": "application/json",
55
+ "Accept": "application/json",
56
+ })
57
+
58
+ if token:
59
+ self.set_token(token)
60
+
61
+ def _build_url(self, path: str) -> str:
62
+ """Construct full URL from path"""
63
+ return urljoin(self.host, path.lstrip("/"))
64
+
65
+ def set_token(self, token: str):
66
+ """Set authorization token"""
67
+ self.session.headers["Authorization"] = f"Bearer {token}"
68
+
69
+ def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
70
+ """Handle API response and raise appropriate errors"""
71
+ try:
72
+ response.raise_for_status()
73
+ return response.json()
74
+ except requests.exceptions.HTTPError:
75
+ try:
76
+ error_data = response.json()
77
+ except ValueError:
78
+ error_data = {"message": response.text}
79
+
80
+ raise APIError(
81
+ message=error_data.get("message", "Unknown error"),
82
+ status_code=response.status_code,
83
+ response=error_data
84
+ )
85
+ except requests.exceptions.RequestException as e:
86
+ raise InfisicalError(f"Request failed: {str(e)}")
87
+ except ValueError:
88
+ raise InfisicalError("Invalid JSON response")
89
+
90
+ def get(
91
+ self,
92
+ path: str,
93
+ model: type[T],
94
+ params: Optional[Dict[str, Any]] = None
95
+ ) -> APIResponse[T]:
96
+
97
+ """
98
+ Make a GET request and parse response into given model
99
+
100
+ Args:
101
+ path: API endpoint path
102
+ model: model class to parse response into
103
+ params: Optional query parameters
104
+ """
105
+ response = self.session.get(self._build_url(path), params=params)
106
+ data = self._handle_response(response)
107
+
108
+ parsed_data = model.from_dict(data) if hasattr(model, 'from_dict') else data
109
+
110
+ return APIResponse(
111
+ data=parsed_data,
112
+ status_code=response.status_code,
113
+ headers=dict(response.headers)
114
+ )
115
+
116
+ def post(
117
+ self,
118
+ path: str,
119
+ model: type[T],
120
+ json: Optional[Dict[str, Any]] = None
121
+ ) -> APIResponse[T]:
122
+
123
+ """Make a POST request with JSON data"""
124
+
125
+ if json is not None:
126
+ # Filter out None values
127
+ json = {k: v for k, v in json.items() if v is not None}
128
+
129
+ response = self.session.post(self._build_url(path), json=json)
130
+ data = self._handle_response(response)
131
+
132
+ parsed_data = model.from_dict(data) if hasattr(model, 'from_dict') else data
133
+
134
+ return APIResponse(
135
+ data=parsed_data,
136
+ status_code=response.status_code,
137
+ headers=dict(response.headers)
138
+ )
139
+
140
+ def patch(
141
+ self,
142
+ path: str,
143
+ model: type[T],
144
+ json: Optional[Dict[str, Any]] = None
145
+ ) -> APIResponse[T]:
146
+
147
+ """Make a PATCH request with JSON data"""
148
+
149
+ if json is not None:
150
+ # Filter out None values
151
+ json = {k: v for k, v in json.items() if v is not None}
152
+
153
+ response = self.session.patch(self._build_url(path), json=json)
154
+ data = self._handle_response(response)
155
+
156
+ parsed_data = model.from_dict(data) if hasattr(model, 'from_dict') else data
157
+
158
+ return APIResponse(
159
+ data=parsed_data,
160
+ status_code=response.status_code,
161
+ headers=dict(response.headers)
162
+ )
163
+
164
+ def delete(
165
+ self,
166
+ path: str,
167
+ model: type[T],
168
+ json: Optional[Dict[str, Any]] = None
169
+ ) -> APIResponse[T]:
170
+
171
+ """Make a PATCH request with JSON data"""
172
+
173
+ if json is not None:
174
+ # Filter out None values
175
+ json = {k: v for k, v in json.items() if v is not None}
176
+
177
+ response = self.session.delete(self._build_url(path), json=json)
178
+ data = self._handle_response(response)
179
+
180
+ parsed_data = model.from_dict(data) if hasattr(model, 'from_dict') else data
181
+
182
+ return APIResponse(
183
+ data=parsed_data,
184
+ status_code=response.status_code,
185
+ headers=dict(response.headers)
186
+ )
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.2
2
+ Name: infisicalsdk
3
+ Version: 1.0.3
4
+ Summary: Infisical API Client
5
+ Home-page: https://github.com/Infisical/python-sdk-official
6
+ Author: Infisical
7
+ Author-email: support@infisical.com
8
+ Keywords: Infisical,Infisical API,Infisical SDK
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: urllib3<2.1.0,>=1.25.3
11
+ Requires-Dist: python-dateutil
12
+ Requires-Dist: aenum
13
+ Requires-Dist: requests~=2.32
14
+ Requires-Dist: boto3~=1.35
15
+ Requires-Dist: botocore~=1.35
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: keywords
22
+ Dynamic: requires-dist
23
+ Dynamic: summary
24
+
25
+ Infisical SDK client for Python. To view documentation, please visit https://github.com/Infisical/python-sdk-official
26
+
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.cfg
4
+ setup.py
5
+ infisical_sdk/__init__.py
6
+ infisical_sdk/api_types.py
7
+ infisical_sdk/client.py
8
+ infisical_sdk/infisical_requests.py
9
+ infisicalsdk.egg-info/PKG-INFO
10
+ infisicalsdk.egg-info/SOURCES.txt
11
+ infisicalsdk.egg-info/dependency_links.txt
12
+ infisicalsdk.egg-info/requires.txt
13
+ infisicalsdk.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ urllib3<2.1.0,>=1.25.3
2
+ python-dateutil
3
+ aenum
4
+ requests~=2.32
5
+ boto3~=1.35
6
+ botocore~=1.35
@@ -0,0 +1 @@
1
+ infisical_sdk
@@ -0,0 +1,26 @@
1
+ [tool.poetry]
2
+ name = "infisicalapi_client"
3
+ version = "1.0.0"
4
+ description = "Infisical API"
5
+ authors = ["OpenAPI Generator Community <team@openapitools.org>"]
6
+ license = "NoLicense"
7
+ readme = "README.md"
8
+ repository = "https://github.com/GIT_USER_ID/GIT_REPO_ID"
9
+ keywords = ["OpenAPI", "OpenAPI-Generator", "Infisical API"]
10
+ include = ["infisicalapi_client/py.typed"]
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.7"
14
+
15
+ urllib3 = ">= 1.25.3"
16
+ python-dateutil = ">=2.8.2"
17
+ aenum = ">=3.1.11"
18
+
19
+ [tool.poetry.dev-dependencies]
20
+ pytest = ">=7.2.1"
21
+ tox = ">=3.9.0"
22
+ flake8 = ">=4.0.0"
23
+
24
+ [build-system]
25
+ requires = ["setuptools"]
26
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,8 @@
1
+ [flake8]
2
+ max-line-length = 99
3
+ exclude = .venv,test.py
4
+
5
+ [egg_info]
6
+ tag_build =
7
+ tag_date = 0
8
+
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ Infisical SDK
5
+
6
+ List of all available APIs that can be consumed
7
+ """ # noqa: E501
8
+
9
+ from setuptools import setup, find_packages # noqa: H301
10
+
11
+ # To install the library, run the following
12
+ #
13
+ # python setup.py install
14
+ #
15
+ # prerequisite: setuptools
16
+ # http://pypi.python.org/pypi/setuptools
17
+ NAME = "infisicalsdk"
18
+ VERSION = "1.0.3"
19
+ PYTHON_REQUIRES = ">=3.7"
20
+ REQUIRES = [
21
+ "urllib3 >= 1.25.3, < 2.1.0",
22
+ "python-dateutil",
23
+ "aenum",
24
+ "requests~=2.32",
25
+ "boto3~=1.35",
26
+ "botocore~=1.35",
27
+ ]
28
+
29
+ setup(
30
+ name=NAME,
31
+ version=VERSION,
32
+ description="Infisical API Client",
33
+ author="Infisical",
34
+ author_email="support@infisical.com",
35
+ url="https://github.com/Infisical/python-sdk-official",
36
+ keywords=["Infisical", "Infisical API", "Infisical SDK"],
37
+ install_requires=REQUIRES,
38
+ packages=find_packages(exclude=["test", "tests"]),
39
+ include_package_data=True,
40
+ long_description_content_type='text/markdown',
41
+ long_description="""\
42
+ Infisical SDK client for Python. To view documentation, please visit https://github.com/Infisical/python-sdk-official
43
+ """, # noqa: E501
44
+ package_data={"infisicalapi_client": ["py.typed"]},
45
+ )