Skip to content

Commit 685f090

Browse files
authored
Merge pull request #7 from phasehq/feat--secrets
Feat: secrets
2 parents c2948b1 + cec10e8 commit 685f090

19 files changed

+2015
-365
lines changed

.github/workflows/pytest.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ jobs:
1717
- name: Set up Python
1818
uses: actions/setup-python@v2
1919
with:
20-
python-version: 3.x
20+
python-version: '3.12'
2121

2222
- name: Install dependencies
23-
run: pip install -r requirements.txt
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install -r requirements.txt
26+
pip install pytest
2427
2528
- name: Run pytest
26-
run: python -m pytest
29+
run: |
30+
python -m pytest -v tests/

CONTRIBUTING.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
## Local setup
2+
3+
Clone the reposistory to your machine and install the required dependencies.
4+
5+
### Create a virtual environment
6+
7+
```fish
8+
python3 -m venv.venv
9+
```
10+
11+
### Install dependencies
12+
13+
```fish
14+
pip install -r requirements.txt
15+
```
16+
17+
### Demo script
18+
19+
This demo python script will create, read, update and delete secrets via the SDK. Just update the host, app, env and token constants at the top.
20+
21+
```python
22+
from src.phase import Phase, CreateSecretsOptions, GetAllSecretsOptions, UpdateSecretOptions, DeleteSecretOptions
23+
24+
CONSOLE_HOST = 'https://console.phase.dev'
25+
APP_NAME = '<app-name>'
26+
ENV_NAME = "<env-name>"
27+
TOKEN = '<service-token>'
28+
29+
# Initialize the Phase object with host and service token
30+
phase = Phase(init=False,
31+
pss=TOKEN,
32+
host=CONSOLE_HOST)
33+
34+
# Create secrets with references
35+
create_options = CreateSecretsOptions(
36+
env_name=ENV_NAME,
37+
app_name=APP_NAME,
38+
key_value_pairs=[
39+
{"BASE_URL": "https://api.example.com"},
40+
{"API_ENDPOINT": "${BASE_URL}/v1/data"},
41+
{"NESTED_REF": "Nested ${API_ENDPOINT}"}
42+
]
43+
)
44+
create_result = phase.create_secrets(create_options)
45+
print(f"Create secrets result: {create_result}")
46+
47+
# Read and resolve references
48+
get_options = GetAllSecretsOptions(
49+
env_name=ENV_NAME,
50+
app_name=APP_NAME
51+
)
52+
secrets = phase.get_all_secrets(get_options)
53+
54+
resolved_secrets = phase.resolve_references(secrets, ENV_NAME, APP_NAME)
55+
56+
print("\nResolved Secrets:")
57+
print("----------------")
58+
for secret in resolved_secrets:
59+
print(f"{secret.key}: {secret.value}")
60+
61+
# Update secrets
62+
update_options = UpdateSecretOptions(
63+
env_name=ENV_NAME,
64+
app_name=APP_NAME,
65+
key="BASE_URL",
66+
value="https://api.acme.com",
67+
secret_path="/",
68+
destination_path="/", # Optional: move secret to a new path
69+
override=False, # Optional: create a personal override
70+
toggle_override=False # Optional: toggle personal override
71+
)
72+
update_result = phase.update_secret(update_options)
73+
74+
print(f"\nUpdate secrets result: {update_result}")
75+
print("----------------")
76+
77+
78+
## Refetch secrets
79+
secrets = phase.get_all_secrets(get_options)
80+
81+
resolved_secrets = phase.resolve_references(secrets, ENV_NAME, APP_NAME)
82+
83+
print("\nResolved Secrets:")
84+
print("----------------")
85+
for secret in resolved_secrets:
86+
print(f"{secret.key}: {secret.value}")
87+
88+
89+
# Delete secrets
90+
delete_options = DeleteSecretOptions(
91+
env_name=ENV_NAME,
92+
app_name=APP_NAME,
93+
key_to_delete="BASE_URL",
94+
secret_path="/"
95+
)
96+
result = phase.delete_secret(delete_options)
97+
print(f"Delete result: {result}")
98+
99+
## Refetch secrets
100+
secrets = phase.get_all_secrets(get_options)
101+
102+
resolved_secrets = phase.resolve_references(secrets, ENV_NAME, APP_NAME)
103+
104+
print("\nResolved Secrets:")
105+
print("----------------")
106+
for secret in resolved_secrets:
107+
print(f"{secret.key}: {secret.value}")
108+
```
109+
110+
## Running Tests
111+
112+
Run the test suite with:
113+
114+
```fish
115+
python -m pytest -v tests/
116+
```

README.md

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,143 @@
11
# Python SDK for Phase
22

3-
SDK to integrate Phase in server-side applications running Python
3+
SDK to integrate Phase in server-side applications running Python. This SDK allows you to manage secrets securely using the Phase platform.
44

55
## Install
66

7-
`pip install phase-dev`
7+
```
8+
pip install phase-dev
9+
```
810

911
## Import
1012

1113
```python
12-
from phase import Phase;
14+
from phase import Phase, CreateSecretsOptions, GetAllSecretsOptions, GetSecretOptions, UpdateSecretOptions, DeleteSecretOptions
1315
```
1416

1517
## Initialize
1618

17-
Initialize the SDK with your `APP_ID` and `APP_SECRET`:
19+
Initialize the SDK with your host and token:
1820

1921
```python
20-
phase = Phase(APP_ID, APP_SECRET)
22+
phase = Phase(
23+
init=False,
24+
host='https://your-phase-host.com',
25+
pss=PHASE_SERVICE_TOKEN
26+
27+
)
2128
```
2229

2330
## Usage
2431

25-
### Encrypt
32+
### Create Secrets
33+
34+
Create one or more secrets in a specified application and environment:
35+
36+
```python
37+
create_options = CreateSecretsOptions(
38+
env_name="Development",
39+
app_name="Your App Name",
40+
key_value_pairs=[
41+
{"API_KEY": "your-api-key"},
42+
{"DB_PASSWORD": "your-db-password"}
43+
],
44+
secret_path="/api"
45+
)
46+
result = phase.create_secrets(create_options)
47+
print(f"Create secrets result: {result}")
48+
```
49+
50+
### Get Secrets
51+
52+
Fetch one or more secrets from a specified application and environment:
53+
54+
```python
55+
get_options = GetAllSecretsOptions(
56+
env_name="Development",
57+
app_name="Your App Name",
58+
tag="api", # Optional: filter by tag
59+
secret_path="/api" # Optional: specify path
60+
)
61+
secrets = phase.get_all_secrets(get_options)
62+
for secret in secrets:
63+
print(f"Key: {secret.key}, Value: {secret.value}")
64+
```
65+
66+
To get a specific secret:
2667

2768
```python
28-
ciphertext = phase.encrypt("hello world");
69+
get_options = GetSecretOptions(
70+
env_name="Development",
71+
app_name="Your App Name",
72+
key_to_find="API_KEY",
73+
secret_path="/api"
74+
)
75+
secret = phase.get_secret(get_options)
76+
if secret:
77+
print(f"Key: {secret.key}, Value: {secret.value}")
2978
```
3079

31-
### Decrypt
80+
### Update Secrets
81+
82+
Update an existing secret in a specified application and environment:
3283

3384
```python
34-
plaintext = phase.decrypt(ciphertext);
85+
update_options = UpdateSecretOptions(
86+
env_name="Development",
87+
app_name="Your App Name",
88+
key="API_KEY",
89+
value="new-api-key-value",
90+
secret_path="/api",
91+
destination_path="/new-api", # Optional: move secret to a new path
92+
override=False, # Optional: create a personal override
93+
toggle_override=False # Optional: toggle personal override
94+
)
95+
result = phase.update_secret(update_options)
96+
print(f"Update result: {result}")
3597
```
98+
99+
### Delete Secrets
100+
101+
Delete a secret from a specified application and environment:
102+
103+
```python
104+
delete_options = DeleteSecretOptions(
105+
env_name="Development",
106+
app_name="Your App Name",
107+
key_to_delete="API_KEY",
108+
secret_path="/api"
109+
)
110+
result = phase.delete_secret(delete_options)
111+
print(f"Delete result: {result}")
112+
```
113+
114+
### Resolve Secret References
115+
116+
Resolve references in secret values:
117+
118+
```python
119+
get_options = GetAllSecretsOptions(
120+
env_name="Development",
121+
app_name="Your App Name"
122+
)
123+
secrets = phase.get_all_secrets(get_options)
124+
resolved_secrets = phase.resolve_references(secrets, "Development", "Your App Name")
125+
for secret in resolved_secrets:
126+
print(f"Key: {secret.key}, Resolved Value: {secret.value}")
127+
```
128+
129+
## Error Handling
130+
131+
The SDK methods may raise exceptions for various error conditions. It's recommended to wrap SDK calls in try-except blocks to handle potential errors:
132+
133+
```python
134+
try:
135+
get_options = GetAllSecretsOptions(env_name="Development", app_name="Your App Name")
136+
secrets = phase.get_all_secrets(get_options)
137+
except ValueError as e:
138+
print(f"An error occurred: {e}")
139+
```
140+
141+
## Note on Security
142+
143+
Never hard-code sensitive information like tokens or secrets directly in your code. Always use environment variables or secure configuration management to provide these values to your application.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "phase_dev"
7-
version = "1.1.0"
8-
description = "Python SDK for Phase"
7+
version = "2.0.0"
8+
description = "Python SDK for Phase secrets manager"
99
readme = "README.md"
1010
requires-python = ">=3.10"
1111
classifiers = [

src/phase/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1-
from .phase import Phase
1+
from .phase import (
2+
Phase,
3+
GetSecretOptions,
4+
GetAllSecretsOptions,
5+
CreateSecretsOptions,
6+
UpdateSecretOptions,
7+
DeleteSecretOptions,
8+
PhaseSecret
9+
)
210

3-
__all__ = ['Phase']
11+
__all__ = [
12+
'Phase',
13+
'GetSecretOptions',
14+
'GetAllSecretsOptions',
15+
'CreateSecretsOptions',
16+
'UpdateSecretOptions',
17+
'DeleteSecretOptions',
18+
'PhaseSecret'
19+
]

0 commit comments

Comments
 (0)