wp_python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. src/wordpress_api/__init__.py +31 -0
  2. src/wordpress_api/auth.py +174 -0
  3. src/wordpress_api/client.py +293 -0
  4. src/wordpress_api/endpoints/__init__.py +43 -0
  5. src/wordpress_api/endpoints/application_passwords.py +235 -0
  6. src/wordpress_api/endpoints/autosaves.py +106 -0
  7. src/wordpress_api/endpoints/base.py +117 -0
  8. src/wordpress_api/endpoints/blocks.py +107 -0
  9. src/wordpress_api/endpoints/categories.py +91 -0
  10. src/wordpress_api/endpoints/comments.py +127 -0
  11. src/wordpress_api/endpoints/media.py +164 -0
  12. src/wordpress_api/endpoints/menus.py +120 -0
  13. src/wordpress_api/endpoints/pages.py +109 -0
  14. src/wordpress_api/endpoints/plugins.py +89 -0
  15. src/wordpress_api/endpoints/post_types.py +61 -0
  16. src/wordpress_api/endpoints/posts.py +131 -0
  17. src/wordpress_api/endpoints/revisions.py +121 -0
  18. src/wordpress_api/endpoints/search.py +81 -0
  19. src/wordpress_api/endpoints/settings.py +55 -0
  20. src/wordpress_api/endpoints/statuses.py +56 -0
  21. src/wordpress_api/endpoints/tags.py +79 -0
  22. src/wordpress_api/endpoints/taxonomies.py +41 -0
  23. src/wordpress_api/endpoints/themes.py +51 -0
  24. src/wordpress_api/endpoints/users.py +129 -0
  25. src/wordpress_api/exceptions.py +79 -0
  26. src/wordpress_api/models/__init__.py +49 -0
  27. src/wordpress_api/models/base.py +65 -0
  28. src/wordpress_api/models/category.py +41 -0
  29. src/wordpress_api/models/comment.py +75 -0
  30. src/wordpress_api/models/media.py +108 -0
  31. src/wordpress_api/models/menu.py +83 -0
  32. src/wordpress_api/models/page.py +80 -0
  33. src/wordpress_api/models/plugin.py +36 -0
  34. src/wordpress_api/models/post.py +112 -0
  35. src/wordpress_api/models/settings.py +32 -0
  36. src/wordpress_api/models/tag.py +38 -0
  37. src/wordpress_api/models/taxonomy.py +49 -0
  38. src/wordpress_api/models/theme.py +50 -0
  39. src/wordpress_api/models/user.py +82 -0
  40. wp_python-0.1.0.dist-info/METADATA +12 -0
  41. wp_python-0.1.0.dist-info/RECORD +42 -0
  42. wp_python-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,129 @@
1
+ """Users endpoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ..models.user import User, UserCreate, UserUpdate
8
+ from .base import CRUDEndpoint
9
+
10
+
11
+ class UsersEndpoint(CRUDEndpoint[User]):
12
+ """Endpoint for managing WordPress users."""
13
+
14
+ _path = "/wp/v2/users"
15
+ _model_class = User
16
+
17
+ def list(
18
+ self,
19
+ page: int = 1,
20
+ per_page: int = 10,
21
+ search: str | None = None,
22
+ order: str = "asc",
23
+ orderby: str = "name",
24
+ roles: str | list[str] | None = None,
25
+ capabilities: str | list[str] | None = None,
26
+ who: str | None = None,
27
+ has_published_posts: bool | str | list[str] | None = None,
28
+ slug: str | list[str] | None = None,
29
+ include: list[int] | None = None,
30
+ exclude: list[int] | None = None,
31
+ **kwargs: Any,
32
+ ) -> list[User]:
33
+ """List users with filtering options."""
34
+ params: dict[str, Any] = {
35
+ "page": page,
36
+ "per_page": per_page,
37
+ "order": order,
38
+ "orderby": orderby,
39
+ }
40
+
41
+ if search:
42
+ params["search"] = search
43
+ if roles:
44
+ if isinstance(roles, list):
45
+ params["roles"] = ",".join(roles)
46
+ else:
47
+ params["roles"] = roles
48
+ if capabilities:
49
+ if isinstance(capabilities, list):
50
+ params["capabilities"] = ",".join(capabilities)
51
+ else:
52
+ params["capabilities"] = capabilities
53
+ if who:
54
+ params["who"] = who
55
+ if has_published_posts is not None:
56
+ if isinstance(has_published_posts, bool):
57
+ params["has_published_posts"] = has_published_posts
58
+ elif isinstance(has_published_posts, list):
59
+ params["has_published_posts"] = ",".join(has_published_posts)
60
+ else:
61
+ params["has_published_posts"] = has_published_posts
62
+ if slug:
63
+ if isinstance(slug, list):
64
+ params["slug"] = ",".join(slug)
65
+ else:
66
+ params["slug"] = slug
67
+ if include:
68
+ params["include"] = ",".join(map(str, include))
69
+ if exclude:
70
+ params["exclude"] = ",".join(map(str, exclude))
71
+
72
+ params.update(kwargs)
73
+ response = self._get(self._path, params=params)
74
+ return [User.model_validate(item) for item in response]
75
+
76
+ def create(self, data: UserCreate | dict[str, Any]) -> User:
77
+ """Create a new user."""
78
+ return super().create(data)
79
+
80
+ def update(self, id: int, data: UserUpdate | dict[str, Any]) -> User:
81
+ """Update an existing user."""
82
+ return super().update(id, data)
83
+
84
+ def me(self, context: str = "view") -> User:
85
+ """Get the current authenticated user."""
86
+ response = self._get(f"{self._path}/me", params={"context": context})
87
+ return User.model_validate(response)
88
+
89
+ def update_me(self, data: UserUpdate | dict[str, Any]) -> User:
90
+ """Update the current authenticated user."""
91
+ if isinstance(data, UserUpdate):
92
+ payload = data.model_dump(exclude_none=True)
93
+ else:
94
+ payload = data
95
+ response = self._post(f"{self._path}/me", data=payload)
96
+ return User.model_validate(response)
97
+
98
+ def delete(
99
+ self,
100
+ id: int,
101
+ force: bool = True,
102
+ reassign: int | None = None,
103
+ **kwargs: Any,
104
+ ) -> dict[str, Any]:
105
+ """Delete a user.
106
+
107
+ Args:
108
+ id: User ID to delete.
109
+ force: Must be true (users cannot be trashed).
110
+ reassign: Reassign posts to this user ID.
111
+ """
112
+ params = {"force": force}
113
+ if reassign is not None:
114
+ params["reassign"] = reassign
115
+ params.update(kwargs)
116
+ return self._delete(f"{self._path}/{id}", params=params)
117
+
118
+ def get_by_slug(self, slug: str) -> User | None:
119
+ """Get a user by their slug."""
120
+ users = self.list(slug=slug)
121
+ return users[0] if users else None
122
+
123
+ def list_authors(self, **kwargs: Any) -> list[User]:
124
+ """List users who have published posts."""
125
+ return self.list(has_published_posts=True, **kwargs)
126
+
127
+ def list_by_role(self, role: str, **kwargs: Any) -> list[User]:
128
+ """List users with a specific role."""
129
+ return self.list(roles=role, **kwargs)
@@ -0,0 +1,79 @@
1
+ """WordPress API Exception Classes."""
2
+
3
+ from typing import Any
4
+
5
+
6
+ class WordPressError(Exception):
7
+ """Base exception for WordPress API errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status_code: int | None = None,
13
+ error_code: str | None = None,
14
+ data: dict[str, Any] | None = None,
15
+ ) -> None:
16
+ super().__init__(message)
17
+ self.message = message
18
+ self.status_code = status_code
19
+ self.error_code = error_code
20
+ self.data = data or {}
21
+
22
+ def __str__(self) -> str:
23
+ parts = [self.message]
24
+ if self.status_code:
25
+ parts.append(f"[HTTP {self.status_code}]")
26
+ if self.error_code:
27
+ parts.append(f"[{self.error_code}]")
28
+ return " ".join(parts)
29
+
30
+
31
+ class AuthenticationError(WordPressError):
32
+ """Authentication failed."""
33
+ pass
34
+
35
+
36
+ class NotFoundError(WordPressError):
37
+ """Resource not found."""
38
+ pass
39
+
40
+
41
+ class ValidationError(WordPressError):
42
+ """Validation error in request data."""
43
+ pass
44
+
45
+
46
+ class RateLimitError(WordPressError):
47
+ """Rate limit exceeded."""
48
+ pass
49
+
50
+
51
+ class ServerError(WordPressError):
52
+ """Server-side error."""
53
+ pass
54
+
55
+
56
+ class PermissionError(WordPressError):
57
+ """Permission denied."""
58
+ pass
59
+
60
+
61
+ def raise_for_status(status_code: int, response_data: dict[str, Any]) -> None:
62
+ """Raise appropriate exception based on status code."""
63
+ message = response_data.get("message", "Unknown error")
64
+ error_code = response_data.get("code", "unknown_error")
65
+
66
+ if status_code == 401:
67
+ raise AuthenticationError(message, status_code, error_code, response_data)
68
+ elif status_code == 403:
69
+ raise PermissionError(message, status_code, error_code, response_data)
70
+ elif status_code == 404:
71
+ raise NotFoundError(message, status_code, error_code, response_data)
72
+ elif status_code == 400:
73
+ raise ValidationError(message, status_code, error_code, response_data)
74
+ elif status_code == 429:
75
+ raise RateLimitError(message, status_code, error_code, response_data)
76
+ elif status_code >= 500:
77
+ raise ServerError(message, status_code, error_code, response_data)
78
+ elif status_code >= 400:
79
+ raise WordPressError(message, status_code, error_code, response_data)
@@ -0,0 +1,49 @@
1
+ """Pydantic models for WordPress API responses."""
2
+
3
+ from .post import Post, PostCreate, PostUpdate, PostStatus, PostFormat
4
+ from .page import Page, PageCreate, PageUpdate
5
+ from .media import Media, MediaCreate, MediaUpdate, MediaType
6
+ from .user import User, UserCreate, UserUpdate, UserRole
7
+ from .comment import Comment, CommentCreate, CommentUpdate, CommentStatus
8
+ from .category import Category, CategoryCreate, CategoryUpdate
9
+ from .tag import Tag, TagCreate, TagUpdate
10
+ from .taxonomy import Taxonomy
11
+ from .settings import Settings
12
+ from .plugin import Plugin
13
+ from .theme import Theme
14
+ from .menu import Menu, MenuItem
15
+
16
+ __all__ = [
17
+ "Post",
18
+ "PostCreate",
19
+ "PostUpdate",
20
+ "PostStatus",
21
+ "PostFormat",
22
+ "Page",
23
+ "PageCreate",
24
+ "PageUpdate",
25
+ "Media",
26
+ "MediaCreate",
27
+ "MediaUpdate",
28
+ "MediaType",
29
+ "User",
30
+ "UserCreate",
31
+ "UserUpdate",
32
+ "UserRole",
33
+ "Comment",
34
+ "CommentCreate",
35
+ "CommentUpdate",
36
+ "CommentStatus",
37
+ "Category",
38
+ "CategoryCreate",
39
+ "CategoryUpdate",
40
+ "Tag",
41
+ "TagCreate",
42
+ "TagUpdate",
43
+ "Taxonomy",
44
+ "Settings",
45
+ "Plugin",
46
+ "Theme",
47
+ "Menu",
48
+ "MenuItem",
49
+ ]
@@ -0,0 +1,65 @@
1
+ """Base models and utilities."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, ConfigDict, field_validator
7
+
8
+
9
+ class WordPressModel(BaseModel):
10
+ """Base model for WordPress objects."""
11
+
12
+ model_config = ConfigDict(
13
+ populate_by_name=True,
14
+ extra="allow",
15
+ )
16
+
17
+
18
+ class RenderedContent(BaseModel):
19
+ """Content with rendered HTML."""
20
+
21
+ rendered: str = ""
22
+ raw: str | None = None
23
+ protected: bool = False
24
+
25
+ model_config = ConfigDict(extra="allow")
26
+
27
+
28
+ class WPLink(BaseModel):
29
+ """WordPress REST API link."""
30
+
31
+ href: str
32
+ embeddable: bool = False
33
+
34
+ model_config = ConfigDict(extra="allow")
35
+
36
+
37
+ class WPLinks(BaseModel):
38
+ """Collection of WordPress REST API links."""
39
+
40
+ self_: list[WPLink] | None = None
41
+ collection: list[WPLink] | None = None
42
+ about: list[WPLink] | None = None
43
+ author: list[WPLink] | None = None
44
+ replies: list[WPLink] | None = None
45
+ version_history: list[WPLink] | None = None
46
+ predecessor_version: list[WPLink] | None = None
47
+ wp_attachment: list[WPLink] | None = None
48
+ wp_term: list[WPLink] | None = None
49
+ curies: list[WPLink] | None = None
50
+
51
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
52
+
53
+
54
+ def parse_datetime(value: Any) -> datetime | None:
55
+ """Parse WordPress datetime string."""
56
+ if value is None:
57
+ return None
58
+ if isinstance(value, datetime):
59
+ return value
60
+ if isinstance(value, str):
61
+ try:
62
+ return datetime.fromisoformat(value.replace("Z", "+00:00"))
63
+ except ValueError:
64
+ return None
65
+ return None
@@ -0,0 +1,41 @@
1
+ """Category models."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import Field
6
+
7
+ from .base import WordPressModel
8
+
9
+
10
+ class Category(WordPressModel):
11
+ """WordPress Category object."""
12
+
13
+ id: int
14
+ count: int = 0
15
+ description: str = ""
16
+ link: str = ""
17
+ name: str = ""
18
+ slug: str = ""
19
+ taxonomy: str = "category"
20
+ parent: int = 0
21
+ meta: dict[str, Any] = Field(default_factory=dict)
22
+
23
+
24
+ class CategoryCreate(WordPressModel):
25
+ """Data for creating a new category."""
26
+
27
+ name: str
28
+ description: str | None = None
29
+ slug: str | None = None
30
+ parent: int | None = None
31
+ meta: dict[str, Any] | None = None
32
+
33
+
34
+ class CategoryUpdate(WordPressModel):
35
+ """Data for updating a category."""
36
+
37
+ name: str | None = None
38
+ description: str | None = None
39
+ slug: str | None = None
40
+ parent: int | None = None
41
+ meta: dict[str, Any] | None = None
@@ -0,0 +1,75 @@
1
+ """Comment models."""
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import Any
6
+
7
+ from pydantic import Field, field_validator
8
+
9
+ from .base import WordPressModel, RenderedContent, parse_datetime
10
+
11
+
12
+ class CommentStatus(str, Enum):
13
+ """Comment status options."""
14
+
15
+ APPROVED = "approved"
16
+ HOLD = "hold"
17
+ SPAM = "spam"
18
+ TRASH = "trash"
19
+
20
+
21
+ class Comment(WordPressModel):
22
+ """WordPress Comment object."""
23
+
24
+ id: int
25
+ post: int = 0
26
+ parent: int = 0
27
+ author: int = 0
28
+ author_name: str = ""
29
+ author_email: str = ""
30
+ author_url: str = ""
31
+ author_ip: str = ""
32
+ author_user_agent: str = ""
33
+ date: datetime | None = None
34
+ date_gmt: datetime | None = None
35
+ content: RenderedContent | None = None
36
+ link: str = ""
37
+ status: str = "approved"
38
+ type: str = "comment"
39
+ author_avatar_urls: dict[str, str] = Field(default_factory=dict)
40
+ meta: dict[str, Any] = Field(default_factory=dict)
41
+
42
+ @field_validator("date", "date_gmt", mode="before")
43
+ @classmethod
44
+ def parse_dates(cls, v: Any) -> datetime | None:
45
+ return parse_datetime(v)
46
+
47
+
48
+ class CommentCreate(WordPressModel):
49
+ """Data for creating a new comment."""
50
+
51
+ post: int
52
+ content: str
53
+ parent: int | None = None
54
+ author: int | None = None
55
+ author_name: str | None = None
56
+ author_email: str | None = None
57
+ author_url: str | None = None
58
+ status: CommentStatus | None = None
59
+ meta: dict[str, Any] | None = None
60
+ date: datetime | str | None = None
61
+
62
+
63
+ class CommentUpdate(WordPressModel):
64
+ """Data for updating a comment."""
65
+
66
+ content: str | None = None
67
+ post: int | None = None
68
+ parent: int | None = None
69
+ author: int | None = None
70
+ author_name: str | None = None
71
+ author_email: str | None = None
72
+ author_url: str | None = None
73
+ status: CommentStatus | None = None
74
+ meta: dict[str, Any] | None = None
75
+ date: datetime | str | None = None
@@ -0,0 +1,108 @@
1
+ """Media models."""
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import Any
6
+
7
+ from pydantic import Field, field_validator
8
+
9
+ from .base import WordPressModel, RenderedContent, parse_datetime
10
+
11
+
12
+ class MediaType(str, Enum):
13
+ """Media type options."""
14
+
15
+ IMAGE = "image"
16
+ FILE = "file"
17
+ AUDIO = "audio"
18
+ VIDEO = "video"
19
+ APPLICATION = "application"
20
+
21
+
22
+ class MediaSize(WordPressModel):
23
+ """Individual media size details."""
24
+
25
+ file: str = ""
26
+ width: int = 0
27
+ height: int = 0
28
+ mime_type: str = ""
29
+ source_url: str = ""
30
+
31
+
32
+ class MediaDetails(WordPressModel):
33
+ """Media file details."""
34
+
35
+ width: int = 0
36
+ height: int = 0
37
+ file: str = ""
38
+ filesize: int = 0
39
+ sizes: dict[str, MediaSize] = Field(default_factory=dict)
40
+ image_meta: dict[str, Any] = Field(default_factory=dict)
41
+
42
+
43
+ class Media(WordPressModel):
44
+ """WordPress Media object."""
45
+
46
+ id: int
47
+ date: datetime | None = None
48
+ date_gmt: datetime | None = None
49
+ guid: RenderedContent | None = None
50
+ modified: datetime | None = None
51
+ modified_gmt: datetime | None = None
52
+ slug: str = ""
53
+ status: str = "inherit"
54
+ type: str = "attachment"
55
+ link: str = ""
56
+ title: RenderedContent | None = None
57
+ author: int = 0
58
+ comment_status: str = "open"
59
+ ping_status: str = "closed"
60
+ template: str = ""
61
+ meta: dict[str, Any] = Field(default_factory=dict)
62
+ description: RenderedContent | None = None
63
+ caption: RenderedContent | None = None
64
+ alt_text: str = ""
65
+ media_type: str = ""
66
+ mime_type: str = ""
67
+ media_details: MediaDetails | None = None
68
+ post: int | None = None
69
+ source_url: str = ""
70
+
71
+ @field_validator("date", "date_gmt", "modified", "modified_gmt", mode="before")
72
+ @classmethod
73
+ def parse_dates(cls, v: Any) -> datetime | None:
74
+ return parse_datetime(v)
75
+
76
+
77
+ class MediaCreate(WordPressModel):
78
+ """Data for creating/uploading new media."""
79
+
80
+ title: str | None = None
81
+ caption: str | None = None
82
+ alt_text: str | None = None
83
+ description: str | None = None
84
+ post: int | None = None
85
+ author: int | None = None
86
+ comment_status: str | None = None
87
+ ping_status: str | None = None
88
+ meta: dict[str, Any] | None = None
89
+ slug: str | None = None
90
+ status: str | None = None
91
+ date: datetime | str | None = None
92
+
93
+
94
+ class MediaUpdate(WordPressModel):
95
+ """Data for updating media."""
96
+
97
+ title: str | None = None
98
+ caption: str | None = None
99
+ alt_text: str | None = None
100
+ description: str | None = None
101
+ post: int | None = None
102
+ author: int | None = None
103
+ comment_status: str | None = None
104
+ ping_status: str | None = None
105
+ meta: dict[str, Any] | None = None
106
+ slug: str | None = None
107
+ status: str | None = None
108
+ date: datetime | str | None = None
@@ -0,0 +1,83 @@
1
+ """Menu models."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import Field
6
+
7
+ from .base import WordPressModel
8
+
9
+
10
+ class MenuItem(WordPressModel):
11
+ """WordPress Menu Item object."""
12
+
13
+ id: int
14
+ title: str = ""
15
+ status: str = "publish"
16
+ url: str = ""
17
+ attr_title: str = ""
18
+ description: str = ""
19
+ type: str = "custom"
20
+ type_label: str = ""
21
+ object: str = "custom"
22
+ object_id: int = 0
23
+ parent: int = 0
24
+ menu_order: int = 0
25
+ target: str = ""
26
+ classes: list[str] = Field(default_factory=list)
27
+ xfn: list[str] = Field(default_factory=list)
28
+ invalid: bool = False
29
+ menus: int = 0
30
+ meta: dict[str, Any] = Field(default_factory=dict)
31
+
32
+
33
+ class Menu(WordPressModel):
34
+ """WordPress Navigation Menu object."""
35
+
36
+ id: int
37
+ name: str = ""
38
+ slug: str = ""
39
+ description: str = ""
40
+ count: int = 0
41
+ auto_add: bool = False
42
+ locations: list[str] = Field(default_factory=list)
43
+ meta: dict[str, Any] = Field(default_factory=dict)
44
+
45
+
46
+ class MenuItemCreate(WordPressModel):
47
+ """Data for creating a menu item."""
48
+
49
+ title: str
50
+ url: str | None = None
51
+ status: str = "publish"
52
+ attr_title: str | None = None
53
+ description: str | None = None
54
+ type: str = "custom"
55
+ object: str | None = None
56
+ object_id: int | None = None
57
+ parent: int | None = None
58
+ menu_order: int | None = None
59
+ target: str | None = None
60
+ classes: list[str] | None = None
61
+ xfn: list[str] | None = None
62
+ menus: int | None = None
63
+ meta: dict[str, Any] | None = None
64
+
65
+
66
+ class MenuItemUpdate(WordPressModel):
67
+ """Data for updating a menu item."""
68
+
69
+ title: str | None = None
70
+ url: str | None = None
71
+ status: str | None = None
72
+ attr_title: str | None = None
73
+ description: str | None = None
74
+ type: str | None = None
75
+ object: str | None = None
76
+ object_id: int | None = None
77
+ parent: int | None = None
78
+ menu_order: int | None = None
79
+ target: str | None = None
80
+ classes: list[str] | None = None
81
+ xfn: list[str] | None = None
82
+ menus: int | None = None
83
+ meta: dict[str, Any] | None = None