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.
- src/wordpress_api/__init__.py +31 -0
- src/wordpress_api/auth.py +174 -0
- src/wordpress_api/client.py +293 -0
- src/wordpress_api/endpoints/__init__.py +43 -0
- src/wordpress_api/endpoints/application_passwords.py +235 -0
- src/wordpress_api/endpoints/autosaves.py +106 -0
- src/wordpress_api/endpoints/base.py +117 -0
- src/wordpress_api/endpoints/blocks.py +107 -0
- src/wordpress_api/endpoints/categories.py +91 -0
- src/wordpress_api/endpoints/comments.py +127 -0
- src/wordpress_api/endpoints/media.py +164 -0
- src/wordpress_api/endpoints/menus.py +120 -0
- src/wordpress_api/endpoints/pages.py +109 -0
- src/wordpress_api/endpoints/plugins.py +89 -0
- src/wordpress_api/endpoints/post_types.py +61 -0
- src/wordpress_api/endpoints/posts.py +131 -0
- src/wordpress_api/endpoints/revisions.py +121 -0
- src/wordpress_api/endpoints/search.py +81 -0
- src/wordpress_api/endpoints/settings.py +55 -0
- src/wordpress_api/endpoints/statuses.py +56 -0
- src/wordpress_api/endpoints/tags.py +79 -0
- src/wordpress_api/endpoints/taxonomies.py +41 -0
- src/wordpress_api/endpoints/themes.py +51 -0
- src/wordpress_api/endpoints/users.py +129 -0
- src/wordpress_api/exceptions.py +79 -0
- src/wordpress_api/models/__init__.py +49 -0
- src/wordpress_api/models/base.py +65 -0
- src/wordpress_api/models/category.py +41 -0
- src/wordpress_api/models/comment.py +75 -0
- src/wordpress_api/models/media.py +108 -0
- src/wordpress_api/models/menu.py +83 -0
- src/wordpress_api/models/page.py +80 -0
- src/wordpress_api/models/plugin.py +36 -0
- src/wordpress_api/models/post.py +112 -0
- src/wordpress_api/models/settings.py +32 -0
- src/wordpress_api/models/tag.py +38 -0
- src/wordpress_api/models/taxonomy.py +49 -0
- src/wordpress_api/models/theme.py +50 -0
- src/wordpress_api/models/user.py +82 -0
- wp_python-0.1.0.dist-info/METADATA +12 -0
- wp_python-0.1.0.dist-info/RECORD +42 -0
- wp_python-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Comments endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..models.comment import Comment, CommentCreate, CommentUpdate, CommentStatus
|
|
8
|
+
from .base import CRUDEndpoint
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CommentsEndpoint(CRUDEndpoint[Comment]):
|
|
12
|
+
"""Endpoint for managing WordPress comments."""
|
|
13
|
+
|
|
14
|
+
_path = "/wp/v2/comments"
|
|
15
|
+
_model_class = Comment
|
|
16
|
+
|
|
17
|
+
def list(
|
|
18
|
+
self,
|
|
19
|
+
page: int = 1,
|
|
20
|
+
per_page: int = 10,
|
|
21
|
+
search: str | None = None,
|
|
22
|
+
order: str = "desc",
|
|
23
|
+
orderby: str = "date_gmt",
|
|
24
|
+
status: str | CommentStatus | None = None,
|
|
25
|
+
post: int | list[int] | None = None,
|
|
26
|
+
parent: int | list[int] | None = None,
|
|
27
|
+
parent_exclude: list[int] | None = None,
|
|
28
|
+
author: int | list[int] | None = None,
|
|
29
|
+
author_exclude: list[int] | None = None,
|
|
30
|
+
author_email: str | None = None,
|
|
31
|
+
before: str | None = None,
|
|
32
|
+
after: str | None = None,
|
|
33
|
+
type: str | None = None,
|
|
34
|
+
password: str | None = None,
|
|
35
|
+
**kwargs: Any,
|
|
36
|
+
) -> list[Comment]:
|
|
37
|
+
"""List comments with filtering options."""
|
|
38
|
+
params: dict[str, Any] = {
|
|
39
|
+
"page": page,
|
|
40
|
+
"per_page": per_page,
|
|
41
|
+
"order": order,
|
|
42
|
+
"orderby": orderby,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if search:
|
|
46
|
+
params["search"] = search
|
|
47
|
+
if status:
|
|
48
|
+
if isinstance(status, CommentStatus):
|
|
49
|
+
params["status"] = status.value
|
|
50
|
+
else:
|
|
51
|
+
params["status"] = status
|
|
52
|
+
if post is not None:
|
|
53
|
+
if isinstance(post, list):
|
|
54
|
+
params["post"] = ",".join(map(str, post))
|
|
55
|
+
else:
|
|
56
|
+
params["post"] = post
|
|
57
|
+
if parent is not None:
|
|
58
|
+
if isinstance(parent, list):
|
|
59
|
+
params["parent"] = ",".join(map(str, parent))
|
|
60
|
+
else:
|
|
61
|
+
params["parent"] = parent
|
|
62
|
+
if parent_exclude:
|
|
63
|
+
params["parent_exclude"] = ",".join(map(str, parent_exclude))
|
|
64
|
+
if author is not None:
|
|
65
|
+
if isinstance(author, list):
|
|
66
|
+
params["author"] = ",".join(map(str, author))
|
|
67
|
+
else:
|
|
68
|
+
params["author"] = author
|
|
69
|
+
if author_exclude:
|
|
70
|
+
params["author_exclude"] = ",".join(map(str, author_exclude))
|
|
71
|
+
if author_email:
|
|
72
|
+
params["author_email"] = author_email
|
|
73
|
+
if before:
|
|
74
|
+
params["before"] = before
|
|
75
|
+
if after:
|
|
76
|
+
params["after"] = after
|
|
77
|
+
if type:
|
|
78
|
+
params["type"] = type
|
|
79
|
+
if password:
|
|
80
|
+
params["password"] = password
|
|
81
|
+
|
|
82
|
+
params.update(kwargs)
|
|
83
|
+
response = self._get(self._path, params=params)
|
|
84
|
+
return [Comment.model_validate(item) for item in response]
|
|
85
|
+
|
|
86
|
+
def create(self, data: CommentCreate | dict[str, Any]) -> Comment:
|
|
87
|
+
"""Create a new comment."""
|
|
88
|
+
return super().create(data)
|
|
89
|
+
|
|
90
|
+
def update(self, id: int, data: CommentUpdate | dict[str, Any]) -> Comment:
|
|
91
|
+
"""Update an existing comment."""
|
|
92
|
+
return super().update(id, data)
|
|
93
|
+
|
|
94
|
+
def approve(self, id: int) -> Comment:
|
|
95
|
+
"""Approve a comment."""
|
|
96
|
+
return self.update(id, CommentUpdate(status=CommentStatus.APPROVED))
|
|
97
|
+
|
|
98
|
+
def unapprove(self, id: int) -> Comment:
|
|
99
|
+
"""Put a comment on hold."""
|
|
100
|
+
return self.update(id, CommentUpdate(status=CommentStatus.HOLD))
|
|
101
|
+
|
|
102
|
+
def spam(self, id: int) -> Comment:
|
|
103
|
+
"""Mark a comment as spam."""
|
|
104
|
+
return self.update(id, CommentUpdate(status=CommentStatus.SPAM))
|
|
105
|
+
|
|
106
|
+
def trash(self, id: int) -> Comment:
|
|
107
|
+
"""Move a comment to trash."""
|
|
108
|
+
return self.update(id, CommentUpdate(status=CommentStatus.TRASH))
|
|
109
|
+
|
|
110
|
+
def list_for_post(self, post_id: int, **kwargs: Any) -> list[Comment]:
|
|
111
|
+
"""List all comments for a specific post."""
|
|
112
|
+
return self.list(post=post_id, **kwargs)
|
|
113
|
+
|
|
114
|
+
def list_replies(self, parent_id: int, **kwargs: Any) -> list[Comment]:
|
|
115
|
+
"""List replies to a specific comment."""
|
|
116
|
+
return self.list(parent=parent_id, **kwargs)
|
|
117
|
+
|
|
118
|
+
def get_thread(self, post_id: int) -> dict[int, list[Comment]]:
|
|
119
|
+
"""Get comments organized by parent for threading."""
|
|
120
|
+
all_comments = list(self.iterate_all(post=post_id))
|
|
121
|
+
threads: dict[int, list[Comment]] = {}
|
|
122
|
+
for comment in all_comments:
|
|
123
|
+
parent = comment.parent
|
|
124
|
+
if parent not in threads:
|
|
125
|
+
threads[parent] = []
|
|
126
|
+
threads[parent].append(comment)
|
|
127
|
+
return threads
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Media endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, BinaryIO
|
|
7
|
+
|
|
8
|
+
from ..models.media import Media, MediaCreate, MediaUpdate
|
|
9
|
+
from .base import CRUDEndpoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MediaEndpoint(CRUDEndpoint[Media]):
|
|
13
|
+
"""Endpoint for managing WordPress media files."""
|
|
14
|
+
|
|
15
|
+
_path = "/wp/v2/media"
|
|
16
|
+
_model_class = Media
|
|
17
|
+
|
|
18
|
+
def list(
|
|
19
|
+
self,
|
|
20
|
+
page: int = 1,
|
|
21
|
+
per_page: int = 10,
|
|
22
|
+
search: str | None = None,
|
|
23
|
+
order: str = "desc",
|
|
24
|
+
orderby: str = "date",
|
|
25
|
+
media_type: str | None = None,
|
|
26
|
+
mime_type: str | None = None,
|
|
27
|
+
author: int | list[int] | None = None,
|
|
28
|
+
author_exclude: list[int] | None = None,
|
|
29
|
+
before: str | None = None,
|
|
30
|
+
after: str | None = None,
|
|
31
|
+
parent: int | list[int] | None = None,
|
|
32
|
+
parent_exclude: list[int] | None = None,
|
|
33
|
+
status: str | None = None,
|
|
34
|
+
slug: str | list[str] | None = None,
|
|
35
|
+
**kwargs: Any,
|
|
36
|
+
) -> list[Media]:
|
|
37
|
+
"""List media items with filtering options."""
|
|
38
|
+
params: dict[str, Any] = {
|
|
39
|
+
"page": page,
|
|
40
|
+
"per_page": per_page,
|
|
41
|
+
"order": order,
|
|
42
|
+
"orderby": orderby,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if search:
|
|
46
|
+
params["search"] = search
|
|
47
|
+
if media_type:
|
|
48
|
+
params["media_type"] = media_type
|
|
49
|
+
if mime_type:
|
|
50
|
+
params["mime_type"] = mime_type
|
|
51
|
+
if author is not None:
|
|
52
|
+
if isinstance(author, list):
|
|
53
|
+
params["author"] = ",".join(map(str, author))
|
|
54
|
+
else:
|
|
55
|
+
params["author"] = author
|
|
56
|
+
if author_exclude:
|
|
57
|
+
params["author_exclude"] = ",".join(map(str, author_exclude))
|
|
58
|
+
if before:
|
|
59
|
+
params["before"] = before
|
|
60
|
+
if after:
|
|
61
|
+
params["after"] = after
|
|
62
|
+
if parent is not None:
|
|
63
|
+
if isinstance(parent, list):
|
|
64
|
+
params["parent"] = ",".join(map(str, parent))
|
|
65
|
+
else:
|
|
66
|
+
params["parent"] = parent
|
|
67
|
+
if parent_exclude:
|
|
68
|
+
params["parent_exclude"] = ",".join(map(str, parent_exclude))
|
|
69
|
+
if status:
|
|
70
|
+
params["status"] = status
|
|
71
|
+
if slug:
|
|
72
|
+
if isinstance(slug, list):
|
|
73
|
+
params["slug"] = ",".join(slug)
|
|
74
|
+
else:
|
|
75
|
+
params["slug"] = slug
|
|
76
|
+
|
|
77
|
+
params.update(kwargs)
|
|
78
|
+
response = self._get(self._path, params=params)
|
|
79
|
+
return [Media.model_validate(item) for item in response]
|
|
80
|
+
|
|
81
|
+
def upload(
|
|
82
|
+
self,
|
|
83
|
+
file: str | Path | BinaryIO,
|
|
84
|
+
filename: str | None = None,
|
|
85
|
+
title: str | None = None,
|
|
86
|
+
caption: str | None = None,
|
|
87
|
+
alt_text: str | None = None,
|
|
88
|
+
description: str | None = None,
|
|
89
|
+
post: int | None = None,
|
|
90
|
+
**kwargs: Any,
|
|
91
|
+
) -> Media:
|
|
92
|
+
"""Upload a media file.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
file: Path to file, Path object, or file-like object.
|
|
96
|
+
filename: Override filename (required if file is a file-like object).
|
|
97
|
+
title: Title for the media item.
|
|
98
|
+
caption: Caption for the media item.
|
|
99
|
+
alt_text: Alt text for images.
|
|
100
|
+
description: Description of the media item.
|
|
101
|
+
post: ID of the post to attach the media to.
|
|
102
|
+
"""
|
|
103
|
+
if isinstance(file, (str, Path)):
|
|
104
|
+
file_path = Path(file)
|
|
105
|
+
filename = filename or file_path.name
|
|
106
|
+
with open(file_path, "rb") as f:
|
|
107
|
+
content = f.read()
|
|
108
|
+
else:
|
|
109
|
+
if not filename:
|
|
110
|
+
raise ValueError("filename required when uploading file-like object")
|
|
111
|
+
content = file.read()
|
|
112
|
+
|
|
113
|
+
headers = {
|
|
114
|
+
"Content-Disposition": f'attachment; filename="{filename}"',
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
data: dict[str, Any] = {}
|
|
118
|
+
if title:
|
|
119
|
+
data["title"] = title
|
|
120
|
+
if caption:
|
|
121
|
+
data["caption"] = caption
|
|
122
|
+
if alt_text:
|
|
123
|
+
data["alt_text"] = alt_text
|
|
124
|
+
if description:
|
|
125
|
+
data["description"] = description
|
|
126
|
+
if post:
|
|
127
|
+
data["post"] = post
|
|
128
|
+
data.update(kwargs)
|
|
129
|
+
|
|
130
|
+
response = self._client._request(
|
|
131
|
+
"POST",
|
|
132
|
+
self._path,
|
|
133
|
+
content=content,
|
|
134
|
+
headers=headers,
|
|
135
|
+
params=data if data else None,
|
|
136
|
+
)
|
|
137
|
+
return Media.model_validate(response)
|
|
138
|
+
|
|
139
|
+
def update(self, id: int, data: MediaUpdate | dict[str, Any]) -> Media:
|
|
140
|
+
"""Update an existing media item."""
|
|
141
|
+
return super().update(id, data)
|
|
142
|
+
|
|
143
|
+
def list_by_type(self, media_type: str, **kwargs: Any) -> list[Media]:
|
|
144
|
+
"""List media items by type (image, video, audio, etc.)."""
|
|
145
|
+
return self.list(media_type=media_type, **kwargs)
|
|
146
|
+
|
|
147
|
+
def list_images(self, **kwargs: Any) -> list[Media]:
|
|
148
|
+
"""List only image files."""
|
|
149
|
+
return self.list_by_type("image", **kwargs)
|
|
150
|
+
|
|
151
|
+
def list_videos(self, **kwargs: Any) -> list[Media]:
|
|
152
|
+
"""List only video files."""
|
|
153
|
+
return self.list_by_type("video", **kwargs)
|
|
154
|
+
|
|
155
|
+
def list_audio(self, **kwargs: Any) -> list[Media]:
|
|
156
|
+
"""List only audio files."""
|
|
157
|
+
return self.list_by_type("audio", **kwargs)
|
|
158
|
+
|
|
159
|
+
def get_sizes(self, id: int) -> dict[str, Any]:
|
|
160
|
+
"""Get available sizes for an image."""
|
|
161
|
+
media = self.get(id)
|
|
162
|
+
if media.media_details and media.media_details.sizes:
|
|
163
|
+
return {k: v.model_dump() for k, v in media.media_details.sizes.items()}
|
|
164
|
+
return {}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Menus endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..models.menu import Menu, MenuItem, MenuItemCreate, MenuItemUpdate
|
|
8
|
+
from .base import BaseEndpoint
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MenusEndpoint(BaseEndpoint):
|
|
12
|
+
"""Endpoint for managing WordPress navigation menus."""
|
|
13
|
+
|
|
14
|
+
_menus_path = "/wp/v2/menus"
|
|
15
|
+
_items_path = "/wp/v2/menu-items"
|
|
16
|
+
_locations_path = "/wp/v2/menu-locations"
|
|
17
|
+
|
|
18
|
+
def list_menus(
|
|
19
|
+
self,
|
|
20
|
+
page: int = 1,
|
|
21
|
+
per_page: int = 100,
|
|
22
|
+
search: str | None = None,
|
|
23
|
+
**kwargs: Any,
|
|
24
|
+
) -> list[Menu]:
|
|
25
|
+
"""List navigation menus."""
|
|
26
|
+
params: dict[str, Any] = {
|
|
27
|
+
"page": page,
|
|
28
|
+
"per_page": per_page,
|
|
29
|
+
}
|
|
30
|
+
if search:
|
|
31
|
+
params["search"] = search
|
|
32
|
+
params.update(kwargs)
|
|
33
|
+
|
|
34
|
+
response = self._get(self._menus_path, params=params)
|
|
35
|
+
return [Menu.model_validate(item) for item in response]
|
|
36
|
+
|
|
37
|
+
def get_menu(self, id: int) -> Menu:
|
|
38
|
+
"""Get a specific menu by ID."""
|
|
39
|
+
response = self._get(f"{self._menus_path}/{id}")
|
|
40
|
+
return Menu.model_validate(response)
|
|
41
|
+
|
|
42
|
+
def create_menu(self, name: str, **kwargs: Any) -> Menu:
|
|
43
|
+
"""Create a new menu."""
|
|
44
|
+
data = {"name": name, **kwargs}
|
|
45
|
+
response = self._post(self._menus_path, data=data)
|
|
46
|
+
return Menu.model_validate(response)
|
|
47
|
+
|
|
48
|
+
def update_menu(self, id: int, **kwargs: Any) -> Menu:
|
|
49
|
+
"""Update a menu."""
|
|
50
|
+
response = self._post(f"{self._menus_path}/{id}", data=kwargs)
|
|
51
|
+
return Menu.model_validate(response)
|
|
52
|
+
|
|
53
|
+
def delete_menu(self, id: int, force: bool = True) -> dict[str, Any]:
|
|
54
|
+
"""Delete a menu."""
|
|
55
|
+
return self._delete(f"{self._menus_path}/{id}", params={"force": force})
|
|
56
|
+
|
|
57
|
+
def list_items(
|
|
58
|
+
self,
|
|
59
|
+
menus: int | list[int] | None = None,
|
|
60
|
+
page: int = 1,
|
|
61
|
+
per_page: int = 100,
|
|
62
|
+
order: str = "asc",
|
|
63
|
+
orderby: str = "menu_order",
|
|
64
|
+
**kwargs: Any,
|
|
65
|
+
) -> list[MenuItem]:
|
|
66
|
+
"""List menu items."""
|
|
67
|
+
params: dict[str, Any] = {
|
|
68
|
+
"page": page,
|
|
69
|
+
"per_page": per_page,
|
|
70
|
+
"order": order,
|
|
71
|
+
"orderby": orderby,
|
|
72
|
+
}
|
|
73
|
+
if menus is not None:
|
|
74
|
+
if isinstance(menus, list):
|
|
75
|
+
params["menus"] = ",".join(map(str, menus))
|
|
76
|
+
else:
|
|
77
|
+
params["menus"] = menus
|
|
78
|
+
params.update(kwargs)
|
|
79
|
+
|
|
80
|
+
response = self._get(self._items_path, params=params)
|
|
81
|
+
return [MenuItem.model_validate(item) for item in response]
|
|
82
|
+
|
|
83
|
+
def get_item(self, id: int) -> MenuItem:
|
|
84
|
+
"""Get a specific menu item."""
|
|
85
|
+
response = self._get(f"{self._items_path}/{id}")
|
|
86
|
+
return MenuItem.model_validate(response)
|
|
87
|
+
|
|
88
|
+
def create_item(self, data: MenuItemCreate | dict[str, Any]) -> MenuItem:
|
|
89
|
+
"""Create a new menu item."""
|
|
90
|
+
if isinstance(data, MenuItemCreate):
|
|
91
|
+
payload = data.model_dump(exclude_none=True)
|
|
92
|
+
else:
|
|
93
|
+
payload = data
|
|
94
|
+
response = self._post(self._items_path, data=payload)
|
|
95
|
+
return MenuItem.model_validate(response)
|
|
96
|
+
|
|
97
|
+
def update_item(self, id: int, data: MenuItemUpdate | dict[str, Any]) -> MenuItem:
|
|
98
|
+
"""Update a menu item."""
|
|
99
|
+
if isinstance(data, MenuItemUpdate):
|
|
100
|
+
payload = data.model_dump(exclude_none=True)
|
|
101
|
+
else:
|
|
102
|
+
payload = data
|
|
103
|
+
response = self._post(f"{self._items_path}/{id}", data=payload)
|
|
104
|
+
return MenuItem.model_validate(response)
|
|
105
|
+
|
|
106
|
+
def delete_item(self, id: int, force: bool = True) -> dict[str, Any]:
|
|
107
|
+
"""Delete a menu item."""
|
|
108
|
+
return self._delete(f"{self._items_path}/{id}", params={"force": force})
|
|
109
|
+
|
|
110
|
+
def list_locations(self) -> dict[str, Any]:
|
|
111
|
+
"""List registered menu locations."""
|
|
112
|
+
return self._get(self._locations_path)
|
|
113
|
+
|
|
114
|
+
def get_location(self, location: str) -> dict[str, Any]:
|
|
115
|
+
"""Get a specific menu location."""
|
|
116
|
+
return self._get(f"{self._locations_path}/{location}")
|
|
117
|
+
|
|
118
|
+
def get_menu_items(self, menu_id: int) -> list[MenuItem]:
|
|
119
|
+
"""Get all items for a specific menu."""
|
|
120
|
+
return self.list_items(menus=menu_id)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Pages endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..models.page import Page, PageCreate, PageUpdate
|
|
8
|
+
from ..models.post import PostStatus
|
|
9
|
+
from .base import CRUDEndpoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PagesEndpoint(CRUDEndpoint[Page]):
|
|
13
|
+
"""Endpoint for managing WordPress pages."""
|
|
14
|
+
|
|
15
|
+
_path = "/wp/v2/pages"
|
|
16
|
+
_model_class = Page
|
|
17
|
+
|
|
18
|
+
def list(
|
|
19
|
+
self,
|
|
20
|
+
page: int = 1,
|
|
21
|
+
per_page: int = 10,
|
|
22
|
+
search: str | None = None,
|
|
23
|
+
order: str = "asc",
|
|
24
|
+
orderby: str = "menu_order",
|
|
25
|
+
status: str | PostStatus | list[str] | None = None,
|
|
26
|
+
parent: int | list[int] | None = None,
|
|
27
|
+
parent_exclude: list[int] | None = None,
|
|
28
|
+
author: int | list[int] | None = None,
|
|
29
|
+
author_exclude: list[int] | None = None,
|
|
30
|
+
before: str | None = None,
|
|
31
|
+
after: str | None = None,
|
|
32
|
+
menu_order: int | None = None,
|
|
33
|
+
slug: str | list[str] | None = None,
|
|
34
|
+
**kwargs: Any,
|
|
35
|
+
) -> list[Page]:
|
|
36
|
+
"""List pages with filtering options."""
|
|
37
|
+
params: dict[str, Any] = {
|
|
38
|
+
"page": page,
|
|
39
|
+
"per_page": per_page,
|
|
40
|
+
"order": order,
|
|
41
|
+
"orderby": orderby,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if search:
|
|
45
|
+
params["search"] = search
|
|
46
|
+
if status:
|
|
47
|
+
if isinstance(status, PostStatus):
|
|
48
|
+
params["status"] = status.value
|
|
49
|
+
elif isinstance(status, list):
|
|
50
|
+
params["status"] = ",".join(status)
|
|
51
|
+
else:
|
|
52
|
+
params["status"] = status
|
|
53
|
+
if parent is not None:
|
|
54
|
+
if isinstance(parent, list):
|
|
55
|
+
params["parent"] = ",".join(map(str, parent))
|
|
56
|
+
else:
|
|
57
|
+
params["parent"] = parent
|
|
58
|
+
if parent_exclude:
|
|
59
|
+
params["parent_exclude"] = ",".join(map(str, parent_exclude))
|
|
60
|
+
if author is not None:
|
|
61
|
+
if isinstance(author, list):
|
|
62
|
+
params["author"] = ",".join(map(str, author))
|
|
63
|
+
else:
|
|
64
|
+
params["author"] = author
|
|
65
|
+
if author_exclude:
|
|
66
|
+
params["author_exclude"] = ",".join(map(str, author_exclude))
|
|
67
|
+
if before:
|
|
68
|
+
params["before"] = before
|
|
69
|
+
if after:
|
|
70
|
+
params["after"] = after
|
|
71
|
+
if menu_order is not None:
|
|
72
|
+
params["menu_order"] = menu_order
|
|
73
|
+
if slug:
|
|
74
|
+
if isinstance(slug, list):
|
|
75
|
+
params["slug"] = ",".join(slug)
|
|
76
|
+
else:
|
|
77
|
+
params["slug"] = slug
|
|
78
|
+
|
|
79
|
+
params.update(kwargs)
|
|
80
|
+
response = self._get(self._path, params=params)
|
|
81
|
+
return [Page.model_validate(item) for item in response]
|
|
82
|
+
|
|
83
|
+
def create(self, data: PageCreate | dict[str, Any]) -> Page:
|
|
84
|
+
"""Create a new page."""
|
|
85
|
+
return super().create(data)
|
|
86
|
+
|
|
87
|
+
def update(self, id: int, data: PageUpdate | dict[str, Any]) -> Page:
|
|
88
|
+
"""Update an existing page."""
|
|
89
|
+
return super().update(id, data)
|
|
90
|
+
|
|
91
|
+
def get_by_slug(self, slug: str) -> Page | None:
|
|
92
|
+
"""Get a page by its slug."""
|
|
93
|
+
pages = self.list(slug=slug)
|
|
94
|
+
return pages[0] if pages else None
|
|
95
|
+
|
|
96
|
+
def get_children(self, parent_id: int, **kwargs: Any) -> list[Page]:
|
|
97
|
+
"""Get child pages of a parent page."""
|
|
98
|
+
return self.list(parent=parent_id, **kwargs)
|
|
99
|
+
|
|
100
|
+
def get_hierarchy(self) -> dict[int, list[Page]]:
|
|
101
|
+
"""Get all pages organized by parent ID."""
|
|
102
|
+
all_pages = list(self.iterate_all())
|
|
103
|
+
hierarchy: dict[int, list[Page]] = {}
|
|
104
|
+
for page in all_pages:
|
|
105
|
+
parent = page.parent
|
|
106
|
+
if parent not in hierarchy:
|
|
107
|
+
hierarchy[parent] = []
|
|
108
|
+
hierarchy[parent].append(page)
|
|
109
|
+
return hierarchy
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Plugins endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..models.plugin import Plugin
|
|
8
|
+
from .base import BaseEndpoint
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PluginsEndpoint(BaseEndpoint):
|
|
12
|
+
"""Endpoint for managing WordPress plugins."""
|
|
13
|
+
|
|
14
|
+
_path = "/wp/v2/plugins"
|
|
15
|
+
|
|
16
|
+
def list(
|
|
17
|
+
self,
|
|
18
|
+
search: str | None = None,
|
|
19
|
+
status: str | None = None,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> list[Plugin]:
|
|
22
|
+
"""List installed plugins.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
search: Limit results to those matching a string.
|
|
26
|
+
status: Filter by plugin status (active, inactive).
|
|
27
|
+
"""
|
|
28
|
+
params: dict[str, Any] = {}
|
|
29
|
+
if search:
|
|
30
|
+
params["search"] = search
|
|
31
|
+
if status:
|
|
32
|
+
params["status"] = status
|
|
33
|
+
params.update(kwargs)
|
|
34
|
+
|
|
35
|
+
response = self._get(self._path, params=params)
|
|
36
|
+
return [Plugin.model_validate(item) for item in response]
|
|
37
|
+
|
|
38
|
+
def get(self, plugin: str, **kwargs: Any) -> Plugin:
|
|
39
|
+
"""Get a specific plugin.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
plugin: Plugin identifier (folder/file format, e.g., "akismet/akismet").
|
|
43
|
+
"""
|
|
44
|
+
encoded_plugin = plugin.replace("/", "%2F")
|
|
45
|
+
response = self._get(f"{self._path}/{encoded_plugin}", params=kwargs)
|
|
46
|
+
return Plugin.model_validate(response)
|
|
47
|
+
|
|
48
|
+
def activate(self, plugin: str) -> Plugin:
|
|
49
|
+
"""Activate a plugin.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
plugin: Plugin identifier (folder/file format).
|
|
53
|
+
"""
|
|
54
|
+
encoded_plugin = plugin.replace("/", "%2F")
|
|
55
|
+
response = self._post(
|
|
56
|
+
f"{self._path}/{encoded_plugin}",
|
|
57
|
+
data={"status": "active"},
|
|
58
|
+
)
|
|
59
|
+
return Plugin.model_validate(response)
|
|
60
|
+
|
|
61
|
+
def deactivate(self, plugin: str) -> Plugin:
|
|
62
|
+
"""Deactivate a plugin.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
plugin: Plugin identifier (folder/file format).
|
|
66
|
+
"""
|
|
67
|
+
encoded_plugin = plugin.replace("/", "%2F")
|
|
68
|
+
response = self._post(
|
|
69
|
+
f"{self._path}/{encoded_plugin}",
|
|
70
|
+
data={"status": "inactive"},
|
|
71
|
+
)
|
|
72
|
+
return Plugin.model_validate(response)
|
|
73
|
+
|
|
74
|
+
def delete(self, plugin: str) -> dict[str, Any]:
|
|
75
|
+
"""Delete a plugin.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
plugin: Plugin identifier (folder/file format).
|
|
79
|
+
"""
|
|
80
|
+
encoded_plugin = plugin.replace("/", "%2F")
|
|
81
|
+
return self._delete(f"{self._path}/{encoded_plugin}")
|
|
82
|
+
|
|
83
|
+
def list_active(self) -> list[Plugin]:
|
|
84
|
+
"""List all active plugins."""
|
|
85
|
+
return self.list(status="active")
|
|
86
|
+
|
|
87
|
+
def list_inactive(self) -> list[Plugin]:
|
|
88
|
+
"""List all inactive plugins."""
|
|
89
|
+
return self.list(status="inactive")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Post Types endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from .base import BaseEndpoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PostType(BaseModel):
|
|
13
|
+
"""WordPress Post Type object."""
|
|
14
|
+
|
|
15
|
+
name: str = ""
|
|
16
|
+
slug: str = ""
|
|
17
|
+
description: str = ""
|
|
18
|
+
hierarchical: bool = False
|
|
19
|
+
has_archive: bool = False
|
|
20
|
+
rest_base: str = ""
|
|
21
|
+
rest_namespace: str = "wp/v2"
|
|
22
|
+
taxonomies: list[str] = Field(default_factory=list)
|
|
23
|
+
capabilities: dict[str, str] = Field(default_factory=dict)
|
|
24
|
+
labels: dict[str, str] = Field(default_factory=dict)
|
|
25
|
+
supports: dict[str, bool] = Field(default_factory=dict)
|
|
26
|
+
icon: str | None = None
|
|
27
|
+
viewable: bool = True
|
|
28
|
+
visibility: dict[str, bool] = Field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PostTypesEndpoint(BaseEndpoint):
|
|
32
|
+
"""Endpoint for viewing WordPress post types."""
|
|
33
|
+
|
|
34
|
+
_path = "/wp/v2/types"
|
|
35
|
+
|
|
36
|
+
def list(self, **kwargs: Any) -> dict[str, PostType]:
|
|
37
|
+
"""List all registered post types."""
|
|
38
|
+
response = self._get(self._path, params=kwargs)
|
|
39
|
+
return {k: PostType.model_validate(v) for k, v in response.items()}
|
|
40
|
+
|
|
41
|
+
def get(self, post_type: str, **kwargs: Any) -> PostType:
|
|
42
|
+
"""Get a specific post type by slug."""
|
|
43
|
+
response = self._get(f"{self._path}/{post_type}", params=kwargs)
|
|
44
|
+
return PostType.model_validate(response)
|
|
45
|
+
|
|
46
|
+
def get_post(self) -> PostType:
|
|
47
|
+
"""Get the 'post' post type."""
|
|
48
|
+
return self.get("post")
|
|
49
|
+
|
|
50
|
+
def get_page(self) -> PostType:
|
|
51
|
+
"""Get the 'page' post type."""
|
|
52
|
+
return self.get("page")
|
|
53
|
+
|
|
54
|
+
def get_attachment(self) -> PostType:
|
|
55
|
+
"""Get the 'attachment' post type."""
|
|
56
|
+
return self.get("attachment")
|
|
57
|
+
|
|
58
|
+
def list_public(self) -> dict[str, PostType]:
|
|
59
|
+
"""List only publicly viewable post types."""
|
|
60
|
+
all_types = self.list()
|
|
61
|
+
return {k: v for k, v in all_types.items() if v.viewable}
|