Metadata-Version: 2.4
Name: ebookerr-sdk
Version: 3.7.1
Summary: The ebookerr plugin SDK: the plugin contract, the plugin host, a test kit, the pack tool and helper kits.
Author: ArcCdr
License-Expression: MIT
Project-URL: Documentation, https://github.com/ArcCdr/lit-ebookerr/blob/main/docs/PLUGIN_DEV.md
Project-URL: API reference, https://arccdr.github.io/lit-ebookerr/
Project-URL: Source, https://github.com/ArcCdr/lit-ebookerr/tree/main/sdk
Project-URL: Plugins, https://github.com/ArcCdr/ebookerr-plugins
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: defusedxml>=0.7
Requires-Dist: tinycss2>=1.3
Requires-Dist: requests>=2.31
Requires-Dist: packaging>=24
Provides-Extra: text
Requires-Dist: charset-normalizer>=3.3; extra == "text"
Provides-Extra: generation
Requires-Dist: pillow>=10; extra == "generation"
Dynamic: license-file

# ebookerr-sdk

The toolkit for writing [ebookerr](https://github.com/ArcCdr/lit-ebookerr) plugins: the plugin
contract (`ebookerr_sdk.spi`), the host that runs a plugin (`ebookerr_sdk.host`), a test kit
(`ebookerr_sdk.testing`), the `pack` command, and helper kits for EPUB files, downloads,
validation, library servers and text generation.

- Guide: <https://github.com/ArcCdr/lit-ebookerr/blob/main/docs/PLUGIN_DEV.md>
- API reference: <https://arccdr.github.io/lit-ebookerr/>
- Plugins: <https://github.com/ArcCdr/ebookerr-plugins>

## Install

```bash
pip install "ebookerr-sdk==3.7.*"
```

The SDK's MAJOR.MINOR is the SPI version it speaks: `ebookerr-sdk` 3.7.x speaks SPI 3.7. It needs
Python 3.11 or later.

## Start a plugin

```bash
python -m ebookerr_sdk.pack new hello_book --type book
pytest hello_book/tests
python -m ebookerr_sdk.pack check hello_book
python -m ebookerr_sdk.pack build hello_book --out dist
```

`new` writes a plugin that passes `check` and its own test; `--type` is `source`, `epub`, `book`
or `catalog`. The guide explains what to write next and how to install the zip in ebookerr.

## Compatibility

| ebookerr | SPI | ebookerr-sdk |
| --- | --- | --- |
| 2.24 | 3.0 | 3.0.x |
| 2.24.1 | 3.1 | 3.1.x |
| 2.24.7 | 3.2 | 3.2.x |
| 2.24.9 | 3.3 | 3.3.x |
| 2.24.10 | 3.4 | 3.4.x |
| 2.24.11 | 3.5 | 3.5.x |
| 2.24.13 | 3.6 | 3.6.x |
| 2.24.14 | 3.7 | 3.7.x |

ebookerr loads a plugin when its `spi_version` has ebookerr's SPI MAJOR and a MINOR not above
ebookerr's; otherwise it shows the plugin as incompatible and offers a compatible release.

## Versioning

1. A MINOR release only adds: an optional manifest key whose default keeps today's behaviour, or
   a new protocol, role, operation, context method, event or trigger scope that you opt into.
2. Anything else — removing or renaming, changing a default or a meaning, making a key required,
   refusing a manifest that was valid — waits for the next MAJOR release.
3. ebookerr ignores manifest keys it does not know, with a warning; your plugin should ignore
   request keys it does not know. `python -m ebookerr_sdk.pack check` refuses unknown manifest
   keys and settings-field types, so a typo is caught before you release.
4. Before it is removed, a public item is deprecated: named in these release notes and logged
   once per plugin as a warning when used, it keeps working for at least two MINOR releases and
   six months.
5. An ebookerr release supports one SPI MAJOR, and moves to a new MAJOR only once every official
   plugin has a compatible release.
6. A PATCH release of the SDK fixes defects only; it never changes the public API.
7. The public API is `manifest.toml`, `ebookerr_sdk.spi`, `ebookerr_sdk.host`,
   `ebookerr_sdk.testing`, `python -m ebookerr_sdk.pack` and the kits `ebookerr_sdk.epub`,
   `.validate`, `.download`, `.generation`, `.providers` and `.domain`. `ebookerr_sdk.wire`,
   `.readpos`, `.registry_index` and every `_`-prefixed name are internal and may change in any
   release.

## Release notes

### 3.7.1

A title page's summary keeps its paragraphs: `TitlePageDocument.description()` — and so
`EpubDocument.read_metadata().description` — turns each block end and `<br>` into a line break and
reads the summary up to the next bold label, and `epub_book_fields` keeps those breaks
(`sanitize_multiline`) instead of folding the description into one line.

### 3.7.0

SPI 3.7. A plugin can check its settings when the user saves them, and the host sends each log
line to ebookerr as it is written, so the lines of a plugin that is stopped or crashes are kept;
later paragraphs of this section name each change. A plugin written for SPI 3.6 needs no change.

The host sends each line a plugin logs to ebookerr as a `log` frame when it is written, instead of in the answer at the end of the call, so the lines of a call that is stopped or crashes are kept; ebookerr keeps at most 500 lines of one call. `ebookerr_sdk.testing.run_wire` still returns them in the answer's `logs` list, and not among the other frames.

A plugin can check its own settings when the user saves them: declare `validator = true` in
`[settings_schema]`, implement `validate_settings(values)` (`SettingsValidating`) and return
`SettingsProblem`s — a blocking one refuses the save of its field, any other is shown as a
warning. The host serves it as the `validate_settings` op.

### 3.6.0

SPI 3.6. A library server can name a second reason it refused a reading position, and say which
places its bookmark stands for; later paragraphs of this section name each change. A plugin
written for SPI 3.5 needs no change.

`READ_STATE_WRITE_STATUSES` gains `moved`: a library server that now files the book under another
item than the one ebookerr links to answers `moved` instead of `stale`, so the reader can say what
happened; `stale` keeps its meaning (ebookerr's chapter table does not describe the file).

`ProviderBookmark.covers` (optional, default `None`) names the in-chapter progress range a server
would store at that bookmark; `capture_position` then treats a stored place inside it as unchanged,
so a server that rounds a position (to a page, to its own position grid) does not read ebookerr's
own write back as a move.

### 3.5.0

SPI 3.5. A library-server plugin can ask `capture_position` to warn about a book it cannot
capture only once; later sentences of this section name each change. A plugin written for SPI
3.4 needs no change.

`ebookerr_sdk.providers.capture_warnings.CaptureWarnings(ctx)` remembers, in the plugin's own state,
the capture failure last warned about for each server item; pass it as `capture_position(...,
warnings=)` and a book that keeps failing the same way is warned about once (again after a week, or
when the failure changes), then logged at DEBUG. `join_anchors` logs its summary at DEBUG.

`parse_text_to_book` decides the paragraph rule per chapter, so a text written one paragraph per
line keeps its paragraphs even when a blank line sets off its chapter headings, and a heading may
end in a full stop or a colon (`CHAPTER I.`, `Chapter One:`).

### 3.4.0

SPI 3.4. The domain and EPUB kits gain the helpers ebookerr's own doors share; later sentences
of this section name each one. A plugin written for SPI 3.3 needs no change.

`ebookerr_sdk.epub.opf.UNTYPED_IDENTIFIER_SCHEMES` names the identifier schemes a package cannot carry as a book identifier (`url`, `uri`, `uuid`, `calibre`, `http`, `https`, `urn`); `PackageDocument.set_typed_identifiers` no longer writes them, so writing back what `typed_identifiers` read changes nothing. `PackageDocument.set_title` drops a changed title's stale sort key (`calibre:title_sort`, or an EPUB 3 `file-as` refinement), so a reader re-derives it. `ebookerr_sdk.domain.authors` is the one parser of a book's `author` text: `split_authors` and `join_authors` read and write the names joined by `AUTHOR_SEPARATOR` (`" & "`, an `&` with whitespace on both sides), `authors_key` compares two texts whatever the order and case, and `includes_authors` tells whether a book is by given people. `ebookerr_sdk.domain.adoption.author_set` and `score_pair`'s author signal compare the set of names, so co-authors listed in another order match. `ebookerr_sdk.domain.ids.normalise_site`, `ebookerr_sdk.domain.series_rules` (`names_itself`, `without_self_references`) and `ebookerr_sdk.domain.metadata`'s `normalise_token_list`, `IDENTIFIER_RE`, `parse_identifiers` and `format_identifiers` are the helpers ebookerr itself uses for those fields. `ebookerr_sdk.domain.epub_metadata.epub_book_fields` maps what an EPUB says to the library fields every door stores — every author, the publisher unless it only names the site, typed identifiers, series, categories and tags — so a book pulled from a link and the same file uploaded agree. `epub_publisher` (the publisher an EPUB names, unless it only names the site) and `title_or_file_stem` (the title, or the file's name without its extension when the title is blank, `Untitled` or `Unknown`) are the two rules it is built from.

### 3.3.0

SPI 3.3. The EPUB kit gains helpers for the EPUBs ebookerr writes; later sentences of this
section name each one. A plugin written for SPI 3.2 needs no change. `build_epub` writes its package through `ebookerr_sdk.epub.builder`: the NCX `dtb:uid` is the OPF identifier and the package carries its creation date, so a built book validates without findings. `EpubDocument.set_title` writes a new title into the package and every table of contents (`NcxDocument.set_doc_title`, `NavDocument.set_doc_title`), so a retitled book's OPF and NCX agree. `retitle_built_epub` gives a `build_epub` output a new title in its package, tables of contents and title page; `convert_stage` and `download_convert_stage` use it, so a converted book's file carries the title the book gets. `ebookerr_sdk.epub.text_convert.is_chapter_heading` names the prose chapter-heading rule. `convert_stage` titles an uploaded document after its own title, else its file name unless the name is a placeholder such as `Document1` or `Untitled`, else a first line that is no chapter heading. `Book.front_matter` holds the text above a document's first heading; `build_epub` puts it on the title page, and `parse_text_to_book` fills it instead of making that text a chapter. `parse_text_to_book` keeps each line of a text that has no blank line at all as its own paragraph. `convert_stage` and `download_convert_stage` pass a `user_facing` conversion error's message through unchanged and answer "The file could not be converted." for any other failure, so no library message or path reaches a user. `EpubDocument.drm_protected()` tells a DRM-protected book (a `rights.xml`, or an `encryption.xml` using anything but font obfuscation) from one whose fonts are only obfuscated.

### 3.2.0

SPI 3.2. A story can name the multi-part work it is one part of: `StoryPatch.work` and
`StoryPatch.work_url`. `series` and `series_url` mean a series the site itself declares —
separate works in a declared order — never the work a chapter belongs to. `BookView.work_url`
carries the work a book is a chapter of. `ebookerr_sdk.domain.adoption` gains `same_series` and
`same_work`; `AdoptionSide.work_url` and `Evidence.same_work` are new, and signal E5 is "same
series or same work". `EBOOKERR_META_KEYS` gains `work_url`. A plugin written for SPI 3.1 needs
no change.

### 3.1.0

SPI 3.1. A `[[ui_triggers]]` entry's `icon` is optional and defaults to the plugin's `icon`;
declare one only when a plugin has more than one action. Every glyph name must be one the
self-hosted Material Symbols font renders (`ebookerr_sdk/spi/material_symbols.txt`): `pack check`
reports an unknown name, two actions of one scope showing the same glyph, and a trigger without
`icon` in a manifest that still declares `spi_version = "3.0"`. `pack check` now reports a declared
requirement that is not installed where it runs instead of guessing its module names. A plugin
written for SPI 3.0 needs no change.

### 3.0.0

SPI 3.0. A plugin written for SPI 2.x declares `spi_version = "3.0"` and applies these changes:

- `spi_version` is required.
- Roles are declared only as `[roles.<name>]` tables. The top-level keys `exclusive_group`,
  `provider`, `delete_mode`, `reader_url_template`, `handles_merge_proposals`, `catch_all` and
  `extract_url_patterns` are gone (`provider`, `delete_mode` and `reader_url_template` remain keys
  of `[roles.library_server]`).
- `network`, `auth_methods`, `headless`, `headed` and `default_enabled` are no longer manifest
  keys. A plugin runs on events when it declares `events`, and runs by hand when it declares a
  `book_selection_action` or `cover_invalidate` trigger; `PluginManifest.headed` is derived from
  its triggers.
- Settings-field types are `string`, `textarea`, `int`, `float`, `bool`, `url`, `url_list`,
  `threshold_list`, `record_list` and `command`: a secret is `secret = true` on a `string`, a
  drop-down is `options` with `options_strict = true` on a `string`; the types `text`, `secret`
  and `select` are gone. `SettingsGroup.collapsed` is gone.
- `UiFilter` and `UiTrigger.filters` are gone.
- `PluginContext.ask_yes_no` returns `True`, `False`, or `None` when the prompt was abandoned or
  timed out; `PluginContext.ask_yes_no_answer` and `PluginContext.queue` are gone.
- `UpdateCheckingSource.check_for_update` always receives `ctx`; its `cancel_event` parameter is
  gone.
- `BookView.erotica_tags` is gone; use `BookView.tags`.
- `ProviderAnchor`, `ProviderBookmark` and `ReadPositionAnchoring` are imported from
  `ebookerr_sdk.providers` only.
- New: `PluginContext.credentials(url)` and `SiteCredential` — the stored sign-in for a site the
  plugin may use: a host in its `auth_sites` (`"*"` for any site it processes) or the host of a
  `credential = true` URL setting.
- New: `python -m ebookerr_sdk.pack new` starts a plugin; `pack check` refuses unknown manifest
  keys, settings-field types and trigger scopes.
- `ebookerr_sdk.spi` is split into modules (`errors`, `settings`, `types`, `views`,
  `declarations`, `context`, `protocols`); every name still imports from `ebookerr_sdk.spi`.
- `ebookerr_sdk.wire`, `ebookerr_sdk.readpos` and `ebookerr_sdk.registry_index` are internal.

## Licence

`ebookerr-sdk` is released under the MIT licence. Each plugin carries its own licence.
