Skip to content

Commit 9a5e071

Browse files
authored
Merge pull request #4 from replicatedhq/example
add example
2 parents 9c64d18 + 97e12ac commit 9a5e071

File tree

6 files changed

+192
-10
lines changed

6 files changed

+192
-10
lines changed

examples/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Replicated Python SDK Examples
2+
3+
This directory contains examples demonstrating how to use the Replicated Python SDK.
4+
5+
## Basic Example
6+
7+
The `basic_example.py` script demonstrates the most fundamental usage of the SDK:
8+
- Initializing the Replicated client
9+
- Creating or retrieving a customer
10+
- Creating or retrieving an instance
11+
12+
### Usage
13+
14+
```bash
15+
python3 basic_example.py \
16+
--publishable-key "your_publishable_key_here" \
17+
--app-slug "your-app-slug" \
18+
--customer-email "[email protected]" \
19+
--customer-name "John Doe" \
20+
--channel "stable"
21+
```
22+
23+
### Options
24+
25+
- `--publishable-key`: Your Replicated publishable key (required)
26+
- `--app-slug`: Your application slug (required)
27+
- `--customer-email`: Customer email address (default: [email protected])
28+
- `--channel`: Channel for the customer (optional)
29+
- `--customer-name`: Customer name (optional)
30+
- `--base-url`: Base URL for the Replicated API (default: https://replicated.app)
31+
32+
### Using Different Environments
33+
34+
To use the SDK against a different environment (not production), use the `--base-url` flag:
35+
36+
```bash
37+
python3 basic_example.py \
38+
--publishable-key "your_publishable_key_here" \
39+
--app-slug "your-app-slug" \
40+
--base-url "https://staging.replicated.app" \
41+
--customer-email "[email protected]" \
42+
--customer-name "Jane Smith" \
43+
--channel "beta"
44+
```
45+
46+
## Other Examples
47+
48+
- `sync_example.py`: More comprehensive synchronous example with metrics and status updates
49+
- `async_example.py`: Asynchronous version of the SDK usage

examples/basic_example.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Basic example of using the Replicated Python SDK.
4+
This script initializes the replicated package, creates a customer and instance.
5+
"""
6+
7+
import argparse
8+
9+
from replicated import ReplicatedClient
10+
11+
12+
def main():
13+
parser = argparse.ArgumentParser(description="Basic Replicated SDK example")
14+
parser.add_argument(
15+
"--base-url",
16+
default="https://replicated.app",
17+
help="Base URL for the Replicated API (default: https://replicated.app)",
18+
)
19+
parser.add_argument(
20+
"--publishable-key",
21+
required=True,
22+
help="Your Replicated publishable key (required)",
23+
)
24+
parser.add_argument(
25+
"--app-slug", required=True, help="Your application slug (required)"
26+
)
27+
parser.add_argument(
28+
"--customer-email",
29+
default="[email protected]",
30+
help="Customer email address (default: [email protected])",
31+
)
32+
parser.add_argument("--channel", help="Channel for the customer (optional)")
33+
parser.add_argument("--customer-name", help="Customer name (optional)")
34+
35+
args = parser.parse_args()
36+
37+
print("Initializing Replicated client...")
38+
print(f"Base URL: {args.base_url}")
39+
print(f"App Slug: {args.app_slug}")
40+
41+
# Initialize the client
42+
with ReplicatedClient(
43+
publishable_key=args.publishable_key,
44+
app_slug=args.app_slug,
45+
base_url=args.base_url,
46+
) as client:
47+
print("✓ Replicated client initialized successfully")
48+
49+
# Create or get customer
50+
channel_info = f" (channel: {args.channel})" if args.channel else ""
51+
name_info = f" (name: {args.customer_name})" if args.customer_name else ""
52+
print(
53+
f"\nCreating/getting customer with email: "
54+
f"{args.customer_email}{channel_info}{name_info}"
55+
)
56+
customer = client.customer.get_or_create(
57+
email_address=args.customer_email,
58+
channel=args.channel,
59+
name=args.customer_name,
60+
)
61+
print(f"✓ Customer created/retrieved - ID: {customer.customer_id}")
62+
63+
# Create or get instance
64+
print("\nCreating/getting instance for customer...")
65+
instance = customer.get_or_create_instance()
66+
print(f"✓ Instance created/retrieved - ID: {instance.instance_id}")
67+
68+
print("\n🎉 Basic example completed successfully!")
69+
print(f"Customer ID: {customer.customer_id}")
70+
print(f"Instance ID: {instance.instance_id}")
71+
72+
73+
if __name__ == "__main__":
74+
main()

replicated/http_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
5151
error_message = json_body.get("message", default_msg)
5252
error_code = json_body.get("code")
5353

54+
# Debug: print the full error response
55+
print(f"DEBUG: HTTP {response.status_code} Error Response:")
56+
print(f"DEBUG: Response body: {response.text}")
57+
print(f"DEBUG: JSON body: {json_body}")
58+
5459
if response.status_code == 401:
5560
raise ReplicatedAuthError(
5661
message=error_message,

replicated/services.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,57 @@ def get_or_create(
1717
self,
1818
email_address: str,
1919
channel: Optional[str] = None,
20+
name: Optional[str] = None,
2021
) -> Customer:
2122
"""Get or create a customer."""
22-
# Check if customer ID is cached
23+
# Check if customer ID is cached and email matches
2324
cached_customer_id = self._client.state_manager.get_customer_id()
24-
if cached_customer_id:
25+
cached_email = self._client.state_manager.get_customer_email()
26+
27+
if cached_customer_id and cached_email == email_address:
28+
print(
29+
f"DEBUG: Using cached customer ID {cached_customer_id} "
30+
f"for email {email_address}"
31+
)
2532
return Customer(
2633
self._client,
2734
cached_customer_id,
2835
email_address,
2936
channel,
3037
)
38+
elif cached_customer_id and cached_email != email_address:
39+
print(
40+
f"DEBUG: Email changed from {cached_email} to "
41+
f"{email_address}, clearing cache"
42+
)
43+
self._client.state_manager.clear_state()
3144

3245
# Create or fetch customer
3346
response = self._client.http_client._make_request(
3447
"POST",
35-
"/api/v1/customers",
48+
"/v3/customer",
3649
json_data={
3750
"email_address": email_address,
3851
"channel": channel,
52+
"name": name,
3953
"app_slug": self._client.app_slug,
4054
},
4155
headers=self._client._get_auth_headers(),
4256
)
4357

44-
customer_id = response["id"]
58+
print(f"DEBUG: API Response: {response}")
59+
customer_id = response["customer"]["id"]
4560
self._client.state_manager.set_customer_id(customer_id)
61+
self._client.state_manager.set_customer_email(email_address)
4662

4763
# Store dynamic token if provided
4864
if "dynamic_token" in response:
4965
dynamic_token = response["dynamic_token"]
5066
self._client.state_manager.set_dynamic_token(dynamic_token)
67+
elif "customer" in response and "serviceToken" in response["customer"]:
68+
service_token = response["customer"]["serviceToken"]
69+
self._client.state_manager.set_dynamic_token(service_token)
70+
print(f"DEBUG: Stored service token: {service_token[:20]}...")
5171

5272
response_data = response.copy()
5373
response_data.pop("email_address", None)
@@ -71,37 +91,57 @@ async def get_or_create(
7191
self,
7292
email_address: str,
7393
channel: Optional[str] = None,
94+
name: Optional[str] = None,
7495
) -> AsyncCustomer:
7596
"""Get or create a customer."""
76-
# Check if customer ID is cached
97+
# Check if customer ID is cached and email matches
7798
cached_customer_id = self._client.state_manager.get_customer_id()
78-
if cached_customer_id:
99+
cached_email = self._client.state_manager.get_customer_email()
100+
101+
if cached_customer_id and cached_email == email_address:
102+
print(
103+
f"DEBUG: Using cached customer ID {cached_customer_id} "
104+
f"for email {email_address}"
105+
)
79106
return AsyncCustomer(
80107
self._client,
81108
cached_customer_id,
82109
email_address,
83110
channel,
84111
)
112+
elif cached_customer_id and cached_email != email_address:
113+
print(
114+
f"DEBUG: Email changed from {cached_email} to "
115+
f"{email_address}, clearing cache"
116+
)
117+
self._client.state_manager.clear_state()
85118

86119
# Create or fetch customer
87120
response = await self._client.http_client._make_request_async(
88121
"POST",
89-
"/api/v1/customers",
122+
"/v3/customer",
90123
json_data={
91124
"email_address": email_address,
92125
"channel": channel,
126+
"name": name,
93127
"app_slug": self._client.app_slug,
94128
},
95129
headers=self._client._get_auth_headers(),
96130
)
97131

98-
customer_id = response["id"]
132+
print(f"DEBUG: API Response: {response}")
133+
customer_id = response["customer"]["id"]
99134
self._client.state_manager.set_customer_id(customer_id)
135+
self._client.state_manager.set_customer_email(email_address)
100136

101137
# Store dynamic token if provided
102138
if "dynamic_token" in response:
103139
dynamic_token = response["dynamic_token"]
104140
self._client.state_manager.set_dynamic_token(dynamic_token)
141+
elif "customer" in response and "serviceToken" in response["customer"]:
142+
service_token = response["customer"]["serviceToken"]
143+
self._client.state_manager.set_dynamic_token(service_token)
144+
print(f"DEBUG: Stored service token: {service_token[:20]}...")
105145

106146
response_data = response.copy()
107147
response_data.pop("email_address", None)

replicated/state.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ def set_dynamic_token(self, token: str) -> None:
9292
state["dynamic_token"] = token
9393
self.save_state(state)
9494

95+
def get_customer_email(self) -> Optional[str]:
96+
"""Get the cached customer email."""
97+
state = self.get_state()
98+
return state.get("customer_email")
99+
100+
def set_customer_email(self, email: str) -> None:
101+
"""Set the customer email in state."""
102+
state = self.get_state()
103+
state["customer_email"] = email
104+
self.save_state(state)
105+
95106
def clear_state(self) -> None:
96107
"""Clear all cached state."""
97108
if self._state_file.exists():

tests/test_client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ def test_customer_creation(self, mock_httpx):
2323
mock_response = Mock()
2424
mock_response.is_success = True
2525
mock_response.json.return_value = {
26-
"id": "customer_123",
27-
"email_address": "[email protected]",
26+
"customer": {
27+
"id": "customer_123",
28+
"email": "[email protected]",
29+
"name": "test user",
30+
}
2831
}
2932

3033
mock_client = Mock()

0 commit comments

Comments
 (0)