finish documenting module

This commit is contained in:
Izalia Mae 2024-04-20 14:38:13 -04:00
parent d48cc7024a
commit cad946555b
9 changed files with 192 additions and 94 deletions

View file

@ -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

View file

@ -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

View file

@ -1,10 +0,0 @@
Application
===========
.. autoclass:: basgi.Application
:members:
:show-inheritance:
.. autoclass:: basgi.StaticHandler
:members:
:show-inheritance:

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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",