diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40ffa68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.venv/ +*.egg-info/ + +# Node +node_modules/ +dist/ + +# Env +.env + +# IDE +.vscode/ +.idea/ + +# Local media storage +media/ diff --git a/yunke-server/.env.example b/yunke-server/.env.example new file mode 100644 index 0000000..0a28082 --- /dev/null +++ b/yunke-server/.env.example @@ -0,0 +1,9 @@ +YUNKE_BASE_URL=https://crm2.yunkecn.com +YUNKE_USERNAME= +YUNKE_PASSWORD= +DB_URL=mysql+asyncmy://user:pass@localhost:3306/yunke +REDIS_URL=redis://localhost:6379/0 +MEDIA_DIR=./media +# Fernet key (e.g. from cryptography.fernet.Fernet.generate_key()) used to encrypt +# stored credentials. Required for real runs; left blank for tests. +ENCRYPTION_KEY= diff --git a/yunke-server/app/__init__.py b/yunke-server/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yunke-server/app/config.py b/yunke-server/app/config.py new file mode 100644 index 0000000..4935fb4 --- /dev/null +++ b/yunke-server/app/config.py @@ -0,0 +1,30 @@ +from functools import lru_cache +from pathlib import Path + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Application settings sourced from environment / .env file.""" + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", + ) + + yunke_base_url: str = "https://crm2.yunkecn.com" + yunke_username: str = "" + yunke_password: str = "" + db_url: str = "mysql+asyncmy://user:pass@localhost:3306/yunke" + redis_url: str = "redis://localhost:6379/0" + media_dir: Path = Path("./media") + # Fernet key for encrypting stored credentials. Required for real runs; + # empty default lets tests construct Settings without a key. + encryption_key: str = "" + + +@lru_cache +def get_settings() -> Settings: + """Return the process-wide Settings singleton.""" + return Settings() diff --git a/yunke-server/pyproject.toml b/yunke-server/pyproject.toml new file mode 100644 index 0000000..9d93e5e --- /dev/null +++ b/yunke-server/pyproject.toml @@ -0,0 +1,43 @@ +[project] +name = "yunke-server" +version = "0.1.0" +description = "云客数据本地化系统 - Python backend" +requires-python = ">=3.11" +dependencies = [ + "fastapi", + "uvicorn[standard]", + "sqlalchemy[asyncio]", + "asyncmy", + "alembic", + "redis", + "httpx", + "playwright", + "apscheduler", + "pydantic", + "pydantic-settings", + "cryptography", +] + +[project.optional-dependencies] +dev = [ + "pytest", + "pytest-asyncio", + "respx", + "ruff", +] + +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] +include = ["app*"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] + +[tool.ruff] +line-length = 100 +target-version = "py311"