added bugis.server package
This commit is contained in:
@@ -2,14 +2,18 @@ from abc import ABC, abstractmethod
|
||||
from asyncio import Queue, AbstractEventLoop
|
||||
from asyncio import get_running_loop
|
||||
from logging import getLogger
|
||||
from typing import Callable, Awaitable, Any, Mapping, Sequence, Optional, Unpack, Tuple
|
||||
|
||||
from typing import Callable, Awaitable, Any, Mapping, Sequence, Optional, Unpack, Tuple, TYPE_CHECKING
|
||||
from pathlib import Path, PurePath
|
||||
from pwo import Maybe, AsyncQueueIterator
|
||||
|
||||
from hashlib import md5
|
||||
from ._http_context import HttpContext
|
||||
from ._http_method import HttpMethod
|
||||
from ._types import StrOrStrings
|
||||
from base64 import b64encode, b64decode
|
||||
from mimetypes import guess_type
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import StrOrBytesPath
|
||||
try:
|
||||
from ._rsgi import RsgiContext
|
||||
from granian._granian import RSGIHTTPProtocol, RSGIHTTPScope # type: ignore
|
||||
@@ -141,3 +145,5 @@ class BugisApp(AbstractBugisApp):
|
||||
|
||||
def PATCH(self, path: str, recursive: bool = False) -> Callable[[HttpHandler], HttpHandler]:
|
||||
return self.route(path, (HttpMethod.PATCH,), recursive)
|
||||
|
||||
|
||||
|
@@ -34,11 +34,11 @@ def decode_headers(headers: Iterable[Tuple[bytes, bytes]]) -> Dict[str, Sequence
|
||||
raise NotImplementedError('This should never happen')
|
||||
if isinstance(value, bytes):
|
||||
value_str = value.decode()
|
||||
elif isinstance(key, str):
|
||||
elif isinstance(value, str):
|
||||
value_str = value
|
||||
else:
|
||||
raise NotImplementedError('This should never happen')
|
||||
ls = result.setdefault(key_str, list())
|
||||
ls = result.setdefault(key_str.lower(), list())
|
||||
ls.append(value_str)
|
||||
return {
|
||||
k: tuple(v) for k, v in result.items()
|
||||
@@ -91,7 +91,7 @@ class AsgiContext(HttpContext):
|
||||
async def stream_body(self,
|
||||
status: int,
|
||||
body_generator: AsyncGenerator[bytes, None],
|
||||
headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
await self._send_head(status, headers)
|
||||
async for chunk in body_generator:
|
||||
await self.send({
|
||||
@@ -105,21 +105,21 @@ class AsgiContext(HttpContext):
|
||||
'more_body': False
|
||||
})
|
||||
|
||||
async def send_bytes(self, status: int, body: bytes, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_bytes(self, status: int, body: bytes, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
await self._send_head(status, headers)
|
||||
await self.send({
|
||||
'type': 'http.response.body',
|
||||
'body': body,
|
||||
})
|
||||
|
||||
async def send_str(self, status: int, body: str, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_str(self, status: int, body: str, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
await self._send_head(status, headers)
|
||||
await self.send({
|
||||
'type': 'http.response.body',
|
||||
'body': body.encode(),
|
||||
})
|
||||
|
||||
async def _send_head(self, status: int, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def _send_head(self, status: int, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
await self.send({
|
||||
'type': 'http.response.start',
|
||||
'status': status,
|
||||
@@ -129,7 +129,7 @@ class AsgiContext(HttpContext):
|
||||
async def send_file(self,
|
||||
status: int,
|
||||
path: Path,
|
||||
headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
if self.pathsend:
|
||||
await self._send_head(status, headers)
|
||||
await self.send({
|
||||
@@ -139,7 +139,7 @@ class AsgiContext(HttpContext):
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
async def send_empty(self, status: int, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_empty(self, status: int, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
await self._send_head(status, headers)
|
||||
await self.send({
|
||||
'type': 'http.response.body',
|
||||
|
@@ -13,6 +13,7 @@ from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
|
||||
from ._http_method import HttpMethod
|
||||
from ._types.base import StrOrStrings
|
||||
|
||||
|
||||
class HttpContext(ABC):
|
||||
@@ -32,20 +33,20 @@ class HttpContext(ABC):
|
||||
async def stream_body(self,
|
||||
status: int,
|
||||
body_generator: AsyncGenerator[bytes, None],
|
||||
headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send_bytes(self, status: int, body: bytes, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_bytes(self, status: int, body: bytes, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
pass
|
||||
|
||||
async def send_str(self, status: int, body: str, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_str(self, status: int, body: str, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
await self.send_bytes(status, body.encode(), headers)
|
||||
|
||||
@abstractmethod
|
||||
async def send_file(self, status: int, path: Path, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_file(self, status: int, path: Path, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def send_empty(self, status: int, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_empty(self, status: int, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
pass
|
||||
|
@@ -17,6 +17,7 @@ from typing import (
|
||||
from granian._granian import RSGIHTTPProtocol, RSGIHTTPScope
|
||||
from pwo import Maybe
|
||||
|
||||
from ._types import StrOrStrings
|
||||
from ._http_context import HttpContext
|
||||
from ._http_method import HttpMethod
|
||||
|
||||
@@ -40,7 +41,7 @@ class RsgiContext(HttpContext):
|
||||
self.query_string = scope.query_string
|
||||
|
||||
def acc(d: Dict[str, List[str]], t: Tuple[str, str]) -> Dict[str, List[str]]:
|
||||
d.setdefault(t[0], list()).append(t[1])
|
||||
d.setdefault(t[0].lower(), list()).append(t[1])
|
||||
return d
|
||||
|
||||
fun = cast(Callable[[Mapping[str, Sequence[str]], tuple[str, str]], Mapping[str, Sequence[str]]], acc)
|
||||
@@ -54,16 +55,27 @@ class RsgiContext(HttpContext):
|
||||
self.request_body = aiter(protocol)
|
||||
self.protocol = protocol
|
||||
|
||||
# @staticmethod
|
||||
# def _rearrange_headers(headers: Mapping[str, Sequence[str]]) -> List[Tuple[str, str]]:
|
||||
# return list(
|
||||
# ((key, value) for key, values in headers.items() for value in values)
|
||||
# )
|
||||
|
||||
@staticmethod
|
||||
def _rearrange_headers(headers: Mapping[str, Sequence[str]]) -> List[Tuple[str, str]]:
|
||||
return list(
|
||||
((key, value) for key, values in headers.items() for value in values)
|
||||
)
|
||||
def _rearrange_headers(headers: Mapping[str, StrOrStrings]) -> List[Tuple[str, str]]:
|
||||
result = []
|
||||
for key, value in headers.items():
|
||||
if isinstance(value, str):
|
||||
result.append((key, value))
|
||||
elif isinstance(value, Sequence):
|
||||
for single_value in value:
|
||||
result.append((key, single_value))
|
||||
return result
|
||||
|
||||
async def stream_body(self,
|
||||
status: int,
|
||||
body_generator: AsyncGenerator[bytes, None],
|
||||
headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
transport = self.protocol.response_stream(status,
|
||||
Maybe.of_nullable(headers)
|
||||
.map(self._rearrange_headers)
|
||||
@@ -71,24 +83,25 @@ class RsgiContext(HttpContext):
|
||||
async for chunk in body_generator:
|
||||
await transport.send_bytes(chunk)
|
||||
|
||||
async def send_bytes(self, status: int, body: bytes, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_bytes(self, status: int, body: bytes, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
rearranged_headers = Maybe.of_nullable(headers).map(RsgiContext._rearrange_headers).or_else(list())
|
||||
if len(body) > 0:
|
||||
self.protocol.response_bytes(status, rearranged_headers, body)
|
||||
else:
|
||||
self.protocol.response_empty(status, rearranged_headers)
|
||||
|
||||
async def send_str(self, status: int, body: str, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_str(self, status: int, body: str, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
rearranged_headers = Maybe.of_nullable(headers).map(RsgiContext._rearrange_headers).or_else(list())
|
||||
if len(body) > 0:
|
||||
self.protocol.response_str(status, rearranged_headers, body)
|
||||
else:
|
||||
self.protocol.response_empty(status, rearranged_headers)
|
||||
|
||||
async def send_file(self, status: int, path: Path, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
rearranged_headers = Maybe.of_nullable(headers).map(RsgiContext._rearrange_headers).or_else(list())
|
||||
async def send_file(self, status: int, path: Path, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
rearranged_headers = (Maybe.of_nullable(headers).map(RsgiContext._rearrange_headers)
|
||||
.or_else(list()))
|
||||
self.protocol.response_file(status, rearranged_headers, str(path))
|
||||
|
||||
async def send_empty(self, status: int, headers: Optional[Mapping[str, Sequence[str]]] = None) -> None:
|
||||
async def send_empty(self, status: int, headers: Optional[Mapping[str, StrOrStrings]] = None) -> None:
|
||||
rearranged_headers = Maybe.of_nullable(headers).map(RsgiContext._rearrange_headers).or_else(list())
|
||||
self.protocol.response_empty(status, rearranged_headers)
|
||||
|
@@ -20,7 +20,8 @@ from ._http_context import HttpContext
|
||||
from ._http_method import HttpMethod
|
||||
from ._path_handler import PathHandler
|
||||
from ._path_matcher import PathMatcher, IntMatcher, GlobMatcher, StrMatcher, Node
|
||||
from ._types import NodeType, Matches
|
||||
from ._path_handler import Matches
|
||||
from ._types import NodeType
|
||||
|
||||
|
||||
class Tree:
|
||||
|
@@ -12,16 +12,12 @@ from typing import (
|
||||
Sequence
|
||||
)
|
||||
|
||||
from .base import StrOrStrings, PathMatcherResult
|
||||
|
||||
from bugis.core._http_method import HttpMethod
|
||||
|
||||
from bugis.core._path_handler import PathHandler, Matches
|
||||
|
||||
type StrOrStrings = (str | Sequence[str])
|
||||
|
||||
type NodeType = (str | HttpMethod)
|
||||
|
||||
type PathMatcherResult = Mapping[str, Any] | Sequence[str]
|
||||
|
||||
|
||||
class ASGIVersions(TypedDict):
|
||||
spec_version: str
|
||||
@@ -90,7 +86,6 @@ __all__ = [
|
||||
'RSGI',
|
||||
'ASGIVersions',
|
||||
'WebSocketScope',
|
||||
'PathHandler',
|
||||
'NodeType',
|
||||
'Matches'
|
||||
'StrOrStrings'
|
||||
]
|
||||
|
4
core/src/bugis/core/_types/base.py
Normal file
4
core/src/bugis/core/_types/base.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from typing import Sequence, Mapping, Any
|
||||
|
||||
type StrOrStrings = (str | Sequence[str])
|
||||
type PathMatcherResult = Mapping[str, Any] | Sequence[str]
|
38
core/tests/test_ciao.py
Normal file
38
core/tests/test_ciao.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import unittest
|
||||
|
||||
|
||||
# Define some test cases
|
||||
class TestAddition(unittest.TestCase):
|
||||
def test_add_positive(self):
|
||||
self.assertEqual(1 + 2, 3)
|
||||
|
||||
def test_add_negative(self):
|
||||
self.assertEqual(-1 + (-1), -2)
|
||||
|
||||
|
||||
class TestSubtraction(unittest.TestCase):
|
||||
def test_subtract_positive(self):
|
||||
self.assertEqual(5 - 3, 2)
|
||||
|
||||
def test_subtract_negative(self):
|
||||
self.assertEqual(-5 - (-2), -3)
|
||||
|
||||
|
||||
# Now let's create a TestSuite
|
||||
def suite():
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
# Add tests to the suite
|
||||
suite.addTest(TestAddition('test_add_positive'))
|
||||
suite.addTest(TestAddition('test_add_negative'))
|
||||
suite.addTest(TestSubtraction('test_subtract_positive'))
|
||||
# suite.addTest(TestSubtraction('test_subtract_negative'))
|
||||
# suite.addTest(TestSubtraction('test_subtract_negative2'))
|
||||
|
||||
return suite
|
||||
|
||||
|
||||
# Running the suite
|
||||
if __name__ == "__main__":
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(suite())
|
Reference in New Issue
Block a user