Initial release — APIClient - Agent v2.0.0
AI-first API testing desktop client built with Python + PyQt6. Features: - Multi-tab HTTP request editor with params/headers/body/auth/tests - KeyValueTable with per-row enable/disable checkboxes and 36px rows - Format JSON button, syntax highlighting, pre-request & test scripts - Collections, environments, history, import/export (Postman v2.1, cURL) - OpenAPI 3.x / Swagger 2.0 local parser (no AI tokens) - EKIKA Odoo API Framework generator — JSON-API, REST JSON, GraphQL, Custom REST JSON with all auth types (instant, no AI tokens) - Persistent AI chat sidebar (Claude-powered co-pilot) with streaming, context-aware suggestions, and one-click Apply to request editor - AI collection generator from any docs URL or pasted spec - WebSocket client, Mock server, Collection runner, Code generator - Dark/light theme engine (global QSS, object-name selectors) - SSL error detection with actionable hints - MIT License Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
103
app/core/importer.py
Normal file
103
app/core/importer.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""Import from Postman Collection v2.1 JSON or curl command."""
|
||||
import json
|
||||
import re
|
||||
import shlex
|
||||
from app.models import HttpRequest
|
||||
|
||||
|
||||
def from_postman_collection(json_text: str) -> tuple[str, list[HttpRequest]]:
|
||||
"""Returns (collection_name, list of HttpRequest)."""
|
||||
data = json.loads(json_text)
|
||||
name = data.get("info", {}).get("name", "Imported Collection")
|
||||
requests = []
|
||||
|
||||
def parse_item(item):
|
||||
if "request" in item:
|
||||
r = item["request"]
|
||||
method = r.get("method", "GET")
|
||||
url_obj = r.get("url", {})
|
||||
if isinstance(url_obj, str):
|
||||
url = url_obj
|
||||
params = {}
|
||||
else:
|
||||
raw = url_obj.get("raw", "")
|
||||
url = raw.split("?")[0] if "?" in raw else raw
|
||||
params = {}
|
||||
for qp in url_obj.get("query", []):
|
||||
if not qp.get("disabled"):
|
||||
params[qp.get("key", "")] = qp.get("value", "")
|
||||
|
||||
headers = {}
|
||||
for h in r.get("header", []):
|
||||
if not h.get("disabled"):
|
||||
headers[h.get("key", "")] = h.get("value", "")
|
||||
|
||||
body_obj = r.get("body", {})
|
||||
body = ""
|
||||
body_type = "raw"
|
||||
if body_obj:
|
||||
mode = body_obj.get("mode", "raw")
|
||||
if mode == "raw":
|
||||
body = body_obj.get("raw", "")
|
||||
body_type = "raw"
|
||||
elif mode == "urlencoded":
|
||||
pairs = body_obj.get("urlencoded", [])
|
||||
body = "&".join(f"{p['key']}={p.get('value','')}" for p in pairs if not p.get("disabled"))
|
||||
body_type = "urlencoded"
|
||||
|
||||
requests.append(HttpRequest(
|
||||
method=method, url=url, headers=headers,
|
||||
params=params, body=body, body_type=body_type,
|
||||
name=item.get("name", "")
|
||||
))
|
||||
elif "item" in item:
|
||||
for sub in item["item"]:
|
||||
parse_item(sub)
|
||||
|
||||
for item in data.get("item", []):
|
||||
parse_item(item)
|
||||
|
||||
return name, requests
|
||||
|
||||
|
||||
def from_curl(curl_cmd: str) -> HttpRequest:
|
||||
"""Parse a curl command string into an HttpRequest."""
|
||||
# Normalize line continuations
|
||||
cmd = curl_cmd.replace("\\\n", " ").strip()
|
||||
try:
|
||||
tokens = shlex.split(cmd)
|
||||
except ValueError:
|
||||
tokens = cmd.split()
|
||||
|
||||
req = HttpRequest(method="GET")
|
||||
i = 1 # skip 'curl'
|
||||
while i < len(tokens):
|
||||
token = tokens[i]
|
||||
if token in ("-X", "--request") and i + 1 < len(tokens):
|
||||
req.method = tokens[i + 1].upper()
|
||||
i += 2
|
||||
elif token in ("-H", "--header") and i + 1 < len(tokens):
|
||||
header = tokens[i + 1]
|
||||
if ":" in header:
|
||||
k, _, v = header.partition(":")
|
||||
req.headers[k.strip()] = v.strip()
|
||||
i += 2
|
||||
elif token in ("-d", "--data", "--data-raw", "--data-binary") and i + 1 < len(tokens):
|
||||
req.body = tokens[i + 1]
|
||||
if req.method == "GET":
|
||||
req.method = "POST"
|
||||
i += 2
|
||||
elif token in ("-u", "--user") and i + 1 < len(tokens):
|
||||
user_pass = tokens[i + 1]
|
||||
if ":" in user_pass:
|
||||
u, _, p = user_pass.partition(":")
|
||||
req.auth_type = "basic"
|
||||
req.auth_data = {"username": u, "password": p}
|
||||
i += 2
|
||||
elif not token.startswith("-") and not req.url:
|
||||
req.url = token.strip("'\"")
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return req
|
||||
Reference in New Issue
Block a user