mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 01:13:11 +02:00
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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user