about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/httpx/_main.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/httpx/_main.py')
-rw-r--r--.venv/lib/python3.12/site-packages/httpx/_main.py509
1 files changed, 509 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/httpx/_main.py b/.venv/lib/python3.12/site-packages/httpx/_main.py
new file mode 100644
index 00000000..72657f8c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/httpx/_main.py
@@ -0,0 +1,509 @@
+from __future__ import annotations
+
+import functools
+import json
+import sys
+import typing
+
+import click
+import httpcore
+import pygments.lexers
+import pygments.util
+import rich.console
+import rich.markup
+import rich.progress
+import rich.syntax
+import rich.table
+
+from ._client import Client
+from ._exceptions import RequestError
+from ._models import Response
+from ._status_codes import codes
+
+
+def print_help() -> None:
+    console = rich.console.Console()
+
+    console.print("[bold]HTTPX :butterfly:", justify="center")
+    console.print()
+    console.print("A next generation HTTP client.", justify="center")
+    console.print()
+    console.print(
+        "Usage: [bold]httpx[/bold] [cyan]<URL> [OPTIONS][/cyan] ", justify="left"
+    )
+    console.print()
+
+    table = rich.table.Table.grid(padding=1, pad_edge=True)
+    table.add_column("Parameter", no_wrap=True, justify="left", style="bold")
+    table.add_column("Description")
+    table.add_row(
+        "-m, --method [cyan]METHOD",
+        "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.\n"
+        "[Default: GET, or POST if a request body is included]",
+    )
+    table.add_row(
+        "-p, --params [cyan]<NAME VALUE> ...",
+        "Query parameters to include in the request URL.",
+    )
+    table.add_row(
+        "-c, --content [cyan]TEXT", "Byte content to include in the request body."
+    )
+    table.add_row(
+        "-d, --data [cyan]<NAME VALUE> ...", "Form data to include in the request body."
+    )
+    table.add_row(
+        "-f, --files [cyan]<NAME FILENAME> ...",
+        "Form files to include in the request body.",
+    )
+    table.add_row("-j, --json [cyan]TEXT", "JSON data to include in the request body.")
+    table.add_row(
+        "-h, --headers [cyan]<NAME VALUE> ...",
+        "Include additional HTTP headers in the request.",
+    )
+    table.add_row(
+        "--cookies [cyan]<NAME VALUE> ...", "Cookies to include in the request."
+    )
+    table.add_row(
+        "--auth [cyan]<USER PASS>",
+        "Username and password to include in the request. Specify '-' for the password"
+        " to use a password prompt. Note that using --verbose/-v will expose"
+        " the Authorization header, including the password encoding"
+        " in a trivially reversible format.",
+    )
+
+    table.add_row(
+        "--proxy [cyan]URL",
+        "Send the request via a proxy. Should be the URL giving the proxy address.",
+    )
+
+    table.add_row(
+        "--timeout [cyan]FLOAT",
+        "Timeout value to use for network operations, such as establishing the"
+        " connection, reading some data, etc... [Default: 5.0]",
+    )
+
+    table.add_row("--follow-redirects", "Automatically follow redirects.")
+    table.add_row("--no-verify", "Disable SSL verification.")
+    table.add_row(
+        "--http2", "Send the request using HTTP/2, if the remote server supports it."
+    )
+
+    table.add_row(
+        "--download [cyan]FILE",
+        "Save the response content as a file, rather than displaying it.",
+    )
+
+    table.add_row("-v, --verbose", "Verbose output. Show request as well as response.")
+    table.add_row("--help", "Show this message and exit.")
+    console.print(table)
+
+
+def get_lexer_for_response(response: Response) -> str:
+    content_type = response.headers.get("Content-Type")
+    if content_type is not None:
+        mime_type, _, _ = content_type.partition(";")
+        try:
+            return typing.cast(
+                str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name
+            )
+        except pygments.util.ClassNotFound:  # pragma: no cover
+            pass
+    return ""  # pragma: no cover
+
+
+def format_request_headers(request: httpcore.Request, http2: bool = False) -> str:
+    version = "HTTP/2" if http2 else "HTTP/1.1"
+    headers = [
+        (name.lower() if http2 else name, value) for name, value in request.headers
+    ]
+    method = request.method.decode("ascii")
+    target = request.url.target.decode("ascii")
+    lines = [f"{method} {target} {version}"] + [
+        f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers
+    ]
+    return "\n".join(lines)
+
+
+def format_response_headers(
+    http_version: bytes,
+    status: int,
+    reason_phrase: bytes | None,
+    headers: list[tuple[bytes, bytes]],
+) -> str:
+    version = http_version.decode("ascii")
+    reason = (
+        codes.get_reason_phrase(status)
+        if reason_phrase is None
+        else reason_phrase.decode("ascii")
+    )
+    lines = [f"{version} {status} {reason}"] + [
+        f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers
+    ]
+    return "\n".join(lines)
+
+
+def print_request_headers(request: httpcore.Request, http2: bool = False) -> None:
+    console = rich.console.Console()
+    http_text = format_request_headers(request, http2=http2)
+    syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True)
+    console.print(syntax)
+    syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True)
+    console.print(syntax)
+
+
+def print_response_headers(
+    http_version: bytes,
+    status: int,
+    reason_phrase: bytes | None,
+    headers: list[tuple[bytes, bytes]],
+) -> None:
+    console = rich.console.Console()
+    http_text = format_response_headers(http_version, status, reason_phrase, headers)
+    syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True)
+    console.print(syntax)
+    syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True)
+    console.print(syntax)
+
+
+def print_response(response: Response) -> None:
+    console = rich.console.Console()
+    lexer_name = get_lexer_for_response(response)
+    if lexer_name:
+        if lexer_name.lower() == "json":
+            try:
+                data = response.json()
+                text = json.dumps(data, indent=4)
+            except ValueError:  # pragma: no cover
+                text = response.text
+        else:
+            text = response.text
+
+        syntax = rich.syntax.Syntax(text, lexer_name, theme="ansi_dark", word_wrap=True)
+        console.print(syntax)
+    else:
+        console.print(f"<{len(response.content)} bytes of binary data>")
+
+
+_PCTRTT = typing.Tuple[typing.Tuple[str, str], ...]
+_PCTRTTT = typing.Tuple[_PCTRTT, ...]
+_PeerCertRetDictType = typing.Dict[str, typing.Union[str, _PCTRTTT, _PCTRTT]]
+
+
+def format_certificate(cert: _PeerCertRetDictType) -> str:  # pragma: no cover
+    lines = []
+    for key, value in cert.items():
+        if isinstance(value, (list, tuple)):
+            lines.append(f"*   {key}:")
+            for item in value:
+                if key in ("subject", "issuer"):
+                    for sub_item in item:
+                        lines.append(f"*     {sub_item[0]}: {sub_item[1]!r}")
+                elif isinstance(item, tuple) and len(item) == 2:
+                    lines.append(f"*     {item[0]}: {item[1]!r}")
+                else:
+                    lines.append(f"*     {item!r}")
+        else:
+            lines.append(f"*   {key}: {value!r}")
+    return "\n".join(lines)
+
+
+def trace(
+    name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False
+) -> None:
+    console = rich.console.Console()
+    if name == "connection.connect_tcp.started" and verbose:
+        host = info["host"]
+        console.print(f"* Connecting to {host!r}")
+    elif name == "connection.connect_tcp.complete" and verbose:
+        stream = info["return_value"]
+        server_addr = stream.get_extra_info("server_addr")
+        console.print(f"* Connected to {server_addr[0]!r} on port {server_addr[1]}")
+    elif name == "connection.start_tls.complete" and verbose:  # pragma: no cover
+        stream = info["return_value"]
+        ssl_object = stream.get_extra_info("ssl_object")
+        version = ssl_object.version()
+        cipher = ssl_object.cipher()
+        server_cert = ssl_object.getpeercert()
+        alpn = ssl_object.selected_alpn_protocol()
+        console.print(f"* SSL established using {version!r} / {cipher[0]!r}")
+        console.print(f"* Selected ALPN protocol: {alpn!r}")
+        if server_cert:
+            console.print("* Server certificate:")
+            console.print(format_certificate(server_cert))
+    elif name == "http11.send_request_headers.started" and verbose:
+        request = info["request"]
+        print_request_headers(request, http2=False)
+    elif name == "http2.send_request_headers.started" and verbose:  # pragma: no cover
+        request = info["request"]
+        print_request_headers(request, http2=True)
+    elif name == "http11.receive_response_headers.complete":
+        http_version, status, reason_phrase, headers = info["return_value"]
+        print_response_headers(http_version, status, reason_phrase, headers)
+    elif name == "http2.receive_response_headers.complete":  # pragma: no cover
+        status, headers = info["return_value"]
+        http_version = b"HTTP/2"
+        reason_phrase = None
+        print_response_headers(http_version, status, reason_phrase, headers)
+
+
+def download_response(response: Response, download: typing.BinaryIO) -> None:
+    console = rich.console.Console()
+    console.print()
+    content_length = response.headers.get("Content-Length")
+    with rich.progress.Progress(
+        "[progress.description]{task.description}",
+        "[progress.percentage]{task.percentage:>3.0f}%",
+        rich.progress.BarColumn(bar_width=None),
+        rich.progress.DownloadColumn(),
+        rich.progress.TransferSpeedColumn(),
+    ) as progress:
+        description = f"Downloading [bold]{rich.markup.escape(download.name)}"
+        download_task = progress.add_task(
+            description,
+            total=int(content_length or 0),
+            start=content_length is not None,
+        )
+        for chunk in response.iter_bytes():
+            download.write(chunk)
+            progress.update(download_task, completed=response.num_bytes_downloaded)
+
+
+def validate_json(
+    ctx: click.Context,
+    param: click.Option | click.Parameter,
+    value: typing.Any,
+) -> typing.Any:
+    if value is None:
+        return None
+
+    try:
+        return json.loads(value)
+    except json.JSONDecodeError:  # pragma: no cover
+        raise click.BadParameter("Not valid JSON")
+
+
+def validate_auth(
+    ctx: click.Context,
+    param: click.Option | click.Parameter,
+    value: typing.Any,
+) -> typing.Any:
+    if value == (None, None):
+        return None
+
+    username, password = value
+    if password == "-":  # pragma: no cover
+        password = click.prompt("Password", hide_input=True)
+    return (username, password)
+
+
+def handle_help(
+    ctx: click.Context,
+    param: click.Option | click.Parameter,
+    value: typing.Any,
+) -> None:
+    if not value or ctx.resilient_parsing:
+        return
+
+    print_help()
+    ctx.exit()
+
+
+@click.command(add_help_option=False)
+@click.argument("url", type=str)
+@click.option(
+    "--method",
+    "-m",
+    "method",
+    type=str,
+    help=(
+        "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD. "
+        "[Default: GET, or POST if a request body is included]"
+    ),
+)
+@click.option(
+    "--params",
+    "-p",
+    "params",
+    type=(str, str),
+    multiple=True,
+    help="Query parameters to include in the request URL.",
+)
+@click.option(
+    "--content",
+    "-c",
+    "content",
+    type=str,
+    help="Byte content to include in the request body.",
+)
+@click.option(
+    "--data",
+    "-d",
+    "data",
+    type=(str, str),
+    multiple=True,
+    help="Form data to include in the request body.",
+)
+@click.option(
+    "--files",
+    "-f",
+    "files",
+    type=(str, click.File(mode="rb")),
+    multiple=True,
+    help="Form files to include in the request body.",
+)
+@click.option(
+    "--json",
+    "-j",
+    "json",
+    type=str,
+    callback=validate_json,
+    help="JSON data to include in the request body.",
+)
+@click.option(
+    "--headers",
+    "-h",
+    "headers",
+    type=(str, str),
+    multiple=True,
+    help="Include additional HTTP headers in the request.",
+)
+@click.option(
+    "--cookies",
+    "cookies",
+    type=(str, str),
+    multiple=True,
+    help="Cookies to include in the request.",
+)
+@click.option(
+    "--auth",
+    "auth",
+    type=(str, str),
+    default=(None, None),
+    callback=validate_auth,
+    help=(
+        "Username and password to include in the request. "
+        "Specify '-' for the password to use a password prompt. "
+        "Note that using --verbose/-v will expose the Authorization header, "
+        "including the password encoding in a trivially reversible format."
+    ),
+)
+@click.option(
+    "--proxy",
+    "proxy",
+    type=str,
+    default=None,
+    help="Send the request via a proxy. Should be the URL giving the proxy address.",
+)
+@click.option(
+    "--timeout",
+    "timeout",
+    type=float,
+    default=5.0,
+    help=(
+        "Timeout value to use for network operations, such as establishing the "
+        "connection, reading some data, etc... [Default: 5.0]"
+    ),
+)
+@click.option(
+    "--follow-redirects",
+    "follow_redirects",
+    is_flag=True,
+    default=False,
+    help="Automatically follow redirects.",
+)
+@click.option(
+    "--no-verify",
+    "verify",
+    is_flag=True,
+    default=True,
+    help="Disable SSL verification.",
+)
+@click.option(
+    "--http2",
+    "http2",
+    type=bool,
+    is_flag=True,
+    default=False,
+    help="Send the request using HTTP/2, if the remote server supports it.",
+)
+@click.option(
+    "--download",
+    type=click.File("wb"),
+    help="Save the response content as a file, rather than displaying it.",
+)
+@click.option(
+    "--verbose",
+    "-v",
+    type=bool,
+    is_flag=True,
+    default=False,
+    help="Verbose. Show request as well as response.",
+)
+@click.option(
+    "--help",
+    is_flag=True,
+    is_eager=True,
+    expose_value=False,
+    callback=handle_help,
+    help="Show this message and exit.",
+)
+def main(
+    url: str,
+    method: str,
+    params: list[tuple[str, str]],
+    content: str,
+    data: list[tuple[str, str]],
+    files: list[tuple[str, click.File]],
+    json: str,
+    headers: list[tuple[str, str]],
+    cookies: list[tuple[str, str]],
+    auth: tuple[str, str] | None,
+    proxy: str,
+    timeout: float,
+    follow_redirects: bool,
+    verify: bool,
+    http2: bool,
+    download: typing.BinaryIO | None,
+    verbose: bool,
+) -> None:
+    """
+    An HTTP command line client.
+    Sends a request and displays the response.
+    """
+    if not method:
+        method = "POST" if content or data or files or json else "GET"
+
+    try:
+        with Client(
+            proxy=proxy,
+            timeout=timeout,
+            verify=verify,
+            http2=http2,
+        ) as client:
+            with client.stream(
+                method,
+                url,
+                params=list(params),
+                content=content,
+                data=dict(data),
+                files=files,  # type: ignore
+                json=json,
+                headers=headers,
+                cookies=dict(cookies),
+                auth=auth,
+                follow_redirects=follow_redirects,
+                extensions={"trace": functools.partial(trace, verbose=verbose)},
+            ) as response:
+                if download is not None:
+                    download_response(response, download)
+                else:
+                    response.read()
+                    if response.content:
+                        print_response(response)
+
+    except RequestError as exc:
+        console = rich.console.Console()
+        console.print(f"[red]{type(exc).__name__}[/red]: {exc}")
+        sys.exit(1)
+
+    sys.exit(0 if response.is_success else 1)