Support multipart HTML5 forms
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-17 12:41:50 +03:00
parent 940d7e884b
commit 2e6fee5fc7
4 changed files with 40 additions and 0 deletions
+1
View File
@@ -28,6 +28,7 @@ dependencies = [
"sfera-ui-semantics",
"smbprotocol>=1.15",
"uvicorn>=0.30",
"python-multipart>=0.0.20",
]
[tool.uv]
@@ -6,6 +6,14 @@ from fastapi import Request
async def html5_form_data(request: Request) -> dict[str, list[str]]:
content_type = request.headers.get("content-type", "")
if content_type.startswith("multipart/form-data"):
parsed = await request.form()
form: dict[str, list[str]] = {}
for key, value in parsed.multi_items():
text = getattr(value, "filename", None) if not isinstance(value, str) else value
form.setdefault(key, []).append(str(text or ""))
return form
body = (await request.body()).decode("utf-8")
return parse_qs(body, keep_blank_values=True)
+20
View File
@@ -484,6 +484,26 @@ def test_html5_project_index_creates_project_with_fragment():
assert deleted_setup.json()["status"] == "NOT_CONFIGURED"
def test_html5_project_create_accepts_multipart_browser_form():
client = TestClient(app)
project_id = f"html5-multipart-{uuid4()}"
created = client.post(
"/html5/projects",
files={
"project_id": (None, project_id),
"name": (None, "HTML5 Multipart"),
},
)
assert created.status_code == 200
assert_html5_response_contract(created, project_id, "HTML5 Multipart")
setup = client.get(f"/html5/projects/{project_id}/setup")
assert setup.status_code == 200
assert_html5_response_contract(setup, "data-html5-page=\"setup\"", "HTML5 Multipart", full_page=True)
def test_html5_object_context_fragment(tmp_path: Path):
project_id = f"html5-object-context-{uuid4()}"
(tmp_path / "metadata.xml").write_text(
Generated
+11
View File
@@ -505,6 +505,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
]
[[package]]
name = "python-multipart"
version = "0.0.28"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/54/a85eb421fbdd5007bc5af39d0f4ed9fa609e0fedbfdc2adcf0b34526870e/python_multipart-0.0.28.tar.gz", hash = "sha256:8550da197eac0f7ab748961fc9509b999fa2662ea25cef857f05249f6893c0f8", size = 45314, upload-time = "2026-05-10T11:05:16.596Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/a2/43bbc5860b5034e2af4ef99a0e04d726ff329c43e192ef3abaa8d7ecfce5/python_multipart-0.0.28-py3-none-any.whl", hash = "sha256:10faac07eb966c3f48dc415f9dee46c04cb10d58d30a35677db8027c825ed9b6", size = 29438, upload-time = "2026-05-10T11:05:15.052Z" },
]
[[package]]
name = "pytz"
version = "2026.2"
@@ -521,6 +530,7 @@ source = { editable = "services/api-server" }
dependencies = [
{ name = "fastapi" },
{ name = "neo4j" },
{ name = "python-multipart" },
{ name = "sfera-collaboration" },
{ name = "sfera-impact-engine" },
{ name = "sfera-incremental-indexer" },
@@ -550,6 +560,7 @@ dependencies = [
requires-dist = [
{ name = "fastapi", specifier = ">=0.115" },
{ name = "neo4j", specifier = ">=5.0" },
{ name = "python-multipart", specifier = ">=0.0.20" },
{ name = "sfera-collaboration", editable = "packages/collaboration" },
{ name = "sfera-impact-engine", editable = "packages/impact-engine" },
{ name = "sfera-incremental-indexer", editable = "packages/incremental-indexer" },