Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
81 lines
3.4 KiB
Python
81 lines
3.4 KiB
Python
"""
|
|
SEO Pydantic Schemas
|
|
|
|
Validation schemas for SEO plugin data (Yoast, RankMath, etc.).
|
|
"""
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
class SEOData(BaseModel):
|
|
"""Schema for SEO data (read)"""
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
title: str | None = Field(None, description="SEO title")
|
|
description: str | None = Field(None, description="Meta description")
|
|
keywords: str | None = Field(None, description="Focus keywords")
|
|
canonical: str | None = Field(None, description="Canonical URL")
|
|
og_title: str | None = Field(None, description="Open Graph title")
|
|
og_description: str | None = Field(None, description="Open Graph description")
|
|
og_image: str | None = Field(None, description="Open Graph image URL")
|
|
twitter_title: str | None = Field(None, description="Twitter card title")
|
|
twitter_description: str | None = Field(None, description="Twitter card description")
|
|
twitter_image: str | None = Field(None, description="Twitter card image URL")
|
|
robots: list[str] | None = Field(None, description="Robots meta tags")
|
|
|
|
class SEOUpdate(BaseModel):
|
|
"""Schema for SEO data updates"""
|
|
|
|
title: str | None = Field(
|
|
None, max_length=60, description="SEO title (max 60 chars recommended)"
|
|
)
|
|
description: str | None = Field(
|
|
None, max_length=160, description="Meta description (max 160 chars recommended)"
|
|
)
|
|
keywords: str | None = Field(None, description="Focus keywords (comma-separated)")
|
|
canonical: str | None = Field(None, description="Canonical URL")
|
|
og_title: str | None = Field(None, description="Open Graph title")
|
|
og_description: str | None = Field(None, description="Open Graph description")
|
|
og_image: str | None = Field(None, description="Open Graph image URL")
|
|
twitter_title: str | None = Field(None, description="Twitter card title")
|
|
twitter_description: str | None = Field(None, description="Twitter card description")
|
|
twitter_image: str | None = Field(None, description="Twitter card image URL")
|
|
robots_index: bool | None = Field(None, description="Allow search engines to index")
|
|
robots_follow: bool | None = Field(None, description="Allow search engines to follow links")
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
@classmethod
|
|
@field_validator("title")
|
|
def validate_title_length(cls, v):
|
|
if v and len(v) > 70:
|
|
# Warning, not error - allow but discourage
|
|
pass
|
|
return v
|
|
|
|
@classmethod
|
|
@field_validator("description")
|
|
def validate_description_length(cls, v):
|
|
if v and len(v) > 200:
|
|
# Warning, not error - allow but discourage
|
|
pass
|
|
return v
|
|
|
|
class YoastSEO(SEOData):
|
|
"""Yoast SEO specific data"""
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
yoast_wpseo_focuskw: str | None = Field(None, description="Yoast focus keyword")
|
|
yoast_wpseo_metadesc: str | None = Field(None, description="Yoast meta description")
|
|
yoast_wpseo_title: str | None = Field(None, description="Yoast SEO title")
|
|
|
|
class RankMathSEO(SEOData):
|
|
"""RankMath SEO specific data"""
|
|
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
rank_math_focus_keyword: str | None = Field(None, description="RankMath focus keyword")
|
|
rank_math_description: str | None = Field(None, description="RankMath meta description")
|
|
rank_math_title: str | None = Field(None, description="RankMath SEO title")
|