infisicalsdk 0.1.4__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.
Potentially problematic release.
This version of infisicalsdk might be problematic. Click here for more details.
- infisicalsdk-0.1.4/PKG-INFO +26 -0
- infisicalsdk-0.1.4/README.md +210 -0
- infisicalsdk-0.1.4/infisical_sdk/__init__.py +3 -0
- infisicalsdk-0.1.4/infisical_sdk/api_types.py +127 -0
- infisicalsdk-0.1.4/infisical_sdk/client.py +345 -0
- infisicalsdk-0.1.4/infisical_sdk/infisical_requests.py +186 -0
- infisicalsdk-0.1.4/infisicalsdk.egg-info/PKG-INFO +26 -0
- infisicalsdk-0.1.4/infisicalsdk.egg-info/SOURCES.txt +13 -0
- infisicalsdk-0.1.4/infisicalsdk.egg-info/dependency_links.txt +1 -0
- infisicalsdk-0.1.4/infisicalsdk.egg-info/requires.txt +6 -0
- infisicalsdk-0.1.4/infisicalsdk.egg-info/top_level.txt +1 -0
- infisicalsdk-0.1.4/pyproject.toml +26 -0
- infisicalsdk-0.1.4/setup.cfg +8 -0
- infisicalsdk-0.1.4/setup.py +45 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: infisicalsdk
|
|
3
|
+
Version: 0.1.4
|
|
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,210 @@
|
|
|
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.3 or above
|
|
6
|
+
|
|
7
|
+
We have recently rolled out our first stable version of the SDK, version `1.0.3` and above.
|
|
8
|
+
|
|
9
|
+
The 1.0.3 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.3 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.3` 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(
|
|
40
|
+
client_id="<machine-identity-client-id>",
|
|
41
|
+
client_secret="<machine-identity-client-secret>"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Use the SDK to interact with Infisical
|
|
45
|
+
secrets = client.secrets.list_secrets(project_id="<project-id>", environment_slug="dev", secret_path="/")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Core Methods
|
|
49
|
+
|
|
50
|
+
The SDK methods are organized into the following high-level categories:
|
|
51
|
+
|
|
52
|
+
1. `auth`: Handles authentication methods.
|
|
53
|
+
2. `secrets`: Manages CRUD operations for secrets.
|
|
54
|
+
|
|
55
|
+
### `auth`
|
|
56
|
+
|
|
57
|
+
The `Auth` component provides methods for authentication:
|
|
58
|
+
|
|
59
|
+
#### Universal Auth
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
response = client.auth.universal_auth.login(
|
|
63
|
+
client_id="<machine-identity-client-id>",
|
|
64
|
+
client_secret="<machine-identity-client-secret>"
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### AWS Auth
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
response = client.auth.aws_auth.login(identity_id="<machine-identity-id>")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `secrets`
|
|
75
|
+
|
|
76
|
+
This sub-class handles operations related to secrets:
|
|
77
|
+
|
|
78
|
+
#### List Secrets
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
secrets = client.secrets.list_secrets(
|
|
82
|
+
project_id="<project-id>",
|
|
83
|
+
environment_slug="dev",
|
|
84
|
+
secret_path="/",
|
|
85
|
+
expand_secret_references=True,
|
|
86
|
+
recursive=False,
|
|
87
|
+
include_imports=True,
|
|
88
|
+
tag_filters=[]
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Parameters:**
|
|
93
|
+
- `project_id` (str): The ID of your project.
|
|
94
|
+
- `environment_slug` (str): The environment in which to list secrets (e.g., "dev").
|
|
95
|
+
- `secret_path` (str): The path to the secrets.
|
|
96
|
+
- `expand_secret_references` (bool): Whether to expand secret references.
|
|
97
|
+
- `recursive` (bool): Whether to list secrets recursively.
|
|
98
|
+
- `include_imports` (bool): Whether to include imported secrets.
|
|
99
|
+
- `tag_filters` (List[str]): Tags to filter secrets.
|
|
100
|
+
|
|
101
|
+
**Returns:**
|
|
102
|
+
- `ListSecretsResponse`: The response containing the list of secrets.
|
|
103
|
+
|
|
104
|
+
#### Create Secret
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
new_secret = client.secrets.create_secret_by_name(
|
|
108
|
+
secret_name="NEW_SECRET",
|
|
109
|
+
project_id="<project-id>",
|
|
110
|
+
secret_path="/",
|
|
111
|
+
environment_slug="dev",
|
|
112
|
+
secret_value="secret_value",
|
|
113
|
+
secret_comment="Optional comment",
|
|
114
|
+
skip_multiline_encoding=False,
|
|
115
|
+
secret_reminder_repeat_days=30, # Optional
|
|
116
|
+
secret_reminder_note="Remember to update this secret" # Optional
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Parameters:**
|
|
121
|
+
- `secret_name` (str): The name of the secret.
|
|
122
|
+
- `project_id` (str): The ID of your project.
|
|
123
|
+
- `secret_path` (str): The path to the secret.
|
|
124
|
+
- `environment_slug` (str): The environment in which to create the secret.
|
|
125
|
+
- `secret_value` (str): The value of the secret.
|
|
126
|
+
- `secret_comment` (str, optional): A comment associated with the secret.
|
|
127
|
+
- `skip_multiline_encoding` (bool, optional): Whether to skip encoding for multiline secrets.
|
|
128
|
+
- `secret_reminder_repeat_days` (Union[float, int], optional): Number of days after which to repeat secret reminders.
|
|
129
|
+
- `secret_reminder_note` (str, optional): A note for the secret reminder.
|
|
130
|
+
|
|
131
|
+
**Returns:**
|
|
132
|
+
- `BaseSecret`: The response after creating the secret.
|
|
133
|
+
|
|
134
|
+
#### Update Secret
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
updated_secret = client.secrets.update_secret_by_name(
|
|
138
|
+
current_secret_name="EXISTING_SECRET",
|
|
139
|
+
project_id="<project-id>",
|
|
140
|
+
secret_path="/",
|
|
141
|
+
environment_slug="dev",
|
|
142
|
+
secret_value="new_secret_value",
|
|
143
|
+
secret_comment="Updated comment", # Optional
|
|
144
|
+
skip_multiline_encoding=False,
|
|
145
|
+
secret_reminder_repeat_days=30, # Optional
|
|
146
|
+
secret_reminder_note="Updated reminder note", # Optional
|
|
147
|
+
new_secret_name="NEW_NAME" # Optional
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Parameters:**
|
|
152
|
+
- `current_secret_name` (str): The current name of the secret.
|
|
153
|
+
- `project_id` (str): The ID of your project.
|
|
154
|
+
- `secret_path` (str): The path to the secret.
|
|
155
|
+
- `environment_slug` (str): The environment in which to update the secret.
|
|
156
|
+
- `secret_value` (str, optional): The new value of the secret.
|
|
157
|
+
- `secret_comment` (str, optional): An updated comment associated with the secret.
|
|
158
|
+
- `skip_multiline_encoding` (bool, optional): Whether to skip encoding for multiline secrets.
|
|
159
|
+
- `secret_reminder_repeat_days` (Union[float, int], optional): Updated number of days after which to repeat secret reminders.
|
|
160
|
+
- `secret_reminder_note` (str, optional): An updated note for the secret reminder.
|
|
161
|
+
- `new_secret_name` (str, optional): A new name for the secret.
|
|
162
|
+
|
|
163
|
+
**Returns:**
|
|
164
|
+
- `BaseSecret`: The response after updating the secret.
|
|
165
|
+
|
|
166
|
+
#### Get Secret by Name
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
secret = client.secrets.get_secret_by_name(
|
|
170
|
+
secret_name="EXISTING_SECRET",
|
|
171
|
+
project_id="<project-id>",
|
|
172
|
+
environment_slug="dev",
|
|
173
|
+
secret_path="/",
|
|
174
|
+
expand_secret_references=True,
|
|
175
|
+
include_imports=True,
|
|
176
|
+
version=None # Optional
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
- `secret_name` (str): The name of the secret.
|
|
182
|
+
- `project_id` (str): The ID of your project.
|
|
183
|
+
- `environment_slug` (str): The environment in which to retrieve the secret.
|
|
184
|
+
- `secret_path` (str): The path to the secret.
|
|
185
|
+
- `expand_secret_references` (bool): Whether to expand secret references.
|
|
186
|
+
- `include_imports` (bool): Whether to include imported secrets.
|
|
187
|
+
- `version` (str, optional): The version of the secret to retrieve. Fetches the latest by default.
|
|
188
|
+
|
|
189
|
+
**Returns:**
|
|
190
|
+
- `BaseSecret`: The response containing the secret.
|
|
191
|
+
|
|
192
|
+
#### Delete Secret by Name
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
deleted_secret = client.secrets.delete_secret_by_name(
|
|
196
|
+
secret_name="EXISTING_SECRET",
|
|
197
|
+
project_id="<project-id>",
|
|
198
|
+
environment_slug="dev",
|
|
199
|
+
secret_path="/"
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Parameters:**
|
|
204
|
+
- `secret_name` (str): The name of the secret to delete.
|
|
205
|
+
- `project_id` (str): The ID of your project.
|
|
206
|
+
- `environment_slug` (str): The environment in which to delete the secret.
|
|
207
|
+
- `secret_path` (str): The path to the secret.
|
|
208
|
+
|
|
209
|
+
**Returns:**
|
|
210
|
+
- `BaseSecret`: The response after deleting the secret.
|
|
@@ -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["tagSlugs"] = ",".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, Type
|
|
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: 0.1.4
|
|
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 @@
|
|
|
1
|
+
|
|
@@ -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,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 = "0.1.4"
|
|
19
|
+
PYTHON_REQUIRES = ">=3.8"
|
|
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
|
+
)
|