Fix Starlette 1.0 incompatibility and bump to Python 3.14

Pin starlette<1.0.0 to avoid TemplateResponse breaking change, update
the TemplateResponse call to new-style request-first signature, and
bump Python version to 3.14 across Dockerfile, pyproject.toml, and
pre-commit hooks.
This commit is contained in:
Louis King
2026-04-11 18:33:26 +01:00
parent 92e9ccdbfa
commit 9664d4ee76
14 changed files with 46 additions and 100 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ repos:
- id: debug-statements
- repo: https://github.com/psf/black
rev: 24.3.0
rev: 26.1.0
hooks:
- id: black
args: ["--line-length=88"]
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "001"
down_revision: Union[str, None] = None
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "002"
down_revision: Union[str, None] = "001"
@@ -64,12 +63,10 @@ def upgrade() -> None:
node_id = str(uuid.uuid4())
connection.execute(
sa.text(
"""
sa.text("""
INSERT INTO member_nodes (id, member_id, public_key, created_at, updated_at)
VALUES (:id, :member_id, :public_key, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
"""
),
"""),
{"id": node_id, "member_id": member_id, "public_key": public_key},
)
@@ -88,9 +85,7 @@ def downgrade() -> None:
# Migrate data back - take the first node for each member
connection = op.get_bind()
member_nodes = connection.execute(
sa.text(
"""
member_nodes = connection.execute(sa.text("""
SELECT DISTINCT member_id, public_key
FROM member_nodes
WHERE (member_id, created_at) IN (
@@ -98,9 +93,7 @@ def downgrade() -> None:
FROM member_nodes
GROUP BY member_id
)
"""
)
).fetchall()
""")).fetchall()
for member_id, public_key in member_nodes:
connection.execute(
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "003"
down_revision: Union[str, None] = "002"
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "004"
down_revision: Union[str, None] = "003"
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = "005"
down_revision: Union[str, None] = "004"
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "0b944542ccd8"
down_revision: Union[str, None] = "005"
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "03b9b2451bd9"
down_revision: Union[str, None] = "0b944542ccd8"
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "aa1162502616"
down_revision: Union[str, None] = "03b9b2451bd9"
@@ -11,7 +11,6 @@ from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "4e2e787a1660"
down_revision: Union[str, None] = "aa1162502616"
+5 -4
View File
@@ -8,7 +8,7 @@ version = "0.0.0"
description = "Python monorepo for managing and orchestrating MeshCore mesh networks"
readme = "README.md"
license = {text = "GPL-3.0-or-later"}
requires-python = ">=3.13"
requires-python = ">=3.14"
authors = [
{name = "MeshCore Hub Contributors"}
]
@@ -18,7 +18,7 @@ classifiers = [
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Communications",
"Topic :: System :: Networking",
]
@@ -31,6 +31,7 @@ dependencies = [
"sqlalchemy>=2.0.0",
"alembic>=1.12.0",
"fastapi>=0.100.0",
"starlette>=0.40.0,<1.0.0",
"uvicorn[standard]>=0.23.0",
"paho-mqtt>=2.0.0",
"jinja2>=3.1.0",
@@ -81,7 +82,7 @@ meshcore_hub = ["py.typed"]
[tool.black]
line-length = 88
target-version = ["py312"]
target-version = ["py313"]
include = '\.pyi?$'
extend-exclude = '''
/(
@@ -100,7 +101,7 @@ extend-exclude = '''
'''
[tool.mypy]
python_version = "3.13"
python_version = "3.14"
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
-1
View File
@@ -6,7 +6,6 @@ from typing import Optional
from meshcore_hub.common.config import LogLevel
# Default log format
DEFAULT_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+2 -7
View File
@@ -648,12 +648,7 @@ def create_app(
disallow_lines.append(line)
disallow_block = "\n".join(disallow_lines)
return (
f"User-agent: *\n"
f"{disallow_block}\n"
f"\n"
f"Sitemap: {base_url}/sitemap.xml\n"
)
return f"User-agent: *\n{disallow_block}\n\nSitemap: {base_url}/sitemap.xml\n"
@app.get("/sitemap.xml")
async def sitemap_xml(request: Request) -> Response:
@@ -721,9 +716,9 @@ def create_app(
config_json = _build_config_json(request.app, request)
return templates_inst.TemplateResponse(
request,
"spa.html",
{
"request": request,
"network_name": request.app.state.network_name,
"network_city": request.app.state.network_city,
"network_country": request.app.state.network_country,
+34 -68
View File
@@ -60,8 +60,7 @@ class TestPageLoader:
"""Test loading a page with full frontmatter."""
with tempfile.TemporaryDirectory() as tmpdir:
page_path = Path(tmpdir) / "about.md"
page_path.write_text(
"""---
page_path.write_text("""---
title: About Us
slug: about
menu_order: 10
@@ -70,8 +69,7 @@ menu_order: 10
# About
This is the about page.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -88,14 +86,12 @@ This is the about page.
"""Test that slug defaults to filename when not specified."""
with tempfile.TemporaryDirectory() as tmpdir:
page_path = Path(tmpdir) / "my-custom-page.md"
page_path.write_text(
"""---
page_path.write_text("""---
title: My Custom Page
---
Content here.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -121,14 +117,12 @@ Content here.
"""Test that menu_order defaults to 100."""
with tempfile.TemporaryDirectory() as tmpdir:
page_path = Path(tmpdir) / "page.md"
page_path.write_text(
"""---
page_path.write_text("""---
title: Test Page
---
Content.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -141,33 +135,27 @@ Content.
"""Test that pages are sorted by menu_order then title."""
with tempfile.TemporaryDirectory() as tmpdir:
# Create pages with different menu_order values
(Path(tmpdir) / "page-z.md").write_text(
"""---
(Path(tmpdir) / "page-z.md").write_text("""---
title: Z Page
menu_order: 30
---
Content.
"""
)
(Path(tmpdir) / "page-a.md").write_text(
"""---
""")
(Path(tmpdir) / "page-a.md").write_text("""---
title: A Page
menu_order: 10
---
Content.
"""
)
(Path(tmpdir) / "page-m.md").write_text(
"""---
""")
(Path(tmpdir) / "page-m.md").write_text("""---
title: M Page
menu_order: 20
---
Content.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -179,24 +167,20 @@ Content.
def test_load_pages_secondary_sort_by_title(self) -> None:
"""Test that pages with same menu_order are sorted by title."""
with tempfile.TemporaryDirectory() as tmpdir:
(Path(tmpdir) / "zebra.md").write_text(
"""---
(Path(tmpdir) / "zebra.md").write_text("""---
title: Zebra
menu_order: 10
---
Content.
"""
)
(Path(tmpdir) / "apple.md").write_text(
"""---
""")
(Path(tmpdir) / "apple.md").write_text("""---
title: Apple
menu_order: 10
---
Content.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -208,24 +192,20 @@ Content.
def test_get_page_returns_correct_page(self) -> None:
"""Test that get_page returns the page with the given slug."""
with tempfile.TemporaryDirectory() as tmpdir:
(Path(tmpdir) / "about.md").write_text(
"""---
(Path(tmpdir) / "about.md").write_text("""---
title: About
slug: about
---
About content.
"""
)
(Path(tmpdir) / "contact.md").write_text(
"""---
""")
(Path(tmpdir) / "contact.md").write_text("""---
title: Contact
slug: contact
---
Contact content.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -250,14 +230,12 @@ Contact content.
"""Test that reload() clears existing pages and reloads from disk."""
with tempfile.TemporaryDirectory() as tmpdir:
page_path = Path(tmpdir) / "page.md"
page_path.write_text(
"""---
page_path.write_text("""---
title: Original
---
Content.
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -267,14 +245,12 @@ Content.
assert pages[0].title == "Original"
# Update the file
page_path.write_text(
"""---
page_path.write_text("""---
title: Updated
---
New content.
"""
)
""")
loader.reload()
@@ -299,16 +275,14 @@ New content.
def test_markdown_tables_rendered(self) -> None:
"""Test that markdown tables are rendered to HTML."""
with tempfile.TemporaryDirectory() as tmpdir:
(Path(tmpdir) / "tables.md").write_text(
"""---
(Path(tmpdir) / "tables.md").write_text("""---
title: Tables
---
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -321,8 +295,7 @@ title: Tables
def test_markdown_fenced_code_rendered(self) -> None:
"""Test that fenced code blocks are rendered."""
with tempfile.TemporaryDirectory() as tmpdir:
(Path(tmpdir) / "code.md").write_text(
"""---
(Path(tmpdir) / "code.md").write_text("""---
title: Code
---
@@ -330,8 +303,7 @@ title: Code
def hello():
print("Hello!")
```
"""
)
""")
loader = PageLoader(tmpdir)
loader.load_pages()
@@ -357,8 +329,7 @@ class TestPagesRoute:
# Create pages subdirectory (CONTENT_HOME/pages)
pages_subdir = Path(tmpdir) / "pages"
pages_subdir.mkdir()
(pages_subdir / "about.md").write_text(
"""---
(pages_subdir / "about.md").write_text("""---
title: About Us
slug: about
menu_order: 10
@@ -367,10 +338,8 @@ menu_order: 10
# About Our Network
Welcome to the network.
"""
)
(pages_subdir / "faq.md").write_text(
"""---
""")
(pages_subdir / "faq.md").write_text("""---
title: FAQ
slug: faq
menu_order: 20
@@ -379,8 +348,7 @@ menu_order: 20
# Frequently Asked Questions
Here are some answers.
"""
)
""")
yield tmpdir
@pytest.fixture
@@ -505,15 +473,13 @@ class TestPagesInSitemap:
# Create pages subdirectory (CONTENT_HOME/pages)
pages_subdir = Path(tmpdir) / "pages"
pages_subdir.mkdir()
(pages_subdir / "about.md").write_text(
"""---
(pages_subdir / "about.md").write_text("""---
title: About
slug: about
---
About page.
"""
)
""")
yield tmpdir
@pytest.fixture