3 Commits
Author SHA1 Message Date
woggioni df5906fd3c tmp 2026-06-30 00:23:32 +08:00
woggioni 77362fd05c updated dependencies 2026-06-30 00:23:15 +08:00
woggioni f158698380 temporary commit 2025-04-02 17:21:30 +08:00
11 changed files with 319 additions and 207 deletions
+39 -8
View File
@@ -1,12 +1,13 @@
from bugis.core import BugisApp, HttpContext
from bugis.core import BugisApp, HttpContext, HttpMethod
from typing import List
class Hello(BugisApp):
pass
async def handle_request(self, ctx: HttpContext) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, 'Hello World')
# async def handle_request(self, ctx: HttpContext) -> None:
# async for chunk in ctx.request_body:
# print(chunk.decode())
# await ctx.send_str(200, 'Hello World')
app = BugisApp()
@@ -16,5 +17,35 @@ app = BugisApp()
@app.GET('/hello2')
async def handle_request(ctx: HttpContext) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, 'Hello World')
print(chunk.decode())
await ctx.send_str(200, 'Hello World')
@app.GET('/hello/alice')
async def hello_alice(ctx: HttpContext) -> None:
async for _ in ctx.request_body:
pass
await ctx.send_str(200, 'Hello Alice 2')
@app.GET('/hello/*')
async def hello_alice(ctx: HttpContext, args: List[str]) -> None:
async for _ in ctx.request_body:
pass
await ctx.send_str(200, f'Hello {args[0]}')
@app.GET('/hello/*/kennedy')
async def hello_alice(ctx: HttpContext, args: List[str]) -> None:
async for _ in ctx.request_body:
pass
await ctx.send_str(200, f'Hello {args[0]} kennedy')
@app.GET('/hello/bob/marley')
async def hello_alice(ctx: HttpContext) -> None:
async for _ in ctx.request_body:
pass
await ctx.send_str(200, f'Hello bob Marley')
@app.route('/hello/*', HttpMethod.PUT, recursive=True)
async def handle_request(ctx: HttpContext, _: List[str]) -> None:
async for chunk in ctx.request_body:
print(chunk.decode())
await ctx.send_str(200, 'Message received')
+75 -59
View File
@@ -1,102 +1,127 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --extra=dev --output-file=requirements-dev.txt
# pip-compile --allow-unsafe --extra=dev --extra=rsgi --output-file=requirements-dev.txt pyproject.toml
#
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple
aiofiles==24.1.0
# via bugis_core (pyproject.toml)
asttokens==2.4.1
anyio==4.14.1
# via httpx
ast-serialize==0.5.0
# via mypy
asttokens==3.0.1
# via stack-data
build==1.2.2.post1
build==1.5.0
# via bugis_core (pyproject.toml)
certifi==2024.8.30
# via requests
cffi==1.17.1
certifi==2026.6.17
# via
# httpcore
# httpx
# requests
cffi==2.0.0
# via cryptography
charset-normalizer==3.4.0
charset-normalizer==3.4.7
# via requests
cryptography==43.0.3
click==8.4.2
# via granian
cryptography==49.0.0
# via secretstorage
decorator==5.1.1
decorator==5.3.1
# via
# ipdb
# ipython
docutils==0.21.2
docutils==0.23
# via readme-renderer
executing==2.1.0
executing==2.2.1
# via stack-data
idna==3.10
# via requests
importlib-metadata==8.5.0
granian==2.7.7
# via bugis_core (pyproject.toml)
h11==0.16.0
# via httpcore
httpcore==1.0.9
# via httpx
httpx==0.28.1
# via bugis_core (pyproject.toml)
id==1.6.1
# via twine
idna==3.18
# via
# anyio
# httpx
# requests
ipdb==0.13.13
# via bugis_core (pyproject.toml)
ipython==8.29.0
ipython==9.15.0
# via ipdb
ipython-pygments-lexers==1.1.1
# via ipython
jaraco-classes==3.4.0
# via keyring
jaraco-context==6.0.1
jaraco-context==6.1.2
# via keyring
jaraco-functools==4.1.0
jaraco-functools==4.5.0
# via keyring
jedi==0.19.1
jedi==0.20.0
# via ipython
jeepney==0.8.0
jeepney==0.9.0
# via
# keyring
# secretstorage
keyring==25.5.0
keyring==25.7.0
# via twine
markdown-it-py==3.0.0
librt==0.11.0
# via mypy
markdown-it-py==4.2.0
# via rich
matplotlib-inline==0.1.7
matplotlib-inline==0.2.2
# via ipython
mdurl==0.1.2
# via markdown-it-py
more-itertools==10.5.0
more-itertools==11.1.0
# via
# jaraco-classes
# jaraco-functools
mypy==1.13.0
mypy==2.1.0
# via bugis_core (pyproject.toml)
mypy-extensions==1.0.0
mypy-extensions==1.1.0
# via mypy
nh3==0.2.18
nh3==0.3.6
# via readme-renderer
packaging==24.1
# via build
parso==0.8.4
packaging==26.2
# via
# build
# twine
parso==0.8.7
# via jedi
pathspec==1.1.1
# via mypy
pexpect==4.9.0
# via ipython
pkginfo==1.10.0
# via twine
prompt-toolkit==3.0.48
prompt-toolkit==3.0.52
# via ipython
psutil==7.2.2
# via ipython
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.3
# via stack-data
pwo==0.0.5
pwo==0.1.2
# via bugis_core (pyproject.toml)
pycparser==2.22
pycparser==3.0
# via cffi
pygments==2.18.0
pygments==2.20.0
# via
# ipython
# ipython-pygments-lexers
# readme-renderer
# rich
pyproject-hooks==1.2.0
# via build
pyyaml==6.0.2
# via bugis_core (pyproject.toml)
readme-renderer==44.0
readme-renderer==45.0
# via twine
requests==2.32.3
requests==2.34.2
# via
# requests-toolbelt
# twine
@@ -104,35 +129,26 @@ requests-toolbelt==1.0.0
# via twine
rfc3986==2.0.0
# via twine
rich==13.9.3
rich==15.0.0
# via twine
secretstorage==3.3.3
secretstorage==3.5.0
# via keyring
six==1.16.0
# via asttokens
sortedcontainers==2.4.0
# via bugis_core (pyproject.toml)
stack-data==0.6.3
# via ipython
traitlets==5.14.3
traitlets==5.15.1
# via
# ipython
# matplotlib-inline
twine==5.1.1
twine==6.2.0
# via bugis_core (pyproject.toml)
types-pyyaml==6.0.12.20240917
# via bugis_core (pyproject.toml)
typing-extensions==4.12.2
typing-extensions==4.15.0
# via
# mypy
# pwo
urllib3==2.2.3
urllib3==2.7.0
# via
# id
# requests
# twine
watchdog==5.0.3
# via bugis_core (pyproject.toml)
wcwidth==0.2.13
wcwidth==0.8.1
# via prompt-toolkit
zipp==3.20.2
# via importlib-metadata
+7 -11
View File
@@ -1,21 +1,17 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --output-file=requirements.txt
# pip-compile --allow-unsafe --extra=rsgi --output-file=requirements.txt pyproject.toml
#
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple
aiofiles==24.1.0
click==8.4.2
# via granian
granian==2.7.7
# via bugis_core (pyproject.toml)
pwo==0.0.5
pwo==0.1.2
# via bugis_core (pyproject.toml)
pyyaml==6.0.2
# via bugis_core (pyproject.toml)
sortedcontainers==2.4.0
# via bugis_core (pyproject.toml)
typing-extensions==4.12.2
typing-extensions==4.15.0
# via pwo
watchdog==5.0.3
# via bugis_core (pyproject.toml)
+1 -1
View File
@@ -52,7 +52,7 @@ class RsgiContext(HttpContext):
self.server = (Maybe.of(scope.server.split(':'))
.map(lambda it: (it[0], int(it[1])))
.or_else_throw(RuntimeError))
self.request_body = aiter(protocol)
self.request_body = protocol
self.protocol = protocol
# @staticmethod
+21 -10
View File
@@ -3,7 +3,7 @@ import json
import httpx
from pwo import async_test
from bugis.core import BugisApp, HttpContext, HttpMethod
from typing import Sequence
from typing import Sequence, List
class AsgiTest(unittest.TestCase):
@@ -15,6 +15,7 @@ class AsgiTest(unittest.TestCase):
@self.app.GET('/hello')
@self.app.GET('/hello2')
@self.app.route('/hello3')
@self.app.GET('/hello/*')
async def handle_request(ctx: HttpContext) -> None:
async for chunk in ctx.request_body:
print(chunk)
@@ -53,34 +54,44 @@ class AsgiTest(unittest.TestCase):
'employee_id': employee_id
}))
@self.app.PUT('/hello/*', recursive=True)
async def handle_request(ctx: HttpContext, _: List[str]) -> None:
await ctx.stream_body(200, (chunk async for chunk in ctx.request_body))
@async_test
async def test_hello(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("/hello")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, "Hello World!")
r = await client.get("/hello2")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, "Hello World!")
r = await client.post("/hello3")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, "Hello World!")
r = await client.get("/hello4")
self.assertEqual(r.status_code, 404)
self.assertEqual(404, r.status_code)
self.assertTrue(len(r.text) == 0)
body = {'name': 'John', 'surname': 'Smith'}
r = await client.put("/hello/foo/bar", json=body)
self.assertEqual(200, r.status_code)
ans = json.loads(r.text)
self.assertTrue(body, ans)
@async_test
async def test_foo(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.put("/foo/fizz/baz")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
response = json.loads(r.text)
self.assertEqual(['fizz', 'baz'], response)
@@ -90,7 +101,7 @@ class AsgiTest(unittest.TestCase):
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.put("/foo/bar/baz")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
self.assertEqual('/foo/bar/baz', r.text)
@async_test
@@ -99,7 +110,7 @@ class AsgiTest(unittest.TestCase):
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("/employee/101325")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, '101325')
@async_test
@@ -109,7 +120,7 @@ class AsgiTest(unittest.TestCase):
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
x = 30
r = await client.get(f"/square/{x}")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, str(x * x))
@async_test
@@ -118,7 +129,7 @@ class AsgiTest(unittest.TestCase):
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("department/189350/employee/101325")
self.assertEqual(r.status_code, 200)
self.assertEqual(200, r.status_code)
response = json.loads(r.text)
self.assertEqual({
'department_id': 189350,
+1 -1
View File
@@ -35,7 +35,7 @@ services:
cpus: "4"
memory: 1G
nginx:
image: gitea.woggioni.net/woggioni/nginx:v1.27.2
image: gitea.woggioni.net/woggioni/nginx:v1.31.2
# container_name: nginx
restart: unless-stopped
depends_on:
+70 -68
View File
@@ -1,131 +1,142 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --extra=dev --output-file=requirements-dev.txt pyproject.toml
# pip-compile --allow-unsafe --extra=dev --output-file=requirements-dev.txt pyproject.toml
#
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple
aiofiles==24.1.0
aiofiles==25.1.0
# via bugis (pyproject.toml)
anyio==4.6.2.post1
anyio==4.14.1
# via httpx
asttokens==2.4.1
ast-serialize==0.5.0
# via mypy
asttokens==3.0.1
# via stack-data
build==1.2.2.post1
build==1.5.0
# via bugis (pyproject.toml)
certifi==2024.8.30
certifi==2026.6.17
# via
# httpcore
# httpx
# requests
cffi==1.17.1
cffi==2.0.0
# via cryptography
charset-normalizer==3.4.0
charset-normalizer==3.4.7
# via requests
click==8.1.7
click==8.4.2
# via granian
cryptography==43.0.3
cryptography==49.0.0
# via secretstorage
decorator==5.1.1
decorator==5.3.1
# via
# ipdb
# ipython
docutils==0.21.2
docutils==0.23
# via readme-renderer
executing==2.1.0
executing==2.2.1
# via stack-data
granian==1.6.1
granian==2.7.7
# via bugis (pyproject.toml)
h11==0.14.0
h11==0.16.0
# via httpcore
h2==4.1.0
h2==4.3.0
# via httpx
hpack==4.0.0
hpack==4.2.0
# via h2
httpcore==1.0.6
httpcore==1.0.9
# via httpx
httpx[http2]==0.27.2
httpx[http2]==0.28.1
# via bugis (pyproject.toml)
hyperframe==6.0.1
hyperframe==6.1.0
# via h2
idna==3.10
id==1.6.1
# via twine
idna==3.18
# via
# anyio
# httpx
# requests
importlib-metadata==8.5.0
# via twine
ipdb==0.13.13
# via bugis (pyproject.toml)
ipython==8.28.0
ipython==9.15.0
# via ipdb
ipython-pygments-lexers==1.1.1
# via ipython
jaraco-classes==3.4.0
# via keyring
jaraco-context==6.0.1
jaraco-context==6.1.2
# via keyring
jaraco-functools==4.1.0
jaraco-functools==4.5.0
# via keyring
jedi==0.19.1
jedi==0.20.0
# via ipython
jeepney==0.8.0
jeepney==0.9.0
# via
# keyring
# secretstorage
keyring==25.4.1
keyring==25.7.0
# via twine
markdown==3.7
librt==0.11.0
# via mypy
markdown==3.10.2
# via bugis (pyproject.toml)
markdown-it-py==3.0.0
markdown-it-py==4.2.0
# via rich
matplotlib-inline==0.1.7
matplotlib-inline==0.2.2
# via ipython
mdurl==0.1.2
# via markdown-it-py
more-itertools==10.5.0
more-itertools==11.1.0
# via
# jaraco-classes
# jaraco-functools
mypy==1.13.0
mypy==2.1.0
# via bugis (pyproject.toml)
mypy-extensions==1.0.0
mypy-extensions==1.1.0
# via mypy
nh3==0.2.18
nh3==0.3.6
# via readme-renderer
packaging==24.1
# via build
parso==0.8.4
packaging==26.2
# via
# build
# twine
parso==0.8.7
# via jedi
pathspec==1.1.1
# via mypy
pexpect==4.9.0
# via ipython
pkginfo==1.10.0
# via twine
prompt-toolkit==3.0.48
prompt-toolkit==3.0.52
# via ipython
psutil==7.2.2
# via ipython
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.3
# via stack-data
pwo==0.0.4
pwo==0.1.2
# via bugis (pyproject.toml)
pycparser==2.22
pycparser==3.0
# via cffi
pygments==2.18.0
pygments==2.20.0
# via
# bugis (pyproject.toml)
# ipython
# ipython-pygments-lexers
# readme-renderer
# rich
pygraphviz==1.14
pygraphviz==2.0
# via bugis (pyproject.toml)
pyproject-hooks==1.2.0
# via build
pyyaml==6.0.2
pyyaml==6.0.3
# via bugis (pyproject.toml)
readme-renderer==44.0
readme-renderer==45.0
# via twine
requests==2.32.3
requests==2.34.2
# via
# requests-toolbelt
# twine
@@ -133,37 +144,28 @@ requests-toolbelt==1.0.0
# via twine
rfc3986==2.0.0
# via twine
rich==13.9.3
rich==15.0.0
# via twine
secretstorage==3.3.3
secretstorage==3.5.0
# via keyring
six==1.16.0
# via asttokens
sniffio==1.3.1
# via
# anyio
# httpx
stack-data==0.6.3
# via ipython
traitlets==5.14.3
traitlets==5.15.1
# via
# ipython
# matplotlib-inline
twine==5.1.1
twine==6.2.0
# via bugis (pyproject.toml)
typing-extensions==4.12.2
typing-extensions==4.15.0
# via
# mypy
# pwo
urllib3==2.2.3
urllib3==2.7.0
# via
# id
# requests
# twine
uvloop==0.21.0
# via granian
watchdog==5.0.3
watchdog==6.0.0
# via bugis (pyproject.toml)
wcwidth==0.2.13
wcwidth==0.8.1
# via prompt-toolkit
zipp==3.20.2
# via importlib-metadata
+19 -23
View File
@@ -1,51 +1,47 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --output-file=requirements.txt pyproject.toml
# pip-compile --allow-unsafe --output-file=requirements.txt pyproject.toml
#
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple
aiofiles==24.1.0
aiofiles==25.1.0
# via bugis (pyproject.toml)
anyio==4.6.2.post1
anyio==4.14.1
# via httpx
certifi==2024.8.30
certifi==2026.6.17
# via
# httpcore
# httpx
h11==0.14.0
h11==0.16.0
# via httpcore
h2==4.1.0
h2==4.3.0
# via httpx
hpack==4.0.0
hpack==4.2.0
# via h2
httpcore==1.0.6
httpcore==1.0.9
# via httpx
httpx[http2]==0.27.2
httpx[http2]==0.28.1
# via bugis (pyproject.toml)
hyperframe==6.0.1
hyperframe==6.1.0
# via h2
idna==3.10
idna==3.18
# via
# anyio
# httpx
markdown==3.7
markdown==3.10.2
# via bugis (pyproject.toml)
pwo==0.0.4
pwo==0.1.2
# via bugis (pyproject.toml)
pygments==2.18.0
pygments==2.20.0
# via bugis (pyproject.toml)
pygraphviz==1.14
pygraphviz==2.0
# via bugis (pyproject.toml)
pyyaml==6.0.2
pyyaml==6.0.3
# via bugis (pyproject.toml)
sniffio==1.3.1
# via
# anyio
# httpx
typing-extensions==4.12.2
typing-extensions==4.15.0
# via pwo
watchdog==5.0.3
watchdog==6.0.0
# via bugis (pyproject.toml)
+37 -9
View File
@@ -1,16 +1,44 @@
from abc import ABC, abstractmethod
from typing import Optional
from typing import Optional, Tuple, Callable
from functools import lru_cache
from pathlib import PurePath
from .renderer import RenderingManager
class Cache(ABC):
_rendering_manager: RenderingManager
@abstractmethod
def get(self, key: bytes) -> Optional[bytes]:
pass
def __init__(self, rendering_manager: RenderingManager):
self._rendering_manager = rendering_manager
@abstractmethod
def put(self, key: bytes, value: bytes):
pass
def get(self, key: bytes, file: PurePath) -> Optional[Tuple[str, bytes]]:
if self.filter(file):
return self.load(key, file)
else:
return self._rendering_manager.render(file)
def __contains__(self, key: bytes):
return self.get(key) is not None
def filter(self, file: PurePath) -> bool:
return file.suffix.lower() in {'md', 'puml', 'dot', 'texi', 'texinfo', 'txi'}
def load(self, key: bytes, file: PurePath) -> Optional[Tuple[str, bytes]]:
return self._rendering_manager.render(file)
class NoCache(Cache):
pass
class InMemoryCache(Cache):
_cached_loader: Callable[[bytes, PurePath], Tuple[str, bytes]]
def __init__(self, rendering_manager: RenderingManager, max_size: int = 1024):
super().__init__(rendering_manager)
@lru_cache(maxsize=max_size)
def cached_loader(key: bytes, file: PurePath) -> Tuple[str, bytes]:
return super().load(key, file)
self._cached_loader = cached_loader
def load(self, key: bytes, file: PurePath) -> Optional[Tuple[str, bytes]]:
return self._cached_loader(key, file)
+16
View File
@@ -0,0 +1,16 @@
from pathlib import PurePath
from typing import Callable, Mapping, Optional, Tuple
from pwo import Maybe
class RenderingManager:
_renderers: Mapping[str, Callable[[PurePath], Tuple[str, bytes]]]
def __init__(self, renderers: Mapping[str, Callable[[PurePath], Tuple[str, bytes]]]):
self._renderers = renderers
def render(self, file: PurePath) -> Optional[Tuple[str, bytes]]:
return Maybe.of_nullable(self._renderers.get(file.suffix.lower())).map(lambda it: it(file)).or_none()
# def register(self, suffix: str, renderer: Callable[[PurePath], Tuple[str, bytes]]) -> None:
# self._renderers[suffix.lower()] = renderer
+33 -17
View File
@@ -1,13 +1,15 @@
from base64 import b64encode
from base64 import b64encode, b64decode
from hashlib import md5
from mimetypes import guess_type
from pathlib import Path, PurePath
from typing import TYPE_CHECKING, Optional, Callable, Awaitable, AsyncGenerator, Any, Unpack
from typing import TYPE_CHECKING, Optional, Callable, Awaitable, AsyncGenerator, Any, Unpack, Mapping, Tuple
from aiofiles.os import listdir
from aiofiles.ospath import isdir, isfile
from bugis.core import HttpContext, BugisApp
from pwo import Maybe
from .cache import Cache, InMemoryCache
from .renderer import RenderingManager
if TYPE_CHECKING:
from _typeshed import StrOrBytesPath
@@ -31,8 +33,13 @@ def static_resources(app: BugisApp,
path: str,
root: 'StrOrBytesPath',
favicon: PurePath = None,
file_filter: Callable[[PurePath], Awaitable[bool]] = None
file_filter: Callable[[PurePath], Awaitable[bool]] = None,
renderers: Mapping[str, Tuple[str, bytes]] = None,
cache_ctor: Callable[[RenderingManager], Cache] = lambda rm: InMemoryCache(rm)
):
renderer = RenderingManager(renderers or {})
cache = cache_ctor(renderer)
async def no_filter(_: PurePath):
return True
@@ -66,24 +73,33 @@ def static_resources(app: BugisApp,
if await isfile(resource):
e_tag_header = ((Maybe.of_nullable(context.headers.get('if-none-match'))
.filter(lambda it: len(it) > 0)
.map(lambda it: it[-1])
.map(parse_etag))
.or_none())
proposed_etag = ((Maybe.of_nullable(context.headers.get('if-none-match'))
.filter(lambda it: len(it) > 0)
.map(lambda it: it[-1])
.map(parse_etag))
.or_none())
current_etag = compute_etag(resource)
if e_tag_header == current_etag:
if proposed_etag == current_etag:
return await context.send_empty(304)
else:
mime_type = (Maybe.of(guess_type(resource.name))
.map(lambda it: it[0])
.or_else('application/octet-stream'))
return await context.send_file(200, resource, {
'content-type': mime_type or 'application/octet-stream',
'etag': 'W/' + current_etag,
'cache-control': 'no-cache',
})
cache_result = cache.get(b64decode(current_etag), resource)
if cache_result is None:
mime_type = (Maybe.of(guess_type(resource.name))
.map(lambda it: it[0])
.or_else('application/octet-stream'))
return await context.send_file(200, resource, {
'content-type': mime_type or 'application/octet-stream',
'etag': 'W/' + current_etag,
'cache-control': 'no-cache',
})
else:
content_type, body = cache_result
await context.send_bytes(200, body, {
'content-type': content_type,
'etag': 'W/' + current_etag,
'cache-control': 'no-cache',
})
elif isdir(resource):
headers = {
'content-type': 'text/html'