proper status code, added detail in reposne

This commit is contained in:
2025-05-31 21:12:51 +05:00
parent b56cfd59a3
commit 247eece2a2
+88 -112
View File
@@ -1,144 +1,120 @@
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from contextlib import asynccontextmanager from fastapi.responses import JSONResponse
import httpx
import re import re
import os import os
from typing import Dict, Optional import httpx
from urllib.parse import unquote
local_mac_vendors: Dict[str, str] = {} app = FastAPI(title="MAC Address Vendor Lookup API")
@asynccontextmanager def normalize_mac(mac_address: str) -> str:
async def lifespan(app: FastAPI): mac_clean = unquote(mac_address)
load_local_mac_vendors() mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_clean)
yield
if len(mac_clean) < 6:
return None
return '-'.join([mac_clean[i:i+2].upper() for i in range(0, len(mac_clean), 2)])
app = FastAPI(title="MAC Address Vendor Lookup", version="1.0.0", lifespan=lifespan) def is_valid_mac(mac_address: str) -> bool:
mac_clean = unquote(mac_address)
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_clean)
return len(mac_clean) >= 6 and len(mac_clean) <= 12 and all(c in '0123456789abcdefABCDEF' for c in mac_clean)
local_mac_vendors: Dict[str, str] = {} def get_mac_prefix(mac_address: str) -> str:
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_address)
return mac_clean[:6].upper()
def load_local_mac_vendors(): def lookup_local_file(mac_prefix: str) -> str:
"""Load MAC vendor data from local file""" if not os.path.exists('mac_list.txt'):
global local_mac_vendors return None
if local_mac_vendors or not os.path.exists("mac_list.txt"):
return
try: try:
with open("mac_list.txt", "r", encoding="utf-8") as file: with open('mac_list.txt', 'r') as f:
for line in file: for line in f:
line = line.strip() line = line.strip()
if line: if line and ' ' in line:
parts = line.split('\t', 1) prefix, vendor = line.split(' ', 1)
if len(parts) == 2: if prefix.upper() == mac_prefix:
oui = parts[0].strip().upper() return vendor
vendor = parts[1].strip()
local_mac_vendors[oui] = vendor
except Exception: except Exception:
pass pass
return None
async def lookup_from_github(oui: str) -> Optional[str]: async def lookup_remote_gist(mac_prefix: str) -> str:
"""Lookup vendor from GitHub gist"""
url = "https://gist.githubusercontent.com/aallan/b4bb86db86079509e6159810ae9bd3e4/raw/846ae1b646ab0f4d646af9115e47365f4118e5f6/mac-vendor.txt" url = "https://gist.githubusercontent.com/aallan/b4bb86db86079509e6159810ae9bd3e4/raw/846ae1b646ab0f4d646af9115e47365f4118e5f6/mac-vendor.txt"
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0) response = await client.get(url, timeout=5.0)
response.raise_for_status()
for line in response.text.strip().split('\n'):
if line.strip():
parts = line.split('\t', 1)
if len(parts) == 2 and parts[0].strip().upper() == oui:
return parts[1].strip()
except Exception:
pass
return None
async def lookup_from_macvendors_api(mac_address: str) -> Optional[str]:
"""Lookup vendor from macvendors.com API"""
try:
formatted_mac = normalize_mac_for_api(mac_address)
url = f"https://api.macvendors.com/{formatted_mac}"
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0)
if response.status_code == 200: if response.status_code == 200:
content_type = response.headers.get("content-type", "") for line in response.text.split('\n'):
if "application/json" in content_type: line = line.strip()
return None if line and ' ' in line:
else: prefix, vendor = line.split(' ', 1)
return response.text.strip() if prefix.upper() == mac_prefix:
return vendor
except Exception: except Exception:
pass pass
return None return None
def normalize_mac_for_api(mac_address: str) -> str: async def lookup_remote_api(normalized_mac: str) -> str:
"""Normalize MAC address for external API (XX-XX-XX format)""" mac_for_api = normalized_mac
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper()) if len(mac_for_api.replace('-', '')) == 6:
if len(clean_mac) >= 6: mac_for_api = mac_for_api + '-00-00-00'
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}"
else:
raise ValueError("Invalid MAC address format")
def normalize_mac_response(mac_address: str) -> str:
"""Normalize MAC address for response format"""
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper())
if len(clean_mac) == 6: url = f"https://api.macvendors.com/{mac_for_api}"
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}"
elif len(clean_mac) == 12: try:
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}-{clean_mac[6:8]}-{clean_mac[8:10]}-{clean_mac[10:12]}" async with httpx.AsyncClient() as client:
else: response = await client.get(url, timeout=5.0)
raise ValueError("Invalid MAC address format") if response.status_code == 200:
return response.text.strip()
def get_oui(mac_address: str) -> str: except Exception:
"""Extract OUI (first 6 hex digits) from MAC address""" pass
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper())
if len(clean_mac) >= 6: return None
return clean_mac[:6]
else:
raise ValueError("MAC address too short")
@app.get("/")
async def root():
return {"message": "MAC Address Vendor Lookup API"}
@app.get("/lookup/{mac_address}") @app.get("/lookup/{mac_address}")
async def lookup_vendor(mac_address: str): async def lookup_mac_vendor(mac_address: str):
try: if not is_valid_mac(mac_address):
oui = get_oui(mac_address) return JSONResponse(
formatted_mac = normalize_mac_response(mac_address) status_code=400,
vendor = None content={
"mac_address": mac_address,
# 1. Try local file first "vendor": None,
if oui in local_mac_vendors: "detail": "Invalid MAC address format"
vendor = local_mac_vendors[oui] }
)
# 2. Try GitHub gist if not found locally
if not vendor: normalized_mac = normalize_mac(mac_address)
vendor = await lookup_from_github(oui) mac_prefix = get_mac_prefix(mac_address)
# 3. Try macvendors.com API if still not found vendor = lookup_local_file(mac_prefix)
if not vendor:
vendor = await lookup_from_macvendors_api(mac_address) if not vendor:
vendor = await lookup_remote_gist(mac_prefix)
# Default to Unknown if nothing found
if not vendor: if not vendor:
vendor = "Unknown" vendor = await lookup_remote_api(normalized_mac)
if vendor:
return { return {
"mac_address": formatted_mac, "mac_address": normalized_mac,
"vendor": vendor "vendor": vendor,
"detail": None
} }
else:
except ValueError as e: return JSONResponse(
raise HTTPException(status_code=400, detail=str(e)) status_code=404,
except Exception as e: content={
raise HTTPException(status_code=500, detail="Internal server error") "mac_address": normalized_mac,
"vendor": None,
"detail": "Vendor not found"
}
)
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn