3f42bfaf0d
- Add test for abstract class instantiation prevention - Add test for incomplete implementation detection - Add test for complete implementation instantiation - Add test for async method signatures - Add shared fixtures for platform tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
123 lines
3.9 KiB
Python
123 lines
3.9 KiB
Python
"""Platform Adapter Base Class
|
|
|
|
Provides an abstract base class for platform-specific content adapters.
|
|
Each platform (Bilibili, Zhihu, etc.) should implement a concrete adapter
|
|
that handles metadata extraction and content fetching.
|
|
|
|
Purpose: Define a unified interface for multi-platform content extraction
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Dict, Any
|
|
|
|
|
|
class PlatformAdapter(ABC):
|
|
"""Abstract base class for platform-specific content adapters
|
|
|
|
A platform adapter encapsulates the logic for:
|
|
- Identifying content type from URL (video, article, answer, etc.)
|
|
- Fetching metadata for a given content ID
|
|
- Fetching content body in Markdown format
|
|
- Generating output filenames
|
|
|
|
Subclasses must implement all abstract methods to provide
|
|
platform-specific functionality.
|
|
|
|
Example:
|
|
class BilibiliAdapter(PlatformAdapter):
|
|
def get_content_type(self, url: str) -> str:
|
|
if '/video/' in url:
|
|
return 'video'
|
|
elif '/read/' in url:
|
|
return 'article'
|
|
return 'unknown'
|
|
|
|
async def fetch_metadata(self, content_id: str, content_type: str) -> Dict[str, Any]:
|
|
# Platform-specific implementation
|
|
...
|
|
"""
|
|
|
|
@abstractmethod
|
|
def get_content_type(self, url: str) -> str:
|
|
"""Identify content type from URL
|
|
|
|
Args:
|
|
url: The content URL to analyze
|
|
|
|
Returns:
|
|
Content type string (e.g., "video", "article", "answer", "zhuanlan")
|
|
|
|
Example:
|
|
adapter.get_content_type("https://www.bilibili.com/video/BV1xx...")
|
|
-> "video"
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def fetch_metadata(self, content_id: str, content_type: str) -> Dict[str, Any]:
|
|
"""Fetch metadata for the given content
|
|
|
|
Args:
|
|
content_id: Platform-specific content identifier (e.g., BV号, CV号)
|
|
content_type: Type of content ("video", "article", etc.)
|
|
|
|
Returns:
|
|
Dictionary containing metadata fields. Structure varies by platform
|
|
and content type, but typically includes:
|
|
- title: Content title
|
|
- author: Author name
|
|
- url: Original content URL
|
|
- Platform-specific fields
|
|
|
|
Raises:
|
|
NetworkError: If network request fails
|
|
AuthError: If authentication fails or quota exceeded
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def fetch_content(self, content_id: str, content_type: str) -> str:
|
|
"""Fetch content body in Markdown format
|
|
|
|
Args:
|
|
content_id: Platform-specific content identifier
|
|
content_type: Type of content
|
|
|
|
Returns:
|
|
Markdown-formatted content string with YAML frontmatter:
|
|
---
|
|
title: Article Title
|
|
author: Author Name
|
|
source_url: https://...
|
|
platform: bilibili
|
|
content_type: article
|
|
fetched_at: 2024-01-15T10:30:00
|
|
---
|
|
|
|
# Article Content...
|
|
|
|
Note:
|
|
For video content, this may return an empty string or
|
|
placeholder text, as video content extraction is handled
|
|
separately (e.g., ASR transcription).
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_output_filename(self, content_id: str, content_type: str, suffix: str) -> str:
|
|
"""Generate output filename for content
|
|
|
|
Args:
|
|
content_id: Platform-specific content identifier
|
|
content_type: Type of content
|
|
suffix: Filename suffix (e.g., "metadata.json", "content.md")
|
|
|
|
Returns:
|
|
Filename string without directory path
|
|
|
|
Example:
|
|
adapter.get_output_filename("BV1xx...", "video", "metadata.json")
|
|
-> "bilibili_BV1xx..._metadata.json"
|
|
"""
|
|
pass
|