Files
wp-materialize/src/wp_cli.py

230 lines
6.7 KiB
Python

from __future__ import annotations
import json
import subprocess
from dataclasses import dataclass
from datetime import timedelta, timezone
from pathlib import Path
from typing import Dict, List, Optional
from zoneinfo import ZoneInfo
from .errors import WordPressError
@dataclass(frozen=True)
class CategoryTerm:
term_id: int
name: str
parent: int
@dataclass(frozen=True)
class TagTerm:
term_id: int
name: str
class WordPressCLI:
def __init__(self, root: Path):
self.root = root
def list_categories(self) -> List[CategoryTerm]:
data = self._run_json([
"wp",
"term",
"list",
"category",
"--fields=term_id,name,parent",
"--format=json",
])
categories: List[CategoryTerm] = []
for entry in data:
categories.append(
CategoryTerm(
term_id=int(entry["term_id"]),
name=entry["name"],
parent=int(entry["parent"]) if entry.get("parent") is not None else 0,
)
)
return categories
def list_tags(self) -> List[TagTerm]:
data = self._run_json([
"wp",
"term",
"list",
"post_tag",
"--fields=term_id,name",
"--format=json",
])
tags: List[TagTerm] = []
for entry in data:
tags.append(TagTerm(term_id=int(entry["term_id"]), name=entry["name"]))
return tags
def create_tag(self, name: str) -> int:
result = self._run(
[
"wp",
"term",
"create",
"post_tag",
name,
"--porcelain",
],
capture_output=True,
)
output = result.stdout.strip()
try:
return int(output)
except ValueError as exc:
raise WordPressError(f"Invalid tag id from wp cli: {output}") from exc
def get_timezone(self):
tz_name = self._run(
["wp", "option", "get", "timezone_string"],
capture_output=True,
).stdout.strip()
if tz_name and tz_name.upper() != "UTC":
try:
return ZoneInfo(tz_name)
except Exception:
pass
offset_value = self._run(
["wp", "option", "get", "gmt_offset"],
capture_output=True,
).stdout.strip()
try:
offset = float(offset_value)
except ValueError:
offset = 0.0
return timezone(timedelta(hours=offset))
def create_category(self, name: str, parent: int) -> int:
result = self._run(
[
"wp",
"term",
"create",
"category",
name,
f"--parent={parent}",
"--porcelain",
],
capture_output=True,
)
output = result.stdout.strip()
try:
return int(output)
except ValueError as exc:
raise WordPressError(f"Invalid category id from wp cli: {output}") from exc
def find_post_id(self, source_identity: str) -> Optional[int]:
result = self._run(
[
"wp",
"post",
"list",
"--post_type=post",
"--meta_key=_wp_materialize_source",
f"--meta_value={source_identity}",
"--field=ID",
],
capture_output=True,
)
output = result.stdout.strip()
if not output:
return None
try:
return int(output.splitlines()[0])
except ValueError as exc:
raise WordPressError(f"Invalid post id from wp cli: {output}") from exc
def create_post(
self,
title: str,
content: str,
categories: List[int],
tags: List[str],
source_identity: str,
created_on: Optional[str] = None,
last_modified: Optional[str] = None,
author: Optional[str] = None,
) -> int:
payload = json.dumps({"_wp_materialize_source": source_identity})
args = [
"wp",
"post",
"create",
"--post_type=post",
"--post_status=publish",
f"--post_title={title}",
f"--post_content={content}",
f"--post_category={','.join(str(cat) for cat in categories)}",
f"--tags_input={','.join(tags)}",
f"--meta_input={payload}",
"--porcelain",
]
if created_on:
args.append(f"--post_date={created_on}")
if last_modified:
args.append(f"--post_modified={last_modified}")
if author:
args.append(f"--post_author={author}")
result = self._run(args, capture_output=True)
output = result.stdout.strip()
try:
return int(output)
except ValueError as exc:
raise WordPressError(f"Invalid post id from wp cli: {output}") from exc
def update_post(
self,
post_id: int,
title: str,
content: str,
categories: List[int],
tags: List[str],
created_on: Optional[str] = None,
last_modified: Optional[str] = None,
author: Optional[str] = None,
) -> None:
args = [
"wp",
"post",
"update",
str(post_id),
"--post_status=publish",
f"--post_title={title}",
f"--post_content={content}",
f"--post_category={','.join(str(cat) for cat in categories)}",
f"--tags_input={','.join(tags)}",
]
if created_on:
args.append(f"--post_date={created_on}")
if last_modified:
args.append(f"--post_modified={last_modified}")
if author:
args.append(f"--post_author={author}")
self._run(args)
def _run_json(self, cmd: List[str]):
result = self._run(cmd, capture_output=True)
try:
return json.loads(result.stdout)
except json.JSONDecodeError as exc:
raise WordPressError(f"Invalid JSON from wp cli: {exc}\n{result.stdout}") from exc
def _run(self, cmd: List[str], capture_output: bool = False) -> subprocess.CompletedProcess:
try:
return subprocess.run(
cmd,
cwd=str(self.root),
check=True,
text=True,
capture_output=capture_output,
)
except subprocess.CalledProcessError as exc:
stderr = exc.stderr.strip() if exc.stderr else ""
raise WordPressError(f"WordPress CLI failed: {' '.join(cmd)}\n{stderr}") from exc