Skip to content

Commit 5f9e154

Browse files
committed
Client Level API
Client Level API Key + Tests
1 parent 84654b9 commit 5f9e154

File tree

9 files changed

+346
-75
lines changed

9 files changed

+346
-75
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ test_logging.py
88
/example_project
99
IGNORE.py
1010
/quick-test
11+
/local-test
1112
.DS_Store
1213

1314
# Byte-compiled / optimized / DLL files

README.md

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,37 @@ Welcome to the official Python library for Runpod API & SDK.
3030

3131
## 💻 | Installation
3232

33+
### Install from PyPI (Stable Release)
34+
3335
```bash
34-
# Install the latest release version
36+
# Install with pip
3537
pip install runpod
3638

37-
# Using uv (faster alternative)
39+
# Install with uv (faster alternative)
3840
uv add runpod
41+
```
42+
43+
### Install from GitHub (Latest Changes)
3944

40-
# Install the latest development version (main branch)
45+
To get the latest changes that haven't been released to PyPI yet:
46+
47+
```bash
48+
# Install latest development version from main branch with pip
4149
pip install git+https://github.com/runpod/runpod-python.git
4250

43-
# Or with uv
51+
# Install with uv
4452
uv add git+https://github.com/runpod/runpod-python.git
53+
54+
# Install a specific branch
55+
pip install git+https://github.com/runpod/runpod-python.git@branch-name
56+
57+
# Install a specific tag/release
58+
pip install git+https://github.com/runpod/[email protected]
59+
60+
# Install in editable mode for development
61+
git clone https://github.com/runpod/runpod-python.git
62+
cd runpod-python
63+
pip install -e .
4564
```
4665

4766
*Python 3.8 or higher is required to use the latest version of this package.*
@@ -101,6 +120,8 @@ runpod.api_key = "your_runpod_api_key_found_under_settings"
101120

102121
You can interact with Runpod endpoints via a `run` or `run_sync` method.
103122

123+
#### Basic Usage
124+
104125
```python
105126
endpoint = runpod.Endpoint("ENDPOINT_ID")
106127

@@ -126,6 +147,77 @@ run_request = endpoint.run_sync(
126147
print(run_request )
127148
```
128149

150+
#### API Key Management
151+
152+
The SDK supports multiple ways to set API keys:
153+
154+
**1. Global API Key** (Default)
155+
```python
156+
import runpod
157+
158+
# Set global API key
159+
runpod.api_key = "your_runpod_api_key"
160+
161+
# All endpoints will use this key by default
162+
endpoint = runpod.Endpoint("ENDPOINT_ID")
163+
result = endpoint.run_sync({"input": "data"})
164+
```
165+
166+
**2. Endpoint-Specific API Key**
167+
```python
168+
# Create endpoint with its own API key
169+
endpoint = runpod.Endpoint("ENDPOINT_ID", api_key="specific_api_key")
170+
171+
# This endpoint will always use the provided API key
172+
result = endpoint.run_sync({"input": "data"})
173+
```
174+
175+
#### API Key Precedence
176+
177+
The SDK uses this precedence order (highest to lowest):
178+
1. Endpoint instance API key (if provided to `Endpoint()`)
179+
2. Global API key (set via `runpod.api_key`)
180+
181+
```python
182+
import runpod
183+
184+
# Example showing precedence
185+
runpod.api_key = "GLOBAL_KEY"
186+
187+
# This endpoint uses GLOBAL_KEY
188+
endpoint1 = runpod.Endpoint("ENDPOINT_ID")
189+
190+
# This endpoint uses ENDPOINT_KEY (overrides global)
191+
endpoint2 = runpod.Endpoint("ENDPOINT_ID", api_key="ENDPOINT_KEY")
192+
193+
# All requests from endpoint2 will use ENDPOINT_KEY
194+
result = endpoint2.run_sync({"input": "data"})
195+
```
196+
197+
#### Thread-Safe Operations
198+
199+
Each `Endpoint` instance maintains its own API key, making concurrent operations safe:
200+
201+
```python
202+
import threading
203+
import runpod
204+
205+
def process_request(api_key, endpoint_id, input_data):
206+
# Each thread gets its own Endpoint instance
207+
endpoint = runpod.Endpoint(endpoint_id, api_key=api_key)
208+
return endpoint.run_sync(input_data)
209+
210+
# Safe concurrent usage with different API keys
211+
threads = []
212+
for customer in customers:
213+
t = threading.Thread(
214+
target=process_request,
215+
args=(customer["api_key"], customer["endpoint_id"], customer["input"])
216+
)
217+
threads.append(t)
218+
t.start()
219+
```
220+
129221
### GPU Cloud (Pods)
130222

131223
```python

runpod/api/ctl_commands.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,59 @@
1818
from .queries import user as user_queries
1919

2020

21-
def get_user() -> dict:
21+
def get_user(api_key: Optional[str] = None) -> dict:
2222
"""
23-
Get the current user
23+
Get the current user with optional API key override.
24+
25+
Args:
26+
api_key: Optional API key to use for this query.
2427
"""
25-
raw_response = run_graphql_query(user_queries.QUERY_USER)
28+
raw_response = run_graphql_query(user_queries.QUERY_USER, api_key=api_key)
2629
cleaned_return = raw_response["data"]["myself"]
2730
return cleaned_return
2831

2932

30-
def update_user_settings(pubkey: str) -> dict:
33+
def update_user_settings(pubkey: str, api_key: Optional[str] = None) -> dict:
3134
"""
3235
Update the current user
3336
34-
:param pubkey: the public key of the user
37+
Args:
38+
pubkey: the public key of the user
39+
api_key: Optional API key to use for this query.
3540
"""
36-
raw_response = run_graphql_query(user_mutations.generate_user_mutation(pubkey))
41+
raw_response = run_graphql_query(
42+
user_mutations.generate_user_mutation(pubkey),
43+
api_key=api_key
44+
)
3745
cleaned_return = raw_response["data"]["updateUserSettings"]
3846
return cleaned_return
3947

4048

41-
def get_gpus() -> dict:
49+
def get_gpus(api_key: Optional[str] = None) -> dict:
4250
"""
4351
Get all GPU types
52+
53+
Args:
54+
api_key: Optional API key to use for this query.
4455
"""
45-
raw_response = run_graphql_query(gpus.QUERY_GPU_TYPES)
56+
raw_response = run_graphql_query(gpus.QUERY_GPU_TYPES, api_key=api_key)
4657
cleaned_return = raw_response["data"]["gpuTypes"]
4758
return cleaned_return
4859

4960

50-
def get_gpu(gpu_id: str, gpu_quantity: int = 1):
61+
def get_gpu(gpu_id: str, gpu_quantity: int = 1, api_key: Optional[str] = None):
5162
"""
5263
Get a specific GPU type
5364
54-
:param gpu_id: the id of the gpu
55-
:param gpu_quantity: how many of the gpu should be returned
65+
Args:
66+
gpu_id: the id of the gpu
67+
gpu_quantity: how many of the gpu should be returned
68+
api_key: Optional API key to use for this query.
5669
"""
57-
raw_response = run_graphql_query(gpus.generate_gpu_query(gpu_id, gpu_quantity))
70+
raw_response = run_graphql_query(
71+
gpus.generate_gpu_query(gpu_id, gpu_quantity),
72+
api_key=api_key
73+
)
5874

5975
cleaned_return = raw_response["data"]["gpuTypes"]
6076

@@ -67,22 +83,30 @@ def get_gpu(gpu_id: str, gpu_quantity: int = 1):
6783
return cleaned_return[0]
6884

6985

70-
def get_pods() -> dict:
86+
def get_pods(api_key: Optional[str] = None) -> dict:
7187
"""
7288
Get all pods
89+
90+
Args:
91+
api_key: Optional API key to use for this query.
7392
"""
74-
raw_return = run_graphql_query(pod_queries.QUERY_POD)
93+
raw_return = run_graphql_query(pod_queries.QUERY_POD, api_key=api_key)
7594
cleaned_return = raw_return["data"]["myself"]["pods"]
7695
return cleaned_return
7796

7897

79-
def get_pod(pod_id: str):
98+
def get_pod(pod_id: str, api_key: Optional[str] = None):
8099
"""
81100
Get a specific pod
82101
83-
:param pod_id: the id of the pod
102+
Args:
103+
pod_id: the id of the pod
104+
api_key: Optional API key to use for this query.
84105
"""
85-
raw_response = run_graphql_query(pod_queries.generate_pod_query(pod_id))
106+
raw_response = run_graphql_query(
107+
pod_queries.generate_pod_query(pod_id),
108+
api_key=api_key
109+
)
86110
return raw_response["data"]["pod"]
87111

88112

runpod/api/graphql.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import json
66
import os
7-
from typing import Any, Dict
7+
from typing import Any, Dict, Optional
88

99
import requests
1010

@@ -14,19 +14,29 @@
1414
HTTP_STATUS_UNAUTHORIZED = 401
1515

1616

17-
def run_graphql_query(query: str) -> Dict[str, Any]:
17+
def run_graphql_query(query: str, api_key: Optional[str] = None) -> Dict[str, Any]:
1818
"""
19-
Run a GraphQL query
19+
Run a GraphQL query with optional API key override.
20+
21+
Args:
22+
query: The GraphQL query to execute.
23+
api_key: Optional API key to use for this query.
2024
"""
21-
from runpod import api_key # pylint: disable=import-outside-toplevel, cyclic-import
25+
from runpod import api_key as global_api_key # pylint: disable=import-outside-toplevel, cyclic-import
26+
27+
# Use provided API key or fall back to global
28+
effective_api_key = api_key or global_api_key
29+
30+
if not effective_api_key:
31+
raise error.AuthenticationError("No API key provided")
2232

2333
api_url_base = os.environ.get("RUNPOD_API_BASE_URL", "https://api.runpod.io")
2434
url = f"{api_url_base}/graphql"
2535

2636
headers = {
2737
"Content-Type": "application/json",
2838
"User-Agent": USER_AGENT,
29-
"Authorization": f"Bearer {api_key}",
39+
"Authorization": f"Bearer {effective_api_key}",
3040
}
3141

3242
data = json.dumps({"query": query})

0 commit comments

Comments
 (0)