33
44import os
55import uuid
6+ import threading
67from dataclasses import dataclass
78from typing import Union , Optional , Dict , Any
89
910import uvicorn
11+ import requests
1012from fastapi import FastAPI , APIRouter
1113from fastapi .encoders import jsonable_encoder
1214from fastapi .responses import RedirectResponse
@@ -53,12 +55,14 @@ class TestJob:
5355 '''
5456 id : Optional [str ] = None
5557 input : Optional [Union [dict , list , str , int , float , bool ]] = None
58+ webhook : Optional [str ] = None
5659
5760
5861@dataclass
59- class DefaultInput :
62+ class DefaultRequest :
6063 """ Represents a test input. """
6164 input : Dict [str , Any ]
65+ webhook : Optional [str ] = None
6266
6367
6468# ------------------------------ Output Objects ------------------------------ #
@@ -80,6 +84,28 @@ class StreamOutput:
8084 error : Optional [str ] = None
8185
8286
87+ # ------------------------------ Webhook Sender ------------------------------ #
88+ def _send_webhook (url : str , payload : Dict [str , Any ]) -> bool :
89+ """
90+ Sends a webhook to the provided URL.
91+
92+ Args:
93+ url (str): The URL to send the webhook to.
94+ payload (Dict[str, Any]): The JSON payload to send.
95+
96+ Returns:
97+ bool: True if the request was successful, False otherwise.
98+ """
99+ with requests .Session () as session :
100+ try :
101+ response = session .post (url , json = payload , timeout = 10 )
102+ response .raise_for_status () # Raises exception for 4xx/5xx responses
103+ return True
104+ except requests .RequestException as err :
105+ print (f"Request to { url } failed: { err } " )
106+ return False
107+
108+
83109# ---------------------------------------------------------------------------- #
84110# API Worker #
85111# ---------------------------------------------------------------------------- #
@@ -176,17 +202,17 @@ async def _realtime(self, job: Job):
176202 # ---------------------------------------------------------------------------- #
177203
178204 # ------------------------------------ run ----------------------------------- #
179- async def _sim_run (self , job_input : DefaultInput ) -> JobOutput :
205+ async def _sim_run (self , job_request : DefaultRequest ) -> JobOutput :
180206 """ Development endpoint to simulate run behavior. """
181207 assigned_job_id = f"test-{ uuid .uuid4 ()} "
182- job_list .add_job (assigned_job_id , job_input .input )
208+ job_list .add_job (assigned_job_id , job_request .input , job_request . webhook )
183209 return jsonable_encoder ({"id" : assigned_job_id , "status" : "IN_PROGRESS" })
184210
185211 # ---------------------------------- runsync --------------------------------- #
186- async def _sim_runsync (self , job_input : DefaultInput ) -> JobOutput :
212+ async def _sim_runsync (self , job_request : DefaultRequest ) -> JobOutput :
187213 """ Development endpoint to simulate runsync behavior. """
188214 assigned_job_id = f"test-{ uuid .uuid4 ()} "
189- job = TestJob (id = assigned_job_id , input = job_input .input )
215+ job = TestJob (id = assigned_job_id , input = job_request .input )
190216
191217 if is_generator (self .config ["handler" ]):
192218 generator_output = run_job_generator (self .config ["handler" ], job .__dict__ )
@@ -203,6 +229,12 @@ async def _sim_runsync(self, job_input: DefaultInput) -> JobOutput:
203229 "error" : job_output ['error' ]
204230 })
205231
232+ if job_request .webhook :
233+ thread = threading .Thread (
234+ target = _send_webhook ,
235+ args = (job_request .webhook , job_output ), daemon = True )
236+ thread .start ()
237+
206238 return jsonable_encoder ({
207239 "id" : job .id ,
208240 "status" : "COMPLETED" ,
@@ -212,15 +244,15 @@ async def _sim_runsync(self, job_input: DefaultInput) -> JobOutput:
212244 # ---------------------------------- stream ---------------------------------- #
213245 async def _sim_stream (self , job_id : str ) -> StreamOutput :
214246 """ Development endpoint to simulate stream behavior. """
215- job_input = job_list .get_job_input (job_id )
216- if job_input is None :
247+ stashed_job = job_list .get_job (job_id )
248+ if stashed_job is None :
217249 return jsonable_encoder ({
218250 "id" : job_id ,
219251 "status" : "FAILED" ,
220252 "error" : "Job ID not found"
221253 })
222254
223- job = TestJob (id = job_id , input = job_input )
255+ job = TestJob (id = job_id , input = stashed_job . input )
224256
225257 if is_generator (self .config ["handler" ]):
226258 generator_output = run_job_generator (self .config ["handler" ], job .__dict__ )
@@ -236,6 +268,12 @@ async def _sim_stream(self, job_id: str) -> StreamOutput:
236268
237269 job_list .remove_job (job .id )
238270
271+ if stashed_job .webhook :
272+ thread = threading .Thread (
273+ target = _send_webhook ,
274+ args = (stashed_job .webhook , stream_accumulator ), daemon = True )
275+ thread .start ()
276+
239277 return jsonable_encoder ({
240278 "id" : job_id ,
241279 "status" : "COMPLETED" ,
@@ -245,15 +283,15 @@ async def _sim_stream(self, job_id: str) -> StreamOutput:
245283 # ---------------------------------- status ---------------------------------- #
246284 async def _sim_status (self , job_id : str ) -> JobOutput :
247285 """ Development endpoint to simulate status behavior. """
248- job_input = job_list .get_job_input (job_id )
249- if job_input is None :
286+ stashed_job = job_list .get_job (job_id )
287+ if stashed_job is None :
250288 return jsonable_encoder ({
251289 "id" : job_id ,
252290 "status" : "FAILED" ,
253291 "error" : "Job ID not found"
254292 })
255293
256- job = TestJob (id = job_id , input = job_input )
294+ job = TestJob (id = stashed_job . id , input = stashed_job . input )
257295
258296 if is_generator (self .config ["handler" ]):
259297 generator_output = run_job_generator (self .config ["handler" ], job .__dict__ )
@@ -272,6 +310,12 @@ async def _sim_status(self, job_id: str) -> JobOutput:
272310 "error" : job_output ['error' ]
273311 })
274312
313+ if stashed_job .webhook :
314+ thread = threading .Thread (
315+ target = _send_webhook ,
316+ args = (stashed_job .webhook , job_output ), daemon = True )
317+ thread .start ()
318+
275319 return jsonable_encoder ({
276320 "id" : job_id ,
277321 "status" : "COMPLETED" ,
0 commit comments