From 5f9e1541a209354de13d2181428a82093b7f708d Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 21 Aug 2025 14:08:36 -0400 Subject: [PATCH] Client Level API Client Level API Key + Tests --- .gitignore | 1 + README.md | 100 ++++++++++++++++++++- runpod/api/ctl_commands.py | 58 ++++++++---- runpod/api/graphql.py | 20 +++-- runpod/endpoint/asyncio/asyncio_runner.py | 77 ++++++++++------ runpod/endpoint/runner.py | 53 +++++++---- tests/test_api/test_ctl_commands.py | 5 ++ tests/test_endpoint/test_asyncio_runner.py | 57 ++++++++++-- tests/test_endpoint/test_runner.py | 50 ++++++++++- 9 files changed, 346 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index 1ccfa853..36fa868c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ test_logging.py /example_project IGNORE.py /quick-test +/local-test .DS_Store # Byte-compiled / optimized / DLL files diff --git a/README.md b/README.md index 7a99b693..b57da4c5 100644 --- a/README.md +++ b/README.md @@ -30,18 +30,37 @@ Welcome to the official Python library for Runpod API & SDK. ## 💻 | Installation +### Install from PyPI (Stable Release) + ```bash -# Install the latest release version +# Install with pip pip install runpod -# Using uv (faster alternative) +# Install with uv (faster alternative) uv add runpod +``` + +### Install from GitHub (Latest Changes) -# Install the latest development version (main branch) +To get the latest changes that haven't been released to PyPI yet: + +```bash +# Install latest development version from main branch with pip pip install git+https://github.com/runpod/runpod-python.git -# Or with uv +# Install with uv uv add git+https://github.com/runpod/runpod-python.git + +# Install a specific branch +pip install git+https://github.com/runpod/runpod-python.git@branch-name + +# Install a specific tag/release +pip install git+https://github.com/runpod/runpod-python.git@v1.0.0 + +# Install in editable mode for development +git clone https://github.com/runpod/runpod-python.git +cd runpod-python +pip install -e . ``` *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" You can interact with Runpod endpoints via a `run` or `run_sync` method. +#### Basic Usage + ```python endpoint = runpod.Endpoint("ENDPOINT_ID") @@ -126,6 +147,77 @@ run_request = endpoint.run_sync( print(run_request ) ``` +#### API Key Management + +The SDK supports multiple ways to set API keys: + +**1. Global API Key** (Default) +```python +import runpod + +# Set global API key +runpod.api_key = "your_runpod_api_key" + +# All endpoints will use this key by default +endpoint = runpod.Endpoint("ENDPOINT_ID") +result = endpoint.run_sync({"input": "data"}) +``` + +**2. Endpoint-Specific API Key** +```python +# Create endpoint with its own API key +endpoint = runpod.Endpoint("ENDPOINT_ID", api_key="specific_api_key") + +# This endpoint will always use the provided API key +result = endpoint.run_sync({"input": "data"}) +``` + +#### API Key Precedence + +The SDK uses this precedence order (highest to lowest): +1. Endpoint instance API key (if provided to `Endpoint()`) +2. Global API key (set via `runpod.api_key`) + +```python +import runpod + +# Example showing precedence +runpod.api_key = "GLOBAL_KEY" + +# This endpoint uses GLOBAL_KEY +endpoint1 = runpod.Endpoint("ENDPOINT_ID") + +# This endpoint uses ENDPOINT_KEY (overrides global) +endpoint2 = runpod.Endpoint("ENDPOINT_ID", api_key="ENDPOINT_KEY") + +# All requests from endpoint2 will use ENDPOINT_KEY +result = endpoint2.run_sync({"input": "data"}) +``` + +#### Thread-Safe Operations + +Each `Endpoint` instance maintains its own API key, making concurrent operations safe: + +```python +import threading +import runpod + +def process_request(api_key, endpoint_id, input_data): + # Each thread gets its own Endpoint instance + endpoint = runpod.Endpoint(endpoint_id, api_key=api_key) + return endpoint.run_sync(input_data) + +# Safe concurrent usage with different API keys +threads = [] +for customer in customers: + t = threading.Thread( + target=process_request, + args=(customer["api_key"], customer["endpoint_id"], customer["input"]) + ) + threads.append(t) + t.start() +``` + ### GPU Cloud (Pods) ```python diff --git a/runpod/api/ctl_commands.py b/runpod/api/ctl_commands.py index adfe7ff8..b90c6b90 100644 --- a/runpod/api/ctl_commands.py +++ b/runpod/api/ctl_commands.py @@ -18,43 +18,59 @@ from .queries import user as user_queries -def get_user() -> dict: +def get_user(api_key: Optional[str] = None) -> dict: """ - Get the current user + Get the current user with optional API key override. + + Args: + api_key: Optional API key to use for this query. """ - raw_response = run_graphql_query(user_queries.QUERY_USER) + raw_response = run_graphql_query(user_queries.QUERY_USER, api_key=api_key) cleaned_return = raw_response["data"]["myself"] return cleaned_return -def update_user_settings(pubkey: str) -> dict: +def update_user_settings(pubkey: str, api_key: Optional[str] = None) -> dict: """ Update the current user - :param pubkey: the public key of the user + Args: + pubkey: the public key of the user + api_key: Optional API key to use for this query. """ - raw_response = run_graphql_query(user_mutations.generate_user_mutation(pubkey)) + raw_response = run_graphql_query( + user_mutations.generate_user_mutation(pubkey), + api_key=api_key + ) cleaned_return = raw_response["data"]["updateUserSettings"] return cleaned_return -def get_gpus() -> dict: +def get_gpus(api_key: Optional[str] = None) -> dict: """ Get all GPU types + + Args: + api_key: Optional API key to use for this query. """ - raw_response = run_graphql_query(gpus.QUERY_GPU_TYPES) + raw_response = run_graphql_query(gpus.QUERY_GPU_TYPES, api_key=api_key) cleaned_return = raw_response["data"]["gpuTypes"] return cleaned_return -def get_gpu(gpu_id: str, gpu_quantity: int = 1): +def get_gpu(gpu_id: str, gpu_quantity: int = 1, api_key: Optional[str] = None): """ Get a specific GPU type - :param gpu_id: the id of the gpu - :param gpu_quantity: how many of the gpu should be returned + Args: + gpu_id: the id of the gpu + gpu_quantity: how many of the gpu should be returned + api_key: Optional API key to use for this query. """ - raw_response = run_graphql_query(gpus.generate_gpu_query(gpu_id, gpu_quantity)) + raw_response = run_graphql_query( + gpus.generate_gpu_query(gpu_id, gpu_quantity), + api_key=api_key + ) cleaned_return = raw_response["data"]["gpuTypes"] @@ -67,22 +83,30 @@ def get_gpu(gpu_id: str, gpu_quantity: int = 1): return cleaned_return[0] -def get_pods() -> dict: +def get_pods(api_key: Optional[str] = None) -> dict: """ Get all pods + + Args: + api_key: Optional API key to use for this query. """ - raw_return = run_graphql_query(pod_queries.QUERY_POD) + raw_return = run_graphql_query(pod_queries.QUERY_POD, api_key=api_key) cleaned_return = raw_return["data"]["myself"]["pods"] return cleaned_return -def get_pod(pod_id: str): +def get_pod(pod_id: str, api_key: Optional[str] = None): """ Get a specific pod - :param pod_id: the id of the pod + Args: + pod_id: the id of the pod + api_key: Optional API key to use for this query. """ - raw_response = run_graphql_query(pod_queries.generate_pod_query(pod_id)) + raw_response = run_graphql_query( + pod_queries.generate_pod_query(pod_id), + api_key=api_key + ) return raw_response["data"]["pod"] diff --git a/runpod/api/graphql.py b/runpod/api/graphql.py index 4b28daa5..c7c3ccc3 100644 --- a/runpod/api/graphql.py +++ b/runpod/api/graphql.py @@ -4,7 +4,7 @@ import json import os -from typing import Any, Dict +from typing import Any, Dict, Optional import requests @@ -14,11 +14,21 @@ HTTP_STATUS_UNAUTHORIZED = 401 -def run_graphql_query(query: str) -> Dict[str, Any]: +def run_graphql_query(query: str, api_key: Optional[str] = None) -> Dict[str, Any]: """ - Run a GraphQL query + Run a GraphQL query with optional API key override. + + Args: + query: The GraphQL query to execute. + api_key: Optional API key to use for this query. """ - from runpod import api_key # pylint: disable=import-outside-toplevel, cyclic-import + from runpod import api_key as global_api_key # pylint: disable=import-outside-toplevel, cyclic-import + + # Use provided API key or fall back to global + effective_api_key = api_key or global_api_key + + if not effective_api_key: + raise error.AuthenticationError("No API key provided") api_url_base = os.environ.get("RUNPOD_API_BASE_URL", "https://api.runpod.io") url = f"{api_url_base}/graphql" @@ -26,7 +36,7 @@ def run_graphql_query(query: str) -> Dict[str, Any]: headers = { "Content-Type": "application/json", "User-Agent": USER_AGENT, - "Authorization": f"Bearer {api_key}", + "Authorization": f"Bearer {effective_api_key}", } data = json.dumps({"query": query}) diff --git a/runpod/endpoint/asyncio/asyncio_runner.py b/runpod/endpoint/asyncio/asyncio_runner.py index e5c458f5..375a0552 100644 --- a/runpod/endpoint/asyncio/asyncio_runner.py +++ b/runpod/endpoint/asyncio/asyncio_runner.py @@ -3,7 +3,7 @@ # pylint: disable=too-few-public-methods,R0801 import asyncio -from typing import Any, Dict +from typing import Any, Dict, Optional from runpod.endpoint.helpers import FINAL_STATES, is_completed from runpod.http_client import ClientSession @@ -12,21 +12,25 @@ class Job: """Class representing a job for an asynchronous endpoint""" - def __init__(self, endpoint_id: str, job_id: str, session: ClientSession): + def __init__(self, endpoint_id: str, job_id: str, session: ClientSession, headers: dict): + """ + Initialize a Job instance. + + Args: + endpoint_id: The identifier for the endpoint. + job_id: The identifier for the job. + session: The aiohttp ClientSession. + headers: Headers to use for requests. + """ from runpod import ( # pylint: disable=import-outside-toplevel,cyclic-import - api_key, endpoint_url_base, ) - + self.endpoint_id = endpoint_id self.job_id = job_id - self.headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {api_key}", - "X-Request-ID": job_id, - } self.session = session self.endpoint_url_base = endpoint_url_base + self.headers = headers self.job_status = None self.job_output = None @@ -112,22 +116,40 @@ async def cancel(self) -> dict: class Endpoint: """Class for running endpoint""" - def __init__(self, endpoint_id: str, session: ClientSession): - from runpod import ( - api_key, # pylint: disable=import-outside-toplevel + def __init__(self, endpoint_id: str, session: ClientSession, + api_key: Optional[str] = None): + """ + Initialize an async Endpoint instance. + + Args: + endpoint_id: The identifier for the endpoint. + session: The aiohttp ClientSession. + api_key: Optional API key for this endpoint instance. + """ + from runpod import ( # pylint: disable=import-outside-toplevel + api_key as global_api_key, endpoint_url_base, ) - + self.endpoint_id = endpoint_id + self.session = session + self.endpoint_url_base = endpoint_url_base self.endpoint_url = f"{endpoint_url_base}/{self.endpoint_id}/run" + + # Store instance API key for future requests + self.api_key = api_key or global_api_key + + if self.api_key is None: + raise RuntimeError("API key must be provided or set globally") + self.headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {api_key}", + "Authorization": f"Bearer {self.api_key}", } - self.session = session async def run(self, endpoint_input: dict) -> Job: - """Runs endpoint with specified input + """ + Runs endpoint with specified input. Args: endpoint_input: any dictionary with input @@ -140,26 +162,31 @@ async def run(self, endpoint_input: dict) -> Job: ) as resp: json_resp = await resp.json() - return Job(self.endpoint_id, json_resp["id"], self.session) + # Create job with endpoint's headers + job_headers = self.headers.copy() + job_headers["X-Request-ID"] = json_resp["id"] + return Job(self.endpoint_id, json_resp["id"], self.session, job_headers) async def health(self) -> dict: - """Checks health of endpoint + """ + Checks health of endpoint Returns: Health of endpoint """ - async with self.session.get( - f"{self.endpoint_id}/health", headers=self.headers - ) as resp: + health_url = f"{self.endpoint_url_base}/{self.endpoint_id}/health" + + async with self.session.get(health_url, headers=self.headers) as resp: return await resp.json() async def purge_queue(self) -> dict: - """Purges queue of endpoint + """ + Purges queue of endpoint Returns: Purge status """ - async with self.session.post( - f"{self.endpoint_id}/purge", headers=self.headers - ) as resp: + purge_url = f"{self.endpoint_url_base}/{self.endpoint_id}/purge-queue" + + async with self.session.post(purge_url, headers=self.headers) as resp: return await resp.json() diff --git a/runpod/endpoint/runner.py b/runpod/endpoint/runner.py index a00f9376..d8c48759 100644 --- a/runpod/endpoint/runner.py +++ b/runpod/endpoint/runner.py @@ -22,19 +22,25 @@ class RunPodClient: """A client for running endpoint calls.""" - def __init__(self): + def __init__(self, api_key: Optional[str] = None): """ Initialize a RunPodClient instance. + Args: + api_key: Optional API key. If not provided, uses global api_key. + Raises: RuntimeError: If the API key has not been initialized. """ from runpod import ( # pylint: disable=import-outside-toplevel, cyclic-import - api_key, + api_key as global_api_key, endpoint_url_base, ) - - if api_key is None: + + # Use provided api_key or fall back to global + self.api_key = api_key or global_api_key + + if self.api_key is None: raise RuntimeError(API_KEY_NOT_SET_MSG) self.rp_session = requests.Session() @@ -43,13 +49,14 @@ def __init__(self): self.headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {api_key}", + "Authorization": f"Bearer {self.api_key}", } self.endpoint_url_base = endpoint_url_base def _request( - self, method: str, endpoint: str, data: Optional[dict] = None, timeout: int = 10 + self, method: str, endpoint: str, data: Optional[dict] = None, + timeout: int = 10 ): """ Make a request to the specified endpoint using the given HTTP method. @@ -168,7 +175,9 @@ def cancel(self, timeout: int = 3) -> Any: timeout: The number of seconds to wait for the server to respond before giving up. """ return self.rp_client.post( - f"{self.endpoint_id}/cancel/{self.job_id}", data=None, timeout=timeout + f"{self.endpoint_id}/cancel/{self.job_id}", + data=None, + timeout=timeout ) @@ -178,12 +187,13 @@ def cancel(self, timeout: int = 3) -> Any: class Endpoint: """Manages an endpoint to run jobs on the Runpod service.""" - def __init__(self, endpoint_id: str): + def __init__(self, endpoint_id: str, api_key: Optional[str] = None): """ Initialize an Endpoint instance with the given endpoint ID. Args: endpoint_id: The identifier for the endpoint. + api_key: Optional API key for this endpoint instance. Example: >>> endpoint = runpod.Endpoint("ENDPOINT_ID") @@ -192,7 +202,7 @@ def __init__(self, endpoint_id: str): >>> print(run_request.output()) """ self.endpoint_id = endpoint_id - self.rp_client = RunPodClient() + self.rp_client = RunPodClient(api_key=api_key) def run(self, request_input: Dict[str, Any]) -> Job: """ @@ -207,7 +217,10 @@ def run(self, request_input: Dict[str, Any]) -> Job: if not request_input.get("input"): request_input = {"input": request_input} - job_request = self.rp_client.post(f"{self.endpoint_id}/run", request_input) + job_request = self.rp_client.post( + f"{self.endpoint_id}/run", + request_input + ) return Job(self.endpoint_id, job_request["id"], self.rp_client) def run_sync( @@ -218,20 +231,23 @@ def run_sync( Args: request_input: The input to pass into the endpoint. + timeout: Maximum time to wait for the job to complete. """ if not request_input.get("input"): request_input = {"input": request_input} job_request = self.rp_client.post( - f"{self.endpoint_id}/runsync", request_input, timeout=timeout + f"{self.endpoint_id}/runsync", + request_input, + timeout=timeout ) if job_request["status"] in FINAL_STATES: return job_request.get("output", None) - return Job(self.endpoint_id, job_request["id"], self.rp_client).output( - timeout=timeout - ) + return Job( + self.endpoint_id, job_request["id"], self.rp_client + ).output(timeout=timeout) def health(self, timeout: int = 3) -> Dict[str, Any]: """ @@ -240,7 +256,10 @@ def health(self, timeout: int = 3) -> Dict[str, Any]: Args: timeout: The number of seconds to wait for the server to respond before giving up. """ - return self.rp_client.get(f"{self.endpoint_id}/health", timeout=timeout) + return self.rp_client.get( + f"{self.endpoint_id}/health", + timeout=timeout + ) def purge_queue(self, timeout: int = 3) -> Dict[str, Any]: """ @@ -250,5 +269,7 @@ def purge_queue(self, timeout: int = 3) -> Dict[str, Any]: timeout: The number of seconds to wait for the server to respond before giving up. """ return self.rp_client.post( - f"{self.endpoint_id}/purge-queue", data=None, timeout=timeout + f"{self.endpoint_id}/purge-queue", + data=None, + timeout=timeout ) diff --git a/tests/test_api/test_ctl_commands.py b/tests/test_api/test_ctl_commands.py index 2b2e4301..91febdee 100644 --- a/tests/test_api/test_ctl_commands.py +++ b/tests/test_api/test_ctl_commands.py @@ -9,6 +9,11 @@ class TestCTL(unittest.TestCase): """Tests for CTL Commands""" + def setUp(self): + """Set up test fixtures""" + import runpod + runpod.api_key = "MOCK_API_KEY" + def test_get_user(self): """ Tests get_user diff --git a/tests/test_endpoint/test_asyncio_runner.py b/tests/test_endpoint/test_asyncio_runner.py index 0ec77caf..77237956 100644 --- a/tests/test_endpoint/test_asyncio_runner.py +++ b/tests/test_endpoint/test_asyncio_runner.py @@ -16,6 +16,11 @@ class TestJob(IsolatedAsyncioTestCase): """Tests the Job class.""" + def setUp(self): + """Set up test fixtures""" + import runpod + runpod.api_key = "MOCK_API_KEY" + async def test_status(self): """ Tests Job.status @@ -30,7 +35,12 @@ async def test_status(self): mock_resp.json.return_value = {"status": "COMPLETED"} mock_get.return_value = mock_resp - job = Job("endpoint_id", "job_id", mock_session) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer MOCK_API_KEY", + "X-Request-ID": "job_id", + } + job = Job("endpoint_id", "job_id", mock_session, headers) status = await job.status() assert status == "COMPLETED" assert await job.status() == "COMPLETED" @@ -56,7 +66,12 @@ async def json_side_effect(): mock_resp.json.side_effect = json_side_effect mock_get.return_value = mock_resp - job = Job("endpoint_id", "job_id", mock_session) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer MOCK_API_KEY", + "X-Request-ID": "job_id", + } + job = Job("endpoint_id", "job_id", mock_session, headers) output_task = asyncio.create_task(job.output(timeout=3)) output = await output_task @@ -77,7 +92,12 @@ async def test_output_timeout(self): mock_resp.json.return_value = {"status": "IN_PROGRESS"} mock_get.return_value = mock_resp - job = Job("endpoint_id", "job_id", mock_session) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer MOCK_API_KEY", + "X-Request-ID": "job_id", + } + job = Job("endpoint_id", "job_id", mock_session, headers) output_task = asyncio.create_task(job.output(timeout=1)) with self.assertRaises(TimeoutError): @@ -109,7 +129,12 @@ async def json_side_effect(): mock_resp.json.side_effect = json_side_effect mock_get.return_value = mock_resp - job = Job("endpoint_id", "job_id", mock_session) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer MOCK_API_KEY", + "X-Request-ID": "job_id", + } + job = Job("endpoint_id", "job_id", mock_session, headers) outputs = [] async for stream_output in job.stream(): @@ -127,7 +152,12 @@ async def test_cancel(self): mock_resp.json.return_value.set_result({"result": "CANCELLED"}) mock_session.post.return_value.__aenter__.return_value = mock_resp - job = Job("endpoint_id", "job_id", mock_session) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer MOCK_API_KEY", + "X-Request-ID": "job_id", + } + job = Job("endpoint_id", "job_id", mock_session, headers) cancel_result = await job.cancel() assert cancel_result == {"result": "CANCELLED"} @@ -155,7 +185,12 @@ async def json_side_effect(): mock_resp.json.side_effect = json_side_effect mock_get.return_value = mock_resp - job = Job("endpoint_id", "job_id", mock_session) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer MOCK_API_KEY", + "X-Request-ID": "job_id", + } + job = Job("endpoint_id", "job_id", mock_session, headers) output = await job.output(timeout=3) assert output == "OUTPUT" @@ -163,6 +198,11 @@ async def json_side_effect(): class TestEndpoint(IsolatedAsyncioTestCase): """Unit tests for the Endpoint class.""" + def setUp(self): + """Set up test fixtures""" + import runpod + runpod.api_key = "MOCK_API_KEY" + async def test_run(self): """ Tests Endpoint.run @@ -209,6 +249,11 @@ async def test_purge_queue(self): class TestEndpointInitialization(unittest.TestCase): """Tests for the Endpoint class initialization.""" + def setUp(self): + """Set up test fixtures""" + import runpod + runpod.api_key = "MOCK_API_KEY" + def test_endpoint_initialization(self): """Tests initialization of Endpoint class.""" with patch("aiohttp.ClientSession"): diff --git a/tests/test_endpoint/test_runner.py b/tests/test_endpoint/test_runner.py index 265b6e17..25960323 100644 --- a/tests/test_endpoint/test_runner.py +++ b/tests/test_endpoint/test_runner.py @@ -22,6 +22,42 @@ def test_no_api_key(self): with self.assertRaises(RuntimeError): runpod.api_key = None RunPodClient() + + def test_client_with_custom_api_key(self): + """Test RunPodClient with custom API key""" + custom_key = "CUSTOM_API_KEY" + client = RunPodClient(api_key=custom_key) + + # Verify headers contain custom key + self.assertEqual( + client.headers["Authorization"], + f"Bearer {custom_key}" + ) + self.assertEqual(client.api_key, custom_key) + + def test_client_fallback_to_global(self): + """Test RunPodClient falls back to global API key""" + runpod.api_key = "GLOBAL_API_KEY" + client = RunPodClient() + + self.assertEqual( + client.headers["Authorization"], + "Bearer GLOBAL_API_KEY" + ) + self.assertEqual(client.api_key, "GLOBAL_API_KEY") + + def test_client_custom_overrides_global(self): + """Test custom API key overrides global""" + runpod.api_key = "GLOBAL_API_KEY" + custom_key = "CUSTOM_API_KEY" + client = RunPodClient(api_key=custom_key) + + self.assertEqual( + client.headers["Authorization"], + f"Bearer {custom_key}" + ) + self.assertEqual(client.api_key, custom_key) + @patch.object(requests.Session, "post") def test_post_with_401(self, mock_post): @@ -96,6 +132,16 @@ def setUp(self): """Common setup for the tests.""" runpod.api_key = self.MOCK_API_KEY self.endpoint = Endpoint(self.ENDPOINT_ID) + + def test_endpoint_with_instance_api_key(self): + """Test Endpoint with instance-level API key""" + custom_key = "INSTANCE_API_KEY" + endpoint = Endpoint(self.ENDPOINT_ID, api_key=custom_key) + + # Verify the client was initialized with custom API key + self.assertEqual(endpoint.rp_client.api_key, custom_key) + + @patch("runpod.endpoint.runner.RunPodClient._request") def test_endpoint_run(self, mock_client_request): @@ -109,7 +155,7 @@ def test_endpoint_run(self, mock_client_request): "POST", f"{self.ENDPOINT_ID}/run", {"input": {"YOUR_MODEL_INPUT_JSON": "YOUR_MODEL_INPUT_VALUE"}}, - 10, + 10 ) self.assertIsInstance(run_request, Job) @@ -138,7 +184,7 @@ def test_endpoint_run_sync(self, mock_client_request): "POST", f"{self.ENDPOINT_ID}/runsync", {"input": {"YOUR_MODEL_INPUT_JSON": "YOUR_MODEL_INPUT_VALUE"}}, - 86400, + 86400 ) @patch("runpod.endpoint.runner.RunPodClient._request")