wp-python 0.1.3__tar.gz

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 (44) hide show
  1. wp_python-0.1.3/.gitignore +26 -0
  2. wp_python-0.1.3/LICENSE +19 -0
  3. wp_python-0.1.3/PKG-INFO +99 -0
  4. wp_python-0.1.3/README.md +81 -0
  5. wp_python-0.1.3/pyproject.toml +41 -0
  6. wp_python-0.1.3/wp_python/__init__.py +31 -0
  7. wp_python-0.1.3/wp_python/auth.py +174 -0
  8. wp_python-0.1.3/wp_python/client.py +293 -0
  9. wp_python-0.1.3/wp_python/endpoints/__init__.py +43 -0
  10. wp_python-0.1.3/wp_python/endpoints/application_passwords.py +235 -0
  11. wp_python-0.1.3/wp_python/endpoints/autosaves.py +106 -0
  12. wp_python-0.1.3/wp_python/endpoints/base.py +117 -0
  13. wp_python-0.1.3/wp_python/endpoints/blocks.py +107 -0
  14. wp_python-0.1.3/wp_python/endpoints/categories.py +91 -0
  15. wp_python-0.1.3/wp_python/endpoints/comments.py +127 -0
  16. wp_python-0.1.3/wp_python/endpoints/media.py +164 -0
  17. wp_python-0.1.3/wp_python/endpoints/menus.py +120 -0
  18. wp_python-0.1.3/wp_python/endpoints/pages.py +109 -0
  19. wp_python-0.1.3/wp_python/endpoints/plugins.py +89 -0
  20. wp_python-0.1.3/wp_python/endpoints/post_types.py +61 -0
  21. wp_python-0.1.3/wp_python/endpoints/posts.py +131 -0
  22. wp_python-0.1.3/wp_python/endpoints/revisions.py +121 -0
  23. wp_python-0.1.3/wp_python/endpoints/search.py +81 -0
  24. wp_python-0.1.3/wp_python/endpoints/settings.py +55 -0
  25. wp_python-0.1.3/wp_python/endpoints/statuses.py +56 -0
  26. wp_python-0.1.3/wp_python/endpoints/tags.py +79 -0
  27. wp_python-0.1.3/wp_python/endpoints/taxonomies.py +41 -0
  28. wp_python-0.1.3/wp_python/endpoints/themes.py +51 -0
  29. wp_python-0.1.3/wp_python/endpoints/users.py +129 -0
  30. wp_python-0.1.3/wp_python/exceptions.py +79 -0
  31. wp_python-0.1.3/wp_python/models/__init__.py +49 -0
  32. wp_python-0.1.3/wp_python/models/base.py +65 -0
  33. wp_python-0.1.3/wp_python/models/category.py +41 -0
  34. wp_python-0.1.3/wp_python/models/comment.py +75 -0
  35. wp_python-0.1.3/wp_python/models/media.py +108 -0
  36. wp_python-0.1.3/wp_python/models/menu.py +83 -0
  37. wp_python-0.1.3/wp_python/models/page.py +80 -0
  38. wp_python-0.1.3/wp_python/models/plugin.py +36 -0
  39. wp_python-0.1.3/wp_python/models/post.py +112 -0
  40. wp_python-0.1.3/wp_python/models/settings.py +32 -0
  41. wp_python-0.1.3/wp_python/models/tag.py +38 -0
  42. wp_python-0.1.3/wp_python/models/taxonomy.py +49 -0
  43. wp_python-0.1.3/wp_python/models/theme.py +50 -0
  44. wp_python-0.1.3/wp_python/models/user.py +82 -0
@@ -0,0 +1,26 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+ .env
22
+ .venv
23
+ env/
24
+ venv/
25
+ .cache/
26
+ *.db
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: wp_python
3
+ Version: 0.1.3
4
+ Summary: A Python client for interacting with the WordPress REST API.
5
+ Author-email: Andrew <andrew.neher1@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: hatch>=1.16.3
12
+ Requires-Dist: httpx>=0.28.1
13
+ Requires-Dist: pydantic>=2.12.5
14
+ Requires-Dist: pytest-mock>=3.15.1
15
+ Requires-Dist: pytest>=9.0.2
16
+ Requires-Dist: respx>=0.22.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # WordPress REST API Python Client
20
+
21
+ A full-featured Python 3.12 interface to the WordPress REST API.
22
+
23
+ ## Overview
24
+
25
+ This library provides a comprehensive, type-safe Python client for interacting with WordPress sites via the REST API. It supports all major WordPress endpoints including posts, pages, media, users, comments, categories, tags, settings, plugins, themes, menus, and more.
26
+
27
+ ## Project Structure
28
+
29
+ ```
30
+ src/wordpress_api/
31
+ ├── __init__.py # Main exports
32
+ ├── client.py # WordPressClient main class
33
+ ├── auth.py # Authentication handlers
34
+ ├── exceptions.py # Custom exceptions
35
+ ├── models/ # Pydantic data models
36
+ │ ├── post.py
37
+ │ ├── page.py
38
+ │ ├── media.py
39
+ │ ├── user.py
40
+ │ ├── comment.py
41
+ │ ├── category.py
42
+ │ ├── tag.py
43
+ │ └── ...
44
+ └── endpoints/ # API endpoint classes
45
+ ├── posts.py
46
+ ├── pages.py
47
+ ├── media.py
48
+ ├── users.py
49
+ ├── comments.py
50
+ └── ...
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from wordpress_api import WordPressClient, ApplicationPasswordAuth
57
+
58
+ # Authenticate with WordPress Application Passwords
59
+ auth = ApplicationPasswordAuth("username", "xxxx xxxx xxxx xxxx")
60
+ client = WordPressClient("https://your-site.com", auth=auth)
61
+
62
+ # List posts
63
+ posts = client.posts.list()
64
+
65
+ # Create a post
66
+ from wordpress_api.models import PostCreate, PostStatus
67
+ post = client.posts.create(PostCreate(
68
+ title="Hello World",
69
+ content="<p>My first post!</p>",
70
+ status=PostStatus.PUBLISH
71
+ ))
72
+
73
+ # Get current user
74
+ me = client.users.me()
75
+ ```
76
+
77
+ ## Features
78
+
79
+ - **Full CRUD Support**: Posts, Pages, Media, Users, Comments, Categories, Tags
80
+ - **Authentication**: Application Passwords, Basic Auth, JWT
81
+ - **Type Safety**: Pydantic models for all WordPress objects
82
+ - **Pagination**: Automatic iteration through all results
83
+ - **Media Upload**: Upload files from path or file objects
84
+ - **Custom Post Types**: Access any registered custom post type
85
+ - **Block Editor**: Access block types and patterns
86
+ - **Error Handling**: Typed exceptions for all API errors
87
+
88
+ ## Dependencies
89
+
90
+ - httpx: Modern HTTP client
91
+ - pydantic: Data validation using Python type annotations
92
+
93
+ ## Authentication Setup
94
+
95
+ 1. Go to your WordPress site
96
+ 2. Navigate to Users → Your Profile
97
+ 3. Scroll to "Application Passwords"
98
+ 4. Create a new application password
99
+ 5. Use it with `ApplicationPasswordAuth`
@@ -0,0 +1,81 @@
1
+ # WordPress REST API Python Client
2
+
3
+ A full-featured Python 3.12 interface to the WordPress REST API.
4
+
5
+ ## Overview
6
+
7
+ This library provides a comprehensive, type-safe Python client for interacting with WordPress sites via the REST API. It supports all major WordPress endpoints including posts, pages, media, users, comments, categories, tags, settings, plugins, themes, menus, and more.
8
+
9
+ ## Project Structure
10
+
11
+ ```
12
+ src/wordpress_api/
13
+ ├── __init__.py # Main exports
14
+ ├── client.py # WordPressClient main class
15
+ ├── auth.py # Authentication handlers
16
+ ├── exceptions.py # Custom exceptions
17
+ ├── models/ # Pydantic data models
18
+ │ ├── post.py
19
+ │ ├── page.py
20
+ │ ├── media.py
21
+ │ ├── user.py
22
+ │ ├── comment.py
23
+ │ ├── category.py
24
+ │ ├── tag.py
25
+ │ └── ...
26
+ └── endpoints/ # API endpoint classes
27
+ ├── posts.py
28
+ ├── pages.py
29
+ ├── media.py
30
+ ├── users.py
31
+ ├── comments.py
32
+ └── ...
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ from wordpress_api import WordPressClient, ApplicationPasswordAuth
39
+
40
+ # Authenticate with WordPress Application Passwords
41
+ auth = ApplicationPasswordAuth("username", "xxxx xxxx xxxx xxxx")
42
+ client = WordPressClient("https://your-site.com", auth=auth)
43
+
44
+ # List posts
45
+ posts = client.posts.list()
46
+
47
+ # Create a post
48
+ from wordpress_api.models import PostCreate, PostStatus
49
+ post = client.posts.create(PostCreate(
50
+ title="Hello World",
51
+ content="<p>My first post!</p>",
52
+ status=PostStatus.PUBLISH
53
+ ))
54
+
55
+ # Get current user
56
+ me = client.users.me()
57
+ ```
58
+
59
+ ## Features
60
+
61
+ - **Full CRUD Support**: Posts, Pages, Media, Users, Comments, Categories, Tags
62
+ - **Authentication**: Application Passwords, Basic Auth, JWT
63
+ - **Type Safety**: Pydantic models for all WordPress objects
64
+ - **Pagination**: Automatic iteration through all results
65
+ - **Media Upload**: Upload files from path or file objects
66
+ - **Custom Post Types**: Access any registered custom post type
67
+ - **Block Editor**: Access block types and patterns
68
+ - **Error Handling**: Typed exceptions for all API errors
69
+
70
+ ## Dependencies
71
+
72
+ - httpx: Modern HTTP client
73
+ - pydantic: Data validation using Python type annotations
74
+
75
+ ## Authentication Setup
76
+
77
+ 1. Go to your WordPress site
78
+ 2. Navigate to Users → Your Profile
79
+ 3. Scroll to "Application Passwords"
80
+ 4. Create a new application password
81
+ 5. Use it with `ApplicationPasswordAuth`
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "wp_python"
3
+ dynamic = ["version"]
4
+ description = "A Python client for interacting with the WordPress REST API."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Andrew", email = "andrew.neher1@gmail.com"}
8
+ ]
9
+ requires-python = ">=3.12"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICEN[CS]E*"]
16
+ dependencies = [
17
+ "hatch>=1.16.3",
18
+ "httpx>=0.28.1",
19
+ "pydantic>=2.12.5",
20
+ "pytest>=9.0.2",
21
+ "pytest-mock>=3.15.1",
22
+ "respx>=0.22.0",
23
+ ]
24
+
25
+ [build-system]
26
+ requires = ["hatchling"]
27
+ build-backend = "hatchling.build"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/wp_python"]
31
+
32
+ [tool.hatch.build.targets.sdist]
33
+ packages = ["src/wp_python"]
34
+
35
+ [tool.hatch.version]
36
+ path = "src/wp_python/__init__.py"
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
40
+ python_files = "test_*.py"
41
+ python_functions = "test_*"
@@ -0,0 +1,31 @@
1
+ """
2
+ WordPress REST API Python Client
3
+
4
+ A full-featured Python 3.12 interface to the WordPress REST API.
5
+ """
6
+
7
+ from .auth import ApplicationPasswordAuth, BasicAuth, JWTAuth, OAuth2Auth
8
+ from .client import WordPressClient
9
+ from .exceptions import (
10
+ AuthenticationError,
11
+ NotFoundError,
12
+ RateLimitError,
13
+ ServerError,
14
+ ValidationError,
15
+ WordPressError,
16
+ )
17
+
18
+ __version__ = "0.1.3"
19
+ __all__ = [
20
+ "WordPressClient",
21
+ "ApplicationPasswordAuth",
22
+ "BasicAuth",
23
+ "JWTAuth",
24
+ "OAuth2Auth",
25
+ "WordPressError",
26
+ "AuthenticationError",
27
+ "NotFoundError",
28
+ "ValidationError",
29
+ "RateLimitError",
30
+ "ServerError",
31
+ ]
@@ -0,0 +1,174 @@
1
+ """Authentication handlers for WordPress REST API."""
2
+
3
+ import base64
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+
10
+ class AuthHandler(ABC):
11
+ """Abstract base class for authentication handlers."""
12
+
13
+ @abstractmethod
14
+ def get_headers(self) -> dict[str, str]:
15
+ """Return headers required for authentication."""
16
+ pass
17
+
18
+ @abstractmethod
19
+ def authenticate(self, request: httpx.Request) -> httpx.Request:
20
+ """Authenticate the request."""
21
+ pass
22
+
23
+
24
+ class BasicAuth(AuthHandler):
25
+ """HTTP Basic Authentication.
26
+
27
+ Note: Basic auth should only be used over HTTPS.
28
+ """
29
+
30
+ def __init__(self, username: str, password: str) -> None:
31
+ self.username = username
32
+ self.password = password
33
+ self._credentials = base64.b64encode(
34
+ f"{username}:{password}".encode()
35
+ ).decode()
36
+
37
+ def get_headers(self) -> dict[str, str]:
38
+ return {"Authorization": f"Basic {self._credentials}"}
39
+
40
+ def authenticate(self, request: httpx.Request) -> httpx.Request:
41
+ request.headers.update(self.get_headers())
42
+ return request
43
+
44
+
45
+ class ApplicationPasswordAuth(AuthHandler):
46
+ """WordPress Application Passwords authentication.
47
+
48
+ Application Passwords were introduced in WordPress 5.6 and provide
49
+ a secure way to authenticate REST API requests without exposing
50
+ the user's main password.
51
+ """
52
+
53
+ def __init__(self, username: str, application_password: str) -> None:
54
+ self.username = username
55
+ self.application_password = application_password.replace(" ", "")
56
+ self._credentials = base64.b64encode(
57
+ f"{username}:{self.application_password}".encode()
58
+ ).decode()
59
+
60
+ def get_headers(self) -> dict[str, str]:
61
+ return {"Authorization": f"Basic {self._credentials}"}
62
+
63
+ def authenticate(self, request: httpx.Request) -> httpx.Request:
64
+ request.headers.update(self.get_headers())
65
+ return request
66
+
67
+
68
+ class JWTAuth(AuthHandler):
69
+ """JWT (JSON Web Token) Authentication.
70
+
71
+ Requires a JWT authentication plugin on the WordPress site.
72
+ """
73
+
74
+ def __init__(self, token: str | None = None) -> None:
75
+ self.token = token
76
+ self._base_url: str | None = None
77
+
78
+ def set_base_url(self, url: str) -> None:
79
+ """Set the base URL for token requests."""
80
+ self._base_url = url
81
+
82
+ def get_headers(self) -> dict[str, str]:
83
+ if not self.token:
84
+ return {}
85
+ return {"Authorization": f"Bearer {self.token}"}
86
+
87
+ def authenticate(self, request: httpx.Request) -> httpx.Request:
88
+ if self.token:
89
+ request.headers.update(self.get_headers())
90
+ return request
91
+
92
+ def obtain_token(
93
+ self,
94
+ client: httpx.Client,
95
+ username: str,
96
+ password: str,
97
+ endpoint: str = "/wp-json/jwt-auth/v1/token",
98
+ ) -> str:
99
+ """Obtain a JWT token from the WordPress site."""
100
+ response = client.post(
101
+ endpoint,
102
+ json={"username": username, "password": password},
103
+ )
104
+ response.raise_for_status()
105
+ data = response.json()
106
+ self.token = data.get("token", data.get("data", {}).get("token"))
107
+ return self.token
108
+
109
+ def validate_token(
110
+ self,
111
+ client: httpx.Client,
112
+ endpoint: str = "/wp-json/jwt-auth/v1/token/validate",
113
+ ) -> bool:
114
+ """Validate the current JWT token."""
115
+ if not self.token:
116
+ return False
117
+ response = client.post(endpoint, headers=self.get_headers())
118
+ return response.status_code == 200
119
+
120
+
121
+ class OAuth2Auth(AuthHandler):
122
+ """OAuth 2.0 Authentication.
123
+
124
+ Requires OAuth 2.0 plugin on the WordPress site.
125
+ """
126
+
127
+ def __init__(
128
+ self,
129
+ access_token: str | None = None,
130
+ refresh_token: str | None = None,
131
+ client_id: str | None = None,
132
+ client_secret: str | None = None,
133
+ ) -> None:
134
+ self.access_token = access_token
135
+ self.refresh_token = refresh_token
136
+ self.client_id = client_id
137
+ self.client_secret = client_secret
138
+
139
+ def get_headers(self) -> dict[str, str]:
140
+ if not self.access_token:
141
+ return {}
142
+ return {"Authorization": f"Bearer {self.access_token}"}
143
+
144
+ def authenticate(self, request: httpx.Request) -> httpx.Request:
145
+ if self.access_token:
146
+ request.headers.update(self.get_headers())
147
+ return request
148
+
149
+ def refresh_access_token(
150
+ self,
151
+ client: httpx.Client,
152
+ token_endpoint: str,
153
+ ) -> str:
154
+ """Refresh the access token using the refresh token."""
155
+ if not self.refresh_token or not self.client_id:
156
+ raise ValueError("Refresh token and client ID required")
157
+
158
+ data: dict[str, Any] = {
159
+ "grant_type": "refresh_token",
160
+ "refresh_token": self.refresh_token,
161
+ "client_id": self.client_id,
162
+ }
163
+ if self.client_secret:
164
+ data["client_secret"] = self.client_secret
165
+
166
+ response = client.post(token_endpoint, data=data)
167
+ response.raise_for_status()
168
+ token_data = response.json()
169
+
170
+ self.access_token = token_data["access_token"]
171
+ if "refresh_token" in token_data:
172
+ self.refresh_token = token_data["refresh_token"]
173
+
174
+ return self.access_token