finish documenting module
This commit is contained in:
parent
d48cc7024a
commit
cad946555b
|
@ -1,4 +1,5 @@
|
|||
import httpx
|
||||
import os
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
|
@ -20,19 +21,34 @@ T = TypeVar("T", bound = JsonBase)
|
|||
|
||||
|
||||
class Client:
|
||||
"HTTP client with useful methods for fetching ActivityPub resources"
|
||||
|
||||
|
||||
def __init__(self, useragent: str = f"BarksharkASGI/{__version__}"):
|
||||
"""
|
||||
Create a new ``Client`` object
|
||||
|
||||
:param useragent: ``User-Agent`` header to send with each request
|
||||
"""
|
||||
|
||||
self._client = httpx.AsyncClient(
|
||||
http2 = True,
|
||||
timeout = 5,
|
||||
max_redirects = 3,
|
||||
headers = {
|
||||
"User-Agent": useragent
|
||||
},
|
||||
proxies = {
|
||||
"http": os.environ.get("all_proxy", os.environ.get("http_proxy")),
|
||||
"https": os.environ.get("all_proxy", os.environ.get("https_proxy"))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def useragent(self) -> str:
|
||||
"The ``User-Agent`` header to send with each request"
|
||||
|
||||
return self._client.headers["User-Agent"]
|
||||
|
||||
|
||||
|
@ -42,6 +58,8 @@ class Client:
|
|||
|
||||
|
||||
async def close(self) -> None:
|
||||
"Close all active connections"
|
||||
|
||||
await self._client.aclose()
|
||||
|
||||
|
||||
|
@ -54,6 +72,18 @@ class Client:
|
|||
algorithm: AlgorithmType | str = AlgorithmType.RSASHA256,
|
||||
stream: bool = False,
|
||||
follow_redirects: bool = False) -> httpx.Response:
|
||||
"""
|
||||
Send an HTTP request
|
||||
|
||||
:param method: HTTP method to use
|
||||
:param url: Resource to fetch
|
||||
:param body: Data to send as the body
|
||||
:param headers: Headers to add to the request
|
||||
:param signer: Actor to sign the headers with
|
||||
:param algorithm: Signature standard to use when signing headers
|
||||
:param stream: Whether or not to keep the conection open after the request is finished
|
||||
:param follow_redirects: Send a new request to the location in any 3xx status responses
|
||||
"""
|
||||
|
||||
url = url.split("#", 1)[0]
|
||||
|
||||
|
@ -80,9 +110,15 @@ class Client:
|
|||
url: str,
|
||||
cls: type[T] | None = None,
|
||||
signer: Signer | None = None) -> T:
|
||||
"""
|
||||
Send a ``GET`` request and return a ``JsonBase`` object
|
||||
|
||||
:param url: Location of the resource to fetch
|
||||
:param cls: Class to use for parsing the JSON body
|
||||
:param signer: Actor to sign the headers with
|
||||
"""
|
||||
|
||||
message_class: type[T] = JsonBase if cls is None else cls # type: ignore
|
||||
|
||||
headers = {
|
||||
"Accept": "application/" + ("activity+json" if cls is Message else "json")
|
||||
}
|
||||
|
@ -92,6 +128,12 @@ class Client:
|
|||
|
||||
|
||||
async def fetch_nodeinfo(self, domain: str) -> Nodeinfo:
|
||||
"""
|
||||
Get the nodeinfo endpoint of a domain
|
||||
|
||||
:param domain: Domain to fetch from
|
||||
"""
|
||||
|
||||
wk_nodeinfo = await self.fetch_json(
|
||||
f"https://{domain}/.well-known/nodeinfo",
|
||||
WellKnownNodeinfo
|
||||
|
@ -107,6 +149,13 @@ class Client:
|
|||
|
||||
|
||||
async def fetch_webfinger(self, username: str, domain: str) -> Webfinger:
|
||||
"""
|
||||
Query webfinger for a user
|
||||
|
||||
:param username: Name of the user to fetch
|
||||
:param domain: Domain to fetch from
|
||||
"""
|
||||
|
||||
url = f"https://{domain}/.well-known/webfinger?resource=acct:{username}@{domain}"
|
||||
return await self.fetch_json(url, Webfinger)
|
||||
|
||||
|
@ -118,6 +167,16 @@ class Client:
|
|||
algorithm: AlgorithmType | str = AlgorithmType.RSASHA256,
|
||||
stream: bool = False,
|
||||
follow_redirects: bool = False) -> httpx.Response:
|
||||
"""
|
||||
Send an HTTP GET request
|
||||
|
||||
:param url: Resource to fetch
|
||||
:param headers: Headers to add to the request
|
||||
:param signer: Actor to sign the headers with
|
||||
:param algorithm: Signature standard to use when signing headers
|
||||
:param stream: Whether or not to keep the conection open after the request is finished
|
||||
:param follow_redirects: Send a new request to the location in any 3xx status responses
|
||||
"""
|
||||
|
||||
return await self.request(
|
||||
"GET", url, None, headers, signer, algorithm, stream, follow_redirects
|
||||
|
@ -130,6 +189,15 @@ class Client:
|
|||
signer: Signer | None = None,
|
||||
algorithm: AlgorithmType | str = AlgorithmType.RSASHA256,
|
||||
follow_redirects: bool = False) -> httpx.Response:
|
||||
"""
|
||||
Send an HTTP HEAD request
|
||||
|
||||
:param url: Resource to fetch
|
||||
:param headers: Headers to add to the request
|
||||
:param signer: Actor to sign the headers with
|
||||
:param algorithm: Signature standard to use when signing headers
|
||||
:param follow_redirects: Send a new request to the location in any 3xx status responses
|
||||
"""
|
||||
|
||||
return await self.request(
|
||||
"HEAD", url, None, headers, signer, algorithm, False, follow_redirects
|
||||
|
@ -144,6 +212,17 @@ class Client:
|
|||
algorithm: AlgorithmType | str = AlgorithmType.RSASHA256,
|
||||
stream: bool = False,
|
||||
follow_redirects: bool = False) -> httpx.Response:
|
||||
"""
|
||||
Send an HTTP POST request
|
||||
|
||||
:param url: Resource to fetch
|
||||
:param body: Data to send as the body
|
||||
:param headers: Headers to add to the request
|
||||
:param signer: Actor to sign the headers with
|
||||
:param algorithm: Signature standard to use when signing headers
|
||||
:param stream: Whether or not to keep the conection open after the request is finished
|
||||
:param follow_redirects: Send a new request to the location in any 3xx status responses
|
||||
"""
|
||||
|
||||
return await self.request(
|
||||
"POST", url, body, headers, signer, algorithm, stream, follow_redirects
|
||||
|
|
|
@ -42,18 +42,67 @@ class ScopeType(TypedDict):
|
|||
|
||||
|
||||
class Request:
|
||||
"Represents an incoming client request"
|
||||
|
||||
|
||||
app: Application[R, RT, AT] # type: ignore[valid-type]
|
||||
"Application the request is associated with"
|
||||
|
||||
stream: Stream
|
||||
"Stream object associated with the request"
|
||||
|
||||
method: str
|
||||
"HTTP method set by the client"
|
||||
|
||||
path: str
|
||||
"Requested HTTP path"
|
||||
|
||||
query: CIMultiDictProxy[str]
|
||||
"Extra data pairs at the end of a request path"
|
||||
|
||||
headers: CIMultiDictProxy[str]
|
||||
"Headers set by the client"
|
||||
|
||||
params: dict[str, Any]
|
||||
"Path parameters for the route if they exist"
|
||||
|
||||
remote_raw: str | None
|
||||
"The remote IP of the client"
|
||||
|
||||
local: str | None
|
||||
"Address of the server"
|
||||
|
||||
cookies: dict[str, Cookie]
|
||||
"Cookies sent by the client"
|
||||
|
||||
state: RT # type: ignore[valid-type]
|
||||
"Data to pass with the request"
|
||||
|
||||
extensions: dict[str, Any]
|
||||
"ASGI extensions"
|
||||
|
||||
asgi: tuple[str, str]
|
||||
"ASGI version and spec version"
|
||||
|
||||
version: float
|
||||
"HTTP version of the request"
|
||||
|
||||
scheme: str
|
||||
"Protocol of the request"
|
||||
|
||||
|
||||
def __init__(self,
|
||||
app: Application[R, RT, AT],
|
||||
scope: ScopeType,
|
||||
stream: Stream) -> None:
|
||||
|
||||
self.app: Application[R, RT, AT] = app
|
||||
self.stream: Stream = stream
|
||||
self._body: bytes = b""
|
||||
|
||||
raw_query = parse_qsl(scope["query_string"].decode("utf-8"), keep_blank_values = True)
|
||||
raw_headers = ((k.decode("utf-8").title(), v.decode("utf-8")) for k, v in scope["headers"])
|
||||
|
||||
self.app: Application[R, RT, AT] = app
|
||||
self.stream: Stream = stream
|
||||
self.method: str = scope["method"].upper()
|
||||
self.path: str = scope["path"]
|
||||
self.query: CIMultiDictProxy[str] = CIMultiDictProxy(CIMultiDict(raw_query))
|
||||
|
@ -62,7 +111,6 @@ class Request:
|
|||
self.remote_raw: str | None = (scope.get("client") or (None,))[0]
|
||||
self.local: str | None = (scope.get("server") or (None, ))[0]
|
||||
self.cookies: dict[str, Cookie] = {}
|
||||
|
||||
self.state: RT = app.request_state_class(scope.get("state") or {})
|
||||
self.extensions: dict[str, Any] = scope.get("extensions", {}) # type: ignore[assignment]
|
||||
|
||||
|
@ -78,25 +126,35 @@ class Request:
|
|||
|
||||
@property
|
||||
def content_length(self) -> int:
|
||||
"Get the ``Content-Length`` header"
|
||||
|
||||
return int(self.headers.get("Content-Length", "0"))
|
||||
|
||||
|
||||
@property
|
||||
def content_type(self) -> str:
|
||||
"Get the first value of the ``Content-Type`` header"
|
||||
|
||||
return self.headers.getone("Content-Type", "")
|
||||
|
||||
|
||||
@property
|
||||
def remote(self) -> str | None:
|
||||
"Get the real IP address of the client"
|
||||
|
||||
return self.headers.get("X-Forwarded-For", self.headers.get("X-Real-Ip", self.remote_raw))
|
||||
|
||||
|
||||
@property
|
||||
def user_agent(self) -> str | None:
|
||||
"Get the ``User-Agent`` header"
|
||||
|
||||
return self.headers.get("User-Agent")
|
||||
|
||||
|
||||
async def body(self) -> bytes:
|
||||
"Read the request body. It will be stored in memory."
|
||||
|
||||
if not self._body:
|
||||
self._body = await self.stream.read()
|
||||
|
||||
|
@ -104,14 +162,24 @@ class Request:
|
|||
|
||||
|
||||
async def text(self) -> str:
|
||||
"Read the request body as a ``str``"
|
||||
|
||||
return (await self.body()).decode("utf-8")
|
||||
|
||||
|
||||
async def json(self, parser_class: type[T] = JsonBase) -> T: # type: ignore[assignment]
|
||||
"""
|
||||
Read the request body as a JSON document
|
||||
|
||||
:param parser_class: Class to use when parsing the body
|
||||
"""
|
||||
|
||||
return parser_class.parse(await self.body())
|
||||
|
||||
|
||||
async def form(self) -> CIMultiDictProxy[str | File]:
|
||||
"Parse form data from the request body"
|
||||
|
||||
if self.content_type not in {"multipart/form-data", "application/x-www-form-urlencoded"}:
|
||||
raise ValueError(f"Invalid mimetype for form data: {self.content_type}")
|
||||
|
||||
|
@ -140,4 +208,4 @@ class Request:
|
|||
|
||||
|
||||
async def stream_response(self, status: int, headers: dict[str, str]) -> None:
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
Application
|
||||
===========
|
||||
|
||||
.. autoclass:: basgi.Application
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.StaticHandler
|
||||
:members:
|
||||
:show-inheritance:
|
|
@ -5,42 +5,45 @@ API
|
|||
Application
|
||||
-----------
|
||||
|
||||
:class:`basgi.Application`
|
||||
|
||||
:class:`basgi.Stream`
|
||||
.. autoclass:: basgi.Application
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
HTTP Messages
|
||||
-------------
|
||||
|
||||
:class:`basgi.Request`
|
||||
.. autoclass:: basgi.Request
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:exclude-members: __init__
|
||||
|
||||
:class:`basgi.Response`
|
||||
.. autoclass:: basgi.Response
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
:class:`basgi.FileResponse`
|
||||
.. autoclass:: basgi.FileResponse
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
:class:`basgi.TemplateResponse`
|
||||
.. autoclass:: basgi.TemplateResponse
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Application Runners
|
||||
-------------------
|
||||
|
||||
:class:`basgi.GranianRunner`
|
||||
.. autoclass:: basgi.Runner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
:class:`basgi.UvicornRunner`
|
||||
.. autoclass:: basgi.GranianRunner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:exclude-members: setup_module
|
||||
|
||||
|
||||
Templates
|
||||
---------
|
||||
|
||||
:class:`basgi.Template`
|
||||
|
||||
:class:`basgi.SassExtension`
|
||||
|
||||
|
||||
Enums
|
||||
-----
|
||||
|
||||
:class:`basgi.PridePalette`
|
||||
|
||||
:class:`basgi.SassOutputStyle`
|
||||
.. autoclass:: basgi.UvicornRunner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:exclude-members: setup_module
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
Messages
|
||||
========
|
||||
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. autoclass:: basgi.Request
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. autoclass:: basgi.Response
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.FileResponse
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.TemplateResponse
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
.. autoclass:: basgi.Cookie
|
||||
:members:
|
||||
:show-inheritance:
|
|
@ -1,10 +1,18 @@
|
|||
Misc
|
||||
====
|
||||
|
||||
.. autoclass:: basgi.Client
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.Color
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.Cookie
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.SassExtension
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
@ -14,6 +22,10 @@ Misc
|
|||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.StaticHandler
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.Stream
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
App Runners
|
||||
===========
|
||||
|
||||
.. autoclass:: basgi.Runner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: basgi.GranianRunner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:exclude-members: setup_module
|
||||
|
||||
.. autoclass:: basgi.UvicornRunner
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:exclude-members: setup_module
|
|
@ -5,13 +5,9 @@ subtrees:
|
|||
- file: src/api/index.rst
|
||||
subtrees:
|
||||
- entries:
|
||||
- file: src/api/app.rst
|
||||
- file: src/api/runners.rst
|
||||
- file: src/api/messages.rst
|
||||
- file: src/api/router.rst
|
||||
- file: src/api/misc.rst
|
||||
- file: src/api/enums.rst
|
||||
- file: src/api/exceptions.rst
|
||||
- url: https://git.barkshark.xyz/barkshark/basgi
|
||||
title: Git Repo
|
||||
- url: https://barkshark.xyz/@izalia
|
||||
|
|
|
@ -36,7 +36,7 @@ classifiers = [
|
|||
requires-python = ">= 3.11"
|
||||
dependencies = [
|
||||
"activitypub-utils == 0.2.3",
|
||||
"barkshark-lib == 0.1.1",
|
||||
"barkshark-lib == 0.1.2",
|
||||
"gemi-python == 0.1.1",
|
||||
"http-router == 4.1.2",
|
||||
"httpx[http2] == 0.27.0",
|
||||
|
|
Loading…
Reference in a new issue