Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v3
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
python-version: "3.11"
enable-cache: true
- name: Set up Python 3.11
run: uv python install 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f backend/requirements.txt ]; then pip install -r backend/requirements.txt; fi
pip install pytest pytest-asyncio
working-directory: ./backend
run: uv sync --group dev
- name: Test
working-directory: ./backend/src
run: |
mkdir -p config
pytest
uv run pytest

webui-test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -295,9 +295,6 @@ jobs:
echo ${{ needs.version-info.outputs.version }}
echo "VERSION='${{ needs.version-info.outputs.version }}'" >> module/__version__.py

- name: Copy requirements.txt
working-directory: ./backend
run: cp requirements.txt src/requirements.txt

- name: Zip app
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__/

# C extensions
*.so
.python-version

# Distribution / packaging
.Python
Expand Down Expand Up @@ -176,6 +177,8 @@ cython_debug/
.run
/backend/src/templates/
/backend/src/config/
/backend/config/
/backend/data/
/src/debuger.py
/backend/src/dist.zip
/pyrightconfig.json
Expand Down
38 changes: 33 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,28 @@ This sets up a Python virtual environment, installs dependencies with Tsinghua m
cd backend/src && python main.py
```

**Frontend Development:**
```bash
cd webui && pnpm install && pnpm run dev # Development server
cd webui && pnpm run build # Production build
./build-frontend.sh # Build and move to backend/src/dist
```

**Linting and Formatting:**
```bash
cd backend && ./venv/bin/ruff check .
cd backend && ./venv/bin/ruff format .
cd backend && ./venv/bin/black .
cd webui && pnpm run lint # Frontend linting
cd webui && pnpm run format # Frontend formatting
```

**Testing:**
```bash
cd backend && ./venv/bin/pytest
cd backend && ./venv/bin/pytest test/test_specific_module.py # Run specific test
cd webui && pnpm run test # Frontend tests
cd webui && pnpm run test:build # TypeScript type checking
```

## Architecture
Expand All @@ -36,9 +47,9 @@ Auto_Bangumi is an RSS-based automatic anime downloading and organization tool w
- `main.py`: FastAPI application entry point with poster serving and static file mounting
- `module/core/`: Core async application framework
- `aiocore.py`: AsyncApplicationCore manages service lifecycle and task scheduling
- `services.py`: BaseService abstract class and service implementations (RSS, Download, Renamer)
- `services/`: Service implementations extending BaseService (RSS, Download, Renamer)
- `monitors/`: Background monitors for downloads, notifications, and renaming
- `task_manager.py`: TaskManager handles async task scheduling and execution
- `events.py`: Event system for inter-service communication
- `module/api/`: REST API endpoints organized by feature (auth, bangumi, config, etc.)
- `module/parser/`: Content parsing system for RSS feeds, torrents, and metadata
- `module/downloader/`: Download client abstractions (qBittorrent, Aria2, Transmission)
Expand All @@ -48,9 +59,12 @@ Auto_Bangumi is an RSS-based automatic anime downloading and organization tool w
- `module/network/`: HTTP client abstractions with proxy and caching support

**Frontend Structure (Vue.js + TypeScript):**
- Located in `webui/` with standard Vue project structure
- Located in `webui/` with Vite build system
- Uses UnoCSS for styling, Naive UI for components
- Auto-import configuration for Vue Composition API
- API client in `src/api/` matching backend endpoints
- Component-based UI with reusable elements in `src/components/`
- Pinia for state management
- Vue Router with file-based routing

**Key Data Flow:**
1. RSS feeds are parsed and analyzed for anime information
Expand All @@ -65,8 +79,22 @@ The application uses an async service-based architecture where:
- AsyncApplicationCore manages service lifecycle
- TaskManager schedules periodic service execution
- Services communicate via events system
- Monitors handle background tasks like download tracking

**Configuration:**
- Main config in `config/config.json`
- Search providers in `config/search_provider.json`
- Environment variables for runtime settings
- Environment variables for runtime settings

## Development Guidelines

**Branch Strategy:**
- `main`: Stable releases only
- `<version>-dev`: Development branches for each minor version (e.g., `3.1-dev`)
- Bug fixes go to current version dev branch
- New features go to next version dev branch

**Code Style:**
- Backend: Ruff + Black formatting, Python 3.10+ target
- Frontend: ESLint + Prettier, TypeScript strict mode
- Pre-commit hooks enforce formatting
15 changes: 10 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,35 @@ ENV LANG="C.UTF-8" \

WORKDIR /app

COPY backend/requirements.txt .
# Copy Python project files
COPY backend/pyproject.toml backend/uv.lock ./

RUN set -ex && \
apk add --no-cache \
bash \
busybox-suid \
python3 \
py3-aiohttp \
py3-bcrypt \
py3-pip \
curl \
su-exec \
shadow \
tini \
openssl \
tzdata && \
python3 -m pip install --no-cache-dir --upgrade pip && \
sed -i '/bcrypt/d' requirements.txt && \
pip install --no-cache-dir -r requirements.txt && \
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh && \
. $HOME/.cargo/env && \
# Install dependencies using uv
uv sync --frozen --no-dev && \
# Add user
mkdir -p /home/ab && \
addgroup -S ab -g 911 && \
adduser -S ab -G ab -h /home/ab -s /sbin/nologin -u 911 && \
# Clear
rm -rf \
/root/.cache \
/root/.cargo \
/tmp/*

COPY --chmod=755 backend/src/. .
Expand Down
Empty file added backend/README.md
Empty file.
87 changes: 80 additions & 7 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,89 @@
[project]
name = "backend"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"bcrypt==4.2.0",
"beautifulsoup4>=4.13.4",
"bencode-py>=4.0.0",
"dotenv>=0.9.9",
"fastapi>=0.116.1",
"httpx[http2,socks]>=0.28.1",
"jinja2>=3.1.6",
"packaging>=25.0",
"passlib>=1.7.4",
"python-jose>=3.5.0",
"python-multipart>=0.0.20",
"sqlmodel>=0.0.24",
"sse-starlette>=3.0.2",
"urllib3>=2.5.0",
"uvicorn>=0.35.0",
]
[tool.ruff]
select = [
# pycodestyle(E): https://beta.ruff.rs/docs/rules/#pycodestyle-e-w
"E",
"E",
# Pyflakes(F): https://beta.ruff.rs/docs/rules/#pyflakes-f
"F",
"F",
# isort(I): https://beta.ruff.rs/docs/rules/#isort-i
"I"
"I",
]
ignore = [
# E501: https://beta.ruff.rs/docs/rules/line-too-long/
'E501',
'E501',
# F401: https://beta.ruff.rs/docs/rules/unused-import/
# avoid unused imports lint in `__init__.py`
'F401',
]

# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
fixable = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"I",
"N",
"Q",
"S",
"T",
"W",
"ANN",
"ARG",
"BLE",
"COM",
"DJ",
"DTZ",
"EM",
"ERA",
"EXE",
"FBT",
"ICN",
"INP",
"ISC",
"NPY",
"PD",
"PGH",
"PIE",
"PL",
"PT",
"PTH",
"PYI",
"RET",
"RSE",
"RUF",
"SIM",
"SLF",
"TID",
"TRY",
"UP",
"YTT",
]
unfixable = []

# Exclude a variety of commonly ignored directories.
Expand Down Expand Up @@ -52,12 +119,18 @@ line-length = 88
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

# Assume Python 3.10.
target-version = "py310"
target-version = "py311"

[tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10

[tool.black]
line-length = 88
target-version = ['py310', 'py311']
target-version = ['py311']

[dependency-groups]
dev = [
"pytest>=8.4.1",
"pytest-asyncio>=1.1.0",
]
5 changes: 0 additions & 5 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
anyio==3.7.0
bencode.py ==4.0.0
bs4==0.0.1
certifi==2023.5.7
charset-normalizer==3.1.0
click==8.1.3
fastapi==0.97.0
h11==0.14.0
idna==3.4
pydantic~=1.10
httpx[http2,socks]==0.25.0
six==1.16.0
sniffio==1.3.0
soupsieve==2.4.1
typing_extensions==4.6.3
urllib3==2.0.3
uvicorn==0.22.0
attrdict==2.0.1
Expand Down
3 changes: 0 additions & 3 deletions backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
}



def create_app() -> FastAPI:
app = FastAPI(lifespan=lifespan)

Expand Down Expand Up @@ -66,8 +65,6 @@ async def get_poster(path: str):
logger.warning(f"[Poster] Path outside allowed directory: {path}")
raise HTTPException(status_code=400, detail="Path outside allowed directory")

logger.debug(f"[Poster] Accessing poster: {post_path}")

# 如果文件不存在,尝试下载
if not post_path.exists():
try:
Expand Down
19 changes: 10 additions & 9 deletions backend/src/module/conf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ def check_config_key(
data: dict | BaseModel, updated_data: BaseModel, config_name: str
) -> bool:
if isinstance(data, BaseModel):
data = data.dict()
updated_data = updated_data.dict()
data = data.model_dump()
# data = data.dict()

updated_data = updated_data.model_dump()
for key in updated_data.keys():
if key not in data:
return False
Expand Down Expand Up @@ -96,10 +98,12 @@ def update_config(base_config: BaseModel | dict, data: dict):
# 部份更新 Config
# # 获取 baseconfig 的当前字段数据
if isinstance(base_config, BaseModel):
updated_data = base_config.dict()
updated_data = base_config.model_dump()
# updated_data = base_config.dict()
updated_data = deep_update(updated_data, data)
updated_instance = base_config.__class__.validate(updated_data)
updata_dict = updated_instance.dict()
updata_dict = updated_instance.model_dump()
# updata_dict = updated_instance.dict()
else:
# 当 baseconfig 是 dict 类型时, 直接更新
updated_data = base_config
Expand Down Expand Up @@ -142,7 +146,7 @@ def load(self):

def save(self, config_dict: dict | None = None):
if not config_dict:
config_dict = self.dict()
config_dict = self.model_dump()
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(config_dict, f, indent=4, ensure_ascii=False)

Expand All @@ -152,7 +156,7 @@ def init(self):
self.save()

def __load_from_env(self):
config_dict = self.dict()
config_dict = self.model_dump()
for key, section in ENV_TO_ATTR.items():
for env, attr in section.items():
if env in os.environ:
Expand All @@ -170,9 +174,6 @@ def __load_from_env(self):
self.__dict__.update(config_obj.__dict__)
logger.info("Config loaded from env")

def model_dump(self, **kwargs):
return self.dict()

@staticmethod
def __val_from_env(env: str, attr: tuple[str, Callable[..., Any]] | str):
if isinstance(attr, tuple):
Expand Down
Loading
Loading