Package ovault

Version: 0.0.4

Python module for managing Obsidian vaults.

Installation

Install ovault using pip

pip install ovault

Examples

Below is a list of simple examples to get you started!



Example 1: Print an overview of the vault

# Example 1: Print an overview of the vault

# Usage: `python3 examples/1_overview.py <vault>`

import ovault
import sys

if len(sys.argv) != 2:
    print(f"Usage: python3 {sys.argv[0]} <vault>")
    exit(1)

vault = ovault.Vault(sys.argv[1])

print()
print("path        :", vault.path)
print("notes       :", len(vault.notes()))
print("attachments :", len(vault.attachments()))
print("tags        :", vault.tags())

Example 2: Print all files with a specific tag

# Example 2: Print all files with a specific tag

import ovault
import sys

if len(sys.argv) != 3:
    print(f"Usage: python3 {sys.argv[0]} <vault> <tag>")
    exit(1)

vault = ovault.Vault(sys.argv[1])

tag = sys.argv[2]

print(f"\nSearching for notes with tag: {tag}")
notes = vault.get_notes_by_tag(tag)
if len(notes) == 0:
    print(f"No notes found with tag '{tag}'.")
else:
    for note in notes:
        print(f"    {note.name} ({note.length} characters)")

Example 3: Find all headings in the "Start Here" note in the Obsidian Sandbox vault

# Example 3: Find all headings in the "Start Here" note in the Obsidian Sandbox vault

import ovault

# Open the sandbox vault
vault = ovault.Vault("./test-vaults/Obsidian Sandbox/")

# Find a note by name
note = vault.get_note_by_name("Start Here")

# Get all tokens in the note
tokens = note.tokens()

# Iterate through tokens and print headings
for token in tokens:
    if isinstance(token, token.Header):
        print(f"Found heading: {token.heading} at level {token.level}")

# Example 4: Check that all external links in the vault are valid

# Usage: python 4_check_external_links.py <vault_path>

# NOTE: This example requires the `requests` package to be installed.

import ovault
import sys
import requests

if len(sys.argv) != 2:
    print("Usage: python 4_check_external_links.py <vault_path>")
    sys.exit(1)

vault = ovault.Vault(sys.argv[1])

# Terninal Colors
GREEN  = "\033[92m"
YELLOW = "\033[93m"
RED    = "\033[91m"
RESET  = "\033[0m"

broken_links = []

for note in vault.notes():
    for token in note.tokens():
        if not isinstance(token, token.ExternalLink): continue
        url = token.link.url
        print(f"[note: {note.name}] Checking '{url}'...", end=' ', flush=True)

        if not url.startswith("http"):
            print(f"{YELLOW}SKIPPED{RESET}")
            continue

        try:
            resp = requests.get(url, timeout=5)
        except requests.RequestException as e:
            print(f"{RED}INVALID URL{RESET} ({e})")
            continue
        if resp.status_code == 200:
            print(f"{GREEN}OK{RESET}")
        else:
            broken_links.append((note.name, url, resp.status_code))
            print(f"{RED}FAILED{RESET} ({resp.status_code})")


if not broken_links:
    print("All external links are valid!")
    exit(0)

print(f"\nBroken links found:")
for note_name, url, status in broken_links:
    print(f"- {note_name}: {url} (status code: {status})")

Example 5: Generates dot code for a connection graph of of the vault

# Example 5: Generates `dot` code for a connection graph of of the vault

# Usage: `python3 examples/5_graph_image.py <vault> | neato -Tpng > graph.png`

import ovault
import sys

if len(sys.argv) != 2:
    print(f"Usage: python3 {sys.argv[0]} <vault>")
    exit(1)

vault = ovault.Vault(sys.argv[1])

notes = vault.notes()

print("digraph {")
print("    overlap=false;")
print('    node [shape=box, style=filled, fillcolor="#0099FF25"];')
print('    edge [color="#00000090"];')

for note in notes:
    print(f'    "{note.normalized_name()}" [label="{note.name}"]')

for note in notes:
    for link in note.links:
        link = link.replace("\\", "")
        print(f'    "{note.normalized_name()}" -> "{link}"')

print("}")

Example 6: Generate an Obsidian Vault

# Example 6: Generate an Obsidian Vault

# This script generates an obsidian vault containing manual entries
# for common GNU/Linux utilities

# NOTE: This will only work on GNU/Linux systems with the `man` command available.

# Usage: python 6_create_vault.py <vault_name>

import ovault
import subprocess
import sys

# List of common GNU/Linux utilities to include in the vault
UTILS = [
    "ls", "pwd", "mkdir", "rm", "cp", "mv", "touch", "grep", "cat",
    "more", "less", "chmod", "ps", "top", "free", "df", "du", "uname",
    "sudo", "su", "passwd", "id", "ping", "ssh", "wget", "curl", "kill",
    "tar", "gzip", "zip", "mount", "nano", "echo", "clear", "man",
    "exit", "reboot", "date"
]

# Sort utilities for consistent order
UTILS.sort()

# Get the manual page for a utility using the `man` command
def manual(util: str) -> str:
    res = subprocess.run(["man", util], capture_output=True, text=True);
    if res.returncode != 0:
        print(f"Command `{util}` does not have a manual entry")
        exit(1)
    return res.stdout

# Extract the description from the manual page.
# The description is usually the first line after the "NAME" section.
def extract_desc(man: str) -> str:
    lines = man.split("\n")
    desc = ""
    for i, line in enumerate(lines):
        if not line.startswith("NAME"): continue
        desc_line = lines[i + 1].strip()
        if " - " in desc_line:
            desc = desc_line.split(" - ")[-1] 
            break
        else:
            desc = desc_line
            break

    if not desc:
        print("Could not extract description from manual page")
        exit(1)

    return desc.strip().capitalize() + "."

# Generate the index page for the vault
def index_page(descs: dict[str, str]) -> str:
    index_content  = "# GNU/Linux Utilities Vault\n\n"
    index_content += "This vault contains manual entries for common GNU/Linux utilities.\n\n"
    index_content += "## Utilities\n\n"

    # Fill in the index with utility names and their descriptions
    for util in UTILS:
        index_content += f"- **[[utils/{util}|{util}]]**: {descs[util]}\n"

    return index_content

if __name__ == "__main__":

    if len(sys.argv) != 2:
        print(f"Usage: python {sys.argv[0]} <vault_name>")
        exit(1)

    # Open the vault, creating it if it doesn't exist
    vault = ovault.Vault(sys.argv[1], create=True)

    print("Getting manual pages for utilities...")
    pages = {}
    for util in UTILS:
        print(f"    Getting '{util}'")
        pages[util] = manual(util)

    print("Extracting descriptions from manual pages...")
    descs = {util: extract_desc(pages[util]) for util in UTILS}

    print("Creating notes...")
    for util in UTILS:
        path = f"utils/{util}.md"
        content = f"{descs[util]}\n\n```text\n{pages[util]}\n```"
        vault.add_note(path, content)

    vault.add_note("Overview.md", index_page(descs))

    print("Re-indexing vault...")
    vault.index()

    print(f"\nVault created successfully at '{vault.path}' with {len(vault.notes())} notes!")

Example 7: Rename a note in an Obsidian vault

# Example 7: Rename a note in an Obsidian vault

# Usage: python 7_rename_note.py <vault_name> <old_note_path> <new_note_path>

import ovault
import sys

if len(sys.argv) != 4:
    print("Usage: python 7_rename_note.py <vault_name> <old_note_name> <new_note_name>")
    sys.exit(1)

vault = ovault.Vault(sys.argv[1])

old_note_name = sys.argv[2]
new_note_name = sys.argv[3]

note = vault.rename_note(old_note_name, new_note_name)

print(f"Renamed note from '{old_note_name}' to '{new_note_name}' in vault '{vault.path}'\n")

print("Patched backlinks:")
for backlink in note.backlinks:
    print(f"  - {vault.get_note_by_name(backlink).path}")

Functions

def normalize(name)

Nomalize a note name to be used in Obsidian links.

Example:

normalize("My Note") => "my-note"
def parse_yaml(source)
def text_to_tokens(text)

Tokenize a string to a list of tokens.

Classes

class Attachment (...)

An attachment in an Obsidian vault. An attachment is any file that is not a markdown file.

Instance variables

var path

Path to the attachment file.

class Callout (...)

Represents a callout block in the document.

Example:

> [!note]- Title
> This is a note callout.

Instance variables

var foldable

Whether the callout can be folded or collapsed.

var kind

The kind of callout, such as "note", "tip", "warning", etc.

var text

The text content of the callout.

var title

The title of the callout.

var tokens

The tokenized content of the callout, which can include text, code blocks, links, etc.

Represents an external link to an external URL.

Example:

![alt text](https://imageimage--link.domain)
[show_how](https://github.com/BalderHolst)

Instance variables

var options

Additional options for the link, such as size or alignment.

var position

The section in the linked document where the link should point to.

var render

Whether the link should be rendered as an image or video.

var show_how

The text that will be displayed for the link.

var url

The URL of the link.

class Frontmatter

Represents the frontmatter of a note, which is a collection of ordered key-value pairs. This class acts like a dictionary, where keys are strings and values can be various types, including numbers, strings, booleans, arrays

The main difference from a standard dictionary is that the order of items is preserved

Methods

def clear(self, /)

Clears all items

def contains(self, /, key)

Checks if the frontmatter contains a specific key.

def copy(self, /)

Creates a copy of this Frontmatter.

def del_item(self, /, key)

Deletes a key-value pair from the frontmatter by key.

def dict(self, /)

Returns a python dictionary representation of the frontmatter.

def get_item(self, /, key)

Retrieves the value associated with a specific key.

def is_empty(self, /)

Checks if the frontmatter is empty.

def items(self, /)

Returns a list of key-value pairs as tuples in the frontmatter.

def keys(self, /)

Returns a list of keys in the frontmatter.

def len(self, /)

Returns the number of items in the frontmatter.

def set_item(self, /, key, value)

Sets the value for a specific key, or adds the key-value pair if it does not exist.

def values(self, /)

Returns a list of values in the frontmatter.

def yaml(self, /, indent=2, list_style=Ellipsis)

Converts the frontmatter to a YAML string representation.

Represents an internal link to another note.

Example:

![[note_name#position|display text]]

Instance variables

var dest

The destination of the link, which is the name of the note.

var options

Additional options for the link, such as size or alignment.

var position

The section in the linked document where the link should point to.

var render

Whether the link should be rendered as an image, video or note.

var show_how

The text that will be displayed for the link.

class ListStyle (...)

Represents the style of lists in frontmatter YAML serialization.

Class variables

var Bracketed
var Dashed
class Note (...)

A note in an Obsidian vault.

Instance variables

Set of backlinks to this note (links from other notes)

var length

Length of the note contents in characters

Set of links to other notes or attachments

var name

Name of the note (file name without extension)

var path

Relative path within vault

var tags

Set of tags in the note

var vault_path

Path to the vault

Methods

def frontmatter(self, /)

Get the frontmatter as a python dictionary

def full_path(self, /)

Get the absolute path to the note file.

def insert_after_token(self, /, token, text, offset=0)

Insert a string into the note after a given token.

NOTE: The token should originate from this note as this method used the internal Span of the note to determine the insertion position.

def insert_at(self, /, pos, text)

Inserts a string at a position in the note.

def insert_before_token(self, /, token, text, offset=0)

Inserts a string into the note before a given token.

NOTE: The token should originate from this note as this method used the internal Span of the note to determine the insertion position.

def normalized_name(self, /)

Get the normalized name of the node.

def read(self, /)

Read the contents of the note and return it as a string

def replace_between(self, /, start, end, text)

Replaces the text between two positions in the note with the given text.

def replace_span(self, /, span, text)

Replaces a Span in the note with the given text. This can be used to replace tokens within the note.

def tokens(self, /)

Get contents note as a list of tokens.

class Span (start, end)

Represents a span in the source text.

Instance variables

var end

The ending index of the span in the source text.

var start

The starting index of the span in the source text.

class Token (...)

Represents a part of a note, such as text, code blocks, links, etc.

Example - Token Stream

A note might contain the following:

# Heading
This is a paragraph with a [link](https://example.com).

This would be represented as a sequence of Token instances:

- `Token::Header { level: 1, heading: "Heading" }`
- `Token::Text { text: "This is a paragraph with a " }`
- `Token::ExternalLink { link: ... }`
- `Token::Text { text: "." }`

Example - Find Headings

To find all headings in a note, you can iterate over the tokens:

# Example 3: Find all headings in the "Start Here" note in the Obsidian Sandbox vault

import ovault

# Open the sandbox vault
vault = ovault.Vault("./test-vaults/Obsidian Sandbox/")

# Find a note by name
note = vault.get_note_by_name("Start Here")

# Get all tokens in the note
tokens = note.tokens()

# Iterate through tokens and print headings
for token in tokens:
    if isinstance(token, token.Header):
        print(f"Found heading: {token.heading} at level {token.level}")

Subclasses

  • builtins.Token_Callout
  • builtins.Token_Code
  • builtins.Token_DisplayMath
  • builtins.Token_Divider
  • builtins.Token_ExternalLink
  • builtins.Token_Frontmatter
  • builtins.Token_Header
  • builtins.Token_InlineMath
  • builtins.Token_InternalLink
  • builtins.Token_Quote
  • builtins.Token_Tag
  • builtins.Token_TemplaterCommand
  • builtins.Token_Text

Class variables

var Callout
var Code
var DisplayMath
var Divider
var Frontmatter
var Header
var InlineMath
var Quote
var Tag
var TemplaterCommand
var Text
class Vault (path, create=False)

An Obsidian vault containing notes and attachments. The vault is indexed on creation and can be re-indexed with the index method.

Instance variables

A map of dangling links. The key is the note name and the value is a list of links that point to non-existing notes or attachments.

var ignored

Paths that were ignored during indexing

var path

Path to Obsidian vault directory

Methods

def add_note(self, /, vault_path, contents)

Add a note to the vault.

Call the index method to reindex the vault after adding notes.

def attachments(self, /)

Get a list of all attachments in the vault. Order is not guaranteed.

def get_note_by_name(self, /, name)

Get note by its name.

def get_notes_by_tag(self, /, tag)

Get all notes that have the given tag.

def index(self, /)

Index the vault. This will clear the current index and re-index the vault.

This is useful if you have edited, added or removed notes or attachments from the vault.

def notes(self, /)

Get a list of all notes in the vault. Order is not guaranteed.

def rename_note(self, /, old_name, new_name)

Rename a note in the vault. This will update the note's name, path, and all backlinks to the note.

def rename_tag(self, /, old_tag, new_tag)

Rename a tag in the vault. This will update all notes that have the tag.

def tags(self, /)

Get a list of all tags in the vault. Order is not guaranteed.

class VaultItem (...)

An item in an Obsidian vault can be either a note or an attachment.

Subclasses

  • builtins.VaultItem_Attachment
  • builtins.VaultItem_Note

Class variables

var Attachment
var Note