angee.knowledge.models
Source models for the knowledge addon.
A :class:Vault is the permission and namespace boundary; every addressable thing inside it is a :class:Page — a thin identity row whose kind-specific content lives in one-to-one sidecars. This addon ships :class:MarkdownPage, the body sidecar for markdown-based kinds; extension addons contribute further kinds by writing new kind values and their own sidecar model with a one-to-one to knowledge.Page.
OutlineEntry
@dataclass(frozen=True)
class OutlineEntry()One ATX heading in a markdown body's outline.
line is the 0-based source line of the heading in the CRLF-normalized body, the coordinate :meth:MarkdownPage.section_range slices on.
parse_wikilinks
def parse_wikilinks(body: str) -> dict[str, str]Return {target_title: display_text} for each [[wikilink]] in body.
The target is the text before | with any fragment stripped; the display text is the part after |. First occurrence of a target wins.
VaultManager
class VaultManager(AngeeManager)Factories for actor-owned vault writes.
create_for
def create_for(owner: Any, **fields: Any) -> AnyCreate a vault owned by owner after the REBAC create preflight.
owner must be the acting user — ownership on behalf of someone else is refused so the row the gate authorized is the row written.
Vault
class Vault(SqidMixin, AuditMixin, AngeeModel, HistoryMixin)Top-level page container; the permission and namespace boundary.
Deleting a vault cascade-deletes every page inside it; the crud delete mutation previews that blast radius before confirming. owner is protected so deleting a user account never silently wipes their vaults.
retrieval_class
Registry key for the retrieval backend this vault searches through.
Meta
class Meta()Django model options.
__str__
def __str__() -> strReturn the vault name for Django displays.
retrieval
@property
def retrieval() -> RetrievalBackendReturn the retrieval backend this vault's retrieval_class selects.
The vault is both the search namespace and the per-namespace selection point: retrieval_class names the backend (default lexical) and this binds it, mirroring InferenceProvider.backend.
retrieval_for
def retrieval_for(key: str) -> RetrievalBackendReturn the registered retrieval backend for key, bound to this vault.
The single public resolution seam over the vault-owned retrieval_class registry: callers (this model's retrieval property, a semantic plugin forcing its own key) ask the vault rather than re-deriving the field's internals — so ImplClassField stays the only thing that decodes the registry, and the boundary is a method, not _meta shape-probing.
PageManager
class PageManager(AngeeManager)Factories for actor-scoped page writes.
create_in
def create_in(vault: Any, **fields: Any) -> AnyCreate a page in vault after the REBAC create preflight.
The preflight evaluates the schema's create = vault->write with the relations the new row would carry, so only actors who can write the vault may add pages to it. A parent from another vault is refused — the vault is the permission boundary, and a foreign parent would extend parent->read/parent->write across it.
Page
class Page(SqidMixin, AuditMixin, AngeeModel, HistoryMixin)Universal addressable content node inside a vault.
A page is thin identity — title, hierarchy, and the kind discriminator. Kind-specific content lives in one-to-one sidecar models; this addon ships :class:MarkdownPage for markdown-based kinds.
Kind
class Kind(models.TextChoices)Built-in page kinds.
kind itself is an open CharField — extension addons store their own kind values and pair them with their own sidecar model (a one-to-one to knowledge.Page) without touching this model.
Meta
class Meta()Django model options.
__str__
def __str__() -> strReturn the page title for Django displays.
StaleBodyError
class StaleBodyError(ValueError)Raised when a body write expects a hash the stored body no longer has.
UnsupportedPageKindError
class UnsupportedPageKindError(ValueError)Raised when a body write targets a page kind without a markdown sidecar.
StructuredEditError
class StructuredEditError(ValueError)Base for a structure-aware markdown edit that cannot be applied.
SectionNotFoundError
class SectionNotFoundError(StructuredEditError)Raised when a heading path (or replace target) matches nothing.
AmbiguousMatchError
class AmbiguousMatchError(StructuredEditError)Raised when a heading path (or replace target) matches more than once.
MarkdownPageManager
class MarkdownPageManager(AngeeManager)Factories for actor-scoped markdown body writes.
write_body
def write_body(page: Any,
body: str,
*,
expected_hash: str | None = None) -> AnyCreate or update page's markdown body, last-write-wins.
expected_hash is an optimistic-concurrency token: when supplied and the stored body_hash differs, the write is rejected with :class:StaleBodyError so the caller can reload and retry.
patch_section
def patch_section(page: Any,
heading_path: str | list[str],
op: str,
content: str,
*,
expected_hash: str | None = None) -> AnyReplace/append/prepend the section at heading_path and write the body.
Splices through :meth:MarkdownPage.spliced_section, which fails fast with :class:SectionNotFoundError/:class:AmbiguousMatchError before any write.
replace_unique
def replace_unique(page: Any,
old: str,
new: str,
*,
expected_hash: str | None = None) -> AnyReplace the single occurrence of old with new and write the body.
Splices through :meth:MarkdownPage.spliced_unique (exact-string, uniqueness enforced), so a non-unique or absent target fails fast before any write.
append
def append(page: Any,
content: str,
*,
expected_hash: str | None = None) -> AnyAppend content to the end of page's body and write it.
prepend
def prepend(page: Any,
content: str,
*,
expected_hash: str | None = None) -> AnyPrepend content to the start of page's body and write it.
MarkdownPage
class MarkdownPage(SqidMixin, AuditMixin, AngeeModel, RevisionMixin)Markdown body sidecar for markdown-based page kinds.
body is the canonical content store; body_hash and word_count are derived on save. Body edits are versioned through revisions so they can be rolled back.
page_kinds
Page kinds that carry a markdown body sidecar.
excerpt_chars
Number of body characters surfaced by :attr:excerpt.
SECTION_OPS
Section splice operations accepted by :meth:spliced_section.
Meta
class Meta()Django model options.
__str__
def __str__() -> strReturn the owning page id for Django displays.
hash_body
@staticmethod
def hash_body(body: str) -> strReturn the canonical content hash for one body text.
excerpt
@property
def excerpt() -> strReturn the leading body characters used for list previews.
outline
@property
def outline() -> list[OutlineEntry]Return this body's heading outline (see :meth:parse_outline).
parse_outline
@staticmethod
def parse_outline(body: str) -> list[OutlineEntry]Return the ordered ATX headings in body as :class:OutlineEntry.
Heading levels and source lines come straight from markdown-it-py's heading_open block tokens (.tag → level, .map[0] → line); the text is the following inline token's .content. Setext (underline) headings are skipped — section addressing keys on single-line ATX headings.
section_range
@staticmethod
def section_range(body: str, heading_path: str | list[str]) -> tuple[int, int]Resolve heading_path to its [start, end) line range in body.
heading_path is a single heading text or an ancestor chain (["Usage", "CLI"]); it tail-matches each heading's ancestor path, case-insensitively, so ["CLI"] and the qualified path both resolve. Lines are 0-based into the CRLF-normalized body. The range runs from the heading line to the next heading of the same-or-higher level (children included), or end-of-body. Fail-fast: :class:SectionNotFoundError when nothing matches, :class:AmbiguousMatchError when more than one does.
spliced_section
@staticmethod
def spliced_section(body: str, heading_path: str | list[str], op: str,
content: str) -> strReturn body with one section's content spliced, never re-rendered.
op is one of :attr:SECTION_OPS: replace swaps the section body, append/prepend add content after/before it (after nested children for append — the range is section-inclusive). The heading line and everything outside the section are byte-identical (after CRLF normalization). One blank line separates the section from the next heading (or terminates the body); blank lines inside the preserved body — e.g. inside a code block — are untouched.
spliced_unique
@staticmethod
def spliced_unique(body: str, old: str, new: str) -> strReturn body with the single occurrence of old replaced by new.
Exact-string match, uniqueness enforced: :class:SectionNotFoundError when old is absent, :class:AmbiguousMatchError when it occurs more than once, so an edit can never silently land on the wrong span.
appended
@staticmethod
def appended(body: str, content: str) -> strReturn body with content joined after it, one blank line apart.
Whole-body assembly counterpart to :meth:spliced_section: content lands after the existing text with the same single-blank seam :meth:_join_blocks gives a section splice — no markdown is parsed or re-rendered.
prepended
@staticmethod
def prepended(body: str, content: str) -> strReturn body with content joined before it, one blank line apart.
The prepend counterpart to :meth:appended (same single-blank seam).
save
def save(*args: Any, **kwargs: Any) -> NonePersist the body together with its derived hash and word count.
LinkManager
class LinkManager(AngeeManager)Owns the wikilink edge set derived from page bodies.
rebuild_for
def rebuild_for(markdown: Any) -> NoneReplace the source page's outgoing links from its current body.
The indexer is the author: it resolves [[title]] targets against the page's own vault and DELETE+INSERTs the edge set under system_context. There is no per-link gate — backlink reads inherit the source page's permissions through the schema. A target created after the link still resolves on the source page's next save.
Link
class Link(SqidMixin, AngeeModel)Wikilink edge from one page to another, derived from the source body.
Indexer-authored (see :class:LinkManager) — no user-facing mutation, no audit author. REBAC read/write inherit through source_page.
Meta
class Meta()Django model options.
__str__
def __str__() -> strReturn the link target text for Django displays.