diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5af8342..1f49c18 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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"] diff --git a/alembic/versions/20241202_0001_001_initial_schema.py b/alembic/versions/20241202_0001_001_initial_schema.py index 0ddbce2..aa91a3b 100644 --- a/alembic/versions/20241202_0001_001_initial_schema.py +++ b/alembic/versions/20241202_0001_001_initial_schema.py @@ -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 diff --git a/alembic/versions/20241205_0001_002_member_nodes.py b/alembic/versions/20241205_0001_002_member_nodes.py index ebf53ab..efd4892 100644 --- a/alembic/versions/20241205_0001_002_member_nodes.py +++ b/alembic/versions/20241205_0001_002_member_nodes.py @@ -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( diff --git a/alembic/versions/20241206_0001_003_add_event_hash.py b/alembic/versions/20241206_0001_003_add_event_hash.py index 085f036..61e6bea 100644 --- a/alembic/versions/20241206_0001_003_add_event_hash.py +++ b/alembic/versions/20241206_0001_003_add_event_hash.py @@ -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" diff --git a/alembic/versions/20241206_0002_004_event_receivers.py b/alembic/versions/20241206_0002_004_event_receivers.py index 2682803..0eba791 100644 --- a/alembic/versions/20241206_0002_004_event_receivers.py +++ b/alembic/versions/20241206_0002_004_event_receivers.py @@ -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" diff --git a/alembic/versions/20241206_0003_005_unique_event_hash.py b/alembic/versions/20241206_0003_005_unique_event_hash.py index 6af0254..5689f5f 100644 --- a/alembic/versions/20241206_0003_005_unique_event_hash.py +++ b/alembic/versions/20241206_0003_005_unique_event_hash.py @@ -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" diff --git a/alembic/versions/20251208_0007_0b944542ccd8_make_node_last_seen_nullable.py b/alembic/versions/20251208_0007_0b944542ccd8_make_node_last_seen_nullable.py index 2af4a64..caf0923 100644 --- a/alembic/versions/20251208_0007_0b944542ccd8_make_node_last_seen_nullable.py +++ b/alembic/versions/20251208_0007_0b944542ccd8_make_node_last_seen_nullable.py @@ -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" diff --git a/alembic/versions/20251208_1434_03b9b2451bd9_add_member_id_field_to_members_table.py b/alembic/versions/20251208_1434_03b9b2451bd9_add_member_id_field_to_members_table.py index da396af..56355f3 100644 --- a/alembic/versions/20251208_1434_03b9b2451bd9_add_member_id_field_to_members_table.py +++ b/alembic/versions/20251208_1434_03b9b2451bd9_add_member_id_field_to_members_table.py @@ -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" diff --git a/alembic/versions/20251208_1504_aa1162502616_remove_member_nodes_table.py b/alembic/versions/20251208_1504_aa1162502616_remove_member_nodes_table.py index 5f98aef..2248e31 100644 --- a/alembic/versions/20251208_1504_aa1162502616_remove_member_nodes_table.py +++ b/alembic/versions/20251208_1504_aa1162502616_remove_member_nodes_table.py @@ -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" diff --git a/alembic/versions/20260109_2004_4e2e787a1660_add_lat_lon_columns_to_nodes.py b/alembic/versions/20260109_2004_4e2e787a1660_add_lat_lon_columns_to_nodes.py index 2ced353..0e24800 100644 --- a/alembic/versions/20260109_2004_4e2e787a1660_add_lat_lon_columns_to_nodes.py +++ b/alembic/versions/20260109_2004_4e2e787a1660_add_lat_lon_columns_to_nodes.py @@ -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" diff --git a/pyproject.toml b/pyproject.toml index 3800b36..1a60290 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 diff --git a/src/meshcore_hub/common/logging.py b/src/meshcore_hub/common/logging.py index b358ec9..e777dd1 100644 --- a/src/meshcore_hub/common/logging.py +++ b/src/meshcore_hub/common/logging.py @@ -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" diff --git a/src/meshcore_hub/web/app.py b/src/meshcore_hub/web/app.py index 683e633..5149a0b 100644 --- a/src/meshcore_hub/web/app.py +++ b/src/meshcore_hub/web/app.py @@ -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, diff --git a/tests/test_web/test_pages.py b/tests/test_web/test_pages.py index 0b8b576..f0b6fd7 100644 --- a/tests/test_web/test_pages.py +++ b/tests/test_web/test_pages.py @@ -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