Add content_type field for SerializedResponses, add conversion pipeline basic docs generation
This commit is contained in:
@@ -9,9 +9,10 @@ license = "LGPL-3.0-or-later"
|
|||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"megasniff>=0.2.0",
|
"megasniff>=0.2.0",
|
||||||
"breakshaft>=0.1.1",
|
"breakshaft>=0.1.3",
|
||||||
"case-insensitive-dictionary>=0.2.1",
|
"case-insensitive-dictionary>=0.2.1",
|
||||||
"mypy>=1.17.0",
|
"mypy>=1.17.0",
|
||||||
|
"jinja2>=3.1.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from turbosloth.interfaces.serialized import SerializedResponse, SerializedReque
|
|||||||
from turbosloth.internal_types import QTYPE, BTYPE, PTYPE, HTYPE
|
from turbosloth.internal_types import QTYPE, BTYPE, PTYPE, HTYPE
|
||||||
from turbosloth.req_schema import UnwrappedRequest
|
from turbosloth.req_schema import UnwrappedRequest
|
||||||
|
|
||||||
app = SlothApp()
|
app = SlothApp(di_autodoc_prefix='/didoc')
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import html
|
||||||
import typing
|
import typing
|
||||||
from typing import Optional, Callable, Awaitable, Protocol, get_type_hints, get_origin, get_args, Any, Annotated
|
from typing import Optional, Callable, Awaitable, Protocol, get_type_hints, get_origin, get_args, Any, Annotated
|
||||||
|
|
||||||
import breakshaft.util_mermaid
|
import breakshaft.util_mermaid
|
||||||
import megasniff.exceptions
|
import megasniff.exceptions
|
||||||
from breakshaft.models import ConversionPoint
|
from breakshaft.models import ConversionPoint, Callgraph
|
||||||
from megasniff import SchemaInflatorGenerator
|
from megasniff import SchemaInflatorGenerator
|
||||||
|
|
||||||
|
from .didoc import create_di_autodoc_handler
|
||||||
from .exceptions import HTTPException
|
from .exceptions import HTTPException
|
||||||
from .interfaces.base import BasicRequest, BasicResponse
|
from .interfaces.base import BasicRequest, BasicResponse
|
||||||
from .interfaces.serialize_selector import SerializeSelector
|
from .interfaces.serialize_selector import SerializeSelector
|
||||||
@@ -179,16 +181,24 @@ class MethodRoutersApp(ASGIApp):
|
|||||||
|
|
||||||
|
|
||||||
class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
|
class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
|
||||||
|
di_autodoc_prefix: Optional[str] = None
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
on_startup: Optional[Callable[[], Awaitable[None]]] = None,
|
on_startup: Optional[Callable[[], Awaitable[None]]] = None,
|
||||||
on_shutdown: Optional[Callable[[], Awaitable[None]]] = None):
|
on_shutdown: Optional[Callable[[], Awaitable[None]]] = None,
|
||||||
|
di_autodoc_prefix: Optional[str] = None,
|
||||||
|
):
|
||||||
self.router = Router()
|
self.router = Router()
|
||||||
self._on_startup = on_startup
|
self._on_startup = on_startup
|
||||||
self._on_shutdown = on_shutdown
|
self._on_shutdown = on_shutdown
|
||||||
self.serialize_selector = SerializeSelector()
|
self.serialize_selector = SerializeSelector()
|
||||||
self.infl_generator = SchemaInflatorGenerator(strict_mode=True)
|
self.infl_generator = SchemaInflatorGenerator(strict_mode=True)
|
||||||
self.inj_repo = ConvRepo()
|
self.di_autodoc_prefix = di_autodoc_prefix
|
||||||
|
|
||||||
|
if di_autodoc_prefix is not None:
|
||||||
|
self.inj_repo = ConvRepo(store_sources=True, store_callseq=True)
|
||||||
|
else:
|
||||||
|
self.inj_repo = ConvRepo()
|
||||||
|
|
||||||
@self.inj_repo.mark_injector()
|
@self.inj_repo.mark_injector()
|
||||||
def extract_query(req: BasicRequest | SerializedRequest) -> QTYPE:
|
def extract_query(req: BasicRequest | SerializedRequest) -> QTYPE:
|
||||||
@@ -231,42 +241,43 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
|
|||||||
if get_origin(tp) == UnwrappedRequest:
|
if get_origin(tp) == UnwrappedRequest:
|
||||||
req_schema = tp
|
req_schema = tp
|
||||||
|
|
||||||
if req_schema is None:
|
if req_schema is not None:
|
||||||
raise ValueError(f'Unable to find request schema in handler {fn}')
|
|
||||||
|
|
||||||
unwrap_types = get_args(req_schema)
|
unwrap_types = get_args(req_schema)
|
||||||
defaults = (QTYPE, BTYPE, PTYPE, HTYPE)
|
defaults = (QTYPE, BTYPE, PTYPE, HTYPE)
|
||||||
|
|
||||||
def none_generator(*args) -> None:
|
def none_generator(*args) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_convertor(t, def_type):
|
def create_convertor(t, def_type):
|
||||||
infl = None
|
infl = None
|
||||||
if t not in [def_type, None, Any]:
|
if t not in [def_type, None, Any]:
|
||||||
infl = self.infl_generator.schema_to_inflator(
|
infl = self.infl_generator.schema_to_inflator(
|
||||||
t,
|
t,
|
||||||
strict_mode_override=False,
|
strict_mode_override=False,
|
||||||
from_type_override=def_type
|
from_type_override=def_type
|
||||||
|
)
|
||||||
|
return ConversionPoint(infl, t, (def_type,), (), )
|
||||||
|
else:
|
||||||
|
return ConversionPoint(none_generator, t, (def_type,), (), )
|
||||||
|
|
||||||
|
fork_with = set(map(lambda x: create_convertor(*x), zip(unwrap_types, defaults)))
|
||||||
|
|
||||||
|
def construct_unwrap(q: QTYPE, b: BTYPE, p: PTYPE, h: HTYPE) -> UnwrappedRequest:
|
||||||
|
return UnwrappedRequest(q, b, p, h)
|
||||||
|
|
||||||
|
fork_with |= {
|
||||||
|
ConversionPoint(
|
||||||
|
construct_unwrap,
|
||||||
|
req_schema,
|
||||||
|
unwrap_types,
|
||||||
|
(),
|
||||||
)
|
)
|
||||||
return ConversionPoint(infl, t, (def_type,), (), )
|
}
|
||||||
else:
|
|
||||||
return ConversionPoint(none_generator, t, (def_type,), (), )
|
|
||||||
|
|
||||||
fork_with = set(map(lambda x: create_convertor(*x), zip(unwrap_types, defaults)))
|
tmp_repo = self.inj_repo.fork(fork_with)
|
||||||
|
else:
|
||||||
def construct_unwrap(q: QTYPE, b: BTYPE, p: PTYPE, h: HTYPE) -> UnwrappedRequest:
|
tmp_repo = self.inj_repo
|
||||||
return UnwrappedRequest(q, b, p, h)
|
|
||||||
|
|
||||||
fork_with |= {
|
|
||||||
ConversionPoint(
|
|
||||||
construct_unwrap,
|
|
||||||
req_schema,
|
|
||||||
unwrap_types,
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp_repo = self.inj_repo.fork(fork_with)
|
|
||||||
|
|
||||||
p = tmp_repo.create_pipeline(
|
p = tmp_repo.create_pipeline(
|
||||||
(Send, BasicRequest),
|
(Send, BasicRequest),
|
||||||
@@ -275,6 +286,12 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.router.add(method, path_pattern, p)
|
self.router.add(method, path_pattern, p)
|
||||||
|
|
||||||
|
if self.di_autodoc_prefix is not None and not path_pattern.startswith(
|
||||||
|
self.di_autodoc_prefix + '/' + method + self.di_autodoc_prefix):
|
||||||
|
self.route('GET', self.di_autodoc_prefix + '/' + method + path_pattern)(
|
||||||
|
create_di_autodoc_handler(method, path_pattern, p))
|
||||||
|
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
50
src/turbosloth/didoc/__init__.py
Normal file
50
src/turbosloth/didoc/__init__.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import html
|
||||||
|
import importlib.resources
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import breakshaft.util_mermaid
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
from turbosloth.interfaces.serialized import SerializedResponse
|
||||||
|
from turbosloth.interfaces.serialized.html import HTMLSerializedResponse
|
||||||
|
from turbosloth.types import InternalHandlerType
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MMDiagramData:
|
||||||
|
name: str
|
||||||
|
data: str
|
||||||
|
|
||||||
|
|
||||||
|
def create_di_autodoc_handler(method: str, path: str, handler: InternalHandlerType) -> Callable[[], SerializedResponse]:
|
||||||
|
callseq = getattr(handler, '__breakshaft_callseq__', [])
|
||||||
|
mmd_flowchart = breakshaft.util_mermaid.draw_callseq_mermaid(callseq)
|
||||||
|
mmd_flowchart1 = breakshaft.util_mermaid.draw_callseq_mermaid([callseq[0]] + callseq)
|
||||||
|
mmd_flowchart2 = breakshaft.util_mermaid.draw_callseq_mermaid([callseq[-1]] + callseq)
|
||||||
|
|
||||||
|
sources = getattr(handler, '__breakshaft_render_src__', '')
|
||||||
|
escaped_sources = html.escape(sources)
|
||||||
|
|
||||||
|
template_path = importlib.resources.files('turbosloth.didoc')
|
||||||
|
loader = jinja2.FileSystemLoader(str(template_path))
|
||||||
|
templateEnv = jinja2.Environment(loader=loader)
|
||||||
|
template = templateEnv.get_template('page.jinja2')
|
||||||
|
|
||||||
|
mmd_diagrams = [
|
||||||
|
MMDiagramData('1', html.escape(mmd_flowchart)),
|
||||||
|
MMDiagramData('2', html.escape(mmd_flowchart1)),
|
||||||
|
MMDiagramData('3', html.escape(mmd_flowchart2)),
|
||||||
|
]
|
||||||
|
|
||||||
|
html_content = template.render(
|
||||||
|
handler_method=method,
|
||||||
|
handler_path=path,
|
||||||
|
escaped_sources=escaped_sources,
|
||||||
|
mmd_diagrams=mmd_diagrams,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _h() -> SerializedResponse:
|
||||||
|
return HTMLSerializedResponse(200, {}, html_content)
|
||||||
|
|
||||||
|
return _h
|
||||||
244
src/turbosloth/didoc/page.jinja2
Normal file
244
src/turbosloth/didoc/page.jinja2
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Code + Mermaid</title>
|
||||||
|
|
||||||
|
<!-- Highlight.js с CDN -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.0/styles/atom-one-dark.min.css">
|
||||||
|
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.0/highlight.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.0/languages/go.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
|
||||||
|
<!-- Mermaid.js с CDN -->
|
||||||
|
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js " defer></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Инициализация Mermaid
|
||||||
|
mermaid.initialize({ startOnLoad: false, theme: 'dark' });
|
||||||
|
|
||||||
|
// Инициализация вкладок
|
||||||
|
const tabs = document.querySelectorAll('.tab');
|
||||||
|
const tabContents = document.querySelectorAll('.tab-content');
|
||||||
|
|
||||||
|
// Активация первой вкладки
|
||||||
|
if (tabs.length > 0) {
|
||||||
|
tabs[0].classList.add('active');
|
||||||
|
tabContents[0].style.display = 'block';
|
||||||
|
mermaid.init(undefined, tabContents[0].querySelector('.mermaid'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик клика по вкладкам
|
||||||
|
tabs.forEach((tab, index) => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
tabs.forEach(t => t.classList.remove('active'));
|
||||||
|
tabContents.forEach(c => c.style.display = 'none');
|
||||||
|
|
||||||
|
tab.classList.add('active');
|
||||||
|
tabContents[index].style.display = 'block';
|
||||||
|
|
||||||
|
// Инициализация Mermaid для активной вкладки, если еще не была инициализирована
|
||||||
|
const diagram = tabContents[index].querySelector('.mermaid');
|
||||||
|
if (!diagram.dataset.initialized) {
|
||||||
|
mermaid.init(undefined, diagram);
|
||||||
|
diagram.dataset.initialized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Логика перетаскивания
|
||||||
|
const resizer = document.getElementById('resizer');
|
||||||
|
const codeContainer = document.querySelector('.code-container');
|
||||||
|
const diagramContainer = document.querySelector('.diagram-container');
|
||||||
|
|
||||||
|
let isDragging = false;
|
||||||
|
|
||||||
|
resizer.addEventListener('mousedown', (e) => {
|
||||||
|
isDragging = true;
|
||||||
|
document.body.style.cursor = 'ew-resize';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
const containerWidth = document.body.clientWidth;
|
||||||
|
const x = e.clientX;
|
||||||
|
const codeWidth = (x / containerWidth) * 100;
|
||||||
|
|
||||||
|
if (codeWidth >= 5 && codeWidth <= 95) {
|
||||||
|
codeContainer.style.flex = `0 0 ${codeWidth}%`;
|
||||||
|
diagramContainer.style.flex = `0 0 ${100 - codeWidth}%`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
isDragging = false;
|
||||||
|
document.body.style.cursor = 'default';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseleave', () => {
|
||||||
|
isDragging = false;
|
||||||
|
document.body.style.cursor = 'default';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
max-width: 100vw;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #e6e6e6;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar span {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-container, .diagram-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-container {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-right: 1px solid #444;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-container pre {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #dcdcdc;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram-container {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
padding: 0 1rem;
|
||||||
|
background-color: #333;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #ccc;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
border-bottom: 2px solid #007acc;
|
||||||
|
color: #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem;
|
||||||
|
display: none;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid {
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid .node rect,
|
||||||
|
.mermaid .node circle,
|
||||||
|
.mermaid .node ellipse {
|
||||||
|
fill: #3a3a3a !important;
|
||||||
|
stroke: #888 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid .edgePath path {
|
||||||
|
stroke: #ccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
width: 5px;
|
||||||
|
background-color: #444;
|
||||||
|
cursor: ew-resize;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizer::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background-color: #888;
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="topbar">
|
||||||
|
<span style="color: #007acc;">{{ handler_method }}</span>
|
||||||
|
<span style="color: #888;">::</span>
|
||||||
|
<span>{{ handler_path }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="code-container">
|
||||||
|
<pre><code class="language-python">
|
||||||
|
{{ escaped_sources }}
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="resizer" id="resizer"></div>
|
||||||
|
|
||||||
|
<div class="diagram-container">
|
||||||
|
<ul class="tabs">
|
||||||
|
{% for diagram in mmd_diagrams %}
|
||||||
|
<li class="tab">{{ diagram.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% for diagram in mmd_diagrams %}
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="mermaid">
|
||||||
|
{{ diagram.data }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -44,6 +44,7 @@ class SerializedResponse:
|
|||||||
code: int
|
code: int
|
||||||
headers: Mapping[str, str]
|
headers: Mapping[str, str]
|
||||||
body: dict[str, Any] | list[Any] | str
|
body: dict[str, Any] | list[Any] | str
|
||||||
|
content_type: str = ''
|
||||||
|
|
||||||
def into_basic(self, charset: str) -> BasicResponse:
|
def into_basic(self, charset: str) -> BasicResponse:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|||||||
17
src/turbosloth/interfaces/serialized/html.py
Normal file
17
src/turbosloth/interfaces/serialized/html.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from case_insensitive_dict import CaseInsensitiveDict
|
||||||
|
|
||||||
|
from turbosloth.interfaces.base import BasicResponse
|
||||||
|
from .base import SerializedResponse
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HTMLSerializedResponse(SerializedResponse):
|
||||||
|
body: str
|
||||||
|
content_type = 'text/html'
|
||||||
|
|
||||||
|
def into_basic(self, charset: str) -> BasicResponse:
|
||||||
|
headers = CaseInsensitiveDict(self.headers).copy()
|
||||||
|
headers['content-type'] = self.content_type + '; charset=' + charset
|
||||||
|
return BasicResponse(self.code, headers, str(self.body).encode(charset))
|
||||||
@@ -19,8 +19,10 @@ class JsonSerializedRequest(SerializedRequest):
|
|||||||
|
|
||||||
|
|
||||||
class JsonSerializedResponse(SerializedResponse):
|
class JsonSerializedResponse(SerializedResponse):
|
||||||
|
content_type = 'application/json'
|
||||||
|
|
||||||
def into_basic(self, charset: str) -> BasicResponse:
|
def into_basic(self, charset: str) -> BasicResponse:
|
||||||
headers = CaseInsensitiveDict(self.headers).copy()
|
headers = CaseInsensitiveDict(self.headers).copy()
|
||||||
headers['content-type'] = 'application/json; charset=utf-8'
|
headers['content-type'] = self.content_type + '; charset=utf-8'
|
||||||
b = orjson.dumps(self.body)
|
b = orjson.dumps(self.body)
|
||||||
return BasicResponse(self.code, headers, b)
|
return BasicResponse(self.code, headers, b)
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ class MessagePackSerializedRequest(SerializedRequest):
|
|||||||
|
|
||||||
|
|
||||||
class MessagePackSerializedResponse(SerializedResponse):
|
class MessagePackSerializedResponse(SerializedResponse):
|
||||||
|
content_type = 'application/vnd.msgpack'
|
||||||
|
|
||||||
def into_basic(self, charset: str) -> BasicResponse:
|
def into_basic(self, charset: str) -> BasicResponse:
|
||||||
headers = CaseInsensitiveDict(self.headers).copy()
|
headers = CaseInsensitiveDict(self.headers).copy()
|
||||||
headers['content-type'] = 'application/vnd.msgpack'
|
headers['content-type'] = self.content_type
|
||||||
b = msgpack.packb(self.body)
|
b = msgpack.packb(self.body)
|
||||||
return BasicResponse(self.code, headers, b)
|
return BasicResponse(self.code, headers, b)
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ class TextSerializedRequest(SerializedRequest):
|
|||||||
|
|
||||||
|
|
||||||
class TextSerializedResponse(SerializedResponse):
|
class TextSerializedResponse(SerializedResponse):
|
||||||
|
content_type = 'text/plain'
|
||||||
|
|
||||||
def into_basic(self, charset: str) -> BasicResponse:
|
def into_basic(self, charset: str) -> BasicResponse:
|
||||||
headers = CaseInsensitiveDict(self.headers).copy()
|
headers = CaseInsensitiveDict(self.headers).copy()
|
||||||
headers['content-type'] = 'text/plain; charset=' + charset
|
headers['content-type'] = self.content_type + '; charset=' + charset
|
||||||
return BasicResponse(self.code, headers, str(self.body).encode(charset))
|
return BasicResponse(self.code, headers, str(self.body).encode(charset))
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ class XMLSerializedRequest(SerializedRequest):
|
|||||||
|
|
||||||
|
|
||||||
class XMLSerializedResponse(SerializedResponse):
|
class XMLSerializedResponse(SerializedResponse):
|
||||||
|
content_type = 'application/xml'
|
||||||
|
|
||||||
def into_basic(self, charset: str) -> BasicResponse:
|
def into_basic(self, charset: str) -> BasicResponse:
|
||||||
headers = CaseInsensitiveDict(self.headers).copy()
|
headers = CaseInsensitiveDict(self.headers).copy()
|
||||||
headers['content-type'] = 'application/xml; charset=' + charset
|
headers['content-type'] = self.content_type + '; charset=' + charset
|
||||||
|
|
||||||
btxt = _into_xml(self.body)
|
btxt = _into_xml(self.body)
|
||||||
|
|
||||||
|
|||||||
10
uv.lock
generated
10
uv.lock
generated
@@ -4,15 +4,15 @@ requires-python = ">=3.13"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breakshaft"
|
name = "breakshaft"
|
||||||
version = "0.1.1.post1"
|
version = "0.1.3"
|
||||||
source = { registry = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }
|
source = { registry = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "hatchling" },
|
{ name = "hatchling" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.1.post1/breakshaft-0.1.1.post1.tar.gz", hash = "sha256:0c880a57eb53122cd1d5c7d2f2520d9f536edd7fac333496da72cc0fa7bf5283" }
|
sdist = { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.3/breakshaft-0.1.3.tar.gz", hash = "sha256:2bf0154a9b0824ca8ca7d0908ea9aef78496d28ec824b677fff49cd8af2d1c56" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.1.post1/breakshaft-0.1.1.post1-py3-none-any.whl", hash = "sha256:46895336aee55b3fbd0664a2f2deb73a04f15f1dc3ac382fa17884bad4c0f818" },
|
{ url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.3/breakshaft-0.1.3-py3-none-any.whl", hash = "sha256:f9693d529a4c5af3f8e196bac191d7549eaf1b4ec601b63db6c1caae38bcec13" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -347,6 +347,7 @@ source = { editable = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "breakshaft" },
|
{ name = "breakshaft" },
|
||||||
{ name = "case-insensitive-dictionary" },
|
{ name = "case-insensitive-dictionary" },
|
||||||
|
{ name = "jinja2" },
|
||||||
{ name = "megasniff" },
|
{ name = "megasniff" },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
]
|
]
|
||||||
@@ -380,8 +381,9 @@ xml = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "breakshaft", specifier = ">=0.1.1", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
|
{ name = "breakshaft", specifier = ">=0.1.3", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
|
||||||
{ name = "case-insensitive-dictionary", specifier = ">=0.2.1" },
|
{ name = "case-insensitive-dictionary", specifier = ">=0.2.1" },
|
||||||
|
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||||
{ name = "megasniff", specifier = ">=0.2.0", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
|
{ name = "megasniff", specifier = ">=0.2.0", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
|
||||||
{ name = "mypy", specifier = ">=1.17.0" },
|
{ name = "mypy", specifier = ">=1.17.0" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user