Other modules#
Module |
Contents |
|---|---|
Tasks that take several requests in the right order, with a result that lists every request. |
|
The MCP server; needs the |
|
Authentication flows for httpx: basic, JWT, and a static token. |
|
Build Volto blocks, including blocks of any type and blocks that hold others, such as grids; convert Markdown and HTML to blocks; and walk blocks and their links. |
|
Connection profiles and the configuration file. |
|
Secrets of profiles: keyring, environment variable, or command. |
|
UID references and |
Operations#
Function |
Requests, in order |
|---|---|
|
|
Create the page, then the transition and |
|
|
|
|
Transitions run only when publish is given.
With a dry_run client, the requests that change the site are recorded and skipped, and created entries have "planned": true with the path the item would most likely get.
Steps that need an answer from the site, such as the final GET and the counts after a copy, are left out.
High-level operations: tasks that take several requests, in the right order.
Each operation gets a task right on any Plone 6 site, Volto sites included,
where doing it request by request means knowing the content model: a News
Item has no image field there, and its lead image is a relation to an
Image stored inside it.
Every operation returns an OperationResult that lists what it
created or changed and every request it sent. With a dry_run client,
nothing is sent: the result shows the planned requests, with the paths the
new items would most likely get. A read-only client refuses the first
change. When a request fails part way, the operation raises
PloneOperationError, whose result lists
what was already done, so the caller can clean up or resume.
Example:
from plone_client import PloneClient
from plone_client import operations
with PloneClient.from_profile("blog") as site:
result = operations.create_with_files(
site,
"/news",
"News Item",
["lead.jpg", "photo-2.jpg"],
title="Our new office",
publish="publish",
)
print(result.item["@id"])
- plone_client.operations.ITEM_KEYS = ('@id', '@type', 'UID', 'id', 'title', 'review_state')#
Keys kept from an item in a result.
- plone_client.operations.PAGE_FORMATS: dict[str, Literal['markdown', 'html']] = {'.htm': 'html', '.html': 'html', '.markdown': 'markdown', '.md': 'markdown'}#
Extensions of the files that
upload_tree()can turn into pages.
- plone_client.operations.INDEX_NAMES = ('index.md', 'README.md', 'index.html')#
Files that hold the content of their folder’s page in
upload_tree().
- class plone_client.operations.OperationResult(operation, dry_run, item=None, created=<factory>, changed=<factory>, requests=<factory>, error=None)[source]#
Bases:
objectWhat an operation did.
- Parameters:
operation (str) – Name of the operation.
dry_run (bool) – Whether the requests that change the site were skipped.
item (dict[str, Any] | None) – The main item, for operations that create one.
created (list[dict[str, Any]]) – Items created, in order;
sourcenames the local file or folder they came from, andplannedmarks a path guessed in a dry run.changed (list[dict[str, Any]]) – Items changed, with what changed.
requests (list[dict[str, Any]]) – Every request sent or skipped:
method,url,statusand, when skipped,dry_run.error (dict[str, Any] | None) – Type and message of the error that stopped the operation.
- plone_client.operations.slug(text)[source]#
A likely id for a title, close to what Plone generates.
Only used to name items in a dry run, before Plone picks the real id.
- plone_client.operations.create_with_files(client, parent, portal_type, files, *, data=None, lead_image='first', publish=None, **fields)[source]#
Create an item, upload files and images inside it, and set its lead image.
The lead image goes where the type keeps it: in the
imagefield when the type has one (the file is then not uploaded again), or otherwise as an Image inside the item thatpreview_image_linkpoints to, which is how News Items work on Volto sites. Files are uploaded asImageorFileitems, in order. Types with blocks get title and description blocks unless blocks are given.The item must be folderish to hold the files: on Volto sites, Documents and News Items are.
- Parameters:
client (PloneClient) – The client.
parent (str) – Path or URL of the folder.
portal_type (str) – Content type, such as
News Item.files (Iterable[str | Path]) – Paths of the files to upload inside the item.
lead_image (int | Literal['first'] | None) – Index of the lead image in
files,"first"for the first image (skipped when the type has no lead image), orNonefor no lead image.publish (str | None) – Workflow transition to run at the end, such as
publish.fields (Any) – More field values, merged over
data.
- Returns:
The result;
itemis the new item.- Raises:
ValueError – If a file is missing, or the lead image is not an image or cannot be stored by the type. Nothing is changed then.
PloneOperationError – If a request fails.
- Return type:
- plone_client.operations.split_title(source, source_format)[source]#
Take a leading level 1 heading out of Markdown or HTML.
- plone_client.operations.page_data(source, source_format='markdown', *, title=None, description=None, default_title='')[source]#
Field values of a page made from Markdown or HTML.
The page gets a title block, a description block and the converted content. Without a title, the leading level 1 heading is the title, and is not repeated in the content.
- Parameters:
source (str) – The Markdown or HTML.
source_format (Literal['markdown', 'html']) –
markdownorhtml.title (str | None) – Title of the page.
description (str | None) – Description of the page.
default_title (str) – Title when there is neither
titlenor a leading heading, such as the name of the file.
- Returns:
title, maybedescription,blocksandblocks_layout.- Raises:
ValueError – If there is no title.
PloneConfigurationError – If
collective.html2blocksis missing.
- Return type:
- plone_client.operations.create_page(client, parent, source, *, source_format='markdown', portal_type='Document', title=None, description=None, data=None, publish=None)[source]#
Create a page from Markdown or HTML, converted to blocks.
See
page_data()for how the page is built. Needs themarkdownextra (Python 3.12 or later).- Parameters:
client (PloneClient) – The client.
parent (str) – Path or URL of the folder.
source (str) – The Markdown or HTML text.
source_format (Literal['markdown', 'html']) –
markdownorhtml.portal_type (str) – Content type; it must have blocks.
title (str | None) – Title; the leading
#heading by default.description (str | None) – Description.
data (Mapping[str, Any] | None) – More field values, such as
idorsubjects.publish (str | None) – Workflow transition to run at the end.
- Returns:
The result;
itemis the new page.- Raises:
ValueError – If there is no title. Nothing is sent then.
PloneConfigurationError – If
collective.html2blocksis missing.PloneOperationError – If a request fails.
- Return type:
- plone_client.operations.upload_tree(client, source, parent, *, folder_type='Document', convert_pages=False, include_hidden=False, publish=None)[source]#
Upload the contents of a local folder, keeping its structure.
Folders become folderish pages (
Documentby default, which is folderish on Volto sites), images becomeImageitems, and other filesFileitems. The contents ofsourcego insideparent, which must exist, as withrsync source/ parent.With
convert_pages, Markdown and HTML files become pages (seepage_data()), and a folder’sindex.md,README.mdorindex.htmlbecomes the content of the folder’s own page. This needs themarkdownextra.- Parameters:
client (PloneClient) – The client.
parent (str) – Path or URL of the folder in the site.
folder_type (str) – Content type for folders.
convert_pages (bool) – Turn Markdown and HTML files into pages.
include_hidden (bool) – Also upload files and folders whose name starts with a dot.
publish (str | None) – Workflow transition to run on each new page, such as
publish; files and images follow their folder.
- Returns:
The result; each created entry names its local
source.- Raises:
ValueError – If
sourceis not a folder. Nothing is sent then.PloneOperationError – If a request fails.
- Return type:
- plone_client.operations.copy_items(client, sources, target)[source]#
Copy items, folders with their contents, into a folder.
Plone may rename a copy whose id is taken (
copy_of_news), so read the@idof each created entry.- Parameters:
client (PloneClient) – The client.
sources (str | Iterable[str]) – Path, URL or UID reference of an item, or several.
target (str) – Path or URL of the folder.
- Returns:
The result; each created entry has the
source, the new@id, and the number ofitems(the item and everything inside it) before and, unless in a dry run, after the copy.- Raises:
PloneOperationError – If a request fails.
- Return type:
- plone_client.operations.move_items(client, sources, target)[source]#
Move items, folders with their contents, into a folder.
- Parameters:
client (PloneClient) – The client.
sources (str | Iterable[str]) – Path, URL or UID reference of an item, or several.
target (str) – Path or URL of the folder.
- Returns:
The result; each changed entry has the old
source, the new@idand the number ofitemsbefore and after the move.- Raises:
PloneOperationError – If a request fails.
- Return type:
MCP server#
An MCP server for Plone sites, built on the client and its operations.
The server gives chat assistants and agents without a shell the same access
the command line gives: the same profiles, secrets in the keyring, and the
same read_only and dry_run switches. It speaks over standard input
and output, for one site or several:
pip install 'plone-client[mcp]'
plone-client mcp --profile blog
plone-client mcp --site blog --site docs
plone-client mcp --group customer
plone-client mcp --all
Rather than one tool per plone.restapi service, it offers the everyday
reads, the high-level operations of plone_client.operations, and one
tool for any other service. With several sites, every tool takes a site
argument, and list_sites describes them. Tools that change a site are
only offered when at least one site accepts changes; a change to a read-only
site is refused before it is sent.
- plone_client.mcp_server.tool_errors(func)[source]#
Report the errors of a tool to the model, as JSON.
Unexpected exceptions reach the model only as “Error executing tool”, so the errors a caller can act on are turned into
ToolError.
- class plone_client.mcp_server.Site(name, url, description='', read_only=False, factory=None, client=None)[source]#
Bases:
objectA site the server works on.
- Parameters:
name (str) – Name the model picks it by: the profile name.
url (str) – URL of the site.
description (str) – What the site is, for the model.
read_only (bool) – Whether changes to it are refused.
factory (Callable[[], PloneClient] | None) – Builds its client, on first use.
client (PloneClient | None)
- class plone_client.mcp_server.Sites(sites, *, dry_run=False)[source]#
Bases:
objectThe sites of one server, each with a client created on first use.
Creating a client looks up its secret, so a site whose secret is missing only fails when a tool uses it.
- Parameters:
- Raises:
ValueError – If there is no site, or two have the same name.
- classmethod from_client(client, name='site')[source]#
One site, served with a client that already exists.
- Parameters:
client (PloneClient) – The client.
name (str) – Name of the site.
- Returns:
The sites.
- Return type:
- classmethod from_profiles(profiles, *, read_only=False, dry_run=False, factory=<class 'plone_client.client.PloneClient'>)[source]#
Sites for profiles.
- Parameters:
- Returns:
The sites.
- Return type:
- client(name=None)[source]#
The client of a site, created on first use.
- Parameters:
name (str | None) – Site name; the only site when there is one.
- Returns:
The client.
- Raises:
PloneConfigurationError – If the site is unknown, or its secret cannot be found.
- Return type:
- plone_client.mcp_server.has_html2blocks()[source]#
Whether
collective.html2blocksis installed.- Returns:
Trueif it can be imported.- Return type:
- plone_client.mcp_server.build_server(target)[source]#
An MCP server whose tools work on one site or several.
- Parameters:
target (PloneClient | Sites) – A client, for one site, or the sites. Each client’s
read_onlyanddry_runsettings apply to its tools.- Returns:
The server, ready to
run().- Return type:
MCPServer
- plone_client.mcp_server.serve(target)[source]#
Serve the tools over standard input and output.
- Parameters:
target (PloneClient | Sites) – A client, for one site, or the sites; their clients are closed when the server stops.
- Return type:
None
Authentication#
Authentication strategies for PloneClient.
Three ways to authenticate against plone.restapi are supported:
HTTP basic authentication (
httpx.BasicAuth), the simplest option and the default when a username and password are given;a JWT obtained from
@login(JWTAuth), which logs in again when the token expires;a token issued beforehand (
TokenAuth).
Any other httpx.Auth can be passed to the client as well.
- class plone_client.auth.TokenAuth(token)[source]#
Bases:
AuthSend a token issued beforehand as
Authorization: Bearer.- Parameters:
token (str) – The JWT.
- auth_flow(request)[source]#
Execute the authentication flow.
To dispatch a request, yield it:
` yield request `The client will .send() the response back into the flow generator. You can access it like so:
` response = yield request `A return (or reaching the end of the generator) will result in the client returning the last response obtained from the server.
You can dispatch as many requests as is necessary.
- Parameters:
request (Request)
- Return type:
Generator[Request, Response, None]
- class plone_client.auth.JWTAuth(username, password, login_url)[source]#
Bases:
AuthLog in with
POST @loginand send the JWT it returns.The login happens on the first request. When the site answers
401, the token is assumed to be expired: the flow logs in again and repeats the request once.- Parameters:
- auth_flow(request)[source]#
Execute the authentication flow.
To dispatch a request, yield it:
` yield request `The client will .send() the response back into the flow generator. You can access it like so:
` response = yield request `A return (or reaching the end of the generator) will result in the client returning the last response obtained from the server.
You can dispatch as many requests as is necessary.
- Parameters:
request (Request)
- Return type:
Generator[Request, Response, None]
- plone_client.auth.build_auth(*, username='', password='', token='', method='basic', login_url='')[source]#
Pick the authentication for a set of credentials.
A token wins over a username and password. Without any credential the client is anonymous.
- Parameters:
- Returns:
The authentication, or
Nonefor anonymous access.- Raises:
ValueError – If the method is unknown or only half of the username/password pair is given.
- Return type:
Auth | None
Blocks#
Helpers for Volto blocks: build them, walk them, find their links.
A page built with Volto stores its content in two fields: blocks, a
mapping of block ids to block data, and blocks_layout, whose items
list the block ids in order. A page without both renders empty.
Example:
from plone_client import blocks
page = blocks.build(
blocks.title(),
blocks.description(),
blocks.text("Hello!"),
)
site.content.create("/", "Document", title="Hello", **page)
Blocks this module has no helper for, such as those of an add-on, are built
with block(), and blocks that hold other blocks with container().
A grid of four teasers:
paths = ["/news/a", "/news/b", "/news/c", "/news/d"]
page = blocks.build(
blocks.title(),
blocks.grid(*(blocks.teaser(path) for path in paths)),
)
Markdown and HTML can be converted to blocks with convert().
- plone_client.blocks.LINK_KEYS = frozenset({'href', 'link', 'preview_image_link', 'url'})#
Keys whose string values are links.
- plone_client.blocks.new_id()[source]#
A fresh block id.
- Returns:
A random UUID, the way Volto writes block ids.
- Return type:
- plone_client.blocks.text(value)[source]#
A text block with one plain paragraph.
The value is plain text, not Markdown:
*and<are kept as they are. Runs of whitespace, newlines included, become one space, as in an HTML paragraph. Withcollective.html2blocksinstalled, it builds the block, as it does forconvert(); without it, the block is built here, the same way.
- plone_client.blocks.block(block_type, /, **data)[source]#
Any block, given its type and its data.
For blocks this module has no helper for, such as those of an add-on.
client.block_typesdescribes the data each block of a site takes.
- plone_client.blocks.container(block_type, /, *items, **data)[source]#
A block that holds other blocks, such as a grid.
The blocks inside get their own
blocksandblocks_layout, as a page has; seebuild().
- plone_client.blocks.grid(*items, **data)[source]#
A Volto grid, which shows its blocks side by side.
Volto allows up to four blocks in a grid, unless the site changes that.
A teaser: a card linking to one item, with its title and image.
The teaser shows the title, description, and image of the target, and follows later changes to it.
- plone_client.blocks.convert(source, source_format='markdown', *, before=())[source]#
Blocks for Markdown or HTML, converted by
collective.html2blocks.Paragraphs, headings, lists and quotes become text (
slate) blocks; images, videos and tables get blocks of their own.Needs the
markdownextra (Python 3.12 or later):pip install 'plone-client[markdown]'
- Parameters:
- Returns:
blocksandblocks_layout, asbuild()returns them.- Raises:
PloneConfigurationError – If
collective.html2blocksis not installed.ValueError – If the format is unknown.
- Return type:
- plone_client.blocks.iter_blocks(blocks, layout=None)[source]#
Every block, nested ones included, depth first.
Blocks are visited in layout order. Container blocks (grids, columns, accordions…) keep their children either in their own
blocksandblocks_layoutor insidedata; both are followed. Blocks missing from the layout come last.
Profiles#
Named connection profiles, so one machine can talk to many sites.
A profile names a site, a user and where that user’s secret is kept. The
profiles live in a TOML file, by default ~/.config/plone-client/config.toml
($XDG_CONFIG_HOME and $PLONE_CLIENT_CONFIG are honored):
current = "blog"
[defaults]
timeout = 60
retries = 5
[profiles.blog]
url = "https://example.org"
username = "editor"
[profiles.local]
url = "http://localhost:8080/Plone"
username = "admin"
auth = "jwt"
[profiles.production]
url = "https://www.example.com"
username = "editor"
read_only = true
secret = "command"
secret_command = "op read op://Private/example/password"
verify = "/etc/ssl/certs/example-ca.pem"
[groups]
sites = ["blog", "production"]
The defaults table holds the connection settings shared by every
profile (see DEFAULT_KEYS); a profile’s own value wins. The
groups table names lists of profiles, which the MCP server can serve
together (plone-client mcp --group sites).
Secrets never go in the file. By default they are kept in the operating
system’s keyring (macOS Keychain, Windows Credential Locker, Secret Service
on Linux); see plone_client.credentials for the other sources.
Which profile is used, from the strongest choice to the weakest: the name
passed explicitly, $PLONE_CLIENT_PROFILE, a .plone-client.toml file
in the working directory or one of its parents (profile = "blog"), and
the current profile of the configuration file.
- plone_client.profiles.DEFAULT_KEYS: tuple[str, ...] = ('timeout', 'verify', 'retries', 'backoff', 'api_prefix', 'read_only')#
Settings allowed in the
defaultstable of the configuration file.
- plone_client.profiles.parse_verify(value)[source]#
Read a
verifysetting written as text.- Parameters:
value (str) –
trueorfalse(also1/0,yes/no,on/off), or the path of a CA bundle.- Returns:
A boolean, or the path.
- Raises:
PloneConfigurationError – If the value is empty.
- Return type:
- plone_client.profiles.default_config_path(environ=None)[source]#
Path of the user’s configuration file.
- plone_client.profiles.find_project_profile(start=None)[source]#
Profile named by a
.plone-client.tomlfile.The file is looked for in
startand in each of its parents, so a repository can say which site it works with. It holds no secrets and can be committed.
- class plone_client.profiles.Profile(name, url, username='', auth='basic', secret='keyring', secret_env='PLONE_CLIENT_PASSWORD', secret_command='', read_only=False, timeout=30.0, verify=True, retries=3, backoff=0.5, api_prefix='++api++', frontend_url='', description='')[source]#
Bases:
objectConnection settings of one site.
- Parameters:
name (str) – Profile name.
url (str) – URL of the site root.
username (str) – Login name; empty for anonymous access.
auth (Literal['basic', 'jwt', 'token']) –
basicorjwtwith a username and password, ortokento send the secret as a JWT.secret (Literal['keyring', 'env', 'command', 'none']) – Where the secret is kept:
keyring,env,commandornone.secret_env (str) – Variable holding the secret, with
secret = "env".secret_command (str) – Command printing the secret, with
secret = "command"; run without a shell.read_only (bool) – Refuse every request that could change the site.
timeout (float) – Seconds to wait for the site.
verify (bool | str) – Verify TLS certificates; a path verifies them against that CA bundle (a file or a directory) instead of the default one.
retries (int) – Times a request is repeated after a network error or an overloaded site; see
PloneClient.backoff (float) – Base delay in seconds between retries.
api_prefix (str) – Traversal prefix of the REST API.
frontend_url (str) – URL of the Volto frontend, when
urlis the backend; used for services that Volto serves (@blocks-schema).description (str) – Free text, shown by
plone-client profile list.
- classmethod from_mapping(name, data)[source]#
Build a profile from a table of the configuration file.
- get_secret()[source]#
The profile’s password or token.
- Returns:
The secret, or an empty string for anonymous profiles.
- Raises:
PloneConfigurationError – If the secret cannot be found.
- Return type:
- client_settings()[source]#
Keyword arguments for
PloneClient.
- class plone_client.profiles.Config(path, data=None)[source]#
Bases:
objectThe profiles of a configuration file.
- group(name)[source]#
The profiles of a group.
- Parameters:
name (str) – Group name.
- Returns:
The profiles, in the group’s order.
- Raises:
PloneConfigurationError – If there is no such group.
- Return type:
- remove_group(name)[source]#
Remove a group; its profiles are kept.
- Parameters:
name (str) – Group name.
- Returns:
The members it had.
- Raises:
PloneConfigurationError – If there is no such group.
- Return type:
- set_defaults(values)[source]#
Change the settings shared by every profile.
Profiles that took a setting from the old defaults take it from the new ones; settings of their own are kept.
- Parameters:
values (Mapping[str, Any]) – Settings to set;
Noneremoves a setting.- Raises:
PloneConfigurationError – If a key is not one of
DEFAULT_KEYSor a value is invalid.- Return type:
None
- get(name)[source]#
A profile by name.
- Parameters:
name (str) – Profile name.
- Returns:
The profile.
- Raises:
PloneConfigurationError – If there is no such profile.
- Return type:
- current_name(environ=None, cwd=None)[source]#
Name of the profile to use when none is given explicitly.
- add(profile, *, replace=False)[source]#
Add a profile.
- Parameters:
- Raises:
PloneConfigurationError – If the name is taken.
- Return type:
None
- save()[source]#
Write the configuration file, readable only by its owner.
- Raises:
PloneConfigurationError – If
tomlkitis not installed.- Return type:
None
- plone_client.profiles.load_config(path=None)[source]#
Read a configuration file.
- Parameters:
path (Path | None) – Path of the file; the user’s by default. A missing file gives an empty configuration.
- Returns:
The configuration.
- Raises:
PloneConfigurationError – If the file is not valid.
- Return type:
- plone_client.profiles.settings_from_env(environ, *, prefix='PLONE_CLIENT_')[source]#
Client settings from environment variables.
Reads
URL,USERNAME(orUSER),PASSWORD,TOKEN,AUTH_METHOD,API_PREFIX,TIMEOUT,VERIFY(true,falseor a CA bundle path),RETRIES,BACKOFF,READ_ONLYandFRONTEND_URL, each after the prefix. Withprefix="REMOTE_", this reads theREMOTE_URL,REMOTE_USERandREMOTE_PASSWORDvariables used by older scripts.- Parameters:
- Returns:
Keyword arguments for
PloneClient.- Raises:
PloneConfigurationError – If the URL is not set.
- Return type:
Credentials#
Where the password or token of a profile is kept.
keyring(default)The operating system’s secret store, through the
keyringpackage: macOS Keychain, Windows Credential Locker, or the Secret Service on Linux. Entries are stored under the serviceplone-client:<profile>.envAn environment variable, named by the profile’s
secret_env(PLONE_CLIENT_PASSWORDby default). Handy in CI.commandThe output of a command, named by the profile’s
secret_command, such asop read op://Private/site/passwordorpass show plone/site. The command runs without a shell.noneNo secret: the profile is anonymous.
- plone_client.credentials.get_secret(profile)[source]#
Look up the secret of a profile.
- Parameters:
profile (Profile) – The profile.
- Returns:
The password or token.
- Raises:
PloneConfigurationError – If the secret is missing.
- Return type:
- plone_client.credentials.set_secret(profile, value)[source]#
Store the secret of a profile in the keyring.
- Parameters:
- Raises:
PloneConfigurationError – If the profile keeps its secret elsewhere.
- Return type:
None
Utilities#
Small helpers shared by the library and the command line.
- plone_client.utils.UID_RE = re.compile('^[0-9a-f]{32}$')#
A Plone UID: 32 lowercase hexadecimal characters.