Path: blob/master/webhooks/webhook_tester/webhook_tester.py
1085 views
import argparse1import json2import os3from datetime import datetime, timezone4from io import BytesIO5from typing import Any67import requests8910class WebhookError(Exception):11"""Raised when a webhook response does not return 200 status."""121314class WebhookTester:15"""Main class to use in testing the webhook."""1617def __init__(18self,19url: str,20token: str | None = None,21camera_id: str | None = None,22plate: str | None = None,23region_code: str | None = None,24timestamp: str | None = None,25):26self.url = url or os.environ.get("URL", "")27if not self.url:28raise ValueError("Set URL environment variable when running docker.")29self.token = token or os.environ.get("TOKEN")30self.camera_id = camera_id or os.environ.get("CAMERA", "camera-1")31self.plate = plate or os.environ.get("PLATE", "pl8rec")32self.region_code = region_code or os.environ.get("REGION", "us-ca")33self.timestamp = timestamp or os.environ.get(34"TIMESTAMP", datetime.now(timezone.utc).isoformat()35)3637def get_webhook_payload(self) -> dict[str, Any]:38"""Return a sample payload to the request."""39return {40"json": json.dumps(41{42"hook": {43"target": self.url,44"id": self.camera_id,45"event": "recognition",46"filename": (f"{self.camera_id}_screenshots/image.jpg"),47},48"data": {49"camera_id": self.camera_id,50"filename": (f"{self.camera_id}_screenshots/image.jpg"),51"timestamp": self.timestamp,52"timestamp_local": self.timestamp,53"results": [54{55"box": {56"xmax": 412,57"xmin": 337,58"ymax": 305,59"ymin": 270,60},61"candidates": [62{"plate": self.plate, "score": 0.902},63{"plate": "plbrec", "score": 0.758},64],65"color": [66{"color": "red", "score": 0.699},67{"color": "black", "score": 0.134},68{"color": "blue", "score": 0.03},69],70"dscore": 0.757,71"model_make": [72{"make": "Porsche", "model": "911", "score": 0.43},73{74"make": "Porsche",75"model": "Carrera",76"score": 0.2,77},78{79"make": "Porsche",80"model": "Carrera GTS",81"score": 0.07,82},83],84"orientation": [85{"orientation": "Rear", "score": 0.883},86{"orientation": "Front", "score": 0.07},87{"orientation": "Unknown", "score": 0.047},88],89"plate": self.plate,90"region": {"code": self.region_code, "score": 0.179},91"score": 0.902,92"vehicle": {93"box": {94"xmax": 590,95"xmin": 155,96"ymax": 373,97"ymin": 71,98},99"score": 0.709,100"type": "Sedan",101},102"direction": 210,103"source_url": ("/user-data/video.mp4"),104"position_sec": 23.47,105}106],107},108}109) # end of json.dumps110}111112def get_files_payload(self) -> dict[str, Any]:113"""Return a request payload containing files."""114url = (115"https://platerecognizer.com/wp-content/uploads/2020/07/"116"ALPR-license-plate-reader-images-API.jpg"117)118response = self.send_request("get", url)119print(f"This request includes a {len(response.content) / 1024:.1f} KB image.")120return {"upload": ("image.jpg", BytesIO(response.content))}121122def send_request(123self,124method: str,125url: str,126data: dict[str, Any] | None = None,127files: dict[str, Any] | None = None,128) -> requests.Response:129"""130Send the actual request to the given URL along with the parameters.131132Args:133url (str): The target URL.134data (Union[Dict[str, Any], None]): Payload to send to the URL.135files (Union[Dict[str, Any], None]): Files to send to the URL.136137Returns:138requests.Response: The Response object.139"""140if method.lower() not in ["get", "post"]:141raise ValueError("Method not supported. Only accepts `get` or `post`.")142headers = {}143if self.token:144headers["Authorization"] = f"Token {self.token}"145try:146response = getattr(requests, method.lower())(147url, data=data, files=files, timeout=30, headers=headers148)149except requests.exceptions.Timeout as exc:150raise WebhookError("The request timed out.") from exc151except requests.exceptions.TooManyRedirects as exc:152raise WebhookError(153"The given URL might be misconfigured. Try a different one."154) from exc155except requests.exceptions.RequestException as request_exception:156raise WebhookError(str(request_exception)) from request_exception157158return response159160def execute(self) -> None:161"""Used to test the webhook."""162print(f"{' Sending Webhook (JSON + Image) ':-^80s}")163164payload = self.get_webhook_payload()165files = self.get_files_payload()166response = self.send_request("post", self.url, payload, files)167content = response.text168if response.status_code >= 300:169print(f"--> Invalid status code: {response.status_code}")170raise WebhookError(content)171else:172print(f"Status code: {response.status_code}")173print(f"Response content: {content}")174print("--> The server successfully received the webhook.")175176@staticmethod177def parse_args():178"""Parse command line arguments."""179parser = argparse.ArgumentParser(180description="Test webhook with license plate recognition data"181)182parser.add_argument("--url", help="Webhook URL to test")183parser.add_argument("--token", help="Header authorization token")184parser.add_argument("--camera-id", help="Camera ID (default: camera-1)")185parser.add_argument("--plate", help="License plate text (default: pl8rec)")186parser.add_argument("--region", help="Region code (default: us-ca)")187parser.add_argument(188"--timestamp",189help="Timestamp (default: current UTC time). Format: 2025-08-11T16:42:58.740143Z",190)191return parser.parse_args()192193194if __name__ == "__main__":195args = WebhookTester.parse_args()196try:197tester = WebhookTester(198url=args.url,199token=args.token,200camera_id=args.camera_id,201plate=args.plate,202region_code=args.region,203timestamp=args.timestamp,204)205tester.execute()206except KeyboardInterrupt:207print("Stopping...")208except Exception as e:209print("--> An error occurred:")210print(e)211print("--> The webhook failed.")212213214