Skip to content

Commit 0487826

Browse files
Merge pull request #216 from runpod/sdk-examples
Sdk examples
2 parents 28fb78a + 95ddc39 commit 0487826

File tree

7 files changed

+400
-200
lines changed

7 files changed

+400
-200
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Change Log
22

3+
## Release 1.3.3 (11/8/23)
4+
5+
### Added
6+
7+
- Method of creating logs with job id.
8+
9+
### Fixed
10+
11+
- Reduced polling when checking for job completion.
12+
- Removed print statements for endpoint calls.
13+
- Serverless progress updates no longer restricted to only strings.
14+
15+
## Changed
16+
17+
- Removed `pillow` dependency.
18+
- Removed `python-dotenv` dependency.
19+
- Removed `setuptools_scm` from required dependencies.
20+
21+
---
22+
323
## Release 1.3.2 (11/3/23)
424

525
### Changed

examples/call_endpoint.py

Lines changed: 0 additions & 18 deletions
This file was deleted.
File renamed without changes.

examples/endpoints/run.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'''
2+
Example of calling an endpoint using the RunPod Python Language Library.
3+
'''
4+
5+
import runpod
6+
7+
# Set your global API key with `runpod config` or uncomment the line below:
8+
# runpod.api_key = "YOUR_RUNPOD_API_KEY"
9+
10+
endpoint = runpod.Endpoint("sdxl") # Where "sdxl" is the endpoint ID
11+
12+
run_request = endpoint.run({
13+
"input": {
14+
"prompt": "a photo of a horse the size of a Boeing 787"
15+
}
16+
})
17+
18+
# Check the status of the run request
19+
print(run_request.status())
20+
21+
# Get the output of the endpoint run request.
22+
print(run_request.output())
23+
24+
# Get the output of the endpoint run request.
25+
# If timeout is greater than 0, this will block until the endpoint run is complete.
26+
print(run_request.output(timeout=60))

examples/endpoints/run_sync.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'''
2+
Example of calling an endpoint using the RunPod Python Language Library.
3+
'''
4+
5+
import runpod
6+
7+
# Set your global API key with `runpod config` or uncomment the line below:
8+
# runpod.api_key = "YOUR_RUNPOD_API_KEY"
9+
10+
endpoint = runpod.Endpoint("sdxl") # Where "sdxl" is the endpoint ID
11+
12+
try:
13+
# Run the endpoint synchronously, blocking until the endpoint run is complete.
14+
run_request = endpoint.run_sync(
15+
{
16+
"input": {
17+
"prompt": "a photo of a horse the size of a Boeing 787"
18+
}
19+
},
20+
timeout=60 # Seconds
21+
)
22+
23+
print(run_request)
24+
except TimeoutError as err:
25+
print("Job timed out.")

runpod/endpoint/runner.py

Lines changed: 133 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,197 @@
11
'''
22
RunPod | Python | Endpoint Runner
33
'''
4-
from typing import Any, Union
4+
from typing import Any, Optional, Dict
55
import time
66
import requests
77
from requests.adapters import HTTPAdapter, Retry
88

9+
# Exception Messages
10+
UNAUTHORIZED_MSG = "401 Unauthorized | Make sure Runpod API key is set and valid."
11+
API_KEY_NOT_SET_MSG = ("Expected `run_pod.api_key` to be initialized. "
12+
"You can solve this by setting `run_pod.api_key = 'your-key'. "
13+
"An API key can be generated at "
14+
"https://runpod.io/console/user/settings")
15+
916

1017
# ---------------------------------------------------------------------------- #
1118
# Client #
1219
# ---------------------------------------------------------------------------- #
1320
class RunPodClient:
14-
''' A client for running endpoint calls. '''
21+
"""A client for running endpoint calls."""
1522

1623
def __init__(self):
17-
'''
18-
Initialize the client.
19-
'''
24+
"""
25+
Initialize a RunPodClient instance.
26+
27+
Raises:
28+
RuntimeError: If the API key has not been initialized.
29+
"""
2030
from runpod import api_key, endpoint_url_base # pylint: disable=import-outside-toplevel, cyclic-import
31+
2132
if api_key is None:
22-
raise RuntimeError(
23-
"Expected `run_pod.api_key` to be initialized. "
24-
"You can solve this by running `run_pod.api_key = 'your-key'. "
25-
"An API key can be generated at "
26-
"https://www.runpod.io/console/user/settings"
27-
)
33+
raise RuntimeError(API_KEY_NOT_SET_MSG)
34+
2835
self.rp_session = requests.Session()
29-
retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[429])
36+
retries = Retry(total=5, backoff_factor=1, status_forcelist=[429])
3037
self.rp_session.mount('http://', HTTPAdapter(max_retries=retries))
3138

32-
self.endpoint_url_base = endpoint_url_base
3339
self.headers = {
3440
"Content-Type": "application/json",
3541
"Authorization": f"Bearer {api_key}"
3642
}
3743

38-
def post(self, endpoint : str, data : dict, timeout : int=10):
39-
'''
40-
Post to the endpoint.
41-
'''
42-
url = f"{self.endpoint_url_base}/{endpoint}"
43-
return self.rp_session.post(url, headers=self.headers, json=data, timeout=timeout)
44+
self.endpoint_url_base = endpoint_url_base
45+
46+
def _request(self,
47+
method: str, endpoint: str, data: Optional[dict] = None, timeout: int = 10):
48+
"""
49+
Make a request to the specified endpoint using the given HTTP method.
50+
51+
Args:
52+
method: The HTTP method to use ('GET' or 'POST').
53+
endpoint: The endpoint path to which the request will be made.
54+
data: The JSON payload to send with the request.
55+
timeout: The number of seconds to wait for the server to send data before giving up.
56+
57+
Returns:
58+
The JSON response from the server.
4459
45-
def get(self, endpoint : str, timeout : int=10):
46-
'''
47-
Get from the endpoint.
48-
'''
60+
Raises:
61+
RuntimeError: If the response returns a 401 Unauthorized status.
62+
requests.HTTPError: If the response contains an unsuccessful status code.
63+
"""
4964
url = f"{self.endpoint_url_base}/{endpoint}"
50-
return self.rp_session.get(url, headers=self.headers, timeout=timeout)
65+
response = self.rp_session.request(
66+
method, url, headers=self.headers, json=data, timeout=timeout)
67+
68+
if response.status_code == 401:
69+
raise RuntimeError(UNAUTHORIZED_MSG)
70+
71+
response.raise_for_status()
72+
return response.json()
73+
74+
def post(self, endpoint: str, data: dict, timeout: int = 10):
75+
""" Post to the endpoint. """
76+
return self._request('POST', endpoint, data, timeout)
77+
78+
def get(self, endpoint: str, timeout: int = 10):
79+
""" Get from the endpoint. """
80+
return self._request('GET', endpoint, timeout=timeout)
81+
5182

5283
# ---------------------------------------------------------------------------- #
5384
# Job #
5485
# ---------------------------------------------------------------------------- #
5586
class Job:
56-
''' Creates a class to run a job. '''
87+
"""Represents a job to be run on the RunPod service."""
5788

58-
def __init__(self, endpoint_id : str, job_id : str):
59-
''' Initializes the class. '''
89+
def __init__(self, endpoint_id: str, job_id: str, client: RunPodClient):
90+
"""
91+
Initialize a Job instance with the given endpoint ID and job ID.
6092
93+
Args:
94+
endpoint_id: The identifier for the endpoint.
95+
job_id: The identifier for the job.
96+
client: An instance of the RunPodClient to make requests with.
97+
"""
6198
self.endpoint_id = endpoint_id
6299
self.job_id = job_id
63-
self.rp_client = RunPodClient()
100+
self.rp_client = client
64101

102+
self.job_status = None
65103
self.job_output = None
66104

67-
def _status_json(self):
68-
"""
69-
Returns the raw json of the status, raises an exception if invalid
70-
"""
71-
105+
def _fetch_job(self):
106+
""" Returns the raw json of the status, raises an exception if invalid """
72107
status_url = f"{self.endpoint_id}/status/{self.job_id}"
108+
job_state = self.rp_client.get(endpoint=status_url)
73109

74-
status_request = self.rp_client.get(endpoint=status_url, timeout=10)
75-
request_json = status_request.json()
110+
if job_state["status"] in ["COMPLETED", "FAILED", "TIMEOUT"]:
111+
self.job_status = job_state["status"]
112+
self.job_output = job_state.get("output", None)
76113

77-
if "error" in request_json:
78-
raise RuntimeError(f"Error from RunPod Server: '{request_json['error']}'")
79-
80-
return request_json
114+
return job_state
81115

82116
def status(self):
83-
'''
84-
Returns the status of the job request.
85-
'''
86-
return self._status_json()["status"]
117+
""" Returns the status of the job request. """
118+
if self.job_status is not None:
119+
return self.job_status
87120

88-
def output(self, timeout : int=60) -> Union[None, dict]:
89-
'''
90-
Gets the output of the endpoint run request.
121+
return self._fetch_job()["status"]
91122

92-
:param timeout: after how much time should the request timeout?
93-
(if it doesn't get a response back)
94-
'''
95-
while self.status() not in ["COMPLETED", "FAILED", "TIMEOUT"]:
96-
time.sleep(.1)
97-
timeout -= .1
123+
def output(self, timeout: int = 0) -> Any:
124+
"""
125+
Returns the output of the job request.
98126
99-
if self.job_output is None:
100-
status_json = self._status_json()
101-
if "output" not in status_json:
102-
return None
103-
self.job_output = status_json["output"]
127+
Args:
128+
timeout: The number of seconds to wait for the server to send data before giving up.
129+
"""
130+
if timeout > 0:
131+
while self.status() not in ["COMPLETED", "FAILED", "TIMEOUT"]:
132+
time.sleep(1)
133+
timeout -= 1
134+
if timeout <= 0:
135+
raise TimeoutError("Job timed out.")
104136

105-
return self.job_output
137+
if self.job_output is not None:
138+
return self.job_output
106139

140+
return self._fetch_job().get("output", None)
107141

108142

109143
# ---------------------------------------------------------------------------- #
110144
# Endpoint #
111145
# ---------------------------------------------------------------------------- #
112146
class Endpoint:
113-
'''Creates a class to run an endpoint.'''
114-
115-
def __init__(self, endpoint_id : str):
116-
'''
117-
Initializes the class
118-
119-
:param endpoint_id: the id of the endpoint
120-
121-
:example:
122-
123-
>>> endpoint = runpod.Endpoint("ENDPOINT_ID")
124-
>>> run_request = endpoint.run(
125-
{"your_model_input_key": "your_model_input_value"}
126-
)
127-
>>> print(run_request.status())
128-
129-
>>> print(run_request.output())
130-
'''
131-
# the endpoint id
132-
self.endpoint_id : str = endpoint_id
147+
"""Manages an endpoint to run jobs on the RunPod service."""
148+
149+
def __init__(self, endpoint_id: str):
150+
"""
151+
Initialize an Endpoint instance with the given endpoint ID.
152+
153+
Args:
154+
endpoint_id: The identifier for the endpoint.
155+
156+
Example:
157+
>>> endpoint = runpod.Endpoint("ENDPOINT_ID")
158+
>>> run_request = endpoint.run({"your_model_input_key": "your_model_input_value"})
159+
>>> print(run_request.status())
160+
>>> print(run_request.output())
161+
"""
162+
self.endpoint_id = endpoint_id
133163
self.rp_client = RunPodClient()
134164

135-
print(f"Initialized endpoint: {self.endpoint_id}")
165+
def run(self, request_input: Dict[str, Any]) -> Job:
166+
"""
167+
Run the endpoint with the given input.
136168
137-
def run(self, endpoint_input : Any) -> Job:
138-
'''
139-
Runs the endpoint.
169+
Args:
170+
request_input: The input to pass into the endpoint.
140171
141-
:param endpoint_input: the input to pass into the endpoint
142-
'''
143-
job_request = self.rp_client.post(
144-
endpoint=f"{self.endpoint_id}/run",
145-
data={"input": endpoint_input},
146-
timeout=10
147-
)
172+
Returns:
173+
A Job instance for the run request.
174+
"""
175+
if not request_input.get("input"):
176+
request_input = {"input": request_input}
148177

149-
if job_request.status_code == 401:
150-
raise RuntimeError("401 Unauthorized | Make sure Runpod API key is set and valid.")
178+
job_request = self.rp_client.post(f"{self.endpoint_id}/run", request_input)
179+
return Job(self.endpoint_id, job_request["id"], self.rp_client)
151180

152-
print(f"Started job: {job_request.json()['id']}")
181+
def run_sync(self, request_input: Dict[str, Any], timeout: int = 86400) -> Dict[str, Any]:
182+
"""
183+
Run the endpoint with the given input synchronously.
153184
154-
return Job(self.endpoint_id, job_request.json()["id"])
185+
Args:
186+
request_input: The input to pass into the endpoint.
187+
"""
188+
if not request_input.get("input"):
189+
request_input = {"input": request_input}
155190

156-
def run_sync(self, endpoint_input : Any) -> dict:
157-
'''
158-
Blocking run where the job results are returned with the call.
191+
job_request = self.rp_client.post(
192+
f"{self.endpoint_id}/runsync", request_input, timeout=timeout)
159193

160-
:param endpoint_input: the input to pass into the endpoint
161-
'''
162-
job_return = self.rp_client.post(
163-
endpoint=f"{self.endpoint_id}/runsync",
164-
data={"input": endpoint_input},
165-
timeout=60
166-
)
194+
if job_request["status"] in ["COMPLETED", "FAILED", "TIMEOUT"]:
195+
return job_request.get("output", None)
167196

168-
return job_return.json()
197+
return Job(self.endpoint_id, job_request["id"], self.rp_client).output(timeout=timeout)

0 commit comments

Comments
 (0)