wagapi 0.2.2__tar.gz → 0.2.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.
- {wagapi-0.2.2 → wagapi-0.2.3}/PKG-INFO +6 -4
- {wagapi-0.2.2 → wagapi-0.2.3}/README.md +5 -3
- {wagapi-0.2.2 → wagapi-0.2.3}/pyproject.toml +1 -1
- {wagapi-0.2.2 → wagapi-0.2.3}/tests/conftest.py +17 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/tests/test_commands.py +66 -2
- wagapi-0.2.3/wagapi/__init__.py +1 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/commands/pages.py +22 -3
- wagapi-0.2.2/wagapi/__init__.py +0 -1
- {wagapi-0.2.2 → wagapi-0.2.3}/.github/workflows/publish.yml +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/.gitignore +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/tests/__init__.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/tests/test_client.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/tests/test_config.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/tests/test_markdown.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/__main__.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/cli.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/client.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/commands/__init__.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/commands/images.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/commands/init.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/commands/schema.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/config.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/exceptions.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/formatting/__init__.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/formatting/markdown.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/formatting/output.py +0 -0
- {wagapi-0.2.2 → wagapi-0.2.3}/wagapi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wagapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: CLI client for the Wagtail Write API, optimised for LLM orchestration
|
|
5
5
|
Project-URL: Repository, https://github.com/tomdyson/wagapi
|
|
6
6
|
Project-URL: Issues, https://github.com/tomdyson/wagapi/issues
|
|
@@ -255,8 +255,8 @@ wagapi pages list [OPTIONS]
|
|
|
255
255
|
| Option | Description |
|
|
256
256
|
|---|---|
|
|
257
257
|
| `--type TYPE` | Filter by page type, e.g. `testapp.BlogPage` |
|
|
258
|
-
| `--parent
|
|
259
|
-
| `--descendant-of
|
|
258
|
+
| `--parent ID_OR_PATH` | Direct children of page ID or URL path (e.g. `5` or `/blog/`) |
|
|
259
|
+
| `--descendant-of ID_OR_PATH` | All descendants of page ID or URL path |
|
|
260
260
|
| `--status STATUS` | `draft`, `live`, or `live+draft` |
|
|
261
261
|
| `--slug SLUG` | Exact slug match |
|
|
262
262
|
| `--path PATH` | Exact URL path match, e.g. `/blog/my-post/` |
|
|
@@ -288,7 +288,9 @@ wagapi pages create <type> --parent ID_OR_PATH --title TITLE [OPTIONS]
|
|
|
288
288
|
| `--publish` | Publish immediately (default: create as draft) |
|
|
289
289
|
| `--raw` | Treat field values as raw JSON (no auto-wrapping) |
|
|
290
290
|
|
|
291
|
-
**Markdown body (auto-
|
|
291
|
+
**Markdown body (auto-detected: StreamField blocks or RichTextField HTML):**
|
|
292
|
+
|
|
293
|
+
`--body` checks the page type schema to determine the field type. For StreamField fields, markdown is converted to blocks. For RichTextField fields, markdown is sent as-is for server-side conversion to HTML.
|
|
292
294
|
|
|
293
295
|
```bash
|
|
294
296
|
wagapi pages create testapp.BlogPage --parent /blog/ \
|
|
@@ -225,8 +225,8 @@ wagapi pages list [OPTIONS]
|
|
|
225
225
|
| Option | Description |
|
|
226
226
|
|---|---|
|
|
227
227
|
| `--type TYPE` | Filter by page type, e.g. `testapp.BlogPage` |
|
|
228
|
-
| `--parent
|
|
229
|
-
| `--descendant-of
|
|
228
|
+
| `--parent ID_OR_PATH` | Direct children of page ID or URL path (e.g. `5` or `/blog/`) |
|
|
229
|
+
| `--descendant-of ID_OR_PATH` | All descendants of page ID or URL path |
|
|
230
230
|
| `--status STATUS` | `draft`, `live`, or `live+draft` |
|
|
231
231
|
| `--slug SLUG` | Exact slug match |
|
|
232
232
|
| `--path PATH` | Exact URL path match, e.g. `/blog/my-post/` |
|
|
@@ -258,7 +258,9 @@ wagapi pages create <type> --parent ID_OR_PATH --title TITLE [OPTIONS]
|
|
|
258
258
|
| `--publish` | Publish immediately (default: create as draft) |
|
|
259
259
|
| `--raw` | Treat field values as raw JSON (no auto-wrapping) |
|
|
260
260
|
|
|
261
|
-
**Markdown body (auto-
|
|
261
|
+
**Markdown body (auto-detected: StreamField blocks or RichTextField HTML):**
|
|
262
|
+
|
|
263
|
+
`--body` checks the page type schema to determine the field type. For StreamField fields, markdown is converted to blocks. For RichTextField fields, markdown is sent as-is for server-side conversion to HTML.
|
|
262
264
|
|
|
263
265
|
```bash
|
|
264
266
|
wagapi pages create testapp.BlogPage --parent /blog/ \
|
|
@@ -51,6 +51,23 @@ SAMPLE_PAGE_TYPE_SCHEMA = {
|
|
|
51
51
|
},
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
SAMPLE_RICHTEXT_PAGE_TYPE_SCHEMA = {
|
|
55
|
+
"name": "home.SimplePage",
|
|
56
|
+
"verbose_name": "simple page",
|
|
57
|
+
"create_schema": {
|
|
58
|
+
"required": ["type", "parent", "title"],
|
|
59
|
+
"properties": {
|
|
60
|
+
"type": {"type": "string"},
|
|
61
|
+
"parent": {"type": "integer"},
|
|
62
|
+
"title": {"type": "string"},
|
|
63
|
+
"body": {"type": "string", "description": "RichTextField body content"},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"allowed_parents": ["wagtailcore.Page"],
|
|
67
|
+
"allowed_children": [],
|
|
68
|
+
"streamfield_blocks": {},
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
SAMPLE_PAGE = {
|
|
55
72
|
"id": 42,
|
|
56
73
|
"title": "Hello",
|
|
@@ -158,7 +158,12 @@ def test_pages_create(runner):
|
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
@respx.mock
|
|
161
|
-
def
|
|
161
|
+
def test_pages_create_with_body_streamfield(runner):
|
|
162
|
+
"""Body is converted to StreamField blocks when the field is a StreamField."""
|
|
163
|
+
schema = {"streamfield_blocks": {"body": [{"type": "heading"}, {"type": "paragraph"}]}}
|
|
164
|
+
respx.get(f"{BASE_URL}/schema/blog.BlogPage/").mock(
|
|
165
|
+
return_value=Response(200, json=schema)
|
|
166
|
+
)
|
|
162
167
|
data = {
|
|
163
168
|
"id": 43,
|
|
164
169
|
"title": "Iris",
|
|
@@ -179,13 +184,45 @@ def test_pages_create_with_body(runner):
|
|
|
179
184
|
],
|
|
180
185
|
)
|
|
181
186
|
assert result.exit_code == 0
|
|
182
|
-
# Verify the request body contained streamfield blocks
|
|
183
187
|
request_body = json.loads(route.calls[0].request.content)
|
|
184
188
|
assert isinstance(request_body["body"], list)
|
|
185
189
|
assert request_body["body"][0]["type"] == "heading"
|
|
186
190
|
assert request_body["body"][1]["type"] == "paragraph"
|
|
187
191
|
|
|
188
192
|
|
|
193
|
+
@respx.mock
|
|
194
|
+
def test_pages_create_with_body_richtext(runner):
|
|
195
|
+
"""Body is sent as richtext format dict when the field is a RichTextField."""
|
|
196
|
+
schema = {"streamfield_blocks": {}}
|
|
197
|
+
respx.get(f"{BASE_URL}/schema/home.SimplePage/").mock(
|
|
198
|
+
return_value=Response(200, json=schema)
|
|
199
|
+
)
|
|
200
|
+
data = {
|
|
201
|
+
"id": 50,
|
|
202
|
+
"title": "About",
|
|
203
|
+
"slug": "about",
|
|
204
|
+
"meta": {"type": "home.SimplePage", "live": False, "parent_id": 3},
|
|
205
|
+
}
|
|
206
|
+
route = respx.post(f"{BASE_URL}/pages/").mock(
|
|
207
|
+
return_value=Response(201, json=data)
|
|
208
|
+
)
|
|
209
|
+
with mock.patch.dict("os.environ", ENV):
|
|
210
|
+
result = runner.invoke(
|
|
211
|
+
cli,
|
|
212
|
+
[
|
|
213
|
+
"pages", "create", "home.SimplePage",
|
|
214
|
+
"--parent", "3",
|
|
215
|
+
"--title", "About",
|
|
216
|
+
"--body", "Hello **world**",
|
|
217
|
+
],
|
|
218
|
+
)
|
|
219
|
+
assert result.exit_code == 0
|
|
220
|
+
request_body = json.loads(route.calls[0].request.content)
|
|
221
|
+
assert isinstance(request_body["body"], dict)
|
|
222
|
+
assert request_body["body"]["format"] == "markdown"
|
|
223
|
+
assert request_body["body"]["content"] == "Hello **world**"
|
|
224
|
+
|
|
225
|
+
|
|
189
226
|
@respx.mock
|
|
190
227
|
def test_pages_create_with_path_parent(runner):
|
|
191
228
|
data = {
|
|
@@ -245,6 +282,33 @@ def test_pages_update(runner):
|
|
|
245
282
|
assert "Updated" in result.output
|
|
246
283
|
|
|
247
284
|
|
|
285
|
+
@respx.mock
|
|
286
|
+
def test_pages_update_with_body_richtext(runner):
|
|
287
|
+
"""Update fetches the page type and sends richtext for RichTextField."""
|
|
288
|
+
# Mock GET to fetch the page (to learn its type)
|
|
289
|
+
page_data = {
|
|
290
|
+
"id": 42, "title": "Old", "body": "<p>old</p>",
|
|
291
|
+
"meta": {"type": "home.SimplePage", "live": True},
|
|
292
|
+
}
|
|
293
|
+
respx.get(f"{BASE_URL}/pages/42/").mock(return_value=Response(200, json=page_data))
|
|
294
|
+
# Mock schema lookup
|
|
295
|
+
schema = {"streamfield_blocks": {}}
|
|
296
|
+
respx.get(f"{BASE_URL}/schema/home.SimplePage/").mock(
|
|
297
|
+
return_value=Response(200, json=schema)
|
|
298
|
+
)
|
|
299
|
+
# Mock PATCH
|
|
300
|
+
updated = {**page_data, "body": "<p>new</p>"}
|
|
301
|
+
route = respx.patch(f"{BASE_URL}/pages/42/").mock(
|
|
302
|
+
return_value=Response(200, json=updated)
|
|
303
|
+
)
|
|
304
|
+
with mock.patch.dict("os.environ", ENV):
|
|
305
|
+
result = runner.invoke(cli, ["pages", "update", "42", "--body", "new content"])
|
|
306
|
+
assert result.exit_code == 0
|
|
307
|
+
request_body = json.loads(route.calls[0].request.content)
|
|
308
|
+
assert isinstance(request_body["body"], dict)
|
|
309
|
+
assert request_body["body"]["format"] == "markdown"
|
|
310
|
+
|
|
311
|
+
|
|
248
312
|
@respx.mock
|
|
249
313
|
def test_pages_delete(runner):
|
|
250
314
|
respx.delete(f"{BASE_URL}/pages/42/").mock(return_value=Response(204))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.3"
|
|
@@ -7,7 +7,7 @@ import click
|
|
|
7
7
|
|
|
8
8
|
from wagapi.cli import Context, handle_api_errors, pass_ctx
|
|
9
9
|
from wagapi.exceptions import UsageError
|
|
10
|
-
from wagapi.formatting.markdown import markdown_to_streamfield
|
|
10
|
+
from wagapi.formatting.markdown import markdown_to_richtext, markdown_to_streamfield
|
|
11
11
|
from wagapi.formatting.output import (
|
|
12
12
|
format_page_created,
|
|
13
13
|
format_page_deleted,
|
|
@@ -27,6 +27,15 @@ def _require_client(ctx: Context):
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def _is_streamfield(ctx: Context, page_type: str, field_name: str) -> bool:
|
|
31
|
+
"""Check if a field is a StreamField by looking for it in streamfield_blocks."""
|
|
32
|
+
try:
|
|
33
|
+
schema = ctx.client.get_page_type_schema(page_type)
|
|
34
|
+
return field_name in schema.get("streamfield_blocks", {})
|
|
35
|
+
except Exception:
|
|
36
|
+
return True # default to StreamField if schema lookup fails
|
|
37
|
+
|
|
38
|
+
|
|
30
39
|
def _parse_parent(value: str) -> int | str:
|
|
31
40
|
"""Parse parent as int ID or string path."""
|
|
32
41
|
try:
|
|
@@ -175,7 +184,11 @@ def create(ctx: Context, page_type, parent, title, slug, fields, body, publish,
|
|
|
175
184
|
except json.JSONDecodeError:
|
|
176
185
|
data["body"] = body
|
|
177
186
|
else:
|
|
178
|
-
|
|
187
|
+
# Check if body is a StreamField or RichTextField
|
|
188
|
+
if _is_streamfield(ctx, page_type, "body"):
|
|
189
|
+
data["body"] = markdown_to_streamfield(body)
|
|
190
|
+
else:
|
|
191
|
+
data["body"] = markdown_to_richtext(body)
|
|
179
192
|
|
|
180
193
|
if publish:
|
|
181
194
|
data["action"] = "publish"
|
|
@@ -225,7 +238,13 @@ def update(ctx: Context, page_id, title, slug, fields, body, publish, raw):
|
|
|
225
238
|
except json.JSONDecodeError:
|
|
226
239
|
data["body"] = body
|
|
227
240
|
else:
|
|
228
|
-
|
|
241
|
+
# Fetch page to determine its type, then check schema
|
|
242
|
+
page_data = ctx.client.get_page(page_id)
|
|
243
|
+
page_type = page_data.get("meta", {}).get("type", "")
|
|
244
|
+
if _is_streamfield(ctx, page_type, "body"):
|
|
245
|
+
data["body"] = markdown_to_streamfield(body)
|
|
246
|
+
else:
|
|
247
|
+
data["body"] = markdown_to_richtext(body)
|
|
229
248
|
|
|
230
249
|
if publish:
|
|
231
250
|
data["action"] = "publish"
|
wagapi-0.2.2/wagapi/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|