项目文件夹

文件
Simon Willison 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
WIP contexts table feature, refs #617
2024-11-12 19:09:44 -08:00

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