From 8b925263a29cbe5167a2e8ca1c5e9a843213ef7f Mon Sep 17 00:00:00 2001 From: walkpan Date: Sun, 29 Mar 2026 21:26:00 +0800 Subject: [PATCH] feat: project skeleton with config, dependencies, and test setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.example | 10 ++++++++++ .gitignore | 11 ++++++++++ pyproject.toml | 38 +++++++++++++++++++++++++++++++++++ src/mqtt_home/__init__.py | 0 src/mqtt_home/config.py | 20 +++++++++++++++++++ src/mqtt_home/database.py | 5 +++++ tests/conftest.py | 42 +++++++++++++++++++++++++++++++++++++++ tests/test_config.py | 17 ++++++++++++++++ 8 files changed, 143 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 pyproject.toml create mode 100644 src/mqtt_home/__init__.py create mode 100644 src/mqtt_home/config.py create mode 100644 src/mqtt_home/database.py create mode 100644 tests/conftest.py create mode 100644 tests/test_config.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f369b2b --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +MQTT_HOST=192.168.0.31 +MQTT_PORT=1883 +MQTT_USERNAME= +MQTT_PASSWORD= +EMQX_API_URL=http://192.168.0.31:18083/api/v5 +EMQX_API_KEY=your-api-key +EMQX_API_SECRET=your-secret +DATABASE_URL=sqlite+aiosqlite:///./data/mqtt_home.db +WEB_HOST=0.0.0.0 +WEB_PORT=8000 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..123311f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +dist/ +build/ +.venv/ +.env +data/ +*.db +frontend/node_modules/ +frontend/dist/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..72fbead --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["setuptools>=68.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "mqtt-home" +version = "0.1.0" +description = "轻量级智能家居 MQTT 设备管理系统" +requires-python = ">=3.11" +dependencies = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", + "aiomqtt>=2.0.0", + "sqlalchemy[asyncio]>=2.0.0", + "aiosqlite>=0.20.0", + "pydantic-settings>=2.0.0", + "click>=8.1.0", + "httpx>=0.27.0", + "websockets>=12.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0.0", + "pytest-asyncio>=0.23.0", + "pytest-httpx>=0.30.0", + "httpx>=0.27.0", +] + +[project.scripts] +mqtt-home = "mqtt_home.cli:cli" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] \ No newline at end of file diff --git a/src/mqtt_home/__init__.py b/src/mqtt_home/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/mqtt_home/config.py b/src/mqtt_home/config.py new file mode 100644 index 0000000..44ac289 --- /dev/null +++ b/src/mqtt_home/config.py @@ -0,0 +1,20 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + mqtt_host: str = "localhost" + mqtt_port: int = 1883 + mqtt_username: str = "" + mqtt_password: str = "" + emqx_api_url: str = "http://localhost:18083/api/v5" + emqx_api_key: str = "" + emqx_api_secret: str = "" + database_url: str = "sqlite+aiosqlite:///./data/mqtt_home.db" + web_host: str = "0.0.0.0" + web_port: int = 8000 + + model_config = {"env_file": ".env", "env_prefix": ""} + + +def get_settings() -> Settings: + return Settings() \ No newline at end of file diff --git a/src/mqtt_home/database.py b/src/mqtt_home/database.py new file mode 100644 index 0000000..1c2dcc4 --- /dev/null +++ b/src/mqtt_home/database.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..04551f7 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,42 @@ +import pytest +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession +from sqlalchemy.pool import StaticPool + +from mqtt_home.database import Base +from mqtt_home.config import Settings + +TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" + + +@pytest.fixture +def settings(): + return Settings( + mqtt_host="localhost", + mqtt_port=1883, + emqx_api_url="http://localhost:18083/api/v5", + emqx_api_key="test-key", + emqx_api_secret="test-secret", + database_url=TEST_DATABASE_URL, + ) + + +@pytest.fixture +async def engine(): + engine = create_async_engine( + TEST_DATABASE_URL, + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + yield engine + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await engine.dispose() + + +@pytest.fixture +async def db_session(engine): + async_session = async_sessionmaker(engine, expire_on_commit=False) + async with async_session() as session: + yield session \ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..d7c1847 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,17 @@ +import os +from mqtt_home.config import Settings + + +def test_default_settings(): + s = Settings() + assert s.mqtt_host == "localhost" + assert s.mqtt_port == 1883 + assert s.web_port == 8000 + + +def test_settings_from_env(monkeypatch): + monkeypatch.setenv("MQTT_HOST", "192.168.1.1") + monkeypatch.setenv("MQTT_PORT", "1884") + s = Settings() + assert s.mqtt_host == "192.168.1.1" + assert s.mqtt_port == 1884 \ No newline at end of file