feat: project skeleton with config, dependencies, and test setup
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
10
.env.example
Normal file
10
.env.example
Normal file
@@ -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
|
||||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.venv/
|
||||||
|
.env
|
||||||
|
data/
|
||||||
|
*.db
|
||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
38
pyproject.toml
Normal file
38
pyproject.toml
Normal file
@@ -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"]
|
||||||
0
src/mqtt_home/__init__.py
Normal file
0
src/mqtt_home/__init__.py
Normal file
20
src/mqtt_home/config.py
Normal file
20
src/mqtt_home/config.py
Normal file
@@ -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()
|
||||||
5
src/mqtt_home/database.py
Normal file
5
src/mqtt_home/database.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
42
tests/conftest.py
Normal file
42
tests/conftest.py
Normal file
@@ -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
|
||||||
17
tests/test_config.py
Normal file
17
tests/test_config.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user