include stubs for python-multipart

This commit is contained in:
Izalia Mae 2024-04-19 13:05:08 -04:00
parent e6a119c871
commit a8e7813318
12 changed files with 249 additions and 5 deletions

0
basgi/py.typed Normal file
View file

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import typing
from aputils import JsonBase
from blib import random_str
from collections.abc import Iterable
from multidict import CIMultiDict, CIMultiDictProxy
from multipart.multipart import Field, File, create_form_parser
@ -122,7 +123,12 @@ class Request:
else:
field.file_object.seek(0)
fields[field.field_name.decode("utf-8")] = field
if field.field_name is None:
fields[f"unnamed-file-{random_str(10)}"] = field
else:
fields[field.field_name.decode("utf-8")] = field
parser = create_form_parser(self.headers, handle_field, handle_field)

View file

@ -44,25 +44,36 @@ class Runner(ABC):
@abstractmethod
def setup_module(self) -> None:
"Import all necessary modules here and add them as attributes to the runner"
...
@abstractmethod
def reload(self) -> None:
"Start the runner with reloading enabled"
...
@abstractmethod
def simple(self) -> None:
"Start the runner without workers"
...
@abstractmethod
def multiprocess(self) -> None:
"Start the runner with multiple process workers"
...
class UvicornRunner(Runner):
"""
Start an ASGI application with Uvicorn
.. note:: Do not start this runner in the same process as the application unless it is
started with the simple runner.
"""
def setup_module(self) -> None:
self.uvicorn = import_module("uvicorn")
self.supervisors = import_module("uvicorn.supervisors")

View file

@ -55,7 +55,7 @@ class SassExtension(Extension):
if (ext := splitext(tpl_name)[1]) not in self._exts:
return source
return sass.compile( # type: ignore[no-any-return]
return sass.compile(
string = source,
output_style = self.output_style.value,
indented = ext == ".sass"

9
dev.py
View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3
import asyncio
import os
import shlex
import subprocess
import sys
@ -90,6 +91,7 @@ def cli_lint(path: Path, watch: bool) -> None:
run_python("-m", "flake8", str(path))
echo("\n----- mypy -----")
os.environ["MYPYPATH"] = str(REPO.joinpath("stubs"))
run_python("-m", "mypy", str(path))
@ -120,6 +122,13 @@ def cli_update_files() -> None:
project_file.write(project)
@cli.command("generate-stubs")
def cli_generate_stubs():
subprocess.run(
["-m", "mypy.stubgen", "-o", "stubs", "-p", "multipart", "--export-less"]
)
def run_python(*arguments: str) -> subprocess.CompletedProcess[bytes]:
return subprocess.run([sys.executable, *arguments])

View file

@ -27,9 +27,9 @@ HTTP Messages
Application Runners
-------------------
:meth:`basgi.run_app`
:class:`basgi.GranianRunner`
:meth:`basgi.handle_run_app`
:class:`basgi.UvicornRunner`
Templates

View file

@ -8,7 +8,9 @@ App Runners
.. autoclass:: basgi.GranianRunner
:members:
:show-inheritance:
:exclude-members: setup_module
.. autoclass:: basgi.UvicornRunner
:members:
:show-inheritance:
:exclude-members: setup_module

View file

@ -69,7 +69,7 @@ dev = [
"mypy == 1.9.0",
"flake8 == 7.0.0",
"pyinstaller == 6.5.0",
"types-jinja2 == 2.11.9",
"types-libsass == 0.23.0.20240311",
"watchfiles == 0.21.0"
]

View file

@ -0,0 +1,3 @@
from .multipart import FormParser as FormParser, MultipartParser as MultipartParser, OctetStreamParser as OctetStreamParser, QuerystringParser as QuerystringParser, create_form_parser as create_form_parser, parse_form as parse_form
__all__ = ['FormParser', 'MultipartParser', 'OctetStreamParser', 'QuerystringParser', 'create_form_parser', 'parse_form']

View file

@ -0,0 +1,20 @@
import typing
if typing.TYPE_CHECKING:
from .multipart import Field, File
class Base64Decoder:
cache: bytearray
underlying: Field | File
def __init__(self, underlying: Field | File) -> None: ...
def write(self, data) -> int: ...
def close(self) -> None: ...
def finalize(self) -> None: ...
class QuotedPrintableDecoder:
cache: bytes
underlying: Field | File
def __init__(self, underlying: Field | File) -> None: ...
def write(self, data) -> int: ...
def close(self) -> None: ...
def finalize(self) -> None: ...

View file

@ -0,0 +1,9 @@
class FormParserError(ValueError): ...
class ParseError(FormParserError):
offset: int
class MultipartParseError(ParseError): ...
class QuerystringParseError(ParseError): ...
class DecodeError(ParseError): ...
class FileError(FormParserError, OSError): ...

View file

@ -0,0 +1,184 @@
import io
import logging
from collections.abc import Mapping, Sequence
from enum import IntEnum
from typing import Any, Callable, TypedDict
ParserCallback = Callable[[Any, int, int], None] | Callable[..., None]
class QuerystringCallbacks(TypedDict, total=False):
on_field_start: Callable[[], None]
on_field_name: Callable[[bytes, int, int], None]
on_field_data: Callable[[bytes, int, int], None]
on_field_end: Callable[[], None]
on_end: Callable[[], None]
class OctetStreamCallbacks(TypedDict, total=False):
on_start: Callable[[], None]
on_data: Callable[[bytes, int, int], None]
on_end: Callable[[], None]
class MultipartCallbacks(TypedDict, total=False):
on_part_begin: Callable[[], None]
on_part_data: Callable[[bytes, int, int], None]
on_part_end: Callable[[], None]
on_headers_begin: Callable[[], None]
on_header_field: Callable[[bytes, int, int], None]
on_header_value: Callable[[bytes, int, int], None]
on_header_end: Callable[[], None]
on_headers_finished: Callable[[], None]
on_end: Callable[[], None]
class FormParserConfig(TypedDict, total=False):
UPLOAD_DIR: str | None
UPLOAD_KEEP_FILENAME: bool
UPLOAD_KEEP_EXTENSIONS: bool
UPLOAD_ERROR_ON_BAD_CTE: bool
MAX_MEMORY_FILE_SIZE: int
MAX_BODY_SIZE: float
class FileConfig(TypedDict, total=False):
UPLOAD_DIR: str | None
UPLOAD_DELETE_TMP: bool
UPLOAD_KEEP_FILENAME: bool
UPLOAD_KEEP_EXTENSIONS: bool
MAX_MEMORY_FILE_SIZE: int
class QuerystringState(IntEnum):
BEFORE_FIELD: int
FIELD_NAME: int
FIELD_DATA: int
class MultipartState(IntEnum):
START: int
START_BOUNDARY: int
HEADER_FIELD_START: int
HEADER_FIELD: int
HEADER_VALUE_START: int
HEADER_VALUE: int
HEADER_VALUE_ALMOST_DONE: int
HEADERS_ALMOST_DONE: int
PART_DATA_START: int
PART_DATA: int
PART_DATA_END: int
END: int
FLAG_PART_BOUNDARY: int
FLAG_LAST_BOUNDARY: int
CR: bytes
LF: bytes
COLON: bytes
SPACE: bytes
HYPHEN: bytes
AMPERSAND: bytes
SEMICOLON: bytes
LOWER_A: bytes
LOWER_Z: bytes
NULL: bytes
def lower_char(c: bytes) -> bytes:...
def ord_char(c: bytes) -> bytes: ...
def join_bytes(b: Sequence[bytes]) -> bytes: ...
def parse_options_header(value: str | bytes) -> tuple[bytes, dict[bytes, bytes]]: ...
class Field:
def __init__(self, name: str) -> None: ...
@classmethod
def from_value(cls, name: str, value: bytes | None) -> Field: ...
def write(self, data: bytes) -> int: ...
def on_data(self, data: bytes) -> int: ...
def on_end(self) -> None: ...
def finalize(self) -> None: ...
def close(self) -> None: ...
def set_none(self) -> None: ...
@property
def field_name(self) -> bytes: ...
@property
def value(self) -> bytes: ...
def __eq__(self, other: object) -> bool: ...
class File:
logger: logging.Logger
def __init__(self, file_name: bytes | None, field_name: bytes | None = None, config: FileConfig = {}) -> None: ...
@property
def field_name(self) -> bytes | None: ...
@property
def file_name(self) -> bytes | None: ...
@property
def actual_file_name(self) -> str | None: ...
@property
def file_object(self) -> io.BytesIO | io.TextIOWrapper | io.BufferedReader: ...
@property
def size(self) -> int: ...
@property
def in_memory(self) -> bool: ...
def flush_to_disk(self) -> None: ...
def write(self, data: bytes) -> int: ...
def on_data(self, data: bytes) -> int: ...
def on_end(self) -> None: ...
def finalize(self) -> None: ...
def close(self) -> None: ...
class BaseParser:
logger: logging.Logger
def __init__(self) -> None: ...
def callback(self, name: str, data: Any | None = None, start: int | None = None, end: int | None = None) -> None: ...
def set_callback(self, name: str, new_func: ParserCallback) -> None: ...
def close(self) -> None: ...
def finalize(self) -> None: ...
class OctetStreamParser(BaseParser):
callbacks: OctetStreamCallbacks
max_size: float
def __init__(self, callbacks: OctetStreamCallbacks = {}, max_size: float = float("inf")) -> None: ...
def write(self, data: bytes) -> int: ...
def finalize(self) -> None: ...
class QuerystringParser(BaseParser):
state: QuerystringState
callbacks: QuerystringCallbacks
max_size: float
strict_parsing: bool
def __init__(self, callbacks: QuerystringCallbacks = {}, strict_parsing: bool = False, max_size: float = float("inf")) -> None: ...
def write(self, data: bytes) -> int: ...
def finalize(self) -> None: ...
class MultipartParser(BaseParser):
state: MultipartState
index: int
callbacks: MultipartCallbacks
max_size: float
marks: dict[str, int]
boundary: bytes
boundary_chars: frozenset[bytes]
lookbehind: Sequence[bytes]
def __init__(self, boundary: bytes | str, callbacks: MultipartCallbacks = {}, max_size: float = float("inf")) -> None: ...
def write(self, data: bytes) -> int: ...
def finalize(self) -> None: ...
OnFieldCallback = Callable[[Field], None]
OnFileCallback = Callable[[File], None]
OnEndCallback = Callable[..., Any | None]
class FormParser:
DEFAULT_CONFIG: FormParserConfig
logger: logging.Logger
content_type: str
boundary: bytes | None
bytes_received: int
parser: BaseParser
on_field: OnFieldCallback
on_file: OnFileCallback
on_end: OnEndCallback | None
FileClass: File
FieldClass: Field
config: FormParserConfig | None
def __init__(self, content_type: str, on_field: OnFieldCallback, on_file: OnFileCallback, on_end: OnEndCallback | None = None, boundary: bytes | None = None, file_name: str | None = None, FileClass: type[File] = File, FieldClass: type[Field] = Field, config: FormParserConfig = {}) -> None: ...
def write(self, data: bytes) -> int: ...
def finalize(self) -> None: ...
def close(self) -> None: ...
def create_form_parser(headers: Mapping[str, str], on_field: OnFieldCallback, on_file: OnFileCallback, trust_x_headers: bool = False, config: FormParserConfig = {}) -> FormParser: ...
def parse_form(headers: Mapping[str, str], input_stream: io.BufferedIOBase, on_field: OnFieldCallback, on_file: OnFileCallback, chunk_size: int = 1048576, **kwargs: Any) -> None: ...