feat(sandbox): own document authoring skills (#13038)

* feat(skills): reserve document provider slots

Separate document provider slots from protected core skill names.
Keep existing same-name shadows declarations compatible.

Normalize legacy document OFF markers in JSON and database stores.
Persist normalization under the existing lock or transaction so an
explicit re-enable cannot be overwritten by a stale read.

Cover persistence, rollback, ownership and concurrent replacement.
Refresh the contracts inventory from the passing Ginkgo report.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* feat(skills): gate Sandbox document procedures

Resolve document providers against the selected isolated command backend.
Keep Office guidance available without command execution and preserve
explicit provider pins when their backend is unavailable.

Reuse authorized chat access during tool construction to avoid reentering
the session lock. Recheck access and capabilities when loading guidance.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* fix(skills): resolve saved-agent child command grants

Describe saved-agent child commands from their current agent scope rather than the inherited tool map reserved for ad-hoc children. Cover index, picker and loader behavior for permitted, absent and revoked Sandbox grants without changing saved skill selections.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* feat(sandbox): install and exercise guest-owned PDF helpers

Preserve PDF helper sources and MIT attribution in the guest image. Exercise valid, empty-text and defective PDFs during the image build and through the packaged isolated command route. Register the packaging regression in the exact suite inventory.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-5

* feat(sandbox): own document skills and consolidated references

Move the four document authoring trees out of core and package their text-only procedures with Sandbox. Preserve source coverage with offline fingerprints, guest PDF helper paths, and package-local license notices.

Exercise actual skill receipt materialization across install, upgrade and uninstall; keep stale built-in cleanup and report-derived suite inventories current.

Assisted-by: nib:gpt-6.1-sol
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat(ui): explain document skill providers and unavailable selections

Show provider-owned document procedures without removed built-in fallbacks, retain remaining built-in replacement notices, and translate install guidance in every locale. Cover picker labels, saved names and real API OFF normalization, and document grants, isolated execution and unchanged extraction.

Acknowledge the intentionally injected GORM callback error in preference regression tests for lint.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* docs(agents): fix document guidance formatting

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

* fix(ui): correct rebased skill label import

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: nib:gpt-6.1-sol

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
mudler-agentandEttore Di Giacinto authored and GitHub committed 2026-11-02 21:03:50 +01:00
1 parent 5b2414f6bc
commit 74e293ef38
497 files changed
+98765 -80238

No files matched your search

+4
View File
@@ -101,6 +101,10 @@ RUN set -eux; \
python3 /tmp/guest-python/check-python.py; \
sh /tmp/guest-python/check-tools.sh; \
rm -rf /tmp/guest-python
# Helpers belong to the guest, not the documentation-only skill package.
COPY addons/sandbox/images/guest/document-skills/ /usr/local/lib/localai/document-skills/
COPY addons/sandbox/images/guest/check-document-skills.py /usr/local/lib/localai/document-skills/check-document-skills.py
RUN python3 /usr/local/lib/localai/document-skills/check-document-skills.py
# The guest has no network device. Every tool must use the proxy on the
# loopback interface; the sandbox serves it with a host port handler.
ENV HTTP_PROXY=http://127.0.0.1:3128 HTTPS_PROXY=http://127.0.0.1:3128 NO_PROXY=localhost,127.0.0.1 \
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Exercise the installed PDF helper CLIs using only guest dependencies."""
import json
from pathlib import Path
import subprocess
import sys
import tempfile
from reportlab.pdfgen import canvas
HELPERS = Path("/usr/local/lib/localai/document-skills/pdf")
SUCCESS = "document-skills fixtures: ok"
def require(condition, message):
if not condition:
raise RuntimeError(message)
def run_helper(name, pdf, exit_code):
result = subprocess.run(
[sys.executable, str(HELPERS / name), str(pdf), "--json"],
capture_output=True, text=True, timeout=30, check=False,
)
require(result.returncode == exit_code,
f"{name} {pdf.name}: exit {result.returncode}, expected {exit_code}: {result.stderr}")
data = json.loads(result.stdout)
require(data["ok"] is (exit_code == 0), f"{name}: incorrect ok field")
require(data["page_count"] == 1 and len(data["pages"]) == 1,
f"{name}: incorrect page count")
require(data["error_count"] == sum(p["severity"] == "error" for p in data["problems"]),
f"{name}: incorrect error count")
require(data["warning_count"] == sum(p["severity"] == "warning" for p in data["problems"]),
f"{name}: incorrect warning count")
return data
def main():
for name in ("content_qa.py", "verify_pdf.py", "notice.txt"):
require((HELPERS / name).is_file(), f"missing guest helper asset: {name}")
notice = (HELPERS / "notice.txt").read_text()
for attribution in ("MIT License", "Copyright (c) 2026 Paper Instruments, Inc.",
"Permission is hereby granted", "THE SOFTWARE IS PROVIDED"):
require(attribution in notice, f"missing helper license attribution: {attribution}")
with tempfile.TemporaryDirectory(prefix="document-skills-") as directory:
root = Path(directory)
for name, x in (("valid", 72), ("empty", None), ("defective", -144)):
pdf = root / f"{name}.pdf"
document = canvas.Canvas(str(pdf), pagesize=(612, 792), invariant=1)
if x is not None:
document.drawString(x, 720, "Sandbox PDF fixture")
document.showPage()
document.save()
for name in ("content_qa.py", "verify_pdf.py"):
valid = run_helper(name, root / "valid.pdf", 0)
require(valid["error_count"] == 0 and valid["warning_count"] == 0,
f"{name}: valid document reported problems")
empty = run_helper(name, root / "empty.pdf", 0)
expected = "empty_page_text" if name == "content_qa.py" else "pdf-no-extractable-text"
require(empty["error_count"] == 0 and empty["warning_count"] == 1,
f"{name}: empty-text document must warn, not fail")
require(empty["problems"][0]["kind"] == expected, f"{name}: missing empty-text warning")
require(empty["problems"][0]["page"] == 1, f"{name}: incorrect warning page")
if name == "content_qa.py":
require(valid["pages"][0]["text"].strip() == "Sandbox PDF fixture",
"content_qa.py: fixture text not extracted")
require(empty["pages"][0]["text"] == "", "empty fixture contains text")
else:
require(valid["pages"][0]["text_line_count"] > 0,
"verify_pdf.py: fixture text geometry not extracted")
# The PDF is openable and contains text, but that text is outside its
# page box. Content QA should accept it; geometry verification must not.
content = run_helper("content_qa.py", root / "defective.pdf", 0)
require(content["error_count"] == 0 and content["warning_count"] == 0,
"content_qa.py: changed content-only behavior")
defective = run_helper("verify_pdf.py", root / "defective.pdf", 1)
require(defective["error_count"] > 0, "verify_pdf.py: missed defective geometry")
require(any(p["kind"] == "layout-boundary-violation" and p["severity"] == "error"
and p["page"] == 1 for p in defective["problems"]),
"verify_pdf.py: missing boundary violation")
print(SUCCESS)
if __name__ == "__main__":
main()
@@ -0,0 +1,30 @@
PDF helpers from Feather
========================
Source: https://github.com/paper-instruments/feather-train
Commit: b7463092af862ebbdf4c5920c4bc9ab38ab17ad9
Folder: feather-harness/src/feather_harness/skills/pdf/scripts
The helper sources are copied without changes.
MIT License
Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+19 -1
View File
@@ -118,7 +118,25 @@
}
],
"title": "Sandbox"
}
},
"skillDefinitions": [
{
"name": "docx",
"shadows": "docx"
},
{
"name": "pptx",
"shadows": "pptx"
},
{
"name": "pdf",
"shadows": "pdf"
},
{
"name": "xlsx",
"shadows": "xlsx"
}
]
},
"dataFormat": {
"max": 1,
+1
View File
@@ -26,6 +26,7 @@ executable: sandbox
manifest: addons/sandbox/manifest.json
# The directory whose content is laid at the package root (ui/sandbox.js ...).
ui: addons/sandbox/ui/dist
skills: addons/sandbox/skills
platforms:
- platform: linux/amd64
enabled: true
@@ -22,7 +22,7 @@ Use your existing knowledge of `python-docx` for normal paragraphs, runs, styles
`paper-docx` is installed under the `docx` import.
The [Paper API companion](references/paper-api.md) maps Paper-specific capabilities to their public symbols. The [generated API reference](references/api/index.md) covers the complete public API, including the surface inherited from `python-docx`, with one page per module. It is large; search by symbol and read only the relevant section.
The [Paper API companion](references/paper-api.md#source-c4b328c0772ccf3b) maps Paper-specific capabilities to their public symbols. The [generated API reference](references/api-002.md#source-56f84baa75bfbe31) covers the complete public API, including the surface inherited from `python-docx`, with one page per module. It is large; search by symbol and read only the relevant section.
## Package behavior
+107
View File
@@ -0,0 +1,107 @@
Sandbox document skills
=============================
The skills "docx", "xlsx", "pptx" and "pdf" are adapted from the skills of the
Feather project.
Source: https://github.com/paper-instruments/feather-train
(folder feather-harness/src/feather_harness/skills)
Commit: b7463092af862ebbdf4c5920c4bc9ab38ab17ad9
Licence: MIT, Copyright (c) 2026 Paper Instruments, Inc.
What was changed: the text about LibreOffice was replaced by a note that
LibreOffice is not installed, the text about OCR was shortened in the same way,
and the generated page of the LibreOffice-backed module "openpyxl.oracle" got
the same note. Each of the four got one line that says to write
helper scripts in the chat's work folder. The remaining built-in skills are not part of this package.
The MIT licence of the Feather project
--------------------------------------
MIT License
Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The generated API pages
-----------------------
The pages under docx/references/api, xlsx/references/api and
pptx/references/api are generated from the docstrings of the Paper libraries
and of the libraries that they extend. The licences of those projects apply to
that text. All three are MIT licences.
paper-docx extends python-docx, and paper-pptx extends python-pptx. Steve Canny
wrote both upstream libraries, so the two licence files of the Paper packages
have the same text. Each applies to its own package.
paper-docx https://pypi.org/project/paper-docx/ (python-docx)
paper-pptx https://pypi.org/project/paper-pptx/ (python-pptx)
The MIT License (MIT)
Copyright (c) 2013 Steve Canny, https://github.com/scanny
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
paper-xlsx extends openpyxl.
paper-xlsx https://pypi.org/project/paper-xlsx/ (openpyxl)
This software is under the MIT Licence
======================================
Copyright (c) 2010 openpyxl
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,3 +1,5 @@
<a id="source-c4b328c0772ccf3b"></a>
<!-- source: docx/references/paper-api.md -->
# Paper DOCX API Companion
This companion covers capabilities beyond familiar `python-docx`. The installed distribution is `paper-docx`, but the import remains `docx`; ordinary `Document`, paragraph, run, style, table, section, picture, header, and footer APIs remain available.
@@ -29,11 +29,11 @@ content; overlays and annotations do not.
## References and helpers
- [Deterministic PDF quality](references/deterministic-pdf-quality.md) describes the mechanical
- [Deterministic PDF quality](references/deterministic-pdf-quality.md#source-1924639cbd4f358c) describes the mechanical
verifier and the limits of extracted-geometry heuristics.
- [Content QA](references/content-qa.md) describes final-PDF text extraction.
- [Content QA](references/content-qa.md#source-7cbccb7ea6593740) describes final-PDF text extraction.
The `scripts/` directory contains mechanical checks for openability, page boxes, text layers,
The guest directory `/usr/local/lib/localai/document-skills/pdf/` contains mechanical checks for openability, page boxes, text layers,
extracted content, and selected geometry. The verifier's `--strict-layout` mode is designed for known
generated layouts with predictable geometry, not arbitrary PDFs, scans, assembled pages, forms,
redactions, logos, watermarks, or background artwork.
+107
View File
@@ -0,0 +1,107 @@
Sandbox document skills
=============================
The skills "docx", "xlsx", "pptx" and "pdf" are adapted from the skills of the
Feather project.
Source: https://github.com/paper-instruments/feather-train
(folder feather-harness/src/feather_harness/skills)
Commit: b7463092af862ebbdf4c5920c4bc9ab38ab17ad9
Licence: MIT, Copyright (c) 2026 Paper Instruments, Inc.
What was changed: the text about LibreOffice was replaced by a note that
LibreOffice is not installed, the text about OCR was shortened in the same way,
and the generated page of the LibreOffice-backed module "openpyxl.oracle" got
the same note. Each of the four got one line that says to write
helper scripts in the chat's work folder. The remaining built-in skills are not part of this package.
The MIT licence of the Feather project
--------------------------------------
MIT License
Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The generated API pages
-----------------------
The pages under docx/references/api, xlsx/references/api and
pptx/references/api are generated from the docstrings of the Paper libraries
and of the libraries that they extend. The licences of those projects apply to
that text. All three are MIT licences.
paper-docx extends python-docx, and paper-pptx extends python-pptx. Steve Canny
wrote both upstream libraries, so the two licence files of the Paper packages
have the same text. Each applies to its own package.
paper-docx https://pypi.org/project/paper-docx/ (python-docx)
paper-pptx https://pypi.org/project/paper-pptx/ (python-pptx)
The MIT License (MIT)
Copyright (c) 2013 Steve Canny, https://github.com/scanny
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
paper-xlsx extends openpyxl.
paper-xlsx https://pypi.org/project/paper-xlsx/ (openpyxl)
This software is under the MIT Licence
======================================
Copyright (c) 2010 openpyxl
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,3 +1,5 @@
<a id="source-7cbccb7ea6593740"></a>
<!-- source: pdf/references/content-qa.md -->
# Content QA
Content QA extracts text from a PDF and reports:
@@ -7,7 +9,7 @@ Content QA extracts text from a PDF and reports:
Command:
```bash
python scripts/content_qa.py artifact.pdf --json
python3 /usr/local/lib/localai/document-skills/pdf/content_qa.py artifact.pdf --json
```
The script does not determine semantic completeness, correctness, or whether omissions are
@@ -1,3 +1,5 @@
<a id="source-1924639cbd4f358c"></a>
<!-- source: pdf/references/deterministic-pdf-quality.md -->
# Deterministic PDF Quality
The final PDF verifier is a mechanical guard for the rendered PDF package. It intentionally avoids
@@ -14,7 +16,7 @@ Default `verify_pdf.py` checks:
Command:
```bash
python scripts/verify_pdf.py artifact.pdf --json
python3 /usr/local/lib/localai/document-skills/pdf/verify_pdf.py artifact.pdf --json
```
The default verifier checks mechanical PDF properties; it does not judge semantic completeness or
@@ -22,7 +22,7 @@ Use your existing knowledge of `python-pptx` for normal slides, shapes, text, ta
`paper-pptx` is installed under the `pptx` import.
The [Paper API companion](references/paper-api.md) maps Paper-specific capabilities to their public symbols. The [generated API reference](references/api/index.md) covers the complete public API, including the surface inherited from `python-pptx`, with one page per module. It is large; search by symbol and read only the relevant section.
The [Paper API companion](references/paper-api.md#source-bd380984355417c8) maps Paper-specific capabilities to their public symbols. The [generated API reference](references/api-002.md#source-21810f9156652e60) covers the complete public API, including the surface inherited from `python-pptx`, with one page per module. It is large; search by symbol and read only the relevant section.
## Package behavior
+107
View File
@@ -0,0 +1,107 @@
Sandbox document skills
=============================
The skills "docx", "xlsx", "pptx" and "pdf" are adapted from the skills of the
Feather project.
Source: https://github.com/paper-instruments/feather-train
(folder feather-harness/src/feather_harness/skills)
Commit: b7463092af862ebbdf4c5920c4bc9ab38ab17ad9
Licence: MIT, Copyright (c) 2026 Paper Instruments, Inc.
What was changed: the text about LibreOffice was replaced by a note that
LibreOffice is not installed, the text about OCR was shortened in the same way,
and the generated page of the LibreOffice-backed module "openpyxl.oracle" got
the same note. Each of the four got one line that says to write
helper scripts in the chat's work folder. The remaining built-in skills are not part of this package.
The MIT licence of the Feather project
--------------------------------------
MIT License
Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The generated API pages
-----------------------
The pages under docx/references/api, xlsx/references/api and
pptx/references/api are generated from the docstrings of the Paper libraries
and of the libraries that they extend. The licences of those projects apply to
that text. All three are MIT licences.
paper-docx extends python-docx, and paper-pptx extends python-pptx. Steve Canny
wrote both upstream libraries, so the two licence files of the Paper packages
have the same text. Each applies to its own package.
paper-docx https://pypi.org/project/paper-docx/ (python-docx)
paper-pptx https://pypi.org/project/paper-pptx/ (python-pptx)
The MIT License (MIT)
Copyright (c) 2013 Steve Canny, https://github.com/scanny
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
paper-xlsx extends openpyxl.
paper-xlsx https://pypi.org/project/paper-xlsx/ (openpyxl)
This software is under the MIT Licence
======================================
Copyright (c) 2010 openpyxl
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
@@ -0,0 +1,689 @@
<a id="source-7265f22343afe4ee"></a>
<!-- source: pptx/references/api/text.bullet.md -->
<!-- Generated from pptx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `pptx.text.bullet`
BulletFormat proxy, providing bullet and numbering control for a paragraph (paper-pptx).
## `BulletFormat`
```python
BulletFormat(p: CT_TextParagraph, part: CT_TextParagraph = None)
```
Bases: `object`
Bullet and numbering state of a single paragraph.
Read properties report this paragraph's **local** `a:pPr` state only: `None` means "nothing
set here", in which case rendering inherits from the placeholder/list-style chain (use the
effective-style inspection API to see what actually renders).
Wrap a paragraph's bullet properties.
Passing `part` enables the attachment check and the rollback boundary that the setters use.
### `char`
```python
char: str | None
```
The bullet character (`a:buChar/@char`), `None` unless a character bullet is set.
### `font_name`
```python
font_name: str | None
```
Bullet-specific typeface (`a:buFont/@typeface`), or `None` when not set locally.
### `number_scheme`
```python
number_scheme: str | None
```
Numbering scheme token (`a:buAutoNum/@type`), e.g. "arabicPeriod", or `None`.
### `set_character`
```python
set_character(char: str = '•', *, font_name: str | None = None, size_percent: float | None = None, left_margin: Length | None = DEFAULT_BULLET_LEFT_MARGIN, hanging_indent: Length | None = DEFAULT_BULLET_HANGING_INDENT) -> None
```
Give this paragraph a real character bullet (`a:buChar`).
`left_margin`/`hanging_indent` write `marL`/`indent` so the bullet hangs correctly;
pass `None` for either to leave the existing paragraph attribute untouched.
`font_name` sets a bullet-specific typeface (`a:buFont`); `size_percent` scales the
bullet relative to the text size (fraction, 0.25–4.0).
### `set_none`
```python
set_none() -> None
```
Set an explicit "no bullet" (`a:buNone`), overriding any inherited bullet.
Margins, bullet font, and bullet size attributes are left untouched.
### `set_numbered`
```python
set_numbered(scheme: str = 'arabicPeriod', *, start_at: int = 1, font_name: str | None = None, size_percent: float | None = None, left_margin: Length | None = DEFAULT_BULLET_LEFT_MARGIN, hanging_indent: Length | None = DEFAULT_BULLET_HANGING_INDENT) -> None
```
Give this paragraph PowerPoint automatic numbering (`a:buAutoNum`).
`scheme` is an ECMA-376 auto-number token like "arabicPeriod" or "romanUcParenR";
an unknown token raises `ValueError`. Numbering restarts at `start_at`.
### `size_percent`
```python
size_percent: float | None
```
Bullet size as a fraction of the text size (`a:buSzPct`), e.g. 0.75, or `None`.
### `start_at`
```python
start_at: int | None
```
First number of an auto-numbered sequence, `None` unless numbering is set.
### `type`
```python
type: PP_BULLET_TYPE | None
```
Kind of bullet explicitly set on this paragraph, `None` when nothing local is set.
## `DEFAULT_BULLET_HANGING_INDENT`
```python
DEFAULT_BULLET_HANGING_INDENT = Emu(171450)
```
## `DEFAULT_BULLET_LEFT_MARGIN`
```python
DEFAULT_BULLET_LEFT_MARGIN = Emu(342900)
```
<a id="source-c6ff4d26749bee76"></a>
<!-- source: pptx/references/api/text.fonts.md -->
<!-- Generated from pptx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `pptx.text.fonts`
Objects related to system font file lookup.
## `FontFiles`
Bases: `object`
A class-based singleton serving as a lazy cache for system font details.
### `find`
```python
find(family_name: str, is_bold: bool, is_italic: bool) -> str
```
Return the absolute path to an installed OpenType font.
File is matched by `family_name` and the styles `is_bold` and `is_italic`.
<a id="source-720ef7cbb39d9baa"></a>
<!-- source: pptx/references/api/text.layout.md -->
<!-- Generated from pptx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `pptx.text.layout`
Objects related to layout of rendered text, such as TextFitter.
## `TextFitter`
Bases: `tuple`
Value object that knows how to fit text into given rectangular extents.
### `best_fit_font_size`
```python
best_fit_font_size(text: str, extents: tuple[Length, Length], max_size: int, font_file: str) -> int
```
Return whole-number best fit point size less than or equal to `max_size`.
The return value is the largest whole-number point size less than or equal to
`max_size` that allows `text` to fit completely within `extents` when rendered
using font defined in `font_file`.
<a id="source-641574f4f1a6c03b"></a>
<!-- source: pptx/references/api/text.text.md -->
<!-- Generated from pptx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `pptx.text.text`
Text-related objects such as TextFrame and Paragraph.
## `Font`
```python
Font(rPr: CT_TextCharacterProperties)
```
Bases: `object`
Character properties object, providing font size, font name, bold, italic, etc.
Corresponds to `a:rPr` child element of a run. Also appears as `a:defRPr` and
`a:endParaRPr` in paragraph and `a:defRPr` in list style elements.
### `bold`
```python
bold: bool | None
```
Get or set boolean bold value of `Font`, e.g. `paragraph.font.bold = True`.
If set to `None`, the bold setting is cleared and is inherited from an enclosing shape's
setting, or a setting in a style or master. Returns None if no bold attribute is present,
meaning the effective bold value is inherited from a master or the theme.
### `color`
```python
color() -> ColorFormat
```
The `ColorFormat` instance that provides access to the color settings for this font.
### `fill`
```python
fill() -> FillFormat
```
`FillFormat` instance for this font.
Provides access to fill properties such as fill color.
### `italic`
```python
italic: bool | None
```
Get or set boolean italic value of `Font` instance.
Has the same behaviors as bold with respect to None values.
### `language_id`
```python
language_id: MSO_LANGUAGE_ID | None
```
Get or set the language id of this `Font` instance.
The language id is a member of the `MsoLanguageId` enumeration. Assigning `None`
removes any language setting, the same behavior as assigning `MSO_LANGUAGE_ID.NONE`.
### `name`
```python
name: str | None
```
Get or set the typeface name for this `Font` instance.
Causes the text it controls to appear in the named font, if a matching font is found.
Returns `None` if the typeface is currently inherited from the theme. Setting it to `None`
removes any override of the theme typeface.
### `size`
```python
size: Length | None
```
Indicates the font height in English Metric Units (EMU).
Read/write. `None` indicates the font size should be inherited from its style hierarchy,
such as a placeholder or document defaults (usually 18pt). `Length` is a subclass of `int`
having properties for convenient conversion into points or other length units. Likewise,
the `pptx.util.Pt` class allows convenient specification of point values::
>>> font.size = Pt(24)
>>> font.size
304800
>>> font.size.pt
24.0
### `underline`
```python
underline: bool | MSO_TEXT_UNDERLINE_TYPE | None
```
Indicaties the underline setting for this font.
Value is `True`, `False`, `None`, or a member of the `MsoTextUnderlineType`
enumeration. `None` is the default and indicates the underline setting should be inherited
from the style hierarchy, such as from a placeholder. `True` indicates single underline.
`False` indicates no underline. Other settings such as double and wavy underlining are
indicated with members of the `MsoTextUnderlineType` enumeration.
## `TextFrame`
```python
TextFrame(txBody: CT_TextBody, parent: ProvidesPart)
```
Bases: `Subshape`
The part of a shape that contains its text.
Not all shapes have a text frame. Corresponds to the `p:txBody` element that can
appear as a child element of `p:sp`. Not intended to be constructed directly.
### `add_paragraph`
```python
add_paragraph()
```
Return new `_Paragraph` instance appended to the sequence of
paragraphs contained in this text frame.
### `auto_size`
```python
auto_size: MSO_AUTO_SIZE | None
```
Resizing strategy used to fit text within this shape.
Determins the type of automatic resizing used to fit the text of this shape within its
bounding box when the text would otherwise extend beyond the shape boundaries. May be
`None`, `MSO_AUTO_SIZE.NONE`, `MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT`, or
`MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE`.
### `clear`
```python
clear()
```
Remove all paragraphs except one empty one.
### `fit_text`
```python
fit_text(font_family: str = 'Calibri', max_size: int = 18, bold: bool = False, italic: bool = False, font_file: str | None = None)
```
Fit text-frame text entirely within bounds of its shape.
Make the text in this text frame fit entirely within the bounds of its shape by setting
word wrap on and applying the "best-fit" font size to all the text it contains.
`TextFrame.auto_size` is set to `MSO_AUTO_SIZE.NONE`. The font size will not
be set larger than `max_size` points. If the path to a matching TrueType font is provided
as `font_file`, that font file will be used for the font metrics. If `font_file` is `None`,
best efforts are made to locate a font file with matchhing `font_family`, `bold`, and
`italic` installed on the current system (usually succeeds if the font is installed).
### `font_scale`
```python
font_scale: float | None
```
Font scale percent of this frame's `a:normAutofit`, e.g. 62.5 (paper-pptx addition).
100.0 when the frame has `a:normAutofit` with no explicit scale; `None` when the
frame's autofit setting is anything other than `a:normAutofit`. Read-only: PowerPoint
owns this value (it records the shrink-to-fit reduction last applied); use
`normalize_autofit` to freeze it into explicit sizes.
### `line_space_reduction`
```python
line_space_reduction: float | None
```
Line-spacing reduction percent of `a:normAutofit`, e.g. 20.0 (paper-pptx addition).
0.0 when the frame has `a:normAutofit` with no explicit reduction; `None` when the
frame's autofit setting is anything other than `a:normAutofit`. Read-only.
### `margin_bottom`
```python
margin_bottom: Length
```
`Length` value representing the inset of text from the bottom text frame border.
`pptx.util.Inches` provides a convenient way of setting the value, e.g.
`text_frame.margin_bottom = Inches(0.05)`.
### `margin_left`
```python
margin_left: Length
```
Inset of text from left text frame border as `Length` value.
### `margin_right`
```python
margin_right: Length
```
Inset of text from right text frame border as `Length` value.
### `margin_top`
```python
margin_top: Length
```
Inset of text from top text frame border as `Length` value.
### `normalize_autofit`
```python
normalize_autofit(*, min_font_size: Length | None = None, resolve: bool = False) -> None
```
Freeze this frame's rendered text metrics and set explicit no-autofit.
paper-pptx addition. What the reader currently sees is made explicit, then the frame's
autofit is set to `a:noAutofit`:
- `a:normAutofit` with a font scale: every explicit font size in the frame (run,
paragraph-default, and end-paragraph properties) is multiplied by the scale. If any
run's size is not locally resolvable (neither its own `sz` nor its paragraph's
default), `UnsupportedStructureError` is raised — unless `resolve=True`, in which
case the size is resolved through the effective-style walk (placeholder → layout →
master → theme) and frozen from the resolved value; what the walk cannot resolve
still refuses. This API never silently guesses.
- `a:normAutofit` with a line-spacing reduction: every paragraph's explicit line
spacing is reduced accordingly; any paragraph without explicit line spacing raises
`UnsupportedStructureError` (`resolve` covers font sizes only in this version).
- `a:spAutoFit`, `a:noAutofit`, or no autofit element: no text metrics change.
`min_font_size` (a `Length`, e.g. `Pt(11)`) is applied after freezing: explicit sizes
below the floor are raised to it. Validation completes fully before the first write
(a refusal leaves the frame byte-identical).
### `paragraphs`
```python
paragraphs: tuple[_Paragraph, ...]
```
Sequence of paragraphs in this text frame.
A text frame always contains at least one paragraph.
### `text`
```python
text: str
```
All text in this text-frame as a single string.
Read/write. The return value contains all text in this text-frame. A line-feed character
(`"\n"`) separates the text for each paragraph. A vertical-tab character (`"\v"`) appears
for each line break (aka. soft carriage-return) encountered.
The vertical-tab character is how PowerPoint represents a soft carriage return in clipboard
text, which is why that encoding was chosen.
Assignment replaces all text in the text frame. A new paragraph is added for each line-feed
character (`"\n"`) encountered. A line-break (soft carriage-return) is inserted for each
vertical-tab character (`"\v"`) encountered.
Any control character other than newline, tab, or vertical-tab are escaped as plain-text
like "_x001B_" (for ESC (ASCII 32) in this example).
### `vertical_anchor`
```python
vertical_anchor: MSO_VERTICAL_ANCHOR | None
```
Represents the vertical alignment of text in this text frame.
`None` indicates the effective value should be inherited from this object's style hierarchy.
### `word_wrap`
```python
word_wrap: bool | None
```
`True` when lines of text in this shape are wrapped to fit within the shape's width.
Read-write. Valid values are True, False, or None. True and False turn word wrap on and
off, respectively. Assigning None to word wrap causes any word wrap setting to be removed
from the text frame, causing it to inherit this setting from its style hierarchy.
<a id="source-8c8b08f561ed110c"></a>
<!-- source: pptx/references/api/types.md -->
<!-- Generated from pptx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `pptx.types`
Abstract types used by `python-pptx`.
## `ProvidesExtents`
Bases: `Protocol`
An object that has width and height.
### `height`
```python
height: Length
```
Distance between top and bottom extents of shape in EMUs.
### `width`
```python
width: Length
```
Distance between left and right extents of shape in EMUs.
## `ProvidesPart`
Bases: `Protocol`
An object that provides access to its XmlPart.
This type is for objects that need access to their part, possibly because they need access to
the package or related parts.
### `part`
```python
part: XmlPart
```
<a id="source-c6f3b84c67d06ef5"></a>
<!-- source: pptx/references/api/util.md -->
<!-- Generated from pptx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `pptx.util`
Utility functions and classes.
## `Centipoints`
Bases: `Length`
Convenience constructor for length in hundredths of a point.
## `Cm`
Bases: `Length`
Convenience constructor for length in centimeters.
## `Emu`
Bases: `Length`
Convenience constructor for length in english metric units.
## `Inches`
Bases: `Length`
Convenience constructor for length in inches.
## `Length`
Bases: `int`
Base class for length classes Inches, Emu, Cm, Mm, and Pt.
Provides properties for converting length values to convenient units.
### `centipoints`
```python
centipoints: int
```
Integer length in hundredths of a point (1/7200 inch).
Used internally because PowerPoint stores font size in centipoints.
### `cm`
```python
cm: float
```
Floating point length in centimeters.
### `emu`
```python
emu: int
```
Integer length in English Metric Units.
### `inches`
```python
inches: float
```
Floating point length in inches.
### `mm`
```python
mm: float
```
Floating point length in millimeters.
### `pt`
```python
pt: float
```
Floating point length in points.
## `Mm`
Bases: `Length`
Convenience constructor for length in millimeters.
## `Pt`
Bases: `Length`
Convenience value class for specifying a length in points.
## `lazyproperty`
```python
lazyproperty(fget: Callable[..., _T]) -> None
```
Bases: `Generic[_T]`
Decorator like @property, but evaluated only on first access.
Like @property, this can only be used to decorate methods having only a `self` parameter, and
is accessed like an attribute on an instance, i.e. trailing parentheses are not used. Unlike
@property, the decorated method is only evaluated on first access; the resulting value is
cached and that same value returned on second and later access without re-evaluation of the
method.
Like @property, this class produces a *data descriptor* object, which is stored in the __dict__
of the *class* under the name of the decorated method ('fget' nominally). The cached value is
stored in the __dict__ of the *instance* under that same name.
Because it is a data descriptor (as opposed to a *non-data descriptor*), its `__get__()` method
is executed on each access of the decorated attribute; the __dict__ item of the same name is
"shadowed" by the descriptor.
While this may represent a performance improvement over a property, its greater benefit may be
its other characteristics. One common use is to construct collaborator objects, removing that
"real work" from the constructor, while still only executing once. It also de-couples client
code from any sequencing considerations; if it's accessed from more than one location, it's
assured it will be ready whenever needed.
Loosely based on: https://stackoverflow.com/a/6849299/1902513.
A lazyproperty is read-only. There is no counterpart to the optional "setter" (or deleter)
behavior of an @property. This is critically important to maintaining its immutability and
idempotence guarantees. Attempting to assign to a lazyproperty raises AttributeError
unconditionally.
The parameter names in the methods below correspond to this usage example::
class Obj(object)
@lazyproperty
def fget(self):
return 'some result'
obj = Obj()
Not suitable for wrapping a function (as opposed to a method) because it is not callable.
*fget* is the decorated method (a "getter" function).
A lazyproperty is read-only, so there is only an *fget* function (a regular
@property can also have an fset and fdel function). This name was chosen for
consistency with Python's `property` class which uses this name for the
corresponding parameter.
@@ -1,3 +1,5 @@
<a id="source-bd380984355417c8"></a>
<!-- source: pptx/references/paper-api.md -->
# Paper PPTX API companion
This companion covers capabilities beyond familiar `python-pptx`. The installed distribution is
@@ -22,7 +22,7 @@ Use your existing knowledge of `openpyxl` for worksheets, cells, formulas, style
`paper-xlsx` is installed under the `openpyxl` import.
The [Paper API companion](references/paper-api.md) maps Paper-specific capabilities to their public symbols. The [generated API reference](references/api/index.md) covers the complete public API, including the surface inherited from `openpyxl`, with one page per module. It is large; search by symbol and read only the relevant section.
The [Paper API companion](references/paper-api.md#source-1d5d2fa5f7f8302a) maps Paper-specific capabilities to their public symbols. The [generated API reference](references/api-003.md#source-52f276c95ae6d391) covers the complete public API, including the surface inherited from `openpyxl`, with one page per module. It is large; search by symbol and read only the relevant section.
## Package behavior
+107
View File
@@ -0,0 +1,107 @@
Sandbox document skills
=============================
The skills "docx", "xlsx", "pptx" and "pdf" are adapted from the skills of the
Feather project.
Source: https://github.com/paper-instruments/feather-train
(folder feather-harness/src/feather_harness/skills)
Commit: b7463092af862ebbdf4c5920c4bc9ab38ab17ad9
Licence: MIT, Copyright (c) 2026 Paper Instruments, Inc.
What was changed: the text about LibreOffice was replaced by a note that
LibreOffice is not installed, the text about OCR was shortened in the same way,
and the generated page of the LibreOffice-backed module "openpyxl.oracle" got
the same note. Each of the four got one line that says to write
helper scripts in the chat's work folder. The remaining built-in skills are not part of this package.
The MIT licence of the Feather project
--------------------------------------
MIT License
Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The generated API pages
-----------------------
The pages under docx/references/api, xlsx/references/api and
pptx/references/api are generated from the docstrings of the Paper libraries
and of the libraries that they extend. The licences of those projects apply to
that text. All three are MIT licences.
paper-docx extends python-docx, and paper-pptx extends python-pptx. Steve Canny
wrote both upstream libraries, so the two licence files of the Paper packages
have the same text. Each applies to its own package.
paper-docx https://pypi.org/project/paper-docx/ (python-docx)
paper-pptx https://pypi.org/project/paper-pptx/ (python-pptx)
The MIT License (MIT)
Copyright (c) 2013 Steve Canny, https://github.com/scanny
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
paper-xlsx extends openpyxl.
paper-xlsx https://pypi.org/project/paper-xlsx/ (openpyxl)
This software is under the MIT Licence
======================================
Copyright (c) 2010 openpyxl
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because one or more lines are too long.
@@ -1,3 +1,5 @@
<a id="source-1d5d2fa5f7f8302a"></a>
<!-- source: xlsx/references/paper-api.md -->
# Paper XLSX API companion
This companion covers capabilities beyond familiar `openpyxl`. The installed distribution is `paper-xlsx`, but the import remains `openpyxl`; ordinary `openpyxl` APIs remain available.
+1 -1
View File
@@ -49,7 +49,7 @@ export const RUNS = {
export const SKILLS = [
{ name: 'citations', description: 'Cite every claim with a numbered source.', layer: 'personal', path: '/skills/citations/SKILL.md', enabled: true, editable: true },
{ name: 'house-style', description: 'The shared writing rules.', layer: 'shared', path: '/shared/house-style/SKILL.md', enabled: true, editable: false },
{ name: 'pdf', description: 'Read and fill PDF forms.', layer: 'builtin', path: '/builtin/pdf/SKILL.md', enabled: true, editable: false },
{ name: 'pdf', description: 'Read and create PDFs with Office tools.', layer: 'addon', addon: 'office', addon_title: 'Office', path: '/addon-skills/office/pdf/SKILL.md', enabled: true, editable: false },
{ name: 'broken', description: '', layer: 'folder', path: '/Notes/.skills/broken/SKILL.md', enabled: true, editable: false, invalid: 'the description is empty' },
]
@@ -25,3 +25,44 @@ test('a skill written on the page shows in the list and can be deleted', async (
await page.getByRole('alertdialog').getByRole('button', { name: 'Delete' }).click()
await expect(page.getByTestId(`skill-row-${name}`)).toHaveCount(0)
})
// No route interception: these checks cover the application's actual skills,
// saved-agent and preference APIs on a server without an authoring provider.
test('document names keep agent selections and normalize OFF without a core provider', async ({ request }) => {
const names = ['docx', 'pptx', 'pdf', 'xlsx']
const list = await request.get('/api/chats/skills')
expect(list.ok()).toBeTruthy()
const { skills } = await list.json()
expect(skills.filter(skill => names.includes(skill.name))).toEqual([])
const saved = await request.post('/api/chats/agents', {
data: { name: `document-selection-${Date.now()}`, model: 'mock-model', skills: names, addons: [] },
})
expect(saved.status()).toBe(201)
const agent = await saved.json()
let chat
try {
expect(agent.skills).toEqual(names)
expect(agent.addons).toEqual([])
const created = await request.post('/api/chats', { data: { model: 'mock-model', agent_id: agent.id } })
expect(created.status()).toBe(201)
chat = await created.json()
const off = await request.put(`/api/chats/${chat.id}/skills`, { data: { off: ['builtin:docx'] } })
expect(off.ok()).toBeTruthy()
expect((await off.json()).off).toEqual(['addon:docx'])
const reload = await request.get(`/api/chats/${chat.id}`)
expect(reload.ok()).toBeTruthy()
expect((await reload.json()).agent_id).toBe(agent.id)
const picker = await request.get(`/api/chats/skills?chat=${chat.id}`)
expect(picker.ok()).toBeTruthy()
expect((await picker.json()).skills.filter(skill => names.includes(skill.name))).toEqual([])
const enabled = await request.put(`/api/chats/${chat.id}/skills`, { data: { off: [] } })
expect(enabled.ok()).toBeTruthy()
expect((await enabled.json()).off).toEqual([])
const reread = await request.get(`/api/chats/agents/${agent.id}`)
expect((await reread.json()).skills).toEqual(names)
} finally {
if (chat) expect((await request.delete(`/api/chats/${chat.id}`)).ok()).toBeTruthy()
expect((await request.delete(`/api/chats/agents/${agent.id}`)).ok()).toBeTruthy()
}
})
@@ -246,34 +246,49 @@ test.describe('Chat skills picker with add-on skills', () => {
return state
}
const office = { id: 'addon:xlsx', name: 'xlsx', description: 'Read and edit spreadsheets with the Office tools', layer: 'addon', addon: 'office', addon_title: 'Office', enabled: true }
const builtinX = { id: 'builtin:xlsx', name: 'xlsx', description: 'Spreadsheets in a sandbox', layer: 'builtin', shadowed: 'addon', shadowed_by: 'office', enabled: true }
const mine = { id: 'personal:review', name: 'review', description: 'Check a change', layer: 'personal', enabled: true }
test('labels each skill with its source and lists the built-in one an add-on replaces, dimmed and with no switch', async ({ page }) => {
await stubAddon(page, () => [office, builtinX, mine])
test('labels document skills with their provider, with no removed built-in', async ({ page }) => {
await stubAddon(page, () => [office, mine])
await open(page)
const sources = page.getByTestId('chat-skills-source')
await expect(sources).toHaveText(['Office', 'Personal'])
await expect(page.getByRole('switch')).toHaveCount(2)
const replaced = page.getByTestId('chat-skills-replaced')
await expect(replaced).toHaveCount(1)
await expect(replaced).toContainText('xlsx')
await expect(replaced).toContainText('Replaced by Office')
await expect(replaced).toHaveCount(0)
await expect(page.getByTestId('chat-skills-fallback')).toHaveCount(0)
await expect(summary(page)).toHaveText('2 of 2 on')
await shot(page, 'skills-picker-addon')
})
test('switching the add-on skill off brings the built-in one back, with one line that says why', async ({ page }) => {
const state = await stubAddon(page, (s) => (s.puts.length === 0
? [office, builtinX, mine]
: [{ ...office, enabled: false }, { ...builtinX, shadowed: undefined, shadowed_by: undefined, fallback: 'Office' }, mine]))
test('switching a document skill off persists OFF without a core fallback', async ({ page }) => {
const state = await stubAddon(page, s => [{ ...office, enabled: !s.puts.at(-1)?.off.includes('addon:xlsx') }, mine])
await open(page)
await sw(page, 'xlsx').click()
await expect.poll(() => state.puts).toEqual([{ off: ['addon:xlsx'] }])
await expect(page.getByTestId('chat-skills-fallback')).toHaveText('Built-in xlsx in use: the Office add-on\'s own skill is off or not running here.')
await expect(sw(page, 'xlsx')).not.toBeChecked()
await expect(page.getByTestId('chat-skills-fallback')).toHaveCount(0)
await expect(page.getByTestId('chat-skills-replaced')).toHaveCount(0)
await expect(page.locator('[data-testid="chat-skills-source"][data-kind="builtin"]')).toHaveText('Built-in')
await shot(page, 'skills-picker-fallback')
await expect(page.locator('[data-testid="chat-skills-source"][data-kind="builtin"]')).toHaveCount(0)
})
for (const addon of ['office', 'sandbox']) {
test(`${addon} supplies familiar document names without a builtin`, async ({ page }) => {
await stubAddon(page, () => ['docx', 'pptx', 'pdf', 'xlsx'].map(name => ({
...office, id: `addon:${name}`, name, addon, addon_title: addon === 'office' ? 'Office' : 'Sandbox',
})))
await open(page)
await expect(page.getByRole('switch')).toHaveCount(4)
await expect(page.getByTestId('chat-skills-source')).toHaveText(Array(4).fill(addon === 'office' ? 'Office' : 'Sandbox'))
await expect(page.getByTestId('chat-skills-fallback')).toHaveCount(0)
await expect(page.getByTestId('chat-skills-replaced')).toHaveCount(0)
})
}
test('an unavailable pinned provider explains why without suggesting a fallback', async ({ page }) => {
await stubAddon(page, () => [{ ...office, unavailable: 'Pinned provider is not available for this chat.' }, mine])
await open(page)
await expect(page.getByTestId('chat-skills-item').first()).toContainText('Pinned provider is not available for this chat.')
await expect(page.getByTestId('chat-skills-fallback')).toHaveCount(0)
})
})
+4 -6
View File
@@ -10,7 +10,7 @@ test.describe('Skills', () => {
await expect(page.getByTestId('skills-page')).toBeVisible()
await expect(page.getByTestId('skill-row-citations')).toContainText('Personal')
await expect(page.getByTestId('skill-used-citations')).toHaveText('Used by research-assistant')
await expect(page.getByTestId('skill-row-pdf')).toContainText('Built-in')
await expect(page.getByTestId('skill-row-pdf')).toContainText('Office')
await expect(page.getByTestId('skill-row-house-style')).toContainText('Shared')
await expect(page.getByTestId('skill-row-broken')).toContainText('Workspace')
await expect(page.getByTestId('skill-used-broken')).toContainText('not a valid skill')
@@ -26,13 +26,11 @@ test.describe('Skills', () => {
await installAgents(page, {
skills: [
{ name: 'xlsx', description: 'Office sheets.', layer: 'addon', addon: 'office', addon_title: 'Office', path: '/addon-skills/office/xlsx/SKILL.md', enabled: true, editable: false },
{ name: 'xlsx', description: 'Sheets sheets.', layer: 'addon', addon: 'sheets', addon_title: 'Sheets', path: '/addon-skills/sheets/xlsx/SKILL.md', enabled: true, editable: false, shadowed: 'addon', shadowed_by: 'office' },
{ name: 'xlsx', description: 'Built-in sheets.', layer: 'builtin', path: '/builtin-skills/xlsx/SKILL.md', enabled: true, editable: false, shadowed: 'addon', shadowed_by: 'office' },
{ name: 'xlsx', description: 'Sandbox Python spreadsheets.', layer: 'addon', addon: 'sandbox', addon_title: 'Sandbox', path: '/addon-skills/sandbox/xlsx/SKILL.md', enabled: true, editable: false, shadowed: 'addon', shadowed_by: 'office' },
],
})
await page.goto('/app/skills')
await expect(page.getByTestId('skills-page')).toContainText('Not active: Office provides it')
await expect(page.getByTestId('skills-page')).toContainText('Replaced by Office')
})
test('only your own skills can be edited or deleted', async ({ page }) => {
@@ -40,7 +38,7 @@ test.describe('Skills', () => {
await page.goto('/app/skills')
await page.getByTestId('skill-row-pdf').click()
await expect(page.getByTestId('skill-title')).toHaveText('pdf')
await expect(page.getByTestId('skill-layer')).toHaveText('Built-in')
await expect(page.getByTestId('skill-layer')).toHaveText('Provided by Office')
await expect(page.getByRole('button', { name: 'Edit' })).toHaveCount(0)
await expect(page.getByRole('button', { name: 'Delete' })).toHaveCount(0)
await page.getByTestId('skill-row-citations').click()
@@ -150,7 +148,7 @@ test.describe('Skill editor', () => {
test('a skill you cannot write opens read-only', async ({ page }) => {
await installAgents(page)
await page.goto('/app/skills/edit/builtin/pdf')
await page.goto('/app/skills/edit/addon/pdf')
await expect(page.getByTestId('skill-readonly')).toBeVisible()
await expect(page.getByRole('button', { name: 'Save changes' })).toHaveCount(0)
})
@@ -691,7 +691,9 @@
"installNamed": "{{name}} installieren",
"shadowWarning": "Dieses Add-on ersetzt das eingebaute {{tool}}. Ein anderes Add-on ({{name}}) tut das bereits. Nur eines kann aktiv sein; welches, wählen Sie unter Einstellungen > Werkzeuge. Solange Sie nichts wählen, stellt das zuerst installierte es bereit.",
"skillShadow": "Dieses Add-on ersetzt den integrierten Skill {{skill}}.",
"skillShadowWarning": "Dieses Add-on ersetzt den integrierten Skill {{skill}}. Ein anderes Add-on ({{name}}) tut das bereits. Nur eines kann aktiv sein; welches, wählen Sie unter Einstellungen > Werkzeuge. Solange Sie nichts wählen, stellt das zuerst installierte es bereit."
"skillShadowWarning": "Dieses Add-on ersetzt den integrierten Skill {{skill}}. Ein anderes Add-on ({{name}}) tut das bereits. Nur eines kann aktiv sein; welches, wählen Sie unter Einstellungen > Werkzeuge. Solange Sie nichts wählen, stellt das zuerst installierte es bereit.",
"skillProvides": "Dieses Add-on stellt die Fähigkeit {{skill}} bereit.",
"skillProvidesWarning": "Dieses Add-on stellt die Fähigkeit {{skill}} bereit. Ein anderes Add-on ({{name}}) stellt sie ebenfalls bereit. Nur eines kann aktiv sein; wähle unter Einstellungen > Werkzeuge. Ohne Auswahl wird das zuerst installierte verwendet."
},
"family": {
"pages": "Du suchst Seiten, die du oder ein Agent erstellt hast? Sie sind unter <go>Seiten</go>."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} Chat-Skill",
"title_other": "{{count}} Chat-Skills",
"provides": "stellt die Fähigkeit {{skill}} bereit",
"replaces": "ersetzt den integrierten Skill",
"serves": "wird in den Chats verwendet",
"notServing": "derzeit nicht verwendet",
@@ -691,7 +691,9 @@
"installNamed": "Install {{name}}",
"shadowWarning": "This add-on replaces the built-in {{tool}}. Another add-on ({{name}}) already does. Only one can be active; you choose which in Settings > Tools. Until you choose, the one installed first serves.",
"skillShadow": "This add-on replaces the built-in skill {{skill}}.",
"skillShadowWarning": "This add-on replaces the built-in skill {{skill}}. Another add-on ({{name}}) already does. Only one can be active; you choose which in Settings > Tools. Until you choose, the one installed first serves."
"skillShadowWarning": "This add-on replaces the built-in skill {{skill}}. Another add-on ({{name}}) already does. Only one can be active; you choose which in Settings > Tools. Until you choose, the one installed first serves.",
"skillProvides": "This add-on provides the {{skill}} skill.",
"skillProvidesWarning": "This add-on provides the {{skill}} skill. Another add-on ({{name}}) also provides it. Only one can be active; choose in Settings > Tools. Without a choice, the one installed first serves."
},
"family": {
"pages": "Looking for pages you or an agent made? They are in <go>Pages</go>."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} chat skill",
"title_other": "{{count}} chat skills",
"provides": "provides the {{skill}} skill",
"replaces": "replaces the built-in skill",
"serves": "serves in the chats",
"notServing": "not serving now",
@@ -691,7 +691,9 @@
"installNamed": "Instalar {{name}}",
"shadowWarning": "Este complemento reemplaza el {{tool}} integrado. Otro complemento ({{name}}) ya lo hace. Solo uno puede estar activo; eliges cuál en Ajustes > Herramientas. Hasta que elijas, lo sirve el que se instaló primero.",
"skillShadow": "Este complemento reemplaza la habilidad integrada {{skill}}.",
"skillShadowWarning": "Este complemento reemplaza la habilidad integrada {{skill}}. Otro complemento ({{name}}) ya lo hace. Solo uno puede estar activo; eliges cuál en Ajustes > Herramientas. Hasta que elijas, lo sirve el que se instaló primero."
"skillShadowWarning": "Este complemento reemplaza la habilidad integrada {{skill}}. Otro complemento ({{name}}) ya lo hace. Solo uno puede estar activo; eliges cuál en Ajustes > Herramientas. Hasta que elijas, lo sirve el que se instaló primero.",
"skillProvides": "Este complemento proporciona la habilidad {{skill}}.",
"skillProvidesWarning": "Este complemento proporciona la habilidad {{skill}}. Otro complemento ({{name}}) también la proporciona. Solo uno puede estar activo; elige en Configuración > Herramientas. Sin una elección, se usa el instalado primero."
},
"family": {
"pages": "¿Buscas páginas que creaste tú o un agente? Están en <go>Páginas</go>."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} habilidad de chat",
"title_other": "{{count}} habilidades de chat",
"provides": "proporciona la habilidad {{skill}}",
"replaces": "reemplaza la habilidad integrada",
"serves": "se usa en los chats",
"notServing": "no se usa ahora",
@@ -695,7 +695,9 @@
"installNamed": "Installer {{name}}",
"shadowWarning": "Ce module remplace l'outil intégré {{tool}}. Un autre module ({{name}}) le fait déjà. Un seul peut être actif ; vous choisissez lequel dans Paramètres > Outils. Tant que vous ne choisissez pas, c'est le premier installé qui le fournit.",
"skillShadow": "Ce module remplace la compétence intégrée {{skill}}.",
"skillShadowWarning": "Ce module remplace la compétence intégrée {{skill}}. Un autre module ({{name}}) le fait déjà. Un seul peut être actif ; vous choisissez lequel dans Paramètres > Outils. Tant que vous ne choisissez pas, c'est le premier installé qui le fournit."
"skillShadowWarning": "Ce module remplace la compétence intégrée {{skill}}. Un autre module ({{name}}) le fait déjà. Un seul peut être actif ; vous choisissez lequel dans Paramètres > Outils. Tant que vous ne choisissez pas, c'est le premier installé qui le fournit.",
"skillProvides": "Cette extension fournit la compétence {{skill}}.",
"skillProvidesWarning": "Cette extension fournit la compétence {{skill}}. Une autre extension ({{name}}) la fournit aussi. Une seule peut être active ; choisissez dans Paramètres > Outils. Sans choix, celle installée en premier est utilisée."
},
"family": {
"pages": "Vous cherchez des pages créées par vous ou par un agent ? Elles sont dans <go>Pages</go>."
@@ -735,6 +737,7 @@
"skills": {
"title_one": "{{count}} compétence de chat",
"title_other": "{{count}} compétences de chat",
"provides": "fournit la compétence {{skill}}",
"replaces": "remplace la compétence intégrée",
"serves": "utilisée dans les discussions",
"notServing": "non utilisée pour l'instant",
@@ -691,7 +691,9 @@
"installNamed": "Pasang {{name}}",
"shadowWarning": "Add-on ini menggantikan {{tool}} bawaan. Add-on lain ({{name}}) sudah melakukannya. Hanya satu yang bisa aktif; Anda memilih yang mana di Pengaturan > Alat. Sebelum Anda memilih, yang dipasang lebih dulu yang melayaninya.",
"skillShadow": "Add-on ini menggantikan skill bawaan {{skill}}.",
"skillShadowWarning": "Add-on ini menggantikan skill bawaan {{skill}}. Add-on lain ({{name}}) sudah melakukannya. Hanya satu yang bisa aktif; Anda memilih yang mana di Pengaturan > Alat. Sebelum Anda memilih, yang dipasang lebih dulu yang melayaninya."
"skillShadowWarning": "Add-on ini menggantikan skill bawaan {{skill}}. Add-on lain ({{name}}) sudah melakukannya. Hanya satu yang bisa aktif; Anda memilih yang mana di Pengaturan > Alat. Sebelum Anda memilih, yang dipasang lebih dulu yang melayaninya.",
"skillProvides": "Add-on ini menyediakan keterampilan {{skill}}.",
"skillProvidesWarning": "Add-on ini menyediakan keterampilan {{skill}}. Add-on lain ({{name}}) juga menyediakannya. Hanya satu yang dapat aktif; pilih di Pengaturan > Alat. Tanpa pilihan, yang dipasang lebih dahulu akan digunakan."
},
"family": {
"pages": "Mencari halaman yang Anda atau agen buat? Ada di <go>Halaman</go>."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} skill obrolan",
"title_other": "{{count}} skill obrolan",
"provides": "menyediakan keterampilan {{skill}}",
"replaces": "menggantikan skill bawaan",
"serves": "dipakai di obrolan",
"notServing": "tidak dipakai sekarang",
@@ -691,7 +691,9 @@
"installNamed": "Installa {{name}}",
"shadowWarning": "Questo componente sostituisce lo strumento integrato {{tool}}. Un altro componente ({{name}}) lo fa già. Solo uno può essere attivo; scegli quale in Impostazioni > Strumenti. Finché non scegli, lo fornisce quello installato per primo.",
"skillShadow": "Questo componente sostituisce la skill integrata {{skill}}.",
"skillShadowWarning": "Questo componente sostituisce la skill integrata {{skill}}. Un altro componente ({{name}}) lo fa già. Solo uno può essere attivo; scegli quale in Impostazioni > Strumenti. Finché non scegli, lo fornisce quello installato per primo."
"skillShadowWarning": "Questo componente sostituisce la skill integrata {{skill}}. Un altro componente ({{name}}) lo fa già. Solo uno può essere attivo; scegli quale in Impostazioni > Strumenti. Finché non scegli, lo fornisce quello installato per primo.",
"skillProvides": "Questo componente aggiuntivo fornisce la competenza {{skill}}.",
"skillProvidesWarning": "Questo componente aggiuntivo fornisce la competenza {{skill}}. Anche un altro componente aggiuntivo ({{name}}) la fornisce. Solo uno può essere attivo; scegli in Impostazioni > Strumenti. Senza una scelta, viene usato quello installato per primo."
},
"family": {
"pages": "Cerchi le pagine create da te o da un agente? Sono in <go>Pagine</go>."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} skill della chat",
"title_other": "{{count}} skill della chat",
"provides": "fornisce la competenza {{skill}}",
"replaces": "sostituisce la skill integrata",
"serves": "usata nelle chat",
"notServing": "non usata ora",
@@ -691,7 +691,9 @@
"installNamed": "{{name}} 설치",
"shadowWarning": "이 애드온은 내장 {{tool}}을 대체합니다. 다른 애드온({{name}})이 이미 대체하고 있습니다. 하나만 활성화할 수 있으며, 설정 > 도구에서 선택합니다. 선택하기 전까지는 먼저 설치된 애드온이 제공합니다.",
"skillShadow": "이 애드온은 기본 제공 스킬 {{skill}}을(를) 대체합니다.",
"skillShadowWarning": "이 애드온은 기본 제공 스킬 {{skill}}을(를) 대체합니다. 다른 애드온({{name}})이 이미 대체하고 있습니다. 하나만 활성화할 수 있으며, 설정 > 도구에서 선택합니다. 선택하기 전까지는 먼저 설치된 애드온이 제공합니다."
"skillShadowWarning": "이 애드온은 기본 제공 스킬 {{skill}}을(를) 대체합니다. 다른 애드온({{name}})이 이미 대체하고 있습니다. 하나만 활성화할 수 있으며, 설정 > 도구에서 선택합니다. 선택하기 전까지는 먼저 설치된 애드온이 제공합니다.",
"skillProvides": "이 애드온은 {{skill}} 스킬을 제공합니다.",
"skillProvidesWarning": "이 애드온은 {{skill}} 스킬을 제공합니다. 다른 애드온({{name}})도 제공합니다. 하나만 활성화할 수 있습니다. 설정 > 도구에서 선택하세요. 선택하지 않으면 먼저 설치된 애드온이 사용됩니다."
},
"family": {
"pages": "본인이나 에이전트가 만든 페이지를 찾으시나요? <go>페이지</go>에 있습니다."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "채팅 스킬 {{count}}개",
"title_other": "채팅 스킬 {{count}}개",
"provides": "{{skill}} 스킬 제공",
"replaces": "기본 제공 스킬을 대체",
"serves": "채팅에서 사용 중",
"notServing": "현재 사용 안 함",
@@ -691,7 +691,9 @@
"installNamed": "Instalar {{name}}",
"shadowWarning": "Este complemento substitui o {{tool}} integrado. Outro complemento ({{name}}) já faz isso. Só um pode estar ativo; você escolhe qual em Configurações > Ferramentas. Até você escolher, o que foi instalado primeiro o fornece.",
"skillShadow": "Este complemento substitui a habilidade integrada {{skill}}.",
"skillShadowWarning": "Este complemento substitui a habilidade integrada {{skill}}. Outro complemento ({{name}}) já faz isso. Só um pode estar ativo; você escolhe qual em Configurações > Ferramentas. Até você escolher, o que foi instalado primeiro o fornece."
"skillShadowWarning": "Este complemento substitui a habilidade integrada {{skill}}. Outro complemento ({{name}}) já faz isso. Só um pode estar ativo; você escolhe qual em Configurações > Ferramentas. Até você escolher, o que foi instalado primeiro o fornece.",
"skillProvides": "Este complemento fornece a habilidade {{skill}}.",
"skillProvidesWarning": "Este complemento fornece a habilidade {{skill}}. Outro complemento ({{name}}) também a fornece. Apenas um pode estar ativo; escolha em Configurações > Ferramentas. Sem uma escolha, o instalado primeiro será usado."
},
"family": {
"pages": "Procurando páginas que você ou um agente criou? Elas estão em <go>Páginas</go>."
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} habilidade de chat",
"title_other": "{{count}} habilidades de chat",
"provides": "fornece a habilidade {{skill}}",
"replaces": "substitui a habilidade integrada",
"serves": "usada nos chats",
"notServing": "não está em uso agora",
@@ -691,7 +691,9 @@
"installNamed": "安装 {{name}}",
"shadowWarning": "此插件会替换内置的 {{tool}}。另一个插件({{name}})已经替换了它。只能有一个生效;你可以在 设置 > 工具 中选择。 在你选择之前,由最先安装的插件提供。",
"skillShadow": "此插件会替换内置技能 {{skill}}。",
"skillShadowWarning": "此插件会替换内置技能 {{skill}}。另一个插件({{name}})已经替换了它。只能有一个生效;你可以在 设置 > 工具 中选择。 在你选择之前,由最先安装的插件提供。"
"skillShadowWarning": "此插件会替换内置技能 {{skill}}。另一个插件({{name}})已经替换了它。只能有一个生效;你可以在 设置 > 工具 中选择。 在你选择之前,由最先安装的插件提供。",
"skillProvides": "此插件提供 {{skill}} 技能。",
"skillProvidesWarning": "此插件提供 {{skill}} 技能。另一个插件({{name}})也提供此技能。只能启用一个;请在设置 > 工具中选择。如果未选择,则使用最先安装的插件。"
},
"family": {
"pages": "在找你或智能体创建的页面?它们在<go>页面</go>中。"
@@ -731,6 +733,7 @@
"skills": {
"title_one": "{{count}} 个聊天技能",
"title_other": "{{count}} 个聊天技能",
"provides": "提供 {{skill}} 技能",
"replaces": "替换内置技能",
"serves": "在聊天中使用",
"notServing": "当前未使用",
@@ -8,10 +8,11 @@ import { createHostSettingsClient } from '../../utils/hostSettingsClient'
import { createAddonsApi } from '../../utils/addonsApi'
import { apiUrl } from '../../utils/basePath'
import { useHostSettings } from '../../hooks/useHostSettings'
import { skillDeclarationLabel } from '../../utils/chatSkills'
import { skillsOf } from './addonState'
// The skills an add-on ships, in its details: each name, and whether it
// replaces the built-in skill of that name. For an administrator, a skill that
// provides a document slot or replaces a remaining built-in. For an administrator, a skill that
// several add-ons replace has a switch: on, this add-on serves it in the
// chats; off, the next add-on that replaces it does. The choice is the pin of
// Settings > Tools (chats_tool_providers), so both places show the same.
@@ -79,7 +80,7 @@ function SkillItem({ skill, app, row, busy, onChoose }) {
<Icon name="book" aria-hidden="true" />
<span className="ad-skill__text">
<span className="dk-mono">{skill.name}</span>
{skill.replaces && <span className="ad-quiet"> · {t('skills.replaces', { skill: skill.name })}</span>}
{skill.replaces && <span className="ad-quiet"> · {t(`skills.${skillDeclarationLabel(skill)}`, { skill: skill.name })}</span>}
{row && !several && <span className="ad-quiet"> · {serves ? t('skills.serves') : t('skills.notServing')}</span>}
</span>
{several && (
@@ -7,6 +7,7 @@ import Button from '../ui/Button'
import { pushLayer } from '../../utils/modalLayers'
import { EntryAdds, UnknownNotice } from './AddonBits'
import { shortDigest, skillShadowNotes } from './addonState'
import { skillInstallNoticeKey } from '../../utils/chatSkills'
import { describeOutcome } from './errors'
import Callout from '../ui/Callout'
@@ -90,7 +91,7 @@ export default function InstallDialog({ entry, flow, actions, onClose, installed
))}
{skillShadowNotes(entry, installed).map(c => (
<Callout key={c.skill} tone={c.name ? 'warn' : 'info'} testId="install-skill-warning" data-skill={c.skill}>
{t(c.name ? 'flow.skillShadowWarning' : 'flow.skillShadow', { skill: c.skill, name: c.name })}
{t(skillInstallNoticeKey(c), { skill: c.skill, name: c.name })}
</Callout>
))}
<dl className="dk-kv">
@@ -112,7 +112,7 @@ function SkillRow({ skill, onSet, testId }) {
return (
<SwitchRow
name={skill.name}
desc={desc ? skill.description : ''}
desc={skill.unavailable || (desc ? skill.description : '')}
meta={source}
metaTestId={`${testId}-source`}
metaKind={src.kind}
+12
View File
@@ -66,3 +66,15 @@ export function fallbackNotice(skills) {
if (on.length === 0) return null
return { skills: on.map((s) => s.name), addons: [...new Set(on.map((s) => s.fallback))] }
}
// These stable names are provider slots; none has a core implementation.
export function skillDeclarationLabel(skill) {
if (!skill.replaces) return null
return ['docx', 'pptx', 'pdf', 'xlsx'].includes(skill.name) ? 'provides' : 'replaces'
}
// Only document slots lack a core fallback; other shadows still replace one.
export function skillInstallNoticeKey({ skill, name }) {
const provides = skillDeclarationLabel({ name: skill, replaces: true }) === 'provides'
return `flow.${provides ? 'skillProvides' : 'skillShadow'}${name ? 'Warning' : ''}`
}
@@ -1,7 +1,7 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
SEARCH_MIN, changedIds, fallbackNotice, filterSkills, offList, pickerSkills, revertSkills, skillCounts, skillKey, skillSource, withSkillsSet,
skillDeclarationLabel, skillInstallNoticeKey, SEARCH_MIN, changedIds, fallbackNotice, filterSkills, offList, pickerSkills, revertSkills, skillCounts, skillKey, skillSource, withSkillsSet,
} from '../../src/utils/chatSkills.js'
const skills = [
@@ -50,15 +50,15 @@ test('chat skills: a failed request puts back its own ids only', () => {
test('chat skills: an add-on skill wins, and the built-in one it replaces is listed apart', () => {
const list = [
{ id: 'addon:xlsx', name: 'xlsx', layer: 'addon', addon: 'office', addon_title: 'Office', enabled: true },
{ id: 'builtin:xlsx', name: 'xlsx', layer: 'builtin', shadowed: 'addon', shadowed_by: 'office', enabled: true },
{ id: 'builtin:docx', name: 'docx', layer: 'builtin', fallback: 'Office', enabled: true },
{ id: 'addon:writing', name: 'writing', layer: 'addon', addon: 'office', addon_title: 'Office', enabled: true },
{ id: 'builtin:writing', name: 'writing', layer: 'builtin', shadowed: 'addon', shadowed_by: 'office', enabled: true },
{ id: 'builtin:apps', name: 'apps', layer: 'builtin', fallback: 'Office', enabled: true },
{ id: 'personal:docx', name: 'docx', layer: 'personal', shadowed: 'x', enabled: true },
{ id: 'addon:bad', name: 'bad', layer: 'addon', invalid: 'no name', enabled: true },
]
const picked = pickerSkills(list)
assert.deepEqual(picked.skills.map(skillKey), ['addon:xlsx', 'builtin:docx'])
assert.deepEqual(picked.replaced.map(skillKey), ['builtin:xlsx'])
assert.deepEqual(picked.skills.map(skillKey), ['addon:writing', 'builtin:apps'])
assert.deepEqual(picked.replaced.map(skillKey), ['builtin:writing'])
assert.deepEqual(pickerSkills(undefined), { skills: [], replaced: [] })
})
@@ -71,9 +71,51 @@ test('chat skills: the source of a row is the add-on title or the layer', () =>
test('chat skills: one notice names the built-in skills that serve for an add-on', () => {
assert.equal(fallbackNotice([{ layer: 'builtin', name: 'xlsx', enabled: true }]), null)
const n = fallbackNotice([
{ layer: 'builtin', name: 'xlsx', fallback: 'Office', enabled: true },
{ layer: 'builtin', name: 'docx', fallback: 'Office', enabled: true },
{ layer: 'builtin', name: 'writing', fallback: 'Office', enabled: true },
{ layer: 'builtin', name: 'apps', fallback: 'Office', enabled: true },
{ layer: 'builtin', name: 'pdf', fallback: 'Office', enabled: false },
])
assert.deepEqual(n, { skills: ['xlsx', 'docx'], addons: ['Office'] })
assert.deepEqual(n, { skills: ['writing', 'apps'], addons: ['Office'] })
})
// Document slots have providers, not a removed core implementation. Ordinary
// replacements still describe and retain their existing built-in fallback.
test('document declarations use provider-neutral labels, remaining built-ins use replacement labels', () => {
for (const name of ['docx', 'pptx', 'pdf', 'xlsx']) {
assert.equal(skillDeclarationLabel({ name, replaces: true }), 'provides')
}
assert.equal(skillDeclarationLabel({ name: 'writing', replaces: true }), 'replaces')
assert.equal(skillDeclarationLabel({ name: 'custom', replaces: false }), null)
})
test('document provider states never invent a built-in or fallback', () => {
for (const name of ['docx', 'pptx', 'pdf', 'xlsx']) {
for (const provider of ['office', 'sandbox']) {
for (const state of [{ enabled: true }, { enabled: false }, { enabled: true, unavailable: 'pinned provider unavailable' }]) {
const row = { id: `addon:${name}`, name, layer: 'addon', addon: provider, ...state }
const picked = pickerSkills([row])
assert.deepEqual(picked, { skills: [row], replaced: [] })
assert.equal(fallbackNotice(picked.skills), null)
assert.deepEqual(skillSource(row), { kind: 'addon', name: provider })
}
}
}
assert.deepEqual(pickerSkills([]), { skills: [], replaced: [] })
})
test('install document notices use flow provider keys and retain built-in replacement notices', async () => {
const { readFile } = await import('node:fs/promises')
const en = JSON.parse(await readFile(new URL('../../public/locales/en/addons.json', import.meta.url)))
assert.equal(en.flow.skillProvides, 'This add-on provides the {{skill}} skill.')
assert.match(en.flow.skillProvidesWarning, /Another add-on/)
assert.match(en.flow.skillShadow, /built-in/)
assert.equal(en.skills.skillShadow, undefined)
const source = await readFile(new URL('../../src/components/addons/InstallDialog.jsx', import.meta.url), 'utf8')
assert.match(source, /skillInstallNoticeKey\(c\)/)
for (const skill of ['docx', 'pptx', 'pdf', 'xlsx']) {
assert.equal(skillInstallNoticeKey({ skill }), 'flow.skillProvides')
assert.equal(skillInstallNoticeKey({ skill, name: 'Office' }), 'flow.skillProvidesWarning')
}
assert.equal(skillInstallNoticeKey({ skill: 'writing' }), 'flow.skillShadow')
assert.equal(skillInstallNoticeKey({ skill: 'writing', name: 'Other' }), 'flow.skillShadowWarning')
})
+111
View File
@@ -23,9 +23,14 @@ import (
"github.com/mudler/LocalAI/core/services/addons/catalog"
"github.com/mudler/LocalAI/core/services/addons/packages"
"github.com/mudler/LocalAI/core/services/studio/files"
"github.com/mudler/LocalAI/core/services/studio/workspace/blob"
"github.com/mudler/LocalAI/core/services/studio/workspace/gormstore"
"github.com/mudler/LocalAI/pkg/addons/contract"
"github.com/mudler/LocalAI/pkg/sqlitedb"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gorm.io/gorm"
)
func TestLifecycle(t *testing.T) {
@@ -693,6 +698,112 @@ var _ = Describe("the skills of a package", func() {
Expect(ids).To(BeEmpty())
})
It("installs upgrades and uninstalls the actual Sandbox document skills through verified receipts", func() {
// Keep the existing sample lifecycle transport/executable fixture; use the
// first-party Sandbox declarations and every byte of its real skill tree.
data, err := os.ReadFile("../../../addons/sandbox/manifest.json")
Expect(err).NotTo(HaveOccurred())
var sandbox contract.Manifest
Expect(json.Unmarshal(data, &sandbox)).To(Succeed())
data, err = os.ReadFile("../../../tests/addons/fixtures/packages/manifest.json")
Expect(err).NotTo(HaveOccurred())
var manifest contract.Manifest
Expect(json.Unmarshal(data, &manifest)).To(Succeed())
manifest.Capabilities.SkillDefinitions = sandbox.Capabilities.SkillDefinitions
data, err = json.Marshal(manifest)
Expect(err).NotTo(HaveOccurred())
tree := os.DirFS("../../../addons/sandbox/skills")
archive := map[string][]byte{}
Expect(fs.WalkDir(tree, ".", func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
return nil
}
body, err := fs.ReadFile(tree, path)
if err != nil {
return err
}
archive["skills/"+path] = body
return nil
})).To(Succeed())
Expect(archive).NotTo(BeEmpty())
cfg, c, installer := fixtureWithFiles(data, "", archive)
manager := open(cfg, c, installer)
db, err := sqlitedb.Open("file:"+filepath.Join(GinkgoT().TempDir(), "workspace.db")+"?_busy_timeout=5000&_txlock=immediate", &gorm.Config{})
Expect(err).NotTo(HaveOccurred())
raw, err := db.DB()
Expect(err).NotTo(HaveOccurred())
DeferCleanup(raw.Close)
blobs, err := blob.NewFS(filepath.Join(GinkgoT().TempDir(), "blobs"))
Expect(err).NotTo(HaveOccurred())
store, err := gormstore.New(db, blobs, gormstore.Options{Admin: files.AdminPrincipal})
Expect(err).NotTo(HaveOccurred())
materialized := files.New(store)
Expect(materialized.EnsureHome(ctx, "alice")).To(Succeed())
var receipts []packages.PackageReceipt
manager.SetSkillSync(ctx, func(ctx context.Context, id string, receipt *packages.PackageReceipt) error {
if receipt == nil {
return materialized.RemoveAddonSkills(ctx, id)
}
Expect(receipt.Skills).To(HaveLen(4))
verified, err := packages.SkillFS(*receipt)
if err != nil {
return err
}
n := 0
for _, skill := range receipt.Skills {
var notice bool
for _, file := range skill.Files {
want := archive["skills/"+skill.Name+"/"+file.Path]
Expect(want).NotTo(BeNil())
Expect(file.Size).To(Equal(int64(len(want))))
Expect(file.SHA256).To(Equal(fmt.Sprintf("%x", sha256.Sum256(want))))
got, err := fs.ReadFile(verified, skill.Name+"/"+file.Path)
Expect(err).NotTo(HaveOccurred())
Expect(got).To(Equal(want))
n++
notice = notice || file.Path == "notice.txt"
}
Expect(notice).To(BeTrue(), skill.Name)
}
Expect(n).To(Equal(len(archive)), "receipt must cover every actual file")
receipts = append(receipts, *receipt)
return materialized.SyncAddonSkills(ctx, id, verified)
})
check := func() {
for _, name := range []string{"docx", "pptx", "pdf", "xlsx"} {
_, body, err := materialized.ReadAddonSkill(ctx, "alice", "sample", name)
Expect(err).NotTo(HaveOccurred())
Expect(body).NotTo(BeEmpty())
}
ids, err := materialized.AddonSkillIDs(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(ids).To(Equal([]string{"sample"}))
}
_, err = manager.Submit(ctx, actor, command("sandbox-tree-install", "install"))
Expect(err).NotTo(HaveOccurred())
check()
_, err = manager.Submit(ctx, actor, command("sandbox-tree-stop", "disable"))
Expect(err).NotTo(HaveOccurred())
_, err = manager.Submit(ctx, actor, command("sandbox-tree-upgrade", "upgrade"))
Expect(err).NotTo(HaveOccurred())
Expect(receipts).To(HaveLen(2))
Expect(receipts[1].Directory).NotTo(Equal(receipts[0].Directory))
Expect(receipts[1].Skills).To(Equal(receipts[0].Skills))
check()
_, err = manager.Submit(ctx, actor, command("sandbox-tree-uninstall", "uninstall"))
Expect(err).NotTo(HaveOccurred())
ids, err := materialized.AddonSkillIDs(ctx)
Expect(err).NotTo(HaveOccurred())
Expect(ids).To(BeEmpty())
for _, name := range []string{"docx", "pptx", "pdf", "xlsx"} {
_, _, err = materialized.ReadAddonSkill(ctx, "alice", "sample", name)
Expect(err).To(HaveOccurred())
}
})
It("never fails the operation when the copy fails", func() {
cfg, c, i := fixture()
m := open(cfg, c, i)
+7 -3
View File
@@ -68,7 +68,7 @@ type addonSkills struct {
// off by the chat (addonsOff) and, for a chat with an agent, granted by it. An
// add-on the broker reports as unavailable is left out. A broker that cannot
// answer gives none and a warning; it never fails the chat.
func (s *Service) addonSkillsFor(ctx context.Context, p string, addonsOff []string, sc *scope) (addonSkills, error) {
func (s *Service) addonSkillsFor(ctx context.Context, p string, addonsOff []string, sc *scope, command Commands) (addonSkills, error) {
var out addonSkills
pins, err := s.toolPins(ctx)
if err != nil {
@@ -101,6 +101,10 @@ func (s *Service) addonSkillsFor(ctx context.Context, p string, addonsOff []stri
if gate != nil && gate.Unavailable(ctx, p, set.AddonID, s.d.Files.Available()) != "" {
continue
}
if set.AddonID == "sandbox" && !sandboxDocumentEligible(command) {
set.Skills = slices.DeleteFunc(slices.Clone(set.Skills), contract.DocumentSkill)
set.Shadows = slices.DeleteFunc(slices.Clone(set.Shadows), contract.DocumentSkill)
}
out.usable = append(out.usable, set)
}
return out, nil
@@ -122,7 +126,7 @@ func (a addonSkills) provider(name string) (string, bool) {
if !contract.ReplaceableSkill(name) {
return "", false
}
if pin := a.pins[name]; pin != "" && shipsReplacement(a.known, pin, name) {
if pin := a.pins[name]; pin != "" && (contract.DocumentSkill(name) || shipsReplacement(a.known, pin, name)) {
if shipsReplacement(a.usable, pin, name) {
return pin, true
}
@@ -131,7 +135,7 @@ func (a addonSkills) provider(name string) (string, bool) {
if id := ChooseProvider(a.shippers(a.usable, name), a.installTimes(), ""); id != "" {
return id, true
}
return "", false
return "", contract.DocumentSkill(name)
}
// shippers lists the add-ons of the sets that ship a replacement for name.
@@ -103,7 +103,7 @@ var _ = Describe("add-on skills in a chat", func() {
It("fails the turn rather than substituting built-ins when provider policy is unavailable", func() {
unavailable := errors.New("owner policy unavailable")
svc.d.ToolPins = func(context.Context) (map[string]string, error) { return nil, unavailable }
prompt, _, _, fallback, err := svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
prompt, _, _, fallback, err := svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(errors.Is(err, unavailable)).To(BeTrue())
Expect(prompt).To(BeEmpty())
Expect(fallback).To(BeEmpty())
@@ -118,7 +118,7 @@ var _ = Describe("add-on skills in a chat", func() {
})
It("offers the skills of a usable add-on, by name", func() {
prompt, _, key, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
prompt, _, key, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).To(ContainSubstring("\n- notes: Use for notes."))
Expect(prompt).NotTo(ContainSubstring("shared-addon-skills"), "the index names no path")
@@ -127,39 +127,39 @@ var _ = Describe("add-on skills in a chat", func() {
})
It("offers nothing for an add-on the chat switched off, that stopped or that cannot work", func() {
prompt, _, _, _, err := svc.skillTurn(ctx, "alice", nil, []string{"notes"}, nil, nil)
prompt, _, _, _, err := svc.skillTurn(ctx, "alice", nil, []string{"notes"}, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).NotTo(ContainSubstring("- notes:"), "addons_off removes the add-on's root")
b.reasons = map[string]string{"notes": "the Files workspace is off"}
prompt, _, _, _, err = svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
prompt, _, _, _, err = svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).NotTo(ContainSubstring("- notes:"), "an unavailable add-on gives no skills")
b.reasons = nil
b.set(AddonSkillSet{AddonID: "notes", Title: "Notes", Skills: []string{"notes", "notes-quick"}}) // stopped
prompt, _, _, _, err = svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
prompt, _, _, _, err = svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).NotTo(ContainSubstring("- notes:"), "a stopped add-on gives no skills; the files stay")
})
It("moves the refresh key when the add-on starts or stops, not on a switch", func() {
_, _, running, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
_, _, running, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
_, _, switched, _, err := svc.skillTurn(ctx, "alice", []string{"addon:notes"}, nil, nil, nil)
_, _, switched, _, err := svc.skillTurn(ctx, "alice", []string{"addon:notes"}, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(switched).To(Equal(running), "a switch changed the key")
b.set()
_, _, stopped, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
_, _, stopped, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(stopped).NotTo(Equal(running), "a stop left the key as it was")
})
It("keeps an agent's grant: an add-on the agent does not grant gives no skills", func() {
sc := &scope{addons: map[string]bool{}, skills: map[string]bool{"notes": true}}
prompt, _, _, _, err := svc.skillTurn(ctx, "alice", nil, nil, sc, nil)
prompt, _, _, _, err := svc.skillTurn(ctx, "alice", nil, nil, sc, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).NotTo(ContainSubstring("- notes:"))
sc.addons["notes"] = true
prompt, _, _, _, err = svc.skillTurn(ctx, "alice", nil, nil, sc, nil)
prompt, _, _, _, err = svc.skillTurn(ctx, "alice", nil, nil, sc, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).To(ContainSubstring("- notes: Use for notes."))
Expect(prompt).NotTo(ContainSubstring("notes-quick"), "the agent grants the skill notes only")
@@ -229,7 +229,7 @@ var _ = Describe("add-on skills that replace built-in skills", func() {
})).To(Succeed())
})
turn := func(off, addonsOff []string) (string, string, string) {
prompt, _, key, fallback, err := svc.skillTurn(ctx, "alice", off, addonsOff, nil, nil)
prompt, _, key, fallback, err := svc.skillTurn(ctx, "alice", off, addonsOff, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
return prompt, key, fallback
}
@@ -346,16 +346,16 @@ var _ = Describe("add-on skills that replace built-in skills", func() {
other, err := New(Deps{Store: svc.d.Store, Provider: provider.New(), StateDir: GinkgoT().TempDir(), Files: f, AddonTools: b})
Expect(err).NotTo(HaveOccurred())
DeferCleanup(func() { _ = other.Close(ctx) })
one, _, k1, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, nil)
one, _, k1, _, err := svc.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
two, _, k2, _, err := other.skillTurn(ctx, "alice", nil, nil, nil, nil)
two, _, k2, _, err := other.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(two).To(Equal(one))
Expect(k2).To(Equal(k1))
down, err := New(Deps{Store: svc.d.Store, Provider: provider.New(), StateDir: GinkgoT().TempDir(), Files: f, AddonTools: &skillBroker{}})
Expect(err).NotTo(HaveOccurred())
DeferCleanup(func() { _ = down.Close(ctx) })
prompt, _, _, _, err := down.skillTurn(ctx, "alice", nil, nil, nil, nil)
prompt, _, _, _, err := down.skillTurn(ctx, "alice", nil, nil, nil, Commands{}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(prompt).To(ContainSubstring("Built-in sheets."))
})
@@ -1,9 +1,6 @@
// Package builtinskills holds the skills that ship with the server: the office
// skills docx, xlsx, pptx and pdf, writing-skills, localai-workspace and localai-apps.
// The tree is embedded in the binary and written into the built-in layer when the
// server starts. The
// office skills are adapted from the Feather project (MIT); the NOTICE file in
// the tree keeps its source and licence, and must stay with them.
// Package builtinskills holds writing-skills, localai-workspace and localai-apps.
// The tree is embedded in the binary and synchronized into the built-in layer
// when the server starts. Document authoring procedures belong to add-ons.
package builtinskills
import (
@@ -20,7 +20,7 @@ import (
"github.com/mudler/LocalAI/pkg/sqlitedb"
)
var names = []string{"docx", "xlsx", "pptx", "pdf", "writing-skills", "localai-workspace", "localai-apps"}
var names = []string{"writing-skills", "localai-workspace", "localai-apps"}
var _ = Describe("the built-in skills", func() {
It("sends the model to another skill by its name, never by a path", func() {
@@ -68,30 +68,25 @@ var _ = Describe("the built-in skills", func() {
})
It("sends the helper scripts of the skills that make files to the chat's work folder", func() {
for _, n := range []string{"docx", "xlsx", "pptx", "pdf", "localai-apps", "localai-workspace"} {
for _, n := range []string{"localai-apps", "localai-workspace"} {
data, err := fs.ReadFile(builtinskills.FS(), n+"/SKILL.md")
Expect(err).NotTo(HaveOccurred(), n)
Expect(string(data)).To(ContainSubstring("the chat's work folder that the prompt names (`/workspace/home/Chats/<chat>/work/`)"), n)
}
})
It("marks the office skills as needing command execution, and only them", func() {
It("keeps remaining built-in skills independent of command execution", func() {
for _, n := range names {
data, err := fs.ReadFile(builtinskills.FS(), n+"/SKILL.md")
Expect(err).NotTo(HaveOccurred(), n)
switch n {
case "docx", "xlsx", "pptx", "pdf":
Expect(skills.Requires(data)).To(Equal([]string{skills.RequireExec}), n)
default:
Expect(skills.Requires(data)).To(BeEmpty(), n)
}
Expect(skills.Requires(data)).To(BeEmpty(), n)
}
})
It("keeps the source and the licence in the notice", func() {
data, err := fs.ReadFile(builtinskills.FS(), "NOTICE")
Expect(err).NotTo(HaveOccurred())
for _, want := range []string{"paper-instruments/feather-train", "b7463092af862ebbdf4c5920c4bc9ab38ab17ad9", "MIT License", "Permission is hereby granted"} {
for _, want := range names {
Expect(string(data)).To(ContainSubstring(want), "the NOTICE lacks %q", want)
}
})
+4 -107
View File
@@ -1,108 +1,5 @@
Built-in skills of the chats
=============================
Built-in chat skills
====================
The skills "docx", "xlsx", "pptx" and "pdf" are adapted from the skills of the
Feather project.
Source: https://github.com/paper-instruments/feather-train
(folder feather-harness/src/feather_harness/skills)
Commit: b7463092af862ebbdf4c5920c4bc9ab38ab17ad9
Licence: MIT, Copyright (c) 2026 Paper Instruments, Inc.
What was changed: the text about LibreOffice was replaced by a note that
LibreOffice is not installed, the text about OCR was shortened in the same way,
and the generated page of the LibreOffice-backed module "openpyxl.oracle" got
the same note. Each of the four got one line that says to write
helper scripts in the chat's work folder. The skills "writing-skills" and "localai-workspace" were written
for the chats.
The MIT licence of the Feather project
--------------------------------------
MIT License
Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The generated API pages
-----------------------
The pages under docx/references/api, xlsx/references/api and
pptx/references/api are generated from the docstrings of the Paper libraries
and of the libraries that they extend. The licences of those projects apply to
that text. All three are MIT licences.
paper-docx extends python-docx, and paper-pptx extends python-pptx. Steve Canny
wrote both upstream libraries, so the two licence files of the Paper packages
have the same text. Each applies to its own package.
paper-docx https://pypi.org/project/paper-docx/ (python-docx)
paper-pptx https://pypi.org/project/paper-pptx/ (python-pptx)
The MIT License (MIT)
Copyright (c) 2013 Steve Canny, https://github.com/scanny
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
paper-xlsx extends openpyxl.
paper-xlsx https://pypi.org/project/paper-xlsx/ (openpyxl)
This software is under the MIT Licence
======================================
Copyright (c) 2010 openpyxl
Portions Copyright (c) 2026 Paper Instruments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
writing-skills, localai-workspace and localai-apps were written for LocalAI
and are distributed under the MIT license in the project LICENSE file.
@@ -1,17 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.api`
Directly exposed API functions and classes, `Document` for now.
## `Document`
```python
Document(docx: str | IO[bytes] | None = None) -> DocumentObject
```
Return a `Document` object loaded from `docx`, where `docx` can be either a path
to a ``.docx`` file (a string) or a file-like object.
If `docx` is missing or ``None``, the built-in default document "template" is
loaded.
@@ -1,79 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.blkcntnr`
Block item container, used by body, cell, header, etc.
## `BlockItemContainer`
```python
BlockItemContainer(element: BlockItemElement, parent: t.ProvidesStoryPart)
```
Bases: `StoryChild`
Base class for proxy objects that can contain block items.
These containers include _Body, _Cell, header, footer, footnote, endnote, comment,
and text box objects. Provides the shared functionality to add a block item like a
paragraph or table.
### `add_paragraph`
```python
add_paragraph(text: str = '', style: str | ParagraphStyle | None = None) -> Paragraph
```
Return paragraph newly added to the end of the content in this container.
The paragraph has `text` in a single run if present, and is given paragraph
style `style`.
If `style` is `None`, no paragraph style is applied, which has the same effect
as applying the 'Normal' style.
### `add_table`
```python
add_table(rows: int, cols: int, width: Length) -> Table
```
Return table of `width` having `rows` rows and `cols` columns.
The table is appended appended at the end of the content in this container.
`width` is evenly distributed between the table columns.
### `iter_inner_content`
```python
iter_inner_content() -> Iterator[Paragraph | Table]
```
Generate each `Paragraph` or `Table` in this container in document order.
### `paragraphs`
```python
paragraphs
```
A list containing the paragraphs in this container, in document order.
Read-only.
### `tables`
```python
tables
```
A list containing the tables in this container, in document order.
Read-only.
## `BlockItemElement`
```python
BlockItemElement: TypeAlias = 'CT_Body | CT_Comment | CT_HdrFtr | CT_Tc'
```
@@ -1,216 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.blocks`
Anchor-relative block operations and clause-level redlines (paper-docx).
## `BlockEditResult`
```python
BlockEditResult(story: str, inserted_blocks: int, deleted_blocks: int, deleted_text: Tuple[str, ...], revision_ids: Tuple[int, ...]) -> None
```
Outcome of a block-level edit.
### `deleted_blocks`
```python
deleted_blocks: int
```
### `deleted_text`
```python
deleted_text: Tuple[str, ...]
```
### `inserted_blocks`
```python
inserted_blocks: int
```
### `revision_ids`
```python
revision_ids: Tuple[int, ...]
```
### `story`
```python
story: str
```
### `to_dict`
```python
to_dict() -> dict
```
## `BlockTarget`
```python
BlockTarget = Union[str, Block, Span]
```
## `ListBlock`
```python
ListBlock(items: Sequence[str], kind: str = 'bullet', level: int = 0) -> None
```
A real bullet/decimal list (backed by a real numbering definition).
### `items`
```python
items: Sequence[str]
```
### `kind`
```python
kind: str = 'bullet'
```
### `level`
```python
level: int = 0
```
## `RichParagraph`
```python
RichParagraph(runs: Sequence[TextRun], style: Optional[str] = None) -> None
```
A paragraph built from styled runs.
### `runs`
```python
runs: Sequence[TextRun]
```
### `style`
```python
style: Optional[str] = None
```
## `TableBlock`
```python
TableBlock(rows: Sequence[Sequence[str]]) -> None
```
A simple rectangular table of plain-text cells.
### `rows`
```python
rows: Sequence[Sequence[str]]
```
## `TextRun`
```python
TextRun(text: str, bold: bool = False, italic: bool = False) -> None
```
One run of a rich paragraph.
### `bold`
```python
bold: bool = False
```
### `italic`
```python
italic: bool = False
```
### `text`
```python
text: str
```
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `insert_blocks_after`
```python
insert_blocks_after(document: 'Document', anchor: BlockTarget, *, blocks: 'Sequence[object]', tracked: bool = False, author: Optional[str] = None, date: Optional[dt.datetime] = None) -> BlockEditResult
```
Insert a typed block list after `anchor`, and return a `BlockEditResult`.
`blocks` mixes `RichParagraph` (styled runs), `ListBlock` (real bullet or decimal lists,
numbering definition created on demand) and `TableBlock` (rectangular tables). Reach for
this when you want structure rather than raw XML. Refuses a protected document, and an
anchor that is missing, ambiguous, foreign, stale, or spans more than one paragraph.
## `insert_section_after`
```python
insert_section_after(document: 'Document', anchor: BlockTarget, *, heading: str, paragraphs: Sequence[str], heading_style: str = 'Heading2', body_style: Optional[str] = None, tracked: bool = False, author: Optional[str] = None, date: Optional[dt.datetime] = None) -> BlockEditResult
```
Insert a heading plus body paragraphs after `anchor`, and return a `BlockEditResult`.
With `tracked=True` each inserted paragraph is a real Word insertion attributed to
`author` (required) and `date`. Refuses a protected document, an undefined heading
style, and an anchor that is missing, ambiguous, foreign, stale, or spans
more than one paragraph.
## `require_anchor_owner`
```python
require_anchor_owner(document: 'Document', anchor: object, *, argument: str = 'anchor') -> None
```
Check ownership for live anchor forms; value/string anchors are inert.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
## `tracked_delete_paragraphs`
```python
tracked_delete_paragraphs(document: 'Document', start_anchor: BlockTarget, *, end_anchor: Optional[BlockTarget] = None, count: int = 1, author: str, date: Optional[dt.datetime] = None) -> BlockEditResult
```
Mark a paragraph range deleted with real tracked changes; returns a `BlockEditResult`.
Runs move into `w:del` with formatting intact and the paragraph mark is stamped, so
accepting removes the paragraphs and rejecting restores them exactly. Refuses a
protected document, an endpoint spanning multiple paragraphs, a range
crossing stories or parents, a bookmarked or non-plain run, and an open field.
## `tracked_replace_paragraphs`
```python
tracked_replace_paragraphs(document: 'Document', start_anchor: BlockTarget, replacement_paragraphs: Sequence[str], *, end_anchor: Optional[BlockTarget] = None, count: int = 1, body_style: Optional[str] = None, author: str, date: Optional[dt.datetime] = None) -> BlockEditResult
```
Tracked-delete a paragraph range and tracked-insert replacements after it.
Returns a `BlockEditResult`. Replacements land after the last deleted paragraph. Carries
the same refusals as `tracked_delete_paragraphs`: protection, a multi-paragraph
endpoint, a range crossing stories, bookmarked or non-plain runs, an open field.
@@ -1,134 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.bookmarks`
Bookmark enumeration, creation, and deletion.
## `BookmarkInfo`
```python
BookmarkInfo(name: str, bookmark_id: int, story: str, text: str) -> None
```
One bookmark: its name, id, story, and the visible text it wraps.
### `bookmark_id`
```python
bookmark_id: int
```
### `is_point`
```python
is_point: bool
```
### `name`
```python
name: str
```
### `story`
```python
story: str
```
### `text`
```python
text: str
```
### `to_dict`
```python
to_dict() -> dict
```
## `DEL_TEXT`
```python
DEL_TEXT = qn('w:delText')
```
## `INSTR_TEXT`
```python
INSTR_TEXT = qn('w:instrText')
```
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `create_bookmark`
```python
create_bookmark(document: 'Document', span: 'Span', name: str) -> BookmarkInfo
```
Bookmark exactly `span`'s text under `name`, and return its `BookmarkInfo`.
Use this to give a span a stable target that survives later edits; cross-references and
`add_reference_field` ride on bookmarks. Splits boundary runs so the markers wrap the
span's characters exactly. Refuses a protected document, a foreign or stale span, and a
name Word rejects or the document already uses.
## `delete_bookmark`
```python
delete_bookmark(document: 'Document', name: str) -> None
```
Remove `name`'s markers, leaving the text in place.
Use this to retire a bookmark without touching content. Refuses while a field
instruction still references the name, since a dangling REF renders "Error! Reference
source not found." in Word. Refuses a protected document and an unknown name.
## `is_direct_run_child`
```python
is_direct_run_child(element: '_Element') -> bool
```
Whether ``element`` is a direct child of a ``w:r`` element.
## `list_bookmarks`
```python
list_bookmarks(document: 'Document') -> 'List[BookmarkInfo]'
```
Every bookmark across every story, in document order.
## `project_run_child`
```python
project_run_child(element: '_Element') -> RunChildProjection
```
Return the conservative text projection for a direct ``w:r`` child.
## `require_span_owner`
```python
require_span_owner(document: 'Document', span: 'Span', *, argument: str = 'span') -> None
```
Refuse a live span captured from a different document package.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,173 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.commentops`
Comment thread operations: anchored text, replies, resolution.
## `COMMENTS_EXTENDED_CONTENT_TYPE`
```python
COMMENTS_EXTENDED_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml'
```
## `COMMENTS_EXTENDED_RELATIONSHIP_TYPE`
```python
COMMENTS_EXTENDED_RELATIONSHIP_TYPE = 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended'
```
## `COMMENTS_EXTENSIBLE_CONTENT_TYPE`
```python
COMMENTS_EXTENSIBLE_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml'
```
## `COMMENTS_EXTENSIBLE_RELATIONSHIP_TYPE`
```python
COMMENTS_EXTENSIBLE_RELATIONSHIP_TYPE = 'http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible'
```
## `COMMENTS_IDS_CONTENT_TYPE`
```python
COMMENTS_IDS_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml'
```
## `COMMENTS_IDS_RELATIONSHIP_TYPE`
```python
COMMENTS_IDS_RELATIONSHIP_TYPE = 'http://schemas.microsoft.com/office/2016/09/relationships/commentsIds'
```
## `DEL_TEXT`
```python
DEL_TEXT = qn('w:delText')
```
## `INSTR_TEXT`
```python
INSTR_TEXT = qn('w:instrText')
```
## `anchored_text`
```python
anchored_text(document: 'Document', comment: 'Comment') -> str
```
The document text `comment` is anchored to, taken from its range marks.
Refuses a stale or foreign comment, and a comment whose range marks are missing or
crossed.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `comment_thread`
```python
comment_thread(document: 'Document') -> Tuple[dict, ...]
```
Every comment with its thread state: id, author, text, resolved, parent id, and anchored
text where available.
Use it to read a whole discussion in one pass rather than walking replies. Refuses a
stale comment and a malformed `commentsExtended` part.
## `delete_comment`
```python
delete_comment(document: 'Document', comment: 'Comment') -> None
```
Delete `comment` and its replies, removing their range marks from the text.
Refuses a protected document, a stale or foreign comment, and a comments part that is
missing or ambiguous.
## `is_direct_run_child`
```python
is_direct_run_child(element: '_Element') -> bool
```
Whether ``element`` is a direct child of a ``w:r`` element.
## `is_resolved`
```python
is_resolved(document: 'Document', comment: 'Comment') -> bool
```
Whether `comment`'s thread is marked resolved.
Reads `commentsExtended`, where Word keeps resolution state. Refuses a stale or foreign
comment, and a malformed `commentsExtended` part.
## `parent_of`
```python
parent_of(document: 'Document', comment: 'Comment') -> Optional[int]
```
The comment `comment` replies to, or None when it starts a thread.
Refuses a stale or foreign comment, and a malformed `commentsExtended` part.
## `project_run_child`
```python
project_run_child(element: '_Element') -> RunChildProjection
```
Return the conservative text projection for a direct ``w:r`` child.
## `reply`
```python
reply(document: 'Document', comment: 'Comment', text: str, *, author: str, initials: Optional[str] = None, date: Optional[dt.datetime] = None) -> 'Comment'
```
Add a threaded reply to `comment` and return the new `Comment`.
The reply gets its own comment-range start, range end, and reference around the same
selected text. Its `w15:paraIdParent` names the parent's final comment-body paragraph; the
reply does not reuse the parent's marker id. Creates `commentsExtended` on first use, since
Word keeps threading outside `w:comment`. Refuses a protected document, a stale or foreign
comment, and a comments part that is missing or ambiguous.
## `require_comment_owner`
```python
require_comment_owner(document: 'Document', comment: 'Comment', *, argument: str = 'comment') -> None
```
Refuse a comment proxy from another part and detect detached proxies.
## `resolve`
```python
resolve(document: 'Document', comment: 'Comment', *, resolved: bool = True) -> None
```
Mark `comment`'s thread resolved, or reopen it with `resolved=False`.
Word keeps resolution in `commentsExtended` rather than on the comment, so this writes
that part. Refuses a protected document, and a stale or foreign comment.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,138 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.comments`
Collection providing access to comments added to this document.
## `Comment`
```python
Comment(comment_elm: CT_Comment, comments_part: CommentsPart)
```
Bases: `BlockItemContainer`
Proxy for a single comment in the document.
Reads author, initials, date and body content, and adds paragraphs or tables. Every
mutation revalidates that the comment is still live first, so a proxy whose part was
removed or replaced refuses instead of writing into a detached tree.
### `add_paragraph`
```python
add_paragraph(text: str = '', style: str | ParagraphStyle | None = None) -> Paragraph
```
Append a paragraph to this comment's body and return it.
Re-keys the comment's `commentEx`, its replies' `paraIdParent`, and its `commentsIds` row
onto the new last paragraph, so appending rewrites thread and resolution state. Refuses a
protected document, and a proxy whose part was removed or replaced.
### `add_table`
```python
add_table(rows: int, cols: int, width: Length) -> Table
```
Append a table to this comment's body and return it.
Re-keys thread and resolution state onto the new last paragraph, as `add_paragraph` does.
Refuses a protected document, and a proxy whose part was removed or replaced.
### `author`
```python
author: str
```
Read/write. The recorded author of this comment.
This field is required but can be set to the empty string.
### `comment_id`
```python
comment_id: int
```
The unique identifier of this comment.
### `initials`
```python
initials: str | None
```
Read/write. The recorded initials of the comment author.
This attribute is optional in the XML, returns `None` if not set. Assigning `None` removes
any existing initials from the XML.
### `text`
```python
text: str
```
The text content of this comment as a string.
Only content in paragraphs is included and of course all emphasis and styling is stripped.
Paragraph boundaries are indicated with a newline (`"\\n"`)
### `timestamp`
```python
timestamp: dt.datetime | None
```
The date and time this comment was authored.
This attribute is optional in the XML, returns `None` if not set.
## `Comments`
```python
Comments(comments_elm: CT_Comments, comments_part: CommentsPart)
```
Collection containing the comments added to this document.
### `add_comment`
```python
add_comment(text: str = '', author: str = '', initials: str | None = '') -> Comment
```
Add a new comment to the document and return it.
Assigns a unique id and writes the modern identity parts (`commentsIds`,
`commentsExtensible`, a `w14:paraId`) that Word needs to thread replies. Refuses a
protected document, a collection whose part was removed or replaced, and an ambiguous
comments relationship.
### `get`
```python
get(comment_id: int) -> Comment | None
```
Return the comment identified by `comment_id`, or `None` if not found.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
## `rollback_xml_on_error`
```python
rollback_xml_on_error(root: '_Element') -> Generator[None, None, None]
```
Restore one live XML tree after an error in a local compound edit.
@@ -1,152 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.composition`
Cross-document composition.
## `CompositionFinding`
```python
CompositionFinding(kind: str, detail: str) -> None
```
### `detail`
```python
detail: str
```
### `kind`
```python
kind: str
```
### `to_dict`
```python
to_dict() -> dict
```
## `CompositionReport`
```python
CompositionReport(inserted_blocks: int = 0, style_map: Dict[str, str] = dict(), imported_styles: List[str] = list(), renamed_styles: Dict[str, str] = dict(), numbering_map: Dict[int, int] = dict(), media_copied: List[str] = list(), bookmarks_renamed: Dict[str, str] = dict(), findings: List[CompositionFinding] = list(), declared_parts: List[str] = list()) -> None
```
Everything one composition call did — the declared changed-part
budget, the reconciliation maps, and what was reported instead of done.
### `bookmarks_renamed`
```python
bookmarks_renamed: Dict[str, str] = field(default_factory=dict)
```
### `declared_parts`
```python
declared_parts: List[str] = field(default_factory=list)
```
### `findings`
```python
findings: List[CompositionFinding] = field(default_factory=list)
```
### `imported_styles`
```python
imported_styles: List[str] = field(default_factory=list)
```
### `inserted_blocks`
```python
inserted_blocks: int = 0
```
### `media_copied`
```python
media_copied: List[str] = field(default_factory=list)
```
### `numbering_map`
```python
numbering_map: Dict[int, int] = field(default_factory=dict)
```
### `renamed_styles`
```python
renamed_styles: Dict[str, str] = field(default_factory=dict)
```
### `style_map`
```python
style_map: Dict[str, str] = field(default_factory=dict)
```
### `to_dict`
```python
to_dict() -> dict
```
## `append_document`
```python
append_document(document: 'Document', source: 'Document', *, section: str = 'new_page', styles: str = 'match_by_name', headers: str = 'destination') -> CompositionReport
```
Append `source`'s whole body to `document`, and return a `CompositionReport`.
Keeps the destination's headers and footers by default and authors no new `w:sectPr`:
`section="new_page"` prefixes a page break, `"continuous"` appends flush.
`headers="source"` overwrites the destination's last-section header and footer and flips
the document-wide even/odd setting. Refuses a protected document and an empty body on
either side. Also refuses when insertion after the destination's final paragraph,
table, or block content control would remain inside an open field result.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `insert_blocks_from`
```python
insert_blocks_from(document: 'Document', source: 'Document', start_anchor: 'Document', *, anchor: 'Document', end_anchor: 'Document' = None, count: int = 1, styles: str = 'match_by_name', include_start: bool = True, include_end: bool = True) -> CompositionReport
```
Copy a block range from `source` after `anchor`, and return a `CompositionReport`.
Reconciles styles, numbering, media and bookmarks so the copy keeps its appearance.
`start_anchor` and `end_anchor` identify SOURCE paragraphs whose containing top-level body
blocks form the range endpoints. A direct body paragraph or a paragraph in a top-level
content control can serve as an endpoint; paragraphs in tables, table cells, and text boxes
cannot. Both endpoints are included by default; setting either inclusion flag false excludes
that endpoint's block. With no `end_anchor`, `count` blocks are copied beginning at the start
block or the following block when `include_start=False`. With an `end_anchor`, `count` is
validated but does not limit the range. `include_end=False` without an end anchor raises
`ValueError`. Refuses an empty adjusted range, a protected destination, an endpoint that is
missing, ambiguous, or spans multiple paragraphs, and source content this package cannot
carry over: revisions, comments, OLE objects, EMF/WMF images. Live source
endpoints may come from any supported inspection view. A live destination
must come from ``view="current"``; reacquire a historical destination before
composing.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,183 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.controls`
Content-control (structured document tag) surface — the templating
## `Control`
```python
Control(sdt: '_Element', story: str, document: 'Document') -> None
```
Live proxy for one content control.
### `alias`
```python
alias: Optional[str]
```
### `control_type`
```python
control_type: str
```
### `info`
```python
info() -> ControlInfo
```
### `is_data_bound`
```python
is_data_bound: bool
```
### `set_value`
```python
set_value(value: 'Union[str, bool, dt.date, dt.datetime]') -> None
```
Set the control's value, validating fully before mutating anything.
Text and rich-text controls take a string, checkboxes a bool, dropdowns a value from
their `w:listItem` choices, dates an ISO date. Refuses a protected document, a stale or
foreign control, a locked or data-bound surface, a picture, group or building-block
control, a dropdown value outside its choices, and an unsupported date format.
### `showing_placeholder`
```python
showing_placeholder: bool
```
### `tag`
```python
tag: Optional[str]
```
### `value`
```python
value: 'Union[str, bool]'
```
The control's current value (bool for checkboxes, text otherwise).
## `ControlInfo`
```python
ControlInfo(tag: Optional[str], alias: Optional[str], control_type: str, value: str, story: str, showing_placeholder: bool, is_data_bound: bool, choices: Tuple[str, ...]) -> None
```
Identity and current state of one content control.
### `alias`
```python
alias: Optional[str]
```
### `choices`
```python
choices: Tuple[str, ...]
```
### `control_type`
```python
control_type: str
```
### `is_data_bound`
```python
is_data_bound: bool
```
### `showing_placeholder`
```python
showing_placeholder: bool
```
### `story`
```python
story: str
```
### `tag`
```python
tag: Optional[str]
```
### `to_dict`
```python
to_dict() -> dict
```
### `value`
```python
value: str
```
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `get_control`
```python
get_control(document: 'Document', *, tag: Optional[str] = None, alias: Optional[str] = None) -> Control
```
The single control matching `tag` and/or `alias`, or a typed refusal.
## `iter_controls`
```python
iter_controls(document: 'Document') -> 'Iterator[Control]'
```
Every content control in every story part, document order.
## `list_controls`
```python
list_controls(document: 'Document') -> Tuple[ControlInfo, ...]
```
Identity and state of every control (schema-stable via .to_dict()).
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
## `set_control_value`
```python
set_control_value(document: 'Document', value: 'Union[str, bool, dt.date, dt.datetime]', *, tag: Optional[str] = None, alias: Optional[str] = None) -> None
```
Set the value of the control that `tag` names.
Refuses when no control carries the tag and when several do, plus every refusal
`Control.set_value` raises.
@@ -1,69 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.dml.color`
DrawingML objects related to color, ColorFormat being the most prominent.
## `ColorFormat`
```python
ColorFormat(rPr_parent: RPrParent)
```
Bases: `ElementProxy`
Provides access to color settings like RGB color, theme color, and luminance adjustments.
### `rgb`
```python
rgb: RGBColor | None
```
An `RGBColor` value or `None` if no RGB color is specified.
When `type` is `MSO_COLOR_TYPE.RGB`, the value of this property will always be an
`RGBColor` value. It may also be an `RGBColor` value if `type` is
`MSO_COLOR_TYPE.THEME`, as Word writes the current value of a theme color when one is
assigned. In that case, the RGB value should be interpreted as no more than a good guess
however, as the theme color takes precedence at rendering time. Its value is `None`
whenever `type` is either `None` or `MSO_COLOR_TYPE.AUTO`.
Assigning an `RGBColor` value causes `type` to become `MSO_COLOR_TYPE.RGB` and any
theme color is removed. Assigning `None` causes any color to be removed such that the
effective color is inherited from the style hierarchy.
### `theme_color`
```python
theme_color: MSO_THEME_COLOR | None
```
Member of `MsoThemeColorIndex` or `None` if no theme color is specified.
When `type` is `MSO_COLOR_TYPE.THEME`, the value of this property will always be a
member of `MsoThemeColorIndex`. When `type` has any other value, the value of
this property is `None`.
Assigning a member of `MsoThemeColorIndex` causes `type` to become
`MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word. Assigning
`None` causes any color specification to be removed such that the effective color is
inherited from the style hierarchy.
### `type`
```python
type: MSO_COLOR_TYPE | None
```
Read-only.
A member of `MsoColorType`, one of RGB, THEME, or AUTO, corresponding to the way this
color is defined. Its value is `None` if no color is applied at this level, which causes
the effective color to be inherited from the style hierarchy.
## `RPrParent`
```python
RPrParent: TypeAlias = 'CT_R'
```
@@ -1,228 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.document`
`Document` and closely related objects.
## `Document`
```python
Document(element: CT_Document, part: DocumentPart)
```
Bases: `ElementProxy`
WordprocessingML (WML) document.
Not intended to be constructed directly. Use `docx.Document` to open or create
a document.
### `add_comment`
```python
add_comment(runs: Run | Sequence[Run], text: str | None = '', author: str = '', initials: str | None = '') -> Comment
```
Add a comment anchored to `runs`, and return it.
Pass one `Run` or a sequence; only the first and last are used, so handing over
`paragraph.runs` works. Reach for `Span.comment` instead when the anchor should match
exact text rather than whole runs. Refuses a protected document, runs belonging to
another document, and a comments part that is missing or ambiguous.
### `add_heading`
```python
add_heading(text: str = '', level: int = 1)
```
Return a heading paragraph newly added to the end of the document.
The heading paragraph will contain `text` and have its paragraph style
determined by `level`. If `level` is 0, the style is set to `Title`. If `level`
is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading
{level}`. Raises `ValueError` if `level` is outside the range 0-9.
### `add_page_break`
```python
add_page_break()
```
Return newly `Paragraph` object containing only a page break.
### `add_paragraph`
```python
add_paragraph(text: str = '', style: str | ParagraphStyle | None = None) -> Paragraph
```
Return paragraph newly added to the end of the document.
The paragraph is populated with `text` and having paragraph style `style`.
`text` can contain tab (``\t``) characters, which are converted to the
appropriate XML form for a tab. `text` can also include newline (``\n``) or
carriage return (``\r``) characters, each of which is converted to a line
break.
### `add_picture`
```python
add_picture(image_path_or_stream: str | IO[bytes], width: int | Length | None = None, height: int | Length | None = None)
```
Return new picture shape added in its own paragraph at end of the document.
The picture contains the image at `image_path_or_stream`, scaled based on
`width` and `height`. If neither width nor height is specified, the picture
appears at its native size. If only one is specified, it is used to compute a
scaling factor that is then applied to the unspecified dimension, preserving the
aspect ratio of the image. The native size of the picture is calculated using
the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi
if no value is specified, as is often the case.
### `add_section`
```python
add_section(start_type: WD_SECTION = WD_SECTION.NEW_PAGE)
```
Return a `Section` object newly added at the end of the document.
The optional `start_type` argument must be a member of the `WdSectionStart`
enumeration, and defaults to ``WD_SECTION.NEW_PAGE`` if not provided.
### `add_table`
```python
add_table(rows: int, cols: int, style: str | _TableStyle | None = None)
```
Add a table having row and column counts of `rows` and `cols` respectively.
`style` may be a table style object or a table style name. If `style` is `None`,
the table inherits the default table style of the document.
### `comments`
```python
comments: Comments
```
A `Comments` object providing access to comments added to the document.
### `core_properties`
```python
core_properties
```
A `CoreProperties` object providing Dublin Core properties of document.
### `inline_shapes`
```python
inline_shapes
```
The `InlineShapes` collection for this document.
An inline shape is a graphical object, such as a picture, contained in a run of
text and behaving like a character glyph, being flowed like other text in a
paragraph.
### `iter_inner_content`
```python
iter_inner_content() -> Iterator[Paragraph | Table]
```
Generate each `Paragraph` or `Table` in this document in document order.
### `paragraphs`
```python
paragraphs: List[Paragraph]
```
The `Paragraph` instances in the document, in document order.
Note that paragraphs within revision marks such as ``<w:ins>`` or ``<w:del>`` do
not appear in this list.
### `part`
```python
part: DocumentPart
```
The `DocumentPart` object of this document.
### `revisions`
```python
revisions
```
`Revisions` object enumerating tracked changes across all story parts.
paper-docx addition: a fresh snapshot on each access; supports
accept/reject of all revisions or filtered by author. See
`docx.revision`.
### `save`
```python
save(path_or_stream: str | IO[bytes])
```
Save this document to `path_or_stream`.
`path_or_stream` can be either a path to a filesystem location (a string) or a
file-like object.
### `sections`
```python
sections: Sections
```
`Sections` object providing access to each section in this document.
### `settings`
```python
settings: Settings
```
A `Settings` object providing access to the document-level settings.
### `styles`
```python
styles
```
A `Styles` object providing access to the styles in this document.
### `tables`
```python
tables: List[Table]
```
All `Table` instances in the document, in document order.
Note that only tables appearing at the top level of the document appear in this
list; a table nested inside a table cell does not appear. A table within
revision marks such as ``<w:ins>`` or ``<w:del>`` will also not appear in the
list.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,64 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.drawing`
DrawingML-related objects are in this subpackage.
## `Drawing`
```python
Drawing(drawing: CT_Drawing, parent: t.ProvidesStoryPart)
```
Bases: `Parented`
Container for a DrawingML object.
### `has_picture`
```python
has_picture: bool
```
True when `drawing` contains an embedded picture.
A drawing can contain a picture, but it can also contain a chart, SmartArt, or a
drawing canvas. Methods related to a picture, like `.image`, will raise when the drawing
does not contain a picture. Use this value to determine whether image methods will succeed.
This value is `False` when a linked picture is present. This should be relatively rare and
the image would only be retrievable from the filesystem.
Note this does not distinguish between inline and floating images. The presence of either
one will cause this value to be `True`.
### `image`
```python
image: Image
```
An `Image` proxy object for the image in this (picture) drawing.
Raises `ValueError` when this drawing does contains something other than a picture. Use
`.has_picture` to qualify drawing objects before using this property.
### `replace_picture`
```python
replace_picture(image_descriptor: str | IO[bytes]) -> None
```
Swap this drawing's image bytes, keeping display size and position.
Always writes a new image part, so other shapes sharing the old part are unchanged;
the old relationship stays behind. Refuses a drawing with no picture, a linked picture
whose bytes live outside the package, and a protected document.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,71 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.enum.base`
Base classes and other objects used by enumerations.
## `BaseEnum`
Bases: `int`, `Enum`
Base class for Enums that do not map XML attr values.
The enum's value will be an integer, corresponding to the integer assigned the
corresponding member in the MS API enum of the same name.
## `BaseXmlEnum`
Bases: `int`, `Enum`
Base class for Enums that also map XML attr values.
The enum's value will be an integer, corresponding to the integer assigned the
corresponding member in the MS API enum of the same name.
### `from_xml`
```python
from_xml(xml_value: str | None) -> Self
```
Enumeration member corresponding to XML attribute value `xml_value`.
Example::
>>> WD_PARAGRAPH_ALIGNMENT.from_xml("center")
WD_PARAGRAPH_ALIGNMENT.CENTER
### `to_xml`
```python
to_xml(value: int | _T | None) -> str | None
```
XML value of this enum member, generally an XML attribute value.
### `xml_value`
```python
xml_value: str | None
```
## `DocsPageFormatter`
```python
DocsPageFormatter(clsname: str, clsdict: Dict[str, Any])
```
Generate an .rst doc page for an enumeration.
Formats a RestructuredText documention page (string) for the enumeration class parts
passed to the constructor. An immutable one-shot service object.
### `page_str`
```python
page_str
```
The RestructuredText documentation page for the enumeration.
This is the only API member for the class.
@@ -1,206 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.enum.dml`
Enumerations used by DrawingML objects.
## `MSO_COLOR_TYPE`
Bases: `BaseEnum`
Specifies the color specification scheme.
Example::
from docx.enum.dml import MSO_COLOR_TYPE
assert font.color.type == MSO_COLOR_TYPE.SCHEME
MS API name: `MsoColorType`
http://msdn.microsoft.com/en-us/library/office/ff864912(v=office.15).aspx
### `AUTO`
```python
AUTO = (101, 'Color is determined automatically by the application.')
```
Color is determined automatically by the application.
### `RGB`
```python
RGB = (1, 'Color is specified by an `RGBColor` value.')
```
Color is specified by an `RGBColor` value.
### `THEME`
```python
THEME = (2, 'Color is one of the preset theme colors.')
```
Color is one of the preset theme colors.
## `MSO_THEME_COLOR`
```python
MSO_THEME_COLOR = MSO_THEME_COLOR_INDEX
```
## `MSO_THEME_COLOR_INDEX`
Bases: `BaseXmlEnum`
Indicates the Office theme color, one of those shown in the color gallery on the
formatting ribbon.
Alias: ``MSO_THEME_COLOR``
Example::
from docx.enum.dml import MSO_THEME_COLOR
font.color.theme_color = MSO_THEME_COLOR.ACCENT_1
MS API name: `MsoThemeColorIndex`
http://msdn.microsoft.com/en-us/library/office/ff860782(v=office.15).aspx
### `ACCENT_1`
```python
ACCENT_1 = (5, 'accent1', 'Specifies the Accent 1 theme color.')
```
Specifies the Accent 1 theme color.
### `ACCENT_2`
```python
ACCENT_2 = (6, 'accent2', 'Specifies the Accent 2 theme color.')
```
Specifies the Accent 2 theme color.
### `ACCENT_3`
```python
ACCENT_3 = (7, 'accent3', 'Specifies the Accent 3 theme color.')
```
Specifies the Accent 3 theme color.
### `ACCENT_4`
```python
ACCENT_4 = (8, 'accent4', 'Specifies the Accent 4 theme color.')
```
Specifies the Accent 4 theme color.
### `ACCENT_5`
```python
ACCENT_5 = (9, 'accent5', 'Specifies the Accent 5 theme color.')
```
Specifies the Accent 5 theme color.
### `ACCENT_6`
```python
ACCENT_6 = (10, 'accent6', 'Specifies the Accent 6 theme color.')
```
Specifies the Accent 6 theme color.
### `BACKGROUND_1`
```python
BACKGROUND_1 = (14, 'background1', 'Specifies the Background 1 theme color.')
```
Specifies the Background 1 theme color.
### `BACKGROUND_2`
```python
BACKGROUND_2 = (16, 'background2', 'Specifies the Background 2 theme color.')
```
Specifies the Background 2 theme color.
### `DARK_1`
```python
DARK_1 = (1, 'dark1', 'Specifies the Dark 1 theme color.')
```
Specifies the Dark 1 theme color.
### `DARK_2`
```python
DARK_2 = (3, 'dark2', 'Specifies the Dark 2 theme color.')
```
Specifies the Dark 2 theme color.
### `FOLLOWED_HYPERLINK`
```python
FOLLOWED_HYPERLINK = (12, 'followedHyperlink', 'Specifies the theme color for a clicked hyperlink.')
```
Specifies the theme color for a clicked hyperlink.
### `HYPERLINK`
```python
HYPERLINK = (11, 'hyperlink', 'Specifies the theme color for a hyperlink.')
```
Specifies the theme color for a hyperlink.
### `LIGHT_1`
```python
LIGHT_1 = (2, 'light1', 'Specifies the Light 1 theme color.')
```
Specifies the Light 1 theme color.
### `LIGHT_2`
```python
LIGHT_2 = (4, 'light2', 'Specifies the Light 2 theme color.')
```
Specifies the Light 2 theme color.
### `NOT_THEME_COLOR`
```python
NOT_THEME_COLOR = (0, 'UNMAPPED', 'Indicates the color is not a theme color.')
```
Indicates the color is not a theme color.
### `TEXT_1`
```python
TEXT_1 = (13, 'text1', 'Specifies the Text 1 theme color.')
```
Specifies the Text 1 theme color.
### `TEXT_2`
```python
TEXT_2 = (15, 'text2', 'Specifies the Text 2 theme color.')
```
Specifies the Text 2 theme color.
@@ -1,150 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.enum.section`
Enumerations related to the main document in WordprocessingML files.
## `WD_HEADER_FOOTER`
```python
WD_HEADER_FOOTER = WD_HEADER_FOOTER_INDEX
```
## `WD_HEADER_FOOTER_INDEX`
Bases: `BaseXmlEnum`
Alias: **WD_HEADER_FOOTER**
Specifies one of the three possible header/footer definitions for a section.
For internal use only; not part of the python-docx API.
MS API name: `WdHeaderFooterIndex`
URL: https://docs.microsoft.com/en-us/office/vba/api/word.wdheaderfooterindex
### `EVEN_PAGE`
```python
EVEN_PAGE = (3, 'even', 'Header for even pages of recto/verso section.')
```
Header for even pages of recto/verso section.
### `FIRST_PAGE`
```python
FIRST_PAGE = (2, 'first', 'Header for first page of section.')
```
Header for first page of section.
### `PRIMARY`
```python
PRIMARY = (1, 'default', 'Header for odd pages or all if no even header.')
```
Header for odd pages or all if no even header.
## `WD_ORIENT`
```python
WD_ORIENT = WD_ORIENTATION
```
## `WD_ORIENTATION`
Bases: `BaseXmlEnum`
Alias: **WD_ORIENT**
Specifies the page layout orientation.
Example::
from docx.enum.section import WD_ORIENT
section = document.sections[-1] section.orientation = WD_ORIENT.LANDSCAPE
MS API name: `WdOrientation`
MS API URL: http://msdn.microsoft.com/en-us/library/office/ff837902.aspx
### `LANDSCAPE`
```python
LANDSCAPE = (1, 'landscape', 'Landscape orientation.')
```
Landscape orientation.
### `PORTRAIT`
```python
PORTRAIT = (0, 'portrait', 'Portrait orientation.')
```
Portrait orientation.
## `WD_SECTION`
```python
WD_SECTION = WD_SECTION_START
```
## `WD_SECTION_START`
Bases: `BaseXmlEnum`
Alias: **WD_SECTION**
Specifies the start type of a section break.
Example::
from docx.enum.section import WD_SECTION
section = document.sections[0] section.start_type = WD_SECTION.NEW_PAGE
MS API name: `WdSectionStart`
MS API URL: http://msdn.microsoft.com/en-us/library/office/ff840975.aspx
### `CONTINUOUS`
```python
CONTINUOUS = (0, 'continuous', 'Continuous section break.')
```
Continuous section break.
### `EVEN_PAGE`
```python
EVEN_PAGE = (3, 'evenPage', 'Even pages section break.')
```
Even pages section break.
### `NEW_COLUMN`
```python
NEW_COLUMN = (1, 'nextColumn', 'New column section break.')
```
New column section break.
### `NEW_PAGE`
```python
NEW_PAGE = (2, 'nextPage', 'New page section break.')
```
New page section break.
### `ODD_PAGE`
```python
ODD_PAGE = (4, 'oddPage', 'Section begins on next odd page.')
```
Section begins on next odd page.
@@ -1,49 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.enum.shape`
Enumerations related to DrawingML shapes in WordprocessingML files.
## `WD_INLINE_SHAPE`
```python
WD_INLINE_SHAPE = WD_INLINE_SHAPE_TYPE
```
## `WD_INLINE_SHAPE_TYPE`
Bases: `Enum`
Corresponds to WdInlineShapeType enumeration.
http://msdn.microsoft.com/en-us/library/office/ff192587.aspx.
### `CHART`
```python
CHART = 12
```
### `LINKED_PICTURE`
```python
LINKED_PICTURE = 4
```
### `NOT_IMPLEMENTED`
```python
NOT_IMPLEMENTED = -6
```
### `PICTURE`
```python
PICTURE = 3
```
### `SMART_ART`
```python
SMART_ART = 15
```
File diff suppressed because it is too large. Load diff
@@ -1,190 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.enum.table`
Enumerations related to tables in WordprocessingML files.
## `WD_ALIGN_VERTICAL`
```python
WD_ALIGN_VERTICAL = WD_CELL_VERTICAL_ALIGNMENT
```
## `WD_CELL_VERTICAL_ALIGNMENT`
Bases: `BaseXmlEnum`
Alias: **WD_ALIGN_VERTICAL**
Specifies the vertical alignment of text in one or more cells of a table.
Example::
from docx.enum.table import WD_ALIGN_VERTICAL
table = document.add_table(3, 3)
table.cell(0, 0).vertical_alignment = WD_ALIGN_VERTICAL.BOTTOM
MS API name: `WdCellVerticalAlignment`
https://msdn.microsoft.com/en-us/library/office/ff193345.aspx
### `BOTH`
```python
BOTH = (101, 'both', "This is an option in the OpenXml spec, but not in Word itself. It's not clear what Word behavior this setting produces. If you find out please let us know and we'll update this documentation. Otherwise, probably best to avoid this option.")
```
This is an option in the OpenXml spec, but not in Word itself.
It's not clear what Word behavior this setting produces. If you find out please let
us know and we'll update this documentation. Otherwise, probably best to avoid this
option.
### `BOTTOM`
```python
BOTTOM = (3, 'bottom', 'Text is aligned to the bottom border of the cell.')
```
Text is aligned to the bottom border of the cell.
### `CENTER`
```python
CENTER = (1, 'center', 'Text is aligned to the center of the cell.')
```
Text is aligned to the center of the cell.
### `TOP`
```python
TOP = (0, 'top', 'Text is aligned to the top border of the cell.')
```
Text is aligned to the top border of the cell.
## `WD_ROW_HEIGHT`
```python
WD_ROW_HEIGHT = WD_ROW_HEIGHT_RULE
```
## `WD_ROW_HEIGHT_RULE`
Bases: `BaseXmlEnum`
Alias: **WD_ROW_HEIGHT**
Specifies the rule for determining the height of a table row
Example::
from docx.enum.table import WD_ROW_HEIGHT_RULE
table = document.add_table(3, 3)
table.rows[0].height_rule = WD_ROW_HEIGHT_RULE.EXACTLY
MS API name: `WdRowHeightRule`
https://msdn.microsoft.com/en-us/library/office/ff193620.aspx
### `AT_LEAST`
```python
AT_LEAST = (1, 'atLeast', 'The row height is at least a minimum specified value.')
```
The row height is at least a minimum specified value.
### `AUTO`
```python
AUTO = (0, 'auto', 'The row height is adjusted to accommodate the tallest value in the row.')
```
The row height is adjusted to accommodate the tallest value in the row.
### `EXACTLY`
```python
EXACTLY = (2, 'exact', 'The row height is an exact value.')
```
The row height is an exact value.
## `WD_TABLE_ALIGNMENT`
Bases: `BaseXmlEnum`
Specifies table justification type.
Example::
from docx.enum.table import WD_TABLE_ALIGNMENT
table = document.add_table(3, 3)
table.alignment = WD_TABLE_ALIGNMENT.CENTER
MS API name: `WdRowAlignment`
http://office.microsoft.com/en-us/word-help/HV080607259.aspx
### `CENTER`
```python
CENTER = (1, 'center', 'Center-aligned.')
```
Center-aligned.
### `LEFT`
```python
LEFT = (0, 'left', 'Left-aligned')
```
Left-aligned
### `RIGHT`
```python
RIGHT = (2, 'right', 'Right-aligned.')
```
Right-aligned.
## `WD_TABLE_DIRECTION`
Bases: `BaseEnum`
Specifies the direction in which an application orders cells in the specified
table or row.
Example::
from docx.enum.table import WD_TABLE_DIRECTION
table = document.add_table(3, 3)
table.direction = WD_TABLE_DIRECTION.RTL
MS API name: `WdTableDirection`
http://msdn.microsoft.com/en-us/library/ff835141.aspx
### `LTR`
```python
LTR = (0, 'The table or row is arranged with the first column in the leftmost position.')
```
The table or row is arranged with the first column in the leftmost position.
### `RTL`
```python
RTL = (1, 'The table or row is arranged with the first column in the rightmost position.')
```
The table or row is arranged with the first column in the rightmost position.
@@ -1,725 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.enum.text`
Enumerations related to text in WordprocessingML files.
## `WD_ALIGN_PARAGRAPH`
```python
WD_ALIGN_PARAGRAPH = WD_PARAGRAPH_ALIGNMENT
```
## `WD_BREAK`
```python
WD_BREAK = WD_BREAK_TYPE
```
## `WD_BREAK_TYPE`
Bases: `Enum`
Corresponds to WdBreakType enumeration.
http://msdn.microsoft.com/en-us/library/office/ff195905.aspx.
### `COLUMN`
```python
COLUMN = 8
```
### `LINE`
```python
LINE = 6
```
### `LINE_CLEAR_ALL`
```python
LINE_CLEAR_ALL = 11
```
### `LINE_CLEAR_LEFT`
```python
LINE_CLEAR_LEFT = 9
```
### `LINE_CLEAR_RIGHT`
```python
LINE_CLEAR_RIGHT = 10
```
### `PAGE`
```python
PAGE = 7
```
### `SECTION_CONTINUOUS`
```python
SECTION_CONTINUOUS = 3
```
### `SECTION_EVEN_PAGE`
```python
SECTION_EVEN_PAGE = 4
```
### `SECTION_NEXT_PAGE`
```python
SECTION_NEXT_PAGE = 2
```
### `SECTION_ODD_PAGE`
```python
SECTION_ODD_PAGE = 5
```
### `TEXT_WRAPPING`
```python
TEXT_WRAPPING = 11
```
## `WD_COLOR`
```python
WD_COLOR = WD_COLOR_INDEX
```
## `WD_COLOR_INDEX`
Bases: `BaseXmlEnum`
Specifies a standard preset color to apply.
Used for font highlighting and perhaps other applications.
* MS API name: `WdColorIndex`
* URL: https://msdn.microsoft.com/EN-US/library/office/ff195343.aspx
### `AUTO`
```python
AUTO = (0, 'default', 'Automatic color. Default; usually black.')
```
Automatic color. Default; usually black.
### `BLACK`
```python
BLACK = (1, 'black', 'Black color.')
```
Black color.
### `BLUE`
```python
BLUE = (2, 'blue', 'Blue color')
```
Blue color
### `BRIGHT_GREEN`
```python
BRIGHT_GREEN = (4, 'green', 'Bright green color.')
```
Bright green color.
### `DARK_BLUE`
```python
DARK_BLUE = (9, 'darkBlue', 'Dark blue color.')
```
Dark blue color.
### `DARK_RED`
```python
DARK_RED = (13, 'darkRed', 'Dark red color.')
```
Dark red color.
### `DARK_YELLOW`
```python
DARK_YELLOW = (14, 'darkYellow', 'Dark yellow color.')
```
Dark yellow color.
### `GRAY_25`
```python
GRAY_25 = (16, 'lightGray', '25% shade of gray color.')
```
25% shade of gray color.
### `GRAY_50`
```python
GRAY_50 = (15, 'darkGray', '50% shade of gray color.')
```
50% shade of gray color.
### `GREEN`
```python
GREEN = (11, 'darkGreen', 'Green color.')
```
Green color.
### `INHERITED`
```python
INHERITED = (-1, None, 'Color is inherited from the style hierarchy.')
```
Color is inherited from the style hierarchy.
### `PINK`
```python
PINK = (5, 'magenta', 'Pink color.')
```
Pink color.
### `RED`
```python
RED = (6, 'red', 'Red color.')
```
Red color.
### `TEAL`
```python
TEAL = (10, 'darkCyan', 'Teal color.')
```
Teal color.
### `TURQUOISE`
```python
TURQUOISE = (3, 'cyan', 'Turquoise color.')
```
Turquoise color.
### `VIOLET`
```python
VIOLET = (12, 'darkMagenta', 'Violet color.')
```
Violet color.
### `WHITE`
```python
WHITE = (8, 'white', 'White color.')
```
White color.
### `YELLOW`
```python
YELLOW = (7, 'yellow', 'Yellow color.')
```
Yellow color.
## `WD_LINE_SPACING`
Bases: `BaseXmlEnum`
Specifies a line spacing format to be applied to a paragraph.
Example::
from docx.enum.text import WD_LINE_SPACING
paragraph = document.add_paragraph()
paragraph.line_spacing_rule = WD_LINE_SPACING.EXACTLY
MS API name: `WdLineSpacing`
URL: http://msdn.microsoft.com/en-us/library/office/ff844910.aspx
### `AT_LEAST`
```python
AT_LEAST = (3, 'atLeast', 'Minimum line spacing is specified amount. Amount is specified separately.')
```
Minimum line spacing is specified amount. Amount is specified separately.
### `DOUBLE`
```python
DOUBLE = (2, 'UNMAPPED', 'Double spaced.')
```
Double spaced.
### `EXACTLY`
```python
EXACTLY = (4, 'exact', 'Line spacing is exactly specified amount. Amount is specified separately.')
```
Line spacing is exactly specified amount. Amount is specified separately.
### `MULTIPLE`
```python
MULTIPLE = (5, 'auto', 'Line spacing is specified as multiple of line heights. Changing font size will change line spacing proportionately.')
```
Line spacing is specified as multiple of line heights. Changing font size will
change the line spacing proportionately.
### `ONE_POINT_FIVE`
```python
ONE_POINT_FIVE = (1, 'UNMAPPED', 'Space-and-a-half line spacing.')
```
Space-and-a-half line spacing.
### `SINGLE`
```python
SINGLE = (0, 'UNMAPPED', 'Single spaced (default).')
```
Single spaced (default).
## `WD_PARAGRAPH_ALIGNMENT`
Bases: `BaseXmlEnum`
Alias: **WD_ALIGN_PARAGRAPH**
Specifies paragraph justification type.
Example::
from docx.enum.text import WD_ALIGN_PARAGRAPH
paragraph = document.add_paragraph()
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
### `CENTER`
```python
CENTER = (1, 'center', 'Center-aligned.')
```
Center-aligned.
### `DISTRIBUTE`
```python
DISTRIBUTE = (4, 'distribute', 'Paragraph characters are distributed to fill entire width of paragraph.')
```
Paragraph characters are distributed to fill entire width of paragraph.
### `JUSTIFY`
```python
JUSTIFY = (3, 'both', 'Fully justified.')
```
Fully justified.
### `JUSTIFY_HI`
```python
JUSTIFY_HI = (7, 'highKashida', 'Justified with a high character compression ratio.')
```
Justified with a high character compression ratio.
### `JUSTIFY_LOW`
```python
JUSTIFY_LOW = (8, 'lowKashida', 'Justified with a low character compression ratio.')
```
Justified with a low character compression ratio.
### `JUSTIFY_MED`
```python
JUSTIFY_MED = (5, 'mediumKashida', 'Justified with a medium character compression ratio.')
```
Justified with a medium character compression ratio.
### `LEFT`
```python
LEFT = (0, 'left', 'Left-aligned')
```
Left-aligned
### `RIGHT`
```python
RIGHT = (2, 'right', 'Right-aligned.')
```
Right-aligned.
### `THAI_JUSTIFY`
```python
THAI_JUSTIFY = (9, 'thaiDistribute', 'Justified according to Thai formatting layout.')
```
Justified according to Thai formatting layout.
## `WD_TAB_ALIGNMENT`
Bases: `BaseXmlEnum`
Specifies the tab stop alignment to apply.
MS API name: `WdTabAlignment`
URL: https://msdn.microsoft.com/EN-US/library/office/ff195609.aspx
### `BAR`
```python
BAR = (4, 'bar', 'Bar-aligned.')
```
Bar-aligned.
### `CENTER`
```python
CENTER = (1, 'center', 'Center-aligned.')
```
Center-aligned.
### `CLEAR`
```python
CLEAR = (101, 'clear', 'Clear an inherited tab stop.')
```
Clear an inherited tab stop.
### `DECIMAL`
```python
DECIMAL = (3, 'decimal', 'Decimal-aligned.')
```
Decimal-aligned.
### `END`
```python
END = (102, 'end', 'Right-aligned. (deprecated)')
```
Right-aligned. (deprecated)
### `LEFT`
```python
LEFT = (0, 'left', 'Left-aligned.')
```
Left-aligned.
### `LIST`
```python
LIST = (6, 'list', 'List-aligned. (deprecated)')
```
List-aligned. (deprecated)
### `NUM`
```python
NUM = (103, 'num', 'Left-aligned. (deprecated)')
```
Left-aligned. (deprecated)
### `RIGHT`
```python
RIGHT = (2, 'right', 'Right-aligned.')
```
Right-aligned.
### `START`
```python
START = (104, 'start', 'Left-aligned. (deprecated)')
```
Left-aligned. (deprecated)
## `WD_TAB_LEADER`
Bases: `BaseXmlEnum`
Specifies the character to use as the leader with formatted tabs.
MS API name: `WdTabLeader`
URL: https://msdn.microsoft.com/en-us/library/office/ff845050.aspx
### `DASHES`
```python
DASHES = (2, 'hyphen', 'Dashes.')
```
Dashes.
### `DOTS`
```python
DOTS = (1, 'dot', 'Dots.')
```
Dots.
### `HEAVY`
```python
HEAVY = (4, 'heavy', 'A heavy line.')
```
A heavy line.
### `LINES`
```python
LINES = (3, 'underscore', 'Double lines.')
```
Double lines.
### `MIDDLE_DOT`
```python
MIDDLE_DOT = (5, 'middleDot', 'A vertically-centered dot.')
```
A vertically-centered dot.
### `SPACES`
```python
SPACES = (0, 'none', 'Spaces. Default.')
```
Spaces. Default.
## `WD_UNDERLINE`
Bases: `BaseXmlEnum`
Specifies the style of underline applied to a run of characters.
MS API name: `WdUnderline`
URL: http://msdn.microsoft.com/en-us/library/office/ff822388.aspx
### `DASH`
```python
DASH = (7, 'dash', 'Dashes.')
```
Dashes.
### `DASH_HEAVY`
```python
DASH_HEAVY = (23, 'dashedHeavy', 'Heavy dashes.')
```
Heavy dashes.
### `DASH_LONG`
```python
DASH_LONG = (39, 'dashLong', 'Long dashes.')
```
Long dashes.
### `DASH_LONG_HEAVY`
```python
DASH_LONG_HEAVY = (55, 'dashLongHeavy', 'Long heavy dashes.')
```
Long heavy dashes.
### `DOTTED`
```python
DOTTED = (4, 'dotted', 'Dots.')
```
Dots.
### `DOTTED_HEAVY`
```python
DOTTED_HEAVY = (20, 'dottedHeavy', 'Heavy dots.')
```
Heavy dots.
### `DOT_DASH`
```python
DOT_DASH = (9, 'dotDash', 'Alternating dots and dashes.')
```
Alternating dots and dashes.
### `DOT_DASH_HEAVY`
```python
DOT_DASH_HEAVY = (25, 'dashDotHeavy', 'Alternating heavy dots and heavy dashes.')
```
Alternating heavy dots and heavy dashes.
### `DOT_DOT_DASH`
```python
DOT_DOT_DASH = (10, 'dotDotDash', 'An alternating dot-dot-dash pattern.')
```
An alternating dot-dot-dash pattern.
### `DOT_DOT_DASH_HEAVY`
```python
DOT_DOT_DASH_HEAVY = (26, 'dashDotDotHeavy', 'An alternating heavy dot-dot-dash pattern.')
```
An alternating heavy dot-dot-dash pattern.
### `DOUBLE`
```python
DOUBLE = (3, 'double', 'A double line.')
```
A double line.
### `INHERITED`
```python
INHERITED = (-1, None, 'Inherit underline setting from containing paragraph.')
```
Inherit underline setting from containing paragraph.
### `NONE`
```python
NONE = (0, 'none', 'No underline.\n\nThis setting overrides any inherited underline value, so can be used to remove underline from a run that inherits underlining from its containing paragraph. Note this is not the same as assigning `None` to Run.underline. `None` is a valid assignment value, but causes the run to inherit its underline value. Assigning `WD_UNDERLINE.NONE` causes underlining to be unconditionally turned off.')
```
No underline.
This setting overrides any inherited underline value, so can be used to remove
underline from a run that inherits underlining from its containing paragraph. Note
this is not the same as assigning `None` to Run.underline. `None` is a valid
assignment value, but causes the run to inherit its underline value. Assigning
``WD_UNDERLINE.NONE`` causes underlining to be unconditionally turned off.
### `SINGLE`
```python
SINGLE = (1, 'single', 'A single line.\n\nNote that this setting is write-only in the sense that `True` (rather than `WD_UNDERLINE.SINGLE`) is returned for a run having this setting.')
```
A single line.
Note that this setting is write-only in the sense that `True`
(rather than ``WD_UNDERLINE.SINGLE``) is returned for a run having this setting.
### `THICK`
```python
THICK = (6, 'thick', 'A single thick line.')
```
A single thick line.
### `WAVY`
```python
WAVY = (11, 'wave', 'A single wavy line.')
```
A single wavy line.
### `WAVY_DOUBLE`
```python
WAVY_DOUBLE = (43, 'wavyDouble', 'A double wavy line.')
```
A double wavy line.
### `WAVY_HEAVY`
```python
WAVY_HEAVY = (27, 'wavyHeavy', 'A heavy wavy line.')
```
A heavy wavy line.
### `WORDS`
```python
WORDS = (2, 'words', 'Underline individual words only.')
```
Underline individual words only.
@@ -1,96 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.errors`
Typed refusals for paper-docx safe-editing APIs.
## `AmbiguousTargetError`
Bases: `PaperRefusal`
The target specification matches more than one location.
Supply a more specific exact target: for example a live span or block, a
narrower story, or an explicit `nth` when supported. To inspect contextual
ranking without hiding candidates, use `find_text(..., near=...)`.
## `BoundaryViolationError`
Bases: `PaperRefusal`
The operation would cross a structural boundary it must respect.
Examples: a character-level replacement spanning a paragraph boundary or
entering/leaving a content control; a block operation selecting
paragraphs that do not share one parent.
## `DocumentProtectedError`
Bases: `PaperRefusal`
The document enforces an editing restriction this operation ignores.
Word honors `w:documentProtection` (read-only, forms-only, comments-only,
tracked-changes-enforced); silently editing a locked template reports
false state. Protection is ADVISORY, not security — after reviewing why
the document is locked, call
`docx.protection.acknowledge_protection(document)` to proceed. paper-docx
never strips the protection setting itself.
## `MalformedPackageError`
Bases: `PaperRefusal`
The file is not a package this library can safely read.
Raised for a damaged or inconsistent ZIP envelope, an encrypted or
unsupported member, a member name that cannot denote one part, and a
`[Content_Types].xml` or relationship graph that does not describe a
readable package. Validation runs before any XML is parsed or any output is
replaced, so this is a safe refusal: nothing was read into the object model
and nothing on disk was touched.
It does not mean the file is too large. Size and member-count ceilings were
removed once Word was measured opening documents past all of them.
## `PaperRefusal`
Bases: `Exception`
Base for every safe refusal raised by paper-docx APIs.
A refused operation mutated nothing, in memory or on disk. The message
states what was found and why it was unsafe to proceed.
## `RelationshipPolicyError`
Bases: `PaperRefusal`
The operation would create or modify a package relationship unsafely.
## `TargetNotFoundError`
Bases: `PaperRefusal`
No location matches the target specification.
Also raised when a previously-valid anchor or span has gone stale — the
underlying content changed since it was captured.
## `UnsupportedStructureError`
Bases: `PaperRefusal`
The target involves structure this operation does not safely support.
Examples: text inside a tracked deletion or field instruction; a table
with merged or nested cells; numbering that would require authoring new
definitions.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
@@ -1,25 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.exceptions`
Exceptions used with python-docx.
## `InvalidSpanError`
Bases: `PythonDocxError`
Raised when an invalid merge region is specified in a request to merge table
cells.
## `InvalidXmlError`
Bases: `PythonDocxError`
Raised when invalid XML is encountered, such as on attempt to access a missing
required child element.
## `PythonDocxError`
Bases: `Exception`
Generic error class.
@@ -1,90 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.fields`
Field authoring — formulas, never values.
## `add_caption`
```python
add_caption(paragraph: 'Paragraph', *, label: str = 'Figure', description: str = '') -> None
```
Append a SEQ caption (`Figure 1`, `Table 1`) to `paragraph`.
Writes the field rather than the displayed integer, so Word recomputes the number on
open. Sets the paragraph style to "Caption" without checking that the style exists.
Refuses a protected document and an unknown label.
## `add_date_field`
```python
add_date_field(paragraph: 'Paragraph', *, date_format: Optional[str] = None) -> None
```
Append a DATE field; `date_format` is Word's \@ picture, for example 'MMMM d, yyyy'.
The result stays a placeholder until a renderer opens the file; this package never
computes a date into the result. Refuses a protected document.
## `add_page_count_field`
```python
add_page_count_field(paragraph: 'Paragraph') -> None
```
Append a NUMPAGES field, typically to a footer paragraph.
Writes the field, not a number, so Word computes the count on open. Refuses a protected
document.
## `add_page_number_field`
```python
add_page_number_field(paragraph: 'Paragraph') -> None
```
Append a PAGE field, typically to a footer paragraph.
Writes the field, not a number, so Word computes the page on open. Refuses a protected
document.
## `add_reference_field`
```python
add_reference_field(paragraph: 'Paragraph', *, bookmark: str, kind: str = 'text') -> None
```
Append a cross-reference to `bookmark`: its text (`kind="text"`), page (`"page"`), or
paragraph number (`"number"`).
Writes a REF field, so Word recomputes it on open. Refuses a protected document and an
unknown bookmark name.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `insert_toc_after`
```python
insert_toc_after(document: 'Document', anchor: 'Document', *, levels: Tuple[int, int] = (1, 3)) -> None
```
Insert a TOC field in a new paragraph after `anchor`.
Marked dirty so the renderer builds the real table on open; heading `levels` maps to the
\o "1-3" switch. Refuses a protected document, and an anchor that is missing, ambiguous
foreign, or spans more than one paragraph.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,94 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.formatting`
Effective-format resolver — read-only, provenance-bearing.
## `EffectiveFormat`
```python
EffectiveFormat(properties: Dict[str, ResolvedValue], unresolved: Tuple[str, ...]) -> None
```
The resolved property map, plus what is declared unresolvable.
### `properties`
```python
properties: Dict[str, ResolvedValue]
```
### `to_dict`
```python
to_dict() -> dict
```
### `unresolved`
```python
unresolved: Tuple[str, ...]
```
### `value_of`
```python
value_of(key: str)
```
## `ResolvedValue`
```python
ResolvedValue(value: object, source: str, chain: Tuple[str, ...] = ()) -> None
```
One property's effective value and where it came from.
`source` is "direct", "character_style:<id>", "paragraph_style:<id>", "doc_defaults",
"toggle_xor", "agreeing_layers", "mixed", or "none". `chain` holds the contributing
layers.
### `chain`
```python
chain: Tuple[str, ...] = ()
```
### `source`
```python
source: str
```
### `to_dict`
```python
to_dict() -> dict
```
### `value`
```python
value: object
```
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `format_of`
```python
format_of(target) -> EffectiveFormat
```
The effective formatting of a `Run`, `Paragraph` or `Span`.
Read-only and provenance-bearing: each `ResolvedValue` says where its value came from.
Runs resolve the supported run-property subset; paragraphs resolve alignment and style
plus the run defaults their style chain implies; spans resolve every run they touch and
report "mixed" where runs disagree. Raises `TypeError` for anything else.
@@ -1,35 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.bmp`
## `Bmp`
Bases: `BaseImageHeader`
Image header parser for BMP images.
### `content_type`
```python
content_type
```
MIME content type for this image, unconditionally `image/bmp` for BMP
images.
### `default_ext`
```python
default_ext
```
Default filename extension, always 'bmp' for BMP images.
### `from_stream`
```python
from_stream(stream)
```
Return `Bmp` instance having header properties parsed from the BMP image in
`stream`.
@@ -1,469 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.constants`
Constants specific the the image sub-package.
## `JPEG_MARKER_CODE`
JPEG marker codes.
### `APP0`
```python
APP0 = b'\xe0'
```
### `APP1`
```python
APP1 = b'\xe1'
```
### `APP2`
```python
APP2 = b'\xe2'
```
### `APP3`
```python
APP3 = b'\xe3'
```
### `APP4`
```python
APP4 = b'\xe4'
```
### `APP5`
```python
APP5 = b'\xe5'
```
### `APP6`
```python
APP6 = b'\xe6'
```
### `APP7`
```python
APP7 = b'\xe7'
```
### `APP8`
```python
APP8 = b'\xe8'
```
### `APP9`
```python
APP9 = b'\xe9'
```
### `APPA`
```python
APPA = b'\xea'
```
### `APPB`
```python
APPB = b'\xeb'
```
### `APPC`
```python
APPC = b'\xec'
```
### `APPD`
```python
APPD = b'\xed'
```
### `APPE`
```python
APPE = b'\xee'
```
### `APPF`
```python
APPF = b'\xef'
```
### `DAC`
```python
DAC = b'\xcc'
```
### `DHP`
```python
DHP = b'\xde'
```
### `DHT`
```python
DHT = b'\xc4'
```
### `DNL`
```python
DNL = b'\xdc'
```
### `DQT`
```python
DQT = b'\xdb'
```
### `DRI`
```python
DRI = b'\xdd'
```
### `EOI`
```python
EOI = b'\xd9'
```
### `EXP`
```python
EXP = b'\xdf'
```
### `JPG`
```python
JPG = b'\xc8'
```
### `RST0`
```python
RST0 = b'\xd0'
```
### `RST1`
```python
RST1 = b'\xd1'
```
### `RST2`
```python
RST2 = b'\xd2'
```
### `RST3`
```python
RST3 = b'\xd3'
```
### `RST4`
```python
RST4 = b'\xd4'
```
### `RST5`
```python
RST5 = b'\xd5'
```
### `RST6`
```python
RST6 = b'\xd6'
```
### `RST7`
```python
RST7 = b'\xd7'
```
### `SOF0`
```python
SOF0 = b'\xc0'
```
### `SOF1`
```python
SOF1 = b'\xc1'
```
### `SOF2`
```python
SOF2 = b'\xc2'
```
### `SOF3`
```python
SOF3 = b'\xc3'
```
### `SOF5`
```python
SOF5 = b'\xc5'
```
### `SOF6`
```python
SOF6 = b'\xc6'
```
### `SOF7`
```python
SOF7 = b'\xc7'
```
### `SOF9`
```python
SOF9 = b'\xc9'
```
### `SOFA`
```python
SOFA = b'\xca'
```
### `SOFB`
```python
SOFB = b'\xcb'
```
### `SOFD`
```python
SOFD = b'\xcd'
```
### `SOFE`
```python
SOFE = b'\xce'
```
### `SOFF`
```python
SOFF = b'\xcf'
```
### `SOF_MARKER_CODES`
```python
SOF_MARKER_CODES = (SOF0, SOF1, SOF2, SOF3, SOF5, SOF6, SOF7, SOF9, SOFA, SOFB, SOFD, SOFE, SOFF)
```
### `SOI`
```python
SOI = b'\xd8'
```
### `SOS`
```python
SOS = b'\xda'
```
### `STANDALONE_MARKERS`
```python
STANDALONE_MARKERS = (TEM, SOI, EOI, RST0, RST1, RST2, RST3, RST4, RST5, RST6, RST7)
```
### `TEM`
```python
TEM = b'\x01'
```
### `is_standalone`
```python
is_standalone(marker_code)
```
### `marker_names`
```python
marker_names = {b'\x00': 'UNKNOWN', b'\xc0': 'SOF0', b'\xc2': 'SOF2', b'\xc4': 'DHT', b'\xda': 'SOS', b'\xd8': 'SOI', b'\xd9': 'EOI', b'\xdb': 'DQT', b'\xe0': 'APP0', b'\xe1': 'APP1', b'\xe2': 'APP2', b'\xed': 'APP13', b'\xee': 'APP14'}
```
## `MIME_TYPE`
Image content types.
### `BMP`
```python
BMP = 'image/bmp'
```
### `GIF`
```python
GIF = 'image/gif'
```
### `JPEG`
```python
JPEG = 'image/jpeg'
```
### `PNG`
```python
PNG = 'image/png'
```
### `TIFF`
```python
TIFF = 'image/tiff'
```
## `PNG_CHUNK_TYPE`
PNG chunk type names.
### `IEND`
```python
IEND = 'IEND'
```
### `IHDR`
```python
IHDR = 'IHDR'
```
### `pHYs`
```python
pHYs = 'pHYs'
```
## `TIFF_FLD`
```python
TIFF_FLD = TIFF_FLD_TYPE
```
## `TIFF_FLD_TYPE`
Tag codes for TIFF Image File Directory (IFD) entries.
### `ASCII`
```python
ASCII = 2
```
### `BYTE`
```python
BYTE = 1
```
### `LONG`
```python
LONG = 4
```
### `RATIONAL`
```python
RATIONAL = 5
```
### `SHORT`
```python
SHORT = 3
```
### `field_type_names`
```python
field_type_names = {1: 'BYTE', 2: 'ASCII char', 3: 'SHORT', 4: 'LONG', 5: 'RATIONAL'}
```
## `TIFF_TAG`
Tag codes for TIFF Image File Directory (IFD) entries.
### `IMAGE_LENGTH`
```python
IMAGE_LENGTH = 257
```
### `IMAGE_WIDTH`
```python
IMAGE_WIDTH = 256
```
### `RESOLUTION_UNIT`
```python
RESOLUTION_UNIT = 296
```
### `X_RESOLUTION`
```python
X_RESOLUTION = 282
```
### `Y_RESOLUTION`
```python
Y_RESOLUTION = 283
```
### `tag_names`
```python
tag_names = {254: 'NewSubfileType', 256: 'ImageWidth', 257: 'ImageLength', 258: 'BitsPerSample', 259: 'Compression', 262: 'PhotometricInterpretation', 270: 'ImageDescription', 271: 'Make', 272: 'Model', 273: 'StripOffsets', 274: 'Orientation', 277: 'SamplesPerPixel', 279: 'StripByteCounts', 282: 'XResolution', 283: 'YResolution', 284: 'PlanarConfiguration', 296: 'ResolutionUnit', 305: 'Software', 306: 'DateTime', 531: 'YCbCrPositioning', 34665: 'ExifTag', 34853: 'GPS IFD', 50341: 'PrintImageMatching'}
```
@@ -1,23 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.exceptions`
Exceptions specific the the image sub-package.
## `InvalidImageStreamError`
Bases: `Exception`
The recognized image stream appears to be corrupted.
## `UnexpectedEndOfFileError`
Bases: `Exception`
EOF was unexpectedly encountered while reading an image stream.
## `UnrecognizedImageError`
Bases: `Exception`
The provided image stream could not be recognized.
@@ -1,38 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.gif`
## `Gif`
Bases: `BaseImageHeader`
Image header parser for GIF images.
Note that the GIF format does not support resolution (DPI) information. Both
horizontal and vertical DPI default to 72.
### `content_type`
```python
content_type
```
MIME content type for this image, unconditionally `image/gif` for GIF
images.
### `default_ext`
```python
default_ext
```
Default filename extension, always 'gif' for GIF images.
### `from_stream`
```python
from_stream(stream)
```
Return `Gif` instance having header properties parsed from GIF image in
`stream`.
@@ -1,90 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.helpers`
## `BIG_ENDIAN`
```python
BIG_ENDIAN = '>'
```
## `LITTLE_ENDIAN`
```python
LITTLE_ENDIAN = '<'
```
## `StreamReader`
```python
StreamReader(stream, byte_order, base_offset = 0)
```
Wraps a file-like object to provide access to structured data from a binary file.
Byte-order is configurable. `base_offset` is added to any base value provided to
calculate actual location for reads.
### `read`
```python
read(count)
```
Allow pass-through read() call.
### `read_byte`
```python
read_byte(base, offset = 0)
```
Return the int value of the byte at the file position defined by
self._base_offset + `base` + `offset`.
If `base` is None, the byte is read from the current position in the stream.
### `read_long`
```python
read_long(base, offset = 0)
```
Return the int value of the four bytes at the file position defined by
self._base_offset + `base` + `offset`.
If `base` is None, the long is read from the current position in the stream. The
endian setting of this instance is used to interpret the byte layout of the
long.
### `read_short`
```python
read_short(base, offset = 0)
```
Return the int value of the two bytes at the file position determined by
`base` and `offset`, similarly to ``read_long()`` above.
### `read_str`
```python
read_str(char_count, base, offset = 0)
```
Return a string containing the `char_count` bytes at the file position
determined by self._base_offset + `base` + `offset`.
### `seek`
```python
seek(base, offset = 0)
```
### `tell`
```python
tell()
```
Allow pass-through tell() call.
@@ -1,214 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.image`
Provides objects that can characterize image streams.
## `BaseImageHeader`
```python
BaseImageHeader(px_width: int, px_height: int, horz_dpi: int, vert_dpi: int)
```
Base class for image header subclasses like `Jpeg` and `Tiff`.
### `content_type`
```python
content_type: str
```
Abstract property definition, must be implemented by all subclasses.
### `default_ext`
```python
default_ext: str
```
Default filename extension for images of this type.
An abstract property definition, must be implemented by all subclasses.
### `horz_dpi`
```python
horz_dpi
```
Integer dots per inch for the width of this image.
Defaults to 72 when not present in the file, as is often the case.
### `px_height`
```python
px_height
```
The vertical pixel dimension of the image.
### `px_width`
```python
px_width
```
The horizontal pixel dimension of the image.
### `vert_dpi`
```python
vert_dpi
```
Integer dots per inch for the height of this image.
Defaults to 72 when not present in the file, as is often the case.
## `Image`
```python
Image(blob: bytes, filename: str, image_header: BaseImageHeader)
```
Graphical image stream such as JPEG, PNG, or GIF with properties and methods
required by ImagePart.
### `blob`
```python
blob
```
The bytes of the image 'file'.
### `content_type`
```python
content_type: str
```
MIME content type for this image, e.g. ``'image/jpeg'`` for a JPEG image.
### `ext`
```python
ext()
```
The file extension for the image.
If an actual one is available from a load filename it is used. Otherwise a
canonical extension is assigned based on the content type. Does not contain the
leading period, e.g. 'jpg', not '.jpg'.
### `filename`
```python
filename
```
Original image file name, if loaded from disk, or a generic filename if
loaded from an anonymous stream.
### `from_blob`
```python
from_blob(blob: bytes) -> Image
```
Return a new `Image` subclass instance parsed from the image binary contained
in `blob`.
### `from_file`
```python
from_file(image_descriptor: str | IO[bytes])
```
Return a new `Image` subclass instance loaded from the image file identified
by `image_descriptor`, a path or file-like object.
### `height`
```python
height: Inches
```
A `Length` value representing the native height of the image, calculated from
the values of `px_height` and `vert_dpi`.
### `horz_dpi`
```python
horz_dpi: int
```
Integer dots per inch for the width of this image.
Defaults to 72 when not present in the file, as is often the case.
### `px_height`
```python
px_height: int
```
The vertical pixel dimension of the image.
### `px_width`
```python
px_width: int
```
The horizontal pixel dimension of the image.
### `scaled_dimensions`
```python
scaled_dimensions(width: int | Length | None = None, height: int | Length | None = None) -> Tuple[Length, Length]
```
(cx, cy) pair representing scaled dimensions of this image.
The native dimensions of the image are scaled by applying the following rules to
the `width` and `height` arguments.
* If both `width` and `height` are specified, the return value is (`width`,
`height`); no scaling is performed.
* If only one is specified, it is used to compute a scaling factor that is then
applied to the unspecified dimension, preserving the aspect ratio of the image.
* If both `width` and `height` are `None`, the native dimensions are returned.
The native dimensions are calculated using the dots-per-inch (dpi) value
embedded in the image, defaulting to 72 dpi if no value is specified, as is
often the case. The returned values are both `Length` objects.
### `sha1`
```python
sha1()
```
SHA1 hash digest of the image blob.
### `vert_dpi`
```python
vert_dpi: int
```
Integer dots per inch for the height of this image.
Defaults to 72 when not present in the file, as is often the case.
### `width`
```python
width: Inches
```
A `Length` value representing the native width of the image, calculated from
the values of `px_width` and `horz_dpi`.
@@ -1,58 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.jpeg`
Objects related to parsing headers of JPEG image streams.
## `Exif`
Bases: `Jpeg`
Image header parser for Exif image format.
### `from_stream`
```python
from_stream(stream)
```
Return `Exif` instance having header properties parsed from Exif image in
`stream`.
## `Jfif`
Bases: `Jpeg`
Image header parser for JFIF image format.
### `from_stream`
```python
from_stream(stream)
```
Return a `Jfif` instance having header properties parsed from image in
`stream`.
## `Jpeg`
Bases: `BaseImageHeader`
Base class for JFIF and EXIF subclasses.
### `content_type`
```python
content_type
```
MIME content type for this image, unconditionally `image/jpeg` for JPEG
images.
### `default_ext`
```python
default_ext
```
Default filename extension, always 'jpg' for JPG images.
@@ -1,11 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image`
Provides objects that can characterize image streams.
## `SIGNATURES`
```python
SIGNATURES = ((Png, 0, b'\x89PNG\r\n\x1a\n'), (Jfif, 6, b'JFIF'), (Exif, 6, b'Exif'), (Gif, 0, b'GIF87a'), (Gif, 0, b'GIF89a'), (Tiff, 0, b'MM\x00*'), (Tiff, 0, b'II*\x00'), (Bmp, 0, b'BM'))
```
@@ -1,35 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.png`
## `Png`
Bases: `BaseImageHeader`
Image header parser for PNG images.
### `content_type`
```python
content_type
```
MIME content type for this image, unconditionally `image/png` for PNG
images.
### `default_ext`
```python
default_ext
```
Default filename extension, always 'png' for PNG images.
### `from_stream`
```python
from_stream(stream)
```
Return a `Png` instance having header properties parsed from image in
`stream`.
@@ -1,37 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.image.tiff`
## `Tiff`
Bases: `BaseImageHeader`
Image header parser for TIFF images.
Handles both big and little endian byte ordering.
### `content_type`
```python
content_type
```
Return the MIME type of this TIFF image, unconditionally the string
``image/tiff``.
### `default_ext`
```python
default_ext
```
Default filename extension, always 'tiff' for TIFF images.
### `from_stream`
```python
from_stream(stream)
```
Return a `Tiff` instance containing the properties of the TIFF image in
`stream`.
File diff suppressed because it is too large. Load diff
@@ -1,42 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.links`
Create and retarget hyperlinks on existing text.
## `add_hyperlink`
```python
add_hyperlink(document: 'Document', span: 'Span', address: str) -> Hyperlink
```
Wrap `span`'s runs in a hyperlink pointing at `address`.
Visible text stays; Word shows it in the Hyperlink character style, which this defines
when the document lacks it. Refuses a protected document, a stale or foreign span, a span
crossing paragraphs, a field result, an existing hyperlink, and a data-bound, locked or
plain-text control surface.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `require_span_owner`
```python
require_span_owner(document: 'Document', span: 'Span', *, argument: str = 'span') -> None
```
Refuse a live span captured from a different document package.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,53 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.notes`
Create footnotes and endnotes, including the note body and the mark.
## `add_endnote`
```python
add_endnote(document: 'Document', span: 'Span', text: str) -> int
```
Attach an endnote to `span` and return its note id.
Creates the endnotes part on first use. Same refusals as `add_footnote`: protection, a
stale or foreign span, a span outside the main body, and one inside a text box, a field
result, or a data-bound, locked or plain-text control.
## `add_footnote`
```python
add_footnote(document: 'Document', span: 'Span', text: str) -> int
```
Attach a footnote to `span` and return its note id.
Creates the footnotes part on first use. Refuses a protected document, a stale or foreign
span, a span outside the main body, and one inside a text box, a field result, or a
data-bound, locked or plain-text control.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `require_span_owner`
```python
require_span_owner(document: 'Document', span: 'Span', *, argument: str = 'span') -> None
```
Refuse a live span captured from a different document package.
## `rollback_on_error`
```python
rollback_on_error(document: 'Document', *participants: Any) -> Generator[None, None, None]
```
Restore the live package and named mutable proxies after an error.
@@ -1,215 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.numbering`
Numbering enumeration, application, and minimal authoring (paper-docx).
## `NumberedParagraph`
```python
NumberedParagraph(story: str, index: int, num_id: int, level: int, text: str, table_cell: 'Optional[Tuple[int, int, int]]' = None) -> None
```
### `index`
```python
index: int
```
### `level`
```python
level: int
```
### `num_id`
```python
num_id: int
```
### `story`
```python
story: str
```
### `table_cell`
```python
table_cell: 'Optional[Tuple[int, int, int]]' = None
```
### `text`
```python
text: str
```
### `to_dict`
```python
to_dict() -> dict
```
## `NumberingDefinition`
```python
NumberingDefinition(num_id: int, abstract_num_id: int, levels: Tuple[NumberingLevel, ...]) -> None
```
### `abstract_num_id`
```python
abstract_num_id: int
```
### `levels`
```python
levels: Tuple[NumberingLevel, ...]
```
### `num_id`
```python
num_id: int
```
### `to_dict`
```python
to_dict() -> dict
```
## `NumberingLevel`
```python
NumberingLevel(level: int, num_fmt: Optional[str], lvl_text: Optional[str]) -> None
```
### `level`
```python
level: int
```
### `lvl_text`
```python
lvl_text: Optional[str]
```
### `num_fmt`
```python
num_fmt: Optional[str]
```
### `to_dict`
```python
to_dict() -> dict
```
## `NumberingReport`
```python
NumberingReport(definitions: Tuple[NumberingDefinition, ...], numbered_paragraphs: Tuple[NumberedParagraph, ...]) -> None
```
### `definitions`
```python
definitions: Tuple[NumberingDefinition, ...]
```
### `numbered_paragraphs`
```python
numbered_paragraphs: Tuple[NumberedParagraph, ...]
```
### `to_dict`
```python
to_dict() -> dict
```
## `apply_list_style`
```python
apply_list_style(paragraph: 'Paragraph', style_name: str) -> None
```
Apply the existing paragraph style `style_name` to `paragraph`.
Refuses a protected document, a style the document does not define, and a style whose
numbering binding does not resolve.
## `apply_numbering`
```python
apply_numbering(paragraph: 'Paragraph', *, num_id: int, level: int = 0) -> None
```
Give `paragraph` the existing numbering definition `num_id` at `level`.
Writes `w:numPr` into the paragraph's `w:pPr`. This never fabricates a definition; create
one deliberately with `ensure_bullet_definition` or `ensure_decimal_definition`. Refuses a
protected document, and a numId or level the document does not define.
## `check_install`
```python
check_install() -> None
```
Refuse when paper-docx and python-docx are both installed.
## `ensure_bullet_definition`
```python
ensure_bullet_definition(document: 'Document') -> int
```
The numId of a canonical bullet-list definition, creating it when absent.
Creates `/word/numbering.xml` and its relationship on first use. Reuse requires an exactly
canonical definition, so a hand-edited one of the same name is left alone and a new
definition is appended. Refuses a protected document.
## `ensure_decimal_definition`
```python
ensure_decimal_definition(document: 'Document') -> int
```
The numId of a canonical decimal-list definition, creating it when absent.
Same creation and exact-shape reuse rules as `ensure_bullet_definition`. Refuses a
protected document.
## `list_numbering`
```python
list_numbering(document: 'Document') -> NumberingReport
```
Every numbering definition and every numbered paragraph in `document`.
Walks all story parts and reports text in the "current" view. Refuses when a paragraph
resolves to a definition the document does not define, or to a level that definition
omits.
## `restart_numbering`
```python
restart_numbering(document: 'Document', *, num_id: int) -> int
```
A NEW numId continuing `num_id`'s formatting but restarting the count at 1.
Paragraphs are not re-pointed; re-point them yourself with `apply_numbering`. Refuses a
protected document, an unknown numId, a malformed `w:numId`, and a definition carrying
`w:lvlOverride`.
File diff suppressed because it is too large. Load diff
@@ -1,104 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.opc.coreprops`
Provides CoreProperties, Dublin-Core attributes of the document.
## `CoreProperties`
```python
CoreProperties(element: CT_CoreProperties)
```
Corresponds to part named ``/docProps/core.xml``, containing the core document
properties for this document package.
### `author`
```python
author
```
### `category`
```python
category
```
### `comments`
```python
comments
```
### `content_status`
```python
content_status
```
### `created`
```python
created
```
### `identifier`
```python
identifier
```
### `keywords`
```python
keywords
```
### `language`
```python
language
```
### `last_modified_by`
```python
last_modified_by
```
### `last_printed`
```python
last_printed
```
### `modified`
```python
modified
```
### `revision`
```python
revision
```
### `subject`
```python
subject
```
### `title`
```python
title
```
### `version`
```python
version
```
@@ -1,17 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.opc.exceptions`
Exceptions specific to python-opc.
## `OpcError`
Bases: `Exception`
Base error class for python-opc.
## `PackageNotFoundError`
Bases: `OpcError`
Raised when a package cannot be found at the specified path.
@@ -1,292 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.opc.oxml`
Temporary stand-in for main oxml module.
## `BaseOxmlElement`
Bases: `ElementBase`
Base class for all custom element classes, to add standardized behavior to all
classes in one place.
### `xml`
```python
xml: str
```
Return XML string for this element, suitable for testing purposes.
Pretty printed for readability and without an XML declaration at the top.
## `CT_Default`
Bases: `BaseOxmlElement`
`<Default>` element that appears in `[Content_Types].xml` part.
Used to specify a default content type to be applied to any part with the specified extension.
### `content_type`
```python
content_type
```
String held in the ``ContentType`` attribute of this ``<Default>``
element.
### `extension`
```python
extension
```
String held in the ``Extension`` attribute of this ``<Default>`` element.
### `new`
```python
new(ext: str, content_type: str)
```
Return a new ``<Default>`` element with attributes set to parameter values.
## `CT_Override`
Bases: `BaseOxmlElement`
``<Override>`` element, specifying the content type to be applied for a part with
the specified partname.
### `content_type`
```python
content_type
```
String held in the ``ContentType`` attribute of this ``<Override>``
element.
### `new`
```python
new(partname, content_type)
```
Return a new ``<Override>`` element with attributes set to parameter values.
### `partname`
```python
partname
```
String held in the ``PartName`` attribute of this ``<Override>`` element.
## `CT_Relationship`
Bases: `BaseOxmlElement`
`<Relationship>` element, representing a single relationship from source to target part.
### `new`
```python
new(rId: str, reltype: str, target: str, target_mode: str = RTM.INTERNAL)
```
Return a new ``<Relationship>`` element.
### `rId`
```python
rId
```
String held in the ``Id`` attribute of this ``<Relationship>`` element.
### `reltype`
```python
reltype
```
String held in the ``Type`` attribute of this ``<Relationship>`` element.
### `target_mode`
```python
target_mode
```
String held in the ``TargetMode`` attribute of this ``<Relationship>``
element, either ``Internal`` or ``External``.
Defaults to ``Internal``.
### `target_ref`
```python
target_ref
```
String held in the ``Target`` attribute of this ``<Relationship>``
element.
## `CT_Relationships`
Bases: `BaseOxmlElement`
``<Relationships>`` element, the root element in a .rels file.
### `Relationship_lst`
```python
Relationship_lst
```
Return a list containing all the ``<Relationship>`` child elements.
### `add_rel`
```python
add_rel(rId: str, reltype: str, target: str, is_external: bool = False)
```
Add a child ``<Relationship>`` element with attributes set according to
parameter values.
### `new`
```python
new() -> CT_Relationships
```
Return a new ``<Relationships>`` element.
### `xml`
```python
xml
```
Return XML string for this element, suitable for saving in a .rels stream,
not pretty printed and with an XML declaration at the top.
## `CT_Types`
Bases: `BaseOxmlElement`
``<Types>`` element, the container element for Default and Override elements in
[Content_Types].xml.
### `add_default`
```python
add_default(ext, content_type)
```
Add a child ``<Default>`` element with attributes set to parameter values.
### `add_override`
```python
add_override(partname, content_type)
```
Add a child ``<Override>`` element with attributes set to parameter
values.
### `defaults`
```python
defaults
```
### `new`
```python
new()
```
Return a new ``<Types>`` element.
### `overrides`
```python
overrides
```
## `ct_namespace`
```python
ct_namespace = element_class_lookup.get_namespace(nsmap['ct'])
```
## `element_class_lookup`
```python
element_class_lookup = etree.ElementNamespaceClassLookup()
```
## `nsmap`
```python
nsmap = {'ct': NS.OPC_CONTENT_TYPES, 'pr': NS.OPC_RELATIONSHIPS, 'r': NS.OFC_RELATIONSHIPS}
```
## `oxml_parser`
```python
oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False)
```
## `parse_xml`
```python
parse_xml(text: str) -> etree._Element
```
`etree.fromstring()` replacement that uses oxml parser.
## `pr_namespace`
```python
pr_namespace = element_class_lookup.get_namespace(nsmap['pr'])
```
## `qn`
```python
qn(tag: str) -> str
```
Stands for "qualified name", a utility function to turn a namespace prefixed tag
name into a Clark-notation qualified tag name for lxml.
For
example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``.
## `serialize_for_reading`
```python
serialize_for_reading(element: etree._Element) -> str
```
Serialize `element` to human-readable XML suitable for tests.
No XML declaration.
## `serialize_part_xml`
```python
serialize_part_xml(part_elm: etree._Element) -> bytes
```
Serialize `part_elm` etree element to XML suitable for storage as an XML part.
That is to say, no insignificant whitespace added for readability, and an
appropriate XML declaration added with UTF-8 encoding specified.
@@ -1,166 +0,0 @@
<!-- Generated from docx 0.2.0 docstrings by griffe2md. Do not edit: fix the docstring in the source. -->
# `docx.opc.package`
Objects that implement reading and writing OPC packages.
## `OpcPackage`
Main API class for `python-opc`.
A new instance is constructed by calling the `open` class method with a path
to a package file or file-like object containing one.
### `after_unmarshal`
```python
after_unmarshal()
```
Entry point for any post-unmarshaling processing.
May be overridden by subclasses without forwarding call to super.
### `core_properties`
```python
core_properties: CoreProperties
```
`CoreProperties` object providing read/write access to the Dublin Core
properties for this document.
### `iter_parts`
```python
iter_parts() -> Iterator[Part]
```
Generate exactly one reference to each of the parts in the package by
performing a depth-first traversal of the rels graph.
### `iter_rels`
```python
iter_rels() -> Iterator[_Relationship]
```
Generate exactly one reference to each relationship in the package by
performing a depth-first traversal of the rels graph.
### `load_rel`
```python
load_rel(reltype: str, target: Part | str, rId: str, is_external: bool = False)
```
Return newly added `_Relationship` instance of `reltype` between this part
and `target` with key `rId`.
Target mode is set to ``RTM.EXTERNAL`` if `is_external` is `True`. Intended for
use during load from a serialized package, where the rId is well known. Other
methods exist for adding a new relationship to the package during processing.
### `main_document_part`
```python
main_document_part
```
Return a reference to the main document part for this package.
Examples include a document part for a WordprocessingML package, a presentation
part for a PresentationML package, or a workbook part for a SpreadsheetML
package.
### `next_partname`
```python
next_partname(template: str) -> PackURI
```
Return a `PackURI` instance representing partname matching `template`.
The returned part-name has the next available numeric suffix to distinguish it
from other parts of its type. `template` is a printf (%)-style template string
containing a single replacement item, a '%d' to be used to insert the integer
portion of the partname. Example: "/word/header%d.xml"
### `open`
```python
open(pkg_file: str | IO[bytes]) -> Self
```
Return an `OpcPackage` instance loaded with the contents of `pkg_file`.
### `part_related_by`
```python
part_related_by(reltype: str) -> Part
```
Return part to which this package has a relationship of `reltype`.
Raises `KeyError` if no such relationship is found and `ValueError` if more than
one such relationship is found.
### `parts`
```python
parts: list[Part]
```
Return a list containing a reference to each of the parts in this package.
### `relate_to`
```python
relate_to(part: Part, reltype: str)
```
Return rId key of new or existing relationship to `part`.
If a relationship of `reltype` to `part` already exists, its rId is returned. Otherwise a
new relationship is created and that rId is returned.
### `rels`
```python
rels()
```
Return a reference to the `Relationships` instance holding the collection of
relationships for this package.
### `save`
```python
save(pkg_file: str | IO[bytes])
```
Save this package to `pkg_file`, a path or a stream positioned at byte zero.
Serializes to a sibling temp file, reparses it to confirm it opens, then replaces the
destination. A failed save leaves the original untouched and never leaves behind a
package Word cannot open. An existing destination keeps its permission bits. A symlink
destination is followed and its target replaced.
Raises `MalformedPackageError` when the serialized output will not reparse, and
`OSError` for a stream in append mode, a stream positioned past byte zero, or a
destination symlink that moves mid-save.
## `Unmarshaller`
Hosts static methods for unmarshalling a package from a `PackageReader`.
### `unmarshal`
```python
unmarshal(pkg_reader, package, part_factory)
```
Construct graph of parts and realized relationships based on the contents of
`pkg_reader`, delegating construction of each part to `part_factory`.
Package relationships are added to `pkg`.
Loaded 100 of 497 files, more files were not shown because too many files have changed in this diff. Show more