style: Format all Python files with ruff

- Formatted 103 files to comply with ruff format requirements
- No code logic changes, only formatting/whitespace
- Fixes CI formatting check failures
This commit is contained in:
yusyus
2026-02-08 14:42:27 +03:00
parent 6e4f623b9d
commit 0265de5816
103 changed files with 2241 additions and 2627 deletions

View File

@@ -32,9 +32,9 @@ from .detector import ChangeDetector
from .models import SyncConfig, ChangeReport, PageChange
__all__ = [
'SyncMonitor',
'ChangeDetector',
'SyncConfig',
'ChangeReport',
'PageChange',
"SyncMonitor",
"ChangeDetector",
"SyncConfig",
"ChangeReport",
"PageChange",
]

View File

@@ -55,7 +55,7 @@ class ChangeDetector:
Returns:
Hexadecimal hash string
"""
return hashlib.sha256(content.encode('utf-8')).hexdigest()
return hashlib.sha256(content.encode("utf-8")).hexdigest()
def fetch_page(self, url: str) -> tuple[str, dict[str, str]]:
"""
@@ -72,17 +72,15 @@ class ChangeDetector:
requests.RequestException: If fetch fails
"""
response = requests.get(
url,
timeout=self.timeout,
headers={'User-Agent': 'SkillSeekers-Sync/1.0'}
url, timeout=self.timeout, headers={"User-Agent": "SkillSeekers-Sync/1.0"}
)
response.raise_for_status()
metadata = {
'last-modified': response.headers.get('Last-Modified'),
'etag': response.headers.get('ETag'),
'content-type': response.headers.get('Content-Type'),
'content-length': response.headers.get('Content-Length'),
"last-modified": response.headers.get("Last-Modified"),
"etag": response.headers.get("ETag"),
"content-type": response.headers.get("Content-Type"),
"content-length": response.headers.get("Content-Length"),
}
return response.text, metadata
@@ -92,7 +90,7 @@ class ChangeDetector:
url: str,
old_hash: str | None = None,
generate_diff: bool = False,
old_content: str | None = None
old_content: str | None = None,
) -> PageChange:
"""
Check if page has changed.
@@ -132,7 +130,7 @@ class ChangeDetector:
old_hash=old_hash,
new_hash=new_hash,
diff=diff,
detected_at=datetime.utcnow()
detected_at=datetime.utcnow(),
)
except requests.RequestException:
@@ -142,14 +140,11 @@ class ChangeDetector:
change_type=ChangeType.DELETED,
old_hash=old_hash,
new_hash=None,
detected_at=datetime.utcnow()
detected_at=datetime.utcnow(),
)
def check_pages(
self,
urls: list[str],
previous_hashes: dict[str, str],
generate_diffs: bool = False
self, urls: list[str], previous_hashes: dict[str, str], generate_diffs: bool = False
) -> ChangeReport:
"""
Check multiple pages for changes.
@@ -185,13 +180,15 @@ class ChangeDetector:
# Check for deleted pages (in previous state but not in current)
for url, old_hash in previous_hashes.items():
if url not in checked_urls:
deleted.append(PageChange(
url=url,
change_type=ChangeType.DELETED,
old_hash=old_hash,
new_hash=None,
detected_at=datetime.utcnow()
))
deleted.append(
PageChange(
url=url,
change_type=ChangeType.DELETED,
old_hash=old_hash,
new_hash=None,
detected_at=datetime.utcnow(),
)
)
return ChangeReport(
skill_name="unknown", # To be set by caller
@@ -200,7 +197,7 @@ class ChangeDetector:
modified=modified,
deleted=deleted,
unchanged=unchanged_count,
checked_at=datetime.utcnow()
checked_at=datetime.utcnow(),
)
def generate_diff(self, old_content: str, new_content: str) -> str:
@@ -217,15 +214,9 @@ class ChangeDetector:
old_lines = old_content.splitlines(keepends=True)
new_lines = new_content.splitlines(keepends=True)
diff = difflib.unified_diff(
old_lines,
new_lines,
fromfile='old',
tofile='new',
lineterm=''
)
diff = difflib.unified_diff(old_lines, new_lines, fromfile="old", tofile="new", lineterm="")
return ''.join(diff)
return "".join(diff)
def generate_summary_diff(self, old_content: str, new_content: str) -> str:
"""
@@ -244,16 +235,15 @@ class ChangeDetector:
diff = difflib.unified_diff(old_lines, new_lines)
diff_lines = list(diff)
added = sum(1 for line in diff_lines if line.startswith('+') and not line.startswith('+++'))
removed = sum(1 for line in diff_lines if line.startswith('-') and not line.startswith('---'))
added = sum(1 for line in diff_lines if line.startswith("+") and not line.startswith("+++"))
removed = sum(
1 for line in diff_lines if line.startswith("-") and not line.startswith("---")
)
return f"+{added} -{removed} lines"
def check_header_changes(
self,
url: str,
old_modified: str | None = None,
old_etag: str | None = None
self, url: str, old_modified: str | None = None, old_etag: str | None = None
) -> bool:
"""
Quick check using HTTP headers (no content download).
@@ -269,14 +259,12 @@ class ChangeDetector:
try:
# Use HEAD request for efficiency
response = requests.head(
url,
timeout=self.timeout,
headers={'User-Agent': 'SkillSeekers-Sync/1.0'}
url, timeout=self.timeout, headers={"User-Agent": "SkillSeekers-Sync/1.0"}
)
response.raise_for_status()
new_modified = response.headers.get('Last-Modified')
new_etag = response.headers.get('ETag')
new_modified = response.headers.get("Last-Modified")
new_etag = response.headers.get("ETag")
# Check if headers indicate change
if old_modified and new_modified and old_modified != new_modified:
@@ -289,9 +277,7 @@ class ChangeDetector:
return True
def batch_check_headers(
self,
urls: list[str],
previous_metadata: dict[str, dict[str, str]]
self, urls: list[str], previous_metadata: dict[str, dict[str, str]]
) -> list[str]:
"""
Batch check URLs using headers only.
@@ -307,8 +293,8 @@ class ChangeDetector:
for url in urls:
old_meta = previous_metadata.get(url, {})
old_modified = old_meta.get('last-modified')
old_etag = old_meta.get('etag')
old_modified = old_meta.get("last-modified")
old_etag = old_meta.get("etag")
if self.check_header_changes(url, old_modified, old_etag):
changed_urls.append(url)

View File

@@ -10,6 +10,7 @@ from pydantic import BaseModel, Field
class ChangeType(str, Enum):
"""Type of change detected."""
ADDED = "added"
MODIFIED = "modified"
DELETED = "deleted"
@@ -25,8 +26,7 @@ class PageChange(BaseModel):
new_hash: str | None = Field(None, description="New content hash")
diff: str | None = Field(None, description="Content diff (if available)")
detected_at: datetime = Field(
default_factory=datetime.utcnow,
description="When change was detected"
default_factory=datetime.utcnow, description="When change was detected"
)
class Config:
@@ -37,7 +37,7 @@ class PageChange(BaseModel):
"old_hash": "abc123",
"new_hash": "def456",
"diff": "@@ -10,3 +10,4 @@\n+New content here",
"detected_at": "2024-01-15T10:30:00Z"
"detected_at": "2024-01-15T10:30:00Z",
}
}
@@ -52,8 +52,7 @@ class ChangeReport(BaseModel):
deleted: list[PageChange] = Field(default_factory=list, description="Deleted pages")
unchanged: int = Field(0, description="Number of unchanged pages")
checked_at: datetime = Field(
default_factory=datetime.utcnow,
description="When check was performed"
default_factory=datetime.utcnow, description="When check was performed"
)
@property
@@ -72,34 +71,19 @@ class SyncConfig(BaseModel):
skill_config: str = Field(..., description="Path to skill config file")
check_interval: int = Field(
default=3600,
description="Check interval in seconds (default: 1 hour)"
default=3600, description="Check interval in seconds (default: 1 hour)"
)
enabled: bool = Field(default=True, description="Whether sync is enabled")
auto_update: bool = Field(
default=False,
description="Automatically rebuild skill on changes"
)
notify_on_change: bool = Field(
default=True,
description="Send notifications on changes"
)
auto_update: bool = Field(default=False, description="Automatically rebuild skill on changes")
notify_on_change: bool = Field(default=True, description="Send notifications on changes")
notification_channels: list[str] = Field(
default_factory=list,
description="Notification channels (email, slack, webhook)"
)
webhook_url: str | None = Field(
None,
description="Webhook URL for change notifications"
default_factory=list, description="Notification channels (email, slack, webhook)"
)
webhook_url: str | None = Field(None, description="Webhook URL for change notifications")
email_recipients: list[str] = Field(
default_factory=list,
description="Email recipients for notifications"
)
slack_webhook: str | None = Field(
None,
description="Slack webhook URL"
default_factory=list, description="Email recipients for notifications"
)
slack_webhook: str | None = Field(None, description="Slack webhook URL")
class Config:
json_schema_extra = {
@@ -111,7 +95,7 @@ class SyncConfig(BaseModel):
"notify_on_change": True,
"notification_channels": ["slack", "webhook"],
"webhook_url": "https://example.com/webhook",
"slack_webhook": "https://hooks.slack.com/services/..."
"slack_webhook": "https://hooks.slack.com/services/...",
}
}
@@ -125,8 +109,7 @@ class SyncState(BaseModel):
total_checks: int = Field(default=0, description="Total checks performed")
total_changes: int = Field(default=0, description="Total changes detected")
page_hashes: dict[str, str] = Field(
default_factory=dict,
description="URL -> content hash mapping"
default_factory=dict, description="URL -> content hash mapping"
)
status: str = Field(default="idle", description="Current status")
error: str | None = Field(None, description="Last error message")
@@ -137,15 +120,9 @@ class WebhookPayload(BaseModel):
event: str = Field(..., description="Event type (change_detected, sync_complete)")
skill_name: str = Field(..., description="Skill name")
timestamp: datetime = Field(
default_factory=datetime.utcnow,
description="Event timestamp"
)
timestamp: datetime = Field(default_factory=datetime.utcnow, description="Event timestamp")
changes: ChangeReport | None = Field(None, description="Change report")
metadata: dict[str, Any] = Field(
default_factory=dict,
description="Additional metadata"
)
metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
class Config:
json_schema_extra = {
@@ -157,8 +134,8 @@ class WebhookPayload(BaseModel):
"total_pages": 150,
"added": [],
"modified": [{"url": "https://react.dev/learn"}],
"deleted": []
"deleted": [],
},
"metadata": {"source": "periodic_check"}
"metadata": {"source": "periodic_check"},
}
}

View File

@@ -51,7 +51,7 @@ class SyncMonitor:
check_interval: int = 3600,
auto_update: bool = False,
state_file: str | None = None,
on_change: Callable[[ChangeReport], None] | None = None
on_change: Callable[[ChangeReport], None] | None = None,
):
"""
Initialize sync monitor.
@@ -72,7 +72,7 @@ class SyncMonitor:
with open(self.config_path) as f:
self.skill_config = json.load(f)
self.skill_name = self.skill_config.get('name', 'unknown')
self.skill_name = self.skill_config.get("name", "unknown")
# State file
if state_file:
@@ -97,10 +97,10 @@ class SyncMonitor:
with open(self.state_file) as f:
data = json.load(f)
# Convert datetime strings back
if data.get('last_check'):
data['last_check'] = datetime.fromisoformat(data['last_check'])
if data.get('last_change'):
data['last_change'] = datetime.fromisoformat(data['last_change'])
if data.get("last_check"):
data["last_check"] = datetime.fromisoformat(data["last_check"])
if data.get("last_change"):
data["last_change"] = datetime.fromisoformat(data["last_change"])
return SyncState(**data)
else:
return SyncState(skill_name=self.skill_name)
@@ -109,12 +109,12 @@ class SyncMonitor:
"""Save current state to file."""
# Convert datetime to ISO format
data = self.state.dict()
if data.get('last_check'):
data['last_check'] = data['last_check'].isoformat()
if data.get('last_change'):
data['last_change'] = data['last_change'].isoformat()
if data.get("last_check"):
data["last_check"] = data["last_check"].isoformat()
if data.get("last_change"):
data["last_change"] = data["last_change"].isoformat()
with open(self.state_file, 'w') as f:
with open(self.state_file, "w") as f:
json.dump(data, f, indent=2)
def check_now(self, generate_diffs: bool = False) -> ChangeReport:
@@ -132,7 +132,7 @@ class SyncMonitor:
try:
# Get URLs to check from config
base_url = self.skill_config.get('base_url')
base_url = self.skill_config.get("base_url")
# TODO: In real implementation, get actual URLs from scraper
# For now, simulate with base URL only
@@ -140,9 +140,7 @@ class SyncMonitor:
# Check for changes
report = self.detector.check_pages(
urls=urls,
previous_hashes=self.state.page_hashes,
generate_diffs=generate_diffs
urls=urls, previous_hashes=self.state.page_hashes, generate_diffs=generate_diffs
)
report.skill_name = self.skill_name
@@ -192,7 +190,7 @@ class SyncMonitor:
event="change_detected",
skill_name=self.skill_name,
changes=report,
metadata={"auto_update": self.auto_update}
metadata={"auto_update": self.auto_update},
)
self.notifier.send(payload)
@@ -214,9 +212,7 @@ class SyncMonitor:
self._running = True
# Schedule checks
schedule.every(self.check_interval).seconds.do(
lambda: self.check_now()
)
schedule.every(self.check_interval).seconds.do(lambda: self.check_now())
# Run in thread
def run_schedule():

View File

@@ -34,7 +34,7 @@ class Notifier:
webhook_url: str | None = None,
slack_webhook: str | None = None,
email_recipients: list[str] | None = None,
console: bool = True
console: bool = True,
):
"""
Initialize notifier.
@@ -45,8 +45,8 @@ class Notifier:
email_recipients: List of email recipients
console: Whether to print to console
"""
self.webhook_url = webhook_url or os.getenv('SYNC_WEBHOOK_URL')
self.slack_webhook = slack_webhook or os.getenv('SLACK_WEBHOOK_URL')
self.webhook_url = webhook_url or os.getenv("SYNC_WEBHOOK_URL")
self.slack_webhook = slack_webhook or os.getenv("SLACK_WEBHOOK_URL")
self.email_recipients = email_recipients or []
self.console = console
@@ -92,8 +92,8 @@ class Notifier:
response = requests.post(
self.webhook_url,
json=payload.dict(),
headers={'Content-Type': 'application/json'},
timeout=10
headers={"Content-Type": "application/json"},
timeout=10,
)
response.raise_for_status()
print(f"✅ Webhook notification sent to {self.webhook_url}")
@@ -124,14 +124,10 @@ class Notifier:
slack_payload = {
"text": text,
"username": "Skill Seekers Sync",
"icon_emoji": ":books:"
"icon_emoji": ":books:",
}
response = requests.post(
self.slack_webhook,
json=slack_payload,
timeout=10
)
response = requests.post(self.slack_webhook, json=slack_payload, timeout=10)
response.raise_for_status()
print("✅ Slack notification sent")
except Exception as e: