simonw--llm
b2fce50aad
Test / test (ubuntu-latest, ==1.10.2, 3.9) (push) Has been cancelled
Test / test (ubuntu-latest, >=2.0.0, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, >=2.0.0, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, >=2.0.0, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, >=2.0.0, 3.13) (push) Has been cancelled
Test / test (ubuntu-latest, >=2.0.0, 3.9) (push) Has been cancelled
Test / test (windows-latest, ==1.10.2, 3.10) (push) Has been cancelled
Test / test (windows-latest, ==1.10.2, 3.11) (push) Has been cancelled
Test / test (windows-latest, ==1.10.2, 3.12) (push) Has been cancelled
Test / test (windows-latest, ==1.10.2, 3.13) (push) Has been cancelled
Test / test (macos-latest, ==1.10.2, 3.9) (push) Has been cancelled
Test / test (windows-latest, ==1.10.2, 3.9) (push) Has been cancelled
Test / test (windows-latest, >=2.0.0, 3.10) (push) Has been cancelled
Test / test (windows-latest, >=2.0.0, 3.11) (push) Has been cancelled
Test / test (windows-latest, >=2.0.0, 3.12) (push) Has been cancelled
Test / test (windows-latest, >=2.0.0, 3.13) (push) Has been cancelled
Test / test (windows-latest, >=2.0.0, 3.9) (push) Has been cancelled
Test / test (macos-latest, >=2.0.0, 3.11) (push) Has been cancelled
Test / test (macos-latest, >=2.0.0, 3.12) (push) Has been cancelled
Test / test (macos-latest, >=2.0.0, 3.13) (push) Has been cancelled
Test / test (macos-latest, >=2.0.0, 3.9) (push) Has been cancelled
Test / test (ubuntu-latest, ==1.10.2, 3.10) (push) Has been cancelled
Test / test (ubuntu-latest, ==1.10.2, 3.11) (push) Has been cancelled
Test / test (ubuntu-latest, ==1.10.2, 3.12) (push) Has been cancelled
Test / test (ubuntu-latest, ==1.10.2, 3.13) (push) Has been cancelled
Test / test (macos-latest, >=2.0.0, 3.10) (push) Has been cancelled
Test / test (macos-latest, ==1.10.2, 3.10) (push) Has been cancelled
Test / test (macos-latest, ==1.10.2, 3.11) (push) Has been cancelled
Test / test (macos-latest, ==1.10.2, 3.12) (push) Has been cancelled
Test / test (macos-latest, ==1.10.2, 3.13) (push) Has been cancelled
176 行
5.4 KiB
Python
176 行
5.4 KiB
Python
import click
|
|
import httpx
|
|
import json
|
|
import puremagic
|
|
import textwrap
|
|
from typing import List, Dict, Optional
|
|
|
|
MIME_TYPE_FIXES = {
|
|
"audio/wave": "audio/wav",
|
|
}
|
|
|
|
|
|
def mimetype_from_string(content) -> Optional[str]:
|
|
try:
|
|
type_ = puremagic.from_string(content, mime=True)
|
|
return MIME_TYPE_FIXES.get(type_, type_)
|
|
except puremagic.PureError:
|
|
return None
|
|
|
|
|
|
def mimetype_from_path(path) -> Optional[str]:
|
|
try:
|
|
type_ = puremagic.from_file(path, mime=True)
|
|
return MIME_TYPE_FIXES.get(type_, type_)
|
|
except puremagic.PureError:
|
|
return None
|
|
|
|
|
|
def dicts_to_table_string(
|
|
headings: List[str], dicts: List[Dict[str, str]]
|
|
) -> List[str]:
|
|
max_lengths = [len(h) for h in headings]
|
|
|
|
# Compute maximum length for each column
|
|
for d in dicts:
|
|
for i, h in enumerate(headings):
|
|
if h in d and len(str(d[h])) > max_lengths[i]:
|
|
max_lengths[i] = len(str(d[h]))
|
|
|
|
# Generate formatted table strings
|
|
res = []
|
|
res.append(" ".join(h.ljust(max_lengths[i]) for i, h in enumerate(headings)))
|
|
|
|
for d in dicts:
|
|
row = []
|
|
for i, h in enumerate(headings):
|
|
row.append(str(d.get(h, "")).ljust(max_lengths[i]))
|
|
res.append(" ".join(row))
|
|
|
|
return res
|
|
|
|
|
|
def remove_dict_none_values(d):
|
|
"""
|
|
Recursively remove keys with value of None or value of a dict that is all values of None
|
|
"""
|
|
if not isinstance(d, dict):
|
|
return d
|
|
new_dict = {}
|
|
for key, value in d.items():
|
|
if value is not None:
|
|
if isinstance(value, dict):
|
|
nested = remove_dict_none_values(value)
|
|
if nested:
|
|
new_dict[key] = nested
|
|
elif isinstance(value, list):
|
|
new_dict[key] = [remove_dict_none_values(v) for v in value]
|
|
else:
|
|
new_dict[key] = value
|
|
return new_dict
|
|
|
|
|
|
class _LogResponse(httpx.Response):
|
|
def iter_bytes(self, *args, **kwargs):
|
|
for chunk in super().iter_bytes(*args, **kwargs):
|
|
click.echo(chunk.decode(), err=True)
|
|
yield chunk
|
|
|
|
|
|
class _LogTransport(httpx.BaseTransport):
|
|
def __init__(self, transport: httpx.BaseTransport):
|
|
self.transport = transport
|
|
|
|
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
response = self.transport.handle_request(request)
|
|
return _LogResponse(
|
|
status_code=response.status_code,
|
|
headers=response.headers,
|
|
stream=response.stream,
|
|
extensions=response.extensions,
|
|
)
|
|
|
|
|
|
def _no_accept_encoding(request: httpx.Request):
|
|
request.headers.pop("accept-encoding", None)
|
|
|
|
|
|
def _log_response(response: httpx.Response):
|
|
request = response.request
|
|
click.echo(f"Request: {request.method} {request.url}", err=True)
|
|
click.echo(" Headers:", err=True)
|
|
for key, value in request.headers.items():
|
|
if key.lower() == "authorization":
|
|
value = "[...]"
|
|
if key.lower() == "cookie":
|
|
value = value.split("=")[0] + "=..."
|
|
click.echo(f" {key}: {value}", err=True)
|
|
click.echo(" Body:", err=True)
|
|
try:
|
|
request_body = json.loads(request.content)
|
|
click.echo(
|
|
textwrap.indent(json.dumps(request_body, indent=2), " "), err=True
|
|
)
|
|
except json.JSONDecodeError:
|
|
click.echo(textwrap.indent(request.content.decode(), " "), err=True)
|
|
click.echo(f"Response: status_code={response.status_code}", err=True)
|
|
click.echo(" Headers:", err=True)
|
|
for key, value in response.headers.items():
|
|
if key.lower() == "set-cookie":
|
|
value = value.split("=")[0] + "=..."
|
|
click.echo(f" {key}: {value}", err=True)
|
|
click.echo(" Body:", err=True)
|
|
|
|
|
|
def logging_client() -> httpx.Client:
|
|
return httpx.Client(
|
|
transport=_LogTransport(httpx.HTTPTransport()),
|
|
event_hooks={"request": [_no_accept_encoding], "response": [_log_response]},
|
|
)
|
|
|
|
|
|
def apply_replacements(obj, replacements):
|
|
if isinstance(obj, dict):
|
|
return {k: apply_replacements(v, replacements) for k, v in obj.items()}
|
|
elif isinstance(obj, list):
|
|
return [apply_replacements(item, replacements) for item in obj]
|
|
elif isinstance(obj, str):
|
|
replaced_parts = []
|
|
last_index = 0
|
|
found = False
|
|
|
|
for value, key in replacements.items():
|
|
index = obj.find(key)
|
|
while index != -1:
|
|
found = True
|
|
if index > last_index:
|
|
replaced_parts.append(obj[last_index:index])
|
|
replaced_parts.append(value)
|
|
last_index = index + len(key)
|
|
index = obj.find(key, last_index)
|
|
|
|
if found:
|
|
if last_index < len(obj):
|
|
replaced_parts.append(obj[last_index:])
|
|
return {"$r": replaced_parts}
|
|
else:
|
|
return obj
|
|
else:
|
|
return obj
|
|
|
|
|
|
def reverse_replacements(obj, replacements):
|
|
if isinstance(obj, dict):
|
|
if "$r" in obj:
|
|
# Reconstruct the original string from the list
|
|
return "".join(
|
|
(replacements[part] if isinstance(part, int) else part)
|
|
for part in obj["$r"]
|
|
)
|
|
else:
|
|
return {k: reverse_replacements(v, replacements) for k, v in obj.items()}
|
|
elif isinstance(obj, list):
|
|
return [reverse_replacements(item, replacements) for item in obj]
|
|
else:
|
|
return obj
|