ARCANA Service#

saia_python.arcana.extract_arcana_name(id_or_name: str) str[source]#

Extract the arcana name from a full ID or plain name.

The ARCANA chat endpoint uses the full owner/name format, while management endpoints (get, upload) use just the name. This function accepts either and returns the name part.

Parameters:

id_or_name – Either "owner/name" or just "name".

Returns:

The name portion (everything after the first /, or the input unchanged if there is no /).

class saia_python.arcana.ArcanaService(session: Session, base_url: str, api_key: str, *, timeout: float | tuple[float, float] | None = (10.0, 60.0), retry: RetryPolicy | bool | None = None)[source]#

Access the ARCANA/RAG endpoints.

The ARCANA API uses a different auth scheme (plain key, no Bearer prefix) and a different URL path. This is handled automatically.

Parameters:
  • session – A requests.Session (auth header will be overridden per-request).

  • base_url – The SAIA API base URL (e.g. https://chat-ai.academiccloud.de/v1).

  • api_key – The raw API key (needed because ARCANA omits the Bearer prefix).

  • timeout – Default (connect, read) timeout in seconds applied to every ARCANA management request that does not set its own. Guards against the server accepting a request but never responding (e.g. while an arcana is locked mid-(re)index), which would otherwise block forever on the socket read. A single float applies to both phases; pass None to disable. Defaults to (10, 60). The long-running chat path (chat()) is exempt.

version() str[source]#

Return the ARCANA API version string.

Calls GET /arcanas/api/v1/version.

Returns:

The version string (e.g. "0.4.16").

heartbeat() bool[source]#

Check whether the ARCANA service is alive.

Calls GET /arcanas/api/v1/heartbeat. Returns True if the service responds with 204, False otherwise (including transport errors — it never raises).

user_info() dict[source]#

Return the current user’s profile and arcana statistics.

Calls GET /user/me. Returns username, email, name, arcana count, file count, and registration date.

Returns:

A dict with user profile fields.

user_summary() str[source]#

Return a formatted overview of the user’s account and all arcanas.

Combines user_info() and list() into a single human-readable string.

Returns:

A multi-line summary.

create(name: str, *, append_uuid: bool = True, update_toml: bool = False, toml_label: str | None = None) dict[source]#

Create a new arcana.

Parameters:
  • name – Name for the new arcana (1–100 characters).

  • append_uuid – If True (default), append a UUID4 suffix to the name (e.g. MyArcana-a1b2c3d4-...). This mirrors the behavior of the SAIA web UI and avoids name collisions.

  • update_toml – If True, add the new arcana to config.toml after creation.

  • toml_label – Label under [saia.arcana.labels] in config.toml. If omitted, the ID is appended to the ids array.

Returns:

A dict with the created arcana name and the full ID (owner/name).

delete(name: str, *, update_toml: bool = False) dict | None[source]#

Delete an arcana entirely.

Accepts either the plain name or the full owner/name ID.

Parameters:
  • name – The arcana name or full owner/name ID.

  • update_toml – If True, remove the arcana from config.toml after deletion.

Returns:

The API response, or None if the response has no body.

recreate(name: str, *, update_toml: bool = False) dict[source]#

Delete an arcana and recreate it empty with the same ID.

The minimal-call way to wipe an entire arcana: two requests (delete() + create()) regardless of how many files it holds, versus one delete_file() per file (thousands of calls, each its own read-timeout risk while the arcana is busy). The name — including any UUID suffix — is preserved verbatim via create(..., append_uuid=False), so a downstream pin on the full owner/name-uuid ID stays valid.

Trade-offs versus emptying file-by-file (delete_file() in a loop, which keeps the container): there is a brief window between the two calls where the arcana does not exist, and the recreated arcana is brand-new — created_at, sharing/permissions and any other container settings reset to defaults. Only the name/ID carries across.

Parameters:
  • name – The arcana name or full owner/name ID to recreate.

  • update_toml – Forwarded to create().

Returns:

The create() result ({"name", "id", "message"}).

Raises:

APIError – If recreation fails after the delete already succeeded — the arcana is then gone. The message says so explicitly so the operator recreates it before any consumer (adapter / manifest pin) points at the ID.

list() list[dict][source]#

List all available arcanas.

Returns:

A list of arcana dicts.

summary(*, arcana_ids: dict[str, str] | None = None) str[source]#

Return a formatted summary of configured and available arcanas.

Combines information from load_arcana_ids() (configured IDs) and list() (server-side arcanas) into a single human-readable string.

Parameters:

arcana_ids – Optional pre-loaded dict from load_arcana_ids(). If omitted, loaded automatically.

Returns:

A multi-line summary string.

get(name: str) dict[source]#

Retrieve details of a specific arcana.

Accepts either the plain name or the full owner/name ID — the owner prefix is stripped automatically.

Parameters:

name – The arcana name or full owner/name ID.

Returns:

Arcana details dict.

info(name: str | None = None, *, data: dict | None = None, verbose: bool = False) str[source]#

Return a formatted summary of an arcana’s details.

Parameters:
  • name – The arcana name or full owner/name ID. Can be omitted if data is provided.

  • data – Optional pre-fetched arcana dict (from get()). Avoids a redundant API call when you already have the data.

  • verbose – If True, include additional fields (CLI version, vector DB version, error message if present).

Returns:

A human-readable multi-line string.

upload(name: str, file_path: str | Path, *, overwrite: bool = False) dict | None[source]#

Upload a file to an arcana for indexing.

Supported formats: PDF, text, markdown. Accepts either the plain name or the full owner/name ID.

Parameters:
  • name – The arcana name or full owner/name ID.

  • file_path – Path to the file to upload.

  • overwrite – If True, replace an existing file with the same name (uses PUT instead of POST).

Returns:

The API response (may be None on success per the API spec).

upload_directory(name: str, directory: str | Path, *, pattern: str = '*', recursive: bool = False, overwrite: bool = False, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) list[dict][source]#

Upload all files in a directory to an arcana.

Parameters:
  • name – The arcana name or full owner/name ID.

  • directory – Path to the directory containing files to upload.

  • pattern – Glob pattern to filter files (default "*" — all files). For example, "*.pdf" to upload only PDFs.

  • recursive – If True, search subdirectories recursively (uses **/<pattern>).

  • overwrite – If True, replace existing files with the same name.

  • verbose – If True, print per-file upload status.

  • on_result – Optional callback invoked as on_result(local_path, entry) after each file (entry is that file’s {"file", "status", ["error"]} dict), for inline per-file provenance / transaction logging.

Returns:

A list of dicts with keys "file" (filename only), "status" ("uploaded" or "failed"), and "error" (message string, only present on failure).

upload_files(name: str, paths: Iterable[str | Path], *, overwrite: bool = True, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) list[dict][source]#

Upload an explicit, caller-chosen list of files to an arcana.

Unlike upload_directory() (which globs a whole directory), this uploads exactly the paths you pass — so the selection of what to (re)upload is entirely the caller’s decision (e.g. the result of your own changed-file / checksum comparison). Pair it with list_files() (whose dicts carry per-file index_info) and a single generate_index() afterwards.

Parameters:
  • name – The arcana name or full owner/name ID.

  • paths – An iterable of paths to upload.

  • overwrite – If True (default), replace existing files (PUT); if False, create new files (POST). Defaults to True because the typical caller has already decided these files are new or changed.

  • verbose – If True, print per-file upload status.

  • on_result – Optional callback invoked as on_result(local_path, entry) after each file (entry is that file’s {"file", "status", ["error"]} dict). Lets a caller record per-file provenance (e.g. a git SHA) or a transaction-log entry as each upload completes, without reimplementing this loop.

Returns:

A list of {"file", "status", ["error"]} dicts — the same shape as upload_directory().

list_files(name: str) list[dict][source]#

List all files in an arcana.

Accepts either the plain name or the full owner/name ID.

Parameters:

name – The arcana name or full owner/name ID.

Returns:

  • name (str): file name — use with download_file(), delete_file(), or an overwrite upload.

  • size (int): size in bytes.

  • owner_user_name (str): the owning user.

  • created_at / updated_at (str): ISO-8601 timestamps.

  • index_info (dict | None): per-file index state, or None if the file has never been indexed. When present it holds index_status (str — e.g. "INDEXED", "NOT_INDEXED", "ERROR") and chunks_indexed (int — number of embedding chunks produced for the file).

  • related_files (list | None): nested entries of the same shape, when the server groups derived files together.

The per-file index_info lets callers see which files are already indexed without doing any work. Indexing itself is triggered per-arcana via generate_index() — the ARCANA API has no per-file index call.

Return type:

A list of file dicts (the API FileOutSchema). Each entry has

delete_file(name: str, file_name: str) dict | None[source]#

Delete a file from an arcana.

Accepts either the plain name or the full owner/name ID.

Parameters:
  • name – The arcana name or full owner/name ID.

  • file_name – The name of the file to delete (as returned by list_files()).

Returns:

The API response, or None if the response has no body.

delete_files(name: str, file_names: Iterable[str], *, verbose: bool = False, on_result: Callable[[str, dict], None] | None = None) list[dict][source]#

Delete an explicit list of files from an arcana, by name.

The batch counterpart to delete_file(): hand it the file names (as returned by list_files()) and it deletes each one, capturing a per-file outcome instead of aborting on the first failure. Unlike delete_directory() no local directory is consulted — the names come straight from the caller — so it can target files that no longer exist on disk (e.g. a name list from a CSV). Pairs with a thin CLI front-end that resolves which names to delete; this method only does the deleting.

Parameters:
  • name – The arcana name or full owner/name ID.

  • file_names – The file names to delete (flat names, as listed by list_files()). Order is preserved; a repeated name is attempted each time (a second delete just reports the server’s response / 404).

  • verbose – If True, print per-file deletion status.

  • on_result – Optional callback invoked as on_result(file_name, entry) after each file (entry is that file’s {"file", "status", ["error"]} dict), for inline per-file logging.

Returns:

A list of dicts with keys "file" (the name), "status" ("deleted" or "failed"), and "error" (only on failure) — the same shape every batch op returns.

download_file(name: str, file_name: str, output_path: str | Path) Path[source]#

Download a file from an arcana to a local path.

Accepts either the plain name or the full owner/name ID.

Parameters:
  • name – The arcana name or full owner/name ID.

  • file_name – The name of the file to download (as returned by list_files()).

  • output_path – Local path to save the file to.

Returns:

The path the file was written to.

delete_directory(name: str, directory: str | Path, *, pattern: str = '*', recursive: bool = False, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) list[dict][source]#

Delete files from an arcana that match filenames in a local directory.

Finds all files in directory matching pattern, then deletes files with the same name from the arcana. Useful for removing a batch of files that were previously uploaded with upload_directory().

Parameters:
  • name – The arcana name or full owner/name ID.

  • directory – Local directory whose filenames to match.

  • pattern – Glob pattern to filter files (default "*").

  • recursive – If True, search subdirectories recursively.

  • verbose – If True, print per-file deletion status.

  • on_result – Optional callback invoked as on_result(local_path, entry) after each file (entry is that file’s {"file", "status", ["error"]} dict), for inline per-file logging.

Returns:

A list of dicts with keys "file" (filename only), "status" ("deleted" or "failed"), and "error" (only present on failure).

sync_directory(name: str, directory: str | Path, *, select: Callable[[Path, dict | None], str], pattern: str = '*', recursive: bool = False, prune: bool = False, index: bool = True, index_wait: bool = True, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) dict[source]#

Sync a local directory into an arcana under caller-defined rules.

The policy — which files to upload, replace, or skip — stays entirely outside the package: you supply a select callback that decides per file. This method only does the plumbing: glob the directory, fetch the remote listing, apply your decisions, and (optionally) trigger a single index pass. ARCANA stores no content hash, so any content-change detection (e.g. SHA-256 against your own manifest) belongs in select.

The single index pass is efficient because the server only (re)embeds files that are not already INDEXED — see generate_index().

Parameters:
  • name – The arcana name or full owner/name ID.

  • directory – Local directory to sync from.

  • select – Called once per local file as select(local_path, remote) where local_path is a pathlib.Path and remote is the matching file dict from list_files() (matched by name) or None if the file is not in the arcana yet. Must return "upload" (POST new), "replace" (PUT over an existing file), or "skip".

  • pattern – Glob pattern for local files (default "*").

  • recursive – If True, recurse into subdirectories.

  • prune – If True, delete remote files that have no local counterpart by name. Defaults to False (never deletes implicitly).

  • index – If True (default), call generate_index() once after the sync — but only when something actually changed.

  • index_wait – Forwarded to generate_index() as wait.

  • verbose – If True, print per-file actions and a summary.

  • on_result – Optional callback invoked as on_result(local_path, entry) for each local file as it is uploaded, replaced, or skipped (entry mirrors the batch-helper shape: {"file", "status", ["error"]}, where status is one of "uploaded" / "replaced" / "skipped" / "failed"). Lets callers record per-file provenance / transaction logs inline. Not called for prune deletions (those are remote-only).

Returns:

A report dict with keys "uploaded", "replaced", "skipped", "deleted" (lists of file names), "failed" (list of {"file", "error"}) and "index" (the generate_index() result, or None if indexing was skipped).

Raises:

ValueError – If select returns anything other than "upload", "replace", or "skip".

generate_index(name: str, *, wait: bool = True, timeout: int = 600, poll_interval: int = 5) dict | None[source]#

Trigger index generation for an arcana.

By default this blocks until indexing completes (synchronous). For large arcanas the server may time out (504). Use wait=False to fire the request and return immediately, then poll with info() to check the index status.

Parameters:
  • name – The arcana name or full owner/name ID.

  • wait – If True (default), poll until indexing finishes. If False, fire the request and return immediately.

  • timeout – Maximum seconds to wait when wait=True. Defaults to 600 (10 minutes).

  • poll_interval – Seconds between status checks when wait=True. Defaults to 5.

Returns:

The arcana details dict (from get()) when wait=True and indexing completed, or None when wait=False or on timeout.

Raises:

TimeoutError – If wait=True and indexing does not complete within timeout seconds.

Note

Indexing is incremental on the server: files already at index_status == "INDEXED" are skipped, so only files added or replaced since the last index (an upload resets a file to NOT_INDEXED) are (re)embedded. This is why the efficient pattern is upload only the changed files, then call this once — the single whole-arcana trigger re-embeds just those. The library relies on this skip-INDEXED behavior; if the server ever stops skipping, the call stays correct but re-embeds the whole arcana.

delete_index(name: str) dict | None[source]#

Delete the index of an arcana.

Accepts either the plain name or the full owner/name ID.

Parameters:

name – The arcana name or full owner/name ID.

Returns:

The API response, or None if the response has no body.

setup_from_directory(name: str, source_dir: str | Path, *, pattern: str = '*.md', append_uuid: bool = True, update_toml: bool = False, toml_label: str | None = None, wait_for_index: bool = True, index_timeout: int = 600, verbose: bool = True) dict[source]#

End-to-end: create an arcana, upload a directory, build the index.

Composes create(), upload_directory(), and generate_index() into a single call. The arcana name passed to upload + index is the one returned by create — i.e. with the UUID suffix when append_uuid=True (default), so the composition stays correct without the caller having to remember the renaming.

Parameters:
  • name – Display name for the new arcana (UUID suffix appended when append_uuid=True).

  • source_dir – Directory whose matching files should be uploaded to the new arcana.

  • pattern – Glob pattern passed to upload_directory(). Defaults to "*.md".

  • append_uuid – Forwarded to create(). If True (default), the UUID suffix avoids name collisions and mirrors the SAIA web UI behaviour.

  • update_toml – Forwarded to create(). If True, add the new arcana to config.toml after creation.

  • toml_label – Label under [saia.arcana.labels]. Ignored when update_toml is False.

  • wait_for_index – Forwarded to generate_index() as wait. If True (default), block until the index reaches INDEXED (or fails / times out).

  • index_timeout – Forwarded to generate_index() as timeout (seconds). Defaults to 600.

  • verbose – Forwarded to upload_directory(). Controls the per-file progress bar.

Returns:

"arcana" (the result from create()), "uploads" (the list from upload_directory()), and "index" (the result from generate_index()). Callers can inspect any step.

Return type:

A dict with three keys

Example:

result = client.arcana.setup_from_directory(
    "MyKB", "./markdown/",
    pattern="**/*.md",
    update_toml=True, toml_label="my_kb",
)
print(result["arcana"]["id"])     # owner/MyKB-<uuid>
print(len(result["uploads"]))     # files uploaded
print(result["index"])            # index_status
chat(model: str, messages: list[dict], arcana_id: str, *, temperature: float | None = None, max_tokens: int | None = None, stream: bool = False, retry: RetryPolicy | bool | None = None, **kwargs) dict | SSEStream[source]#

Chat with RAG context from an arcana.

This uses the standard /chat/completions endpoint with arcana parameters injected.

Parameters:
  • model – Model identifier.

  • messages – Chat messages.

  • arcana_id – The arcana ID to use for retrieval.

  • stream – If True, return a generator yielding chunks.

  • **kwargs – Additional parameters forwarded to the API.

Returns:

the API response dict, with an extra "_rate_limits" key (a JSON-serializable dict; see RateLimitInfo). When stream=True: an SSEStream whose rate_limits attribute exposes the same dict.

Return type:

When stream=False