Package ovault
Version: 0.0.11
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 2: Print all files with a specific tag
- Example 3: Find all headings in the "Start Here" note in the Obsidian Sandbox vault
- Example 4: Generates
dotcode for a connection graph of of the vault - Example 5: Generate an Obsidian Vault
- Example 6: Rename a note in an Obsidian vault
- Example 7: Add frontmatter to all notes that don't have it
- Example 8: Standardize note formatting
Example 1: Print an overview of the vault
# Example 1: Print an overview of the vault
# Usage: `python3 examples/1_overview.py <vault>`
# NOTE: Sorting is only done to make the output stable for testing purposes.
import ovault
import sys
import os
if len(sys.argv) != 2:
print(f"Usage: python3 {sys.argv[0]} <vault>")
exit(1)
vault = ovault.Vault(sys.argv[1])
print()
print("path :", os.path.relpath(vault.path, os.getcwd()))
print("notes :", len(vault.notes()))
print("attachments :", len(vault.attachments()))
print("tags :", sorted(vault.tags()))
Example 2: Print all files with a specific tag
# Example 2: Print all files with a specific tag
# NOTE: Sorting is only done to make the output stable for testing purposes.
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 = sorted(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: Generates dot code for a connection graph of of the vault
# Example 4: Generates `dot` code for a connection graph of of the vault
# Usage: `python3 examples/4_graph_image.py <vault> | neato -Tpng > graph.png`
# NOTE: Sorting is only done to make the output stable for testing purposes.
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("digraph {")
print(" overlap=false;")
print(' node [shape=box, style=filled, fillcolor="#0099FF25"];')
print(' edge [color="#00000090"];')
print()
notes = [
(note.normalized_name(), note, f"{i}{note.normalized_name()}")
for i, note in enumerate(sorted(vault.notes()))
]
map = {k: v for k, _, v in notes}
for name, note, node in notes:
print(f' "{node}" [label="{name}"]')
print()
for _, note, node in notes:
for link in sorted(note.links):
link = link.replace("\\", "")
if link not in map:
continue
link_node = map[link]
print(f' "{node}" -> "{link_node}"')
print("}")
Example 5: Generate an Obsidian Vault
# Example 5: 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 5_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",
"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)
vault_path = sys.argv[1]
# Open the vault, creating it if it doesn't exist
vault = ovault.Vault(vault_path, 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 6: Rename a note in an Obsidian vault
# Example 6: Rename a note in an Obsidian vault
# Usage: python 6_rename_note.py <vault_name> <old_note_path> <new_note_path>
import ovault
import sys
if len(sys.argv) != 4:
print("Usage: python 6_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(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}")
Example 7: Add frontmatter to all notes that don't have it
# Example 7: Add frontmatter to all notes that don't have it
# Usage: python 7_add_frontmatter.py <vault_name>
# NOTE: Sorting is only done to make the output stable for testing purposes.
import ovault
import sys
from pathlib import Path
if len(sys.argv) != 2:
print("Usage: python 7_add_frontmatter.py <vault_name>")
sys.exit(1)
vault = ovault.Vault(sys.argv[1])
for note in sorted(vault.notes()):
if note.frontmatter() is not None:
print(f"'{note.name}' already has frontmatter, skipping.")
continue
note_file = Path(note.full_path())
tags = ['added-tag', 'this-is-a-frontmatter-tag']
note_dir = note_file.parent
if note_dir != vault.path:
print(f"Adding frontmatter to '{note.name}' in directory '{note_dir.name}'")
tags.append(note_dir.name)
frontmatter = ovault.Frontmatter()
frontmatter.set('tags', tags)
frontmatter.set('created', note_file.stat().st_ctime)
note.set_frontmatter(frontmatter)
Example 8: Standardize note formatting
# Example 8: Standardize note formatting
# Usage: python 8_formatter.py [--vault] <path>
# Supply a markdown file with the `<path>` argument.
# If `--vault` is provided, `<path>` is treated as a vault path.
# If `--tokens` is provided, the note tokens will be printed.
# NOTE: Sorting is only done to make the output stable for testing purposes.
import ovault
import sys
import os
PROGRAM = os.path.basename(__file__)
def usage(error: str = None):
if error:
print(f"ERROR: {error}")
print()
print(f"Usage: python {PROGRAM} [--tokens] [--vault] <path>")
print()
print("Supply a markdown file with the <path> argument.")
print("If --vault is provided, <path> is treated as a vault path.")
print("If --tokens is provided, the note tokens will be printed.")
exit(1)
# Format a file by reading its content, parsing it into tokens,
# and converting the tokens back to markdown using the `ovault.to_markdown` function,
# which formats the tokens back into markdown text.
#
# To create a custom formatter, change out the `ovault.to_markdown` function
# for your own implementation.
def format_note(note: str, print_tokens: bool = False):
print(f"Formatting note: {os.path.relpath(note, os.getcwd())}")
if not os.path.isfile(note):
print(f"ERROR: '{note}' is not a file.")
exit(1)
with open(note, 'r+', encoding='utf-8') as f:
content = f.read()
tokens = ovault.text_to_tokens(content)
if print_tokens:
for t in tokens:
print(f" {t}")
formatted = ovault.to_markdown(tokens)
# Write the formatted content back to the file
f.seek(0)
f.write(formatted)
# Iterate over all notes in the vault and format them
def format_vault(path: str, print_tokens: bool = False):
if not os.path.isdir(path):
usage(f"ERROR: Vault path '{path}' is not a directory.")
exit(1)
for note in sorted(ovault.Vault(path).notes()):
format_note(note.full_path(), print_tokens=print_tokens)
# Main function to parse command-line arguments and call the appropriate formatting function
def main():
global PROGRAM
[PROGRAM, *args] = sys.argv
if len(args) == 0: usage("No arguments provided.")
is_vault = False
print_tokens = False
while len(args) > 1:
[arg, *args] = args
match arg:
case '--vault': is_vault = True
case '--tokens': print_tokens = True
case _: usage(f"Unknown argument '{arg}'.")
if len(args) != 1: usage("No vault path provided.")
path = args[0]
match is_vault:
case True: format_vault(path, print_tokens)
case False: format_note (path, print_tokens)
if __name__ == "__main__":
main()
Sub-modules
ovault.ansi-
This module contains ANSI color codes for terminal text formatting.
ovault.check_links-
Check all external links to websites in an Obsidian vault and report any broken links.
ovault.filename_compatibility-
Check filenames for compatibility across different operating systems.
ovault.help-
Show a list of all utility modules included in
ovault. ovault.info-
Show information about an Obsidian vault.
ovault.mv-
Rename a file within an Obsidian vault, updating all links accordingly.
ovault.name_pasted_images-
Rename pasted images in the vault based on the note they are pasted into …
ovault.to_html-
Convert an obsidian vault to a simple static HTML site.
ovault.visualize_graph-
Visualize your Obsidian vault graph using pyvis.
Functions
def normalize(name)-
Nomalize a note name or path 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.
def to_markdown(obj)-
Converts a Python objects to a Markdown string.
Classes
class Attachment (...)-
An attachment in an Obsidian vault. An attachment is any file that is not a markdown file.
Instance variables
var backlinks-
List of paths to markdown files that link to this attachment.
var path-
Path to the attachment file.
var vault_path-
Absolute path to the vault root.
Methods
def full_path(self, /)-
Get the full (absolute) path to the attachment file as a string.
def read_bytes(self, /)-
Read the attachment file as bytes.
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 title-
The title of the callout.
var tokens-
The tokenized content of the callout, which can include text, code blocks, links, etc.
Methods
def to_markdown(self, /)-
Returns the markdown representation of the callout.
class ExternalLink (...)-
Represents an external link to an external URL.
Example:
 [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.
Methods
def to_markdown(self, /)
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 dict(self, /)-
Returns a python dictionary representation of the frontmatter.
def get(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 remove(self, /, key)-
Deletes a key-value pair from the frontmatter by key.
def set(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)-
Converts the frontmatter to a YAML string representation.
class InternalLink (...)-
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.
Methods
def to_markdown(self, /)
class Note (path, vault_path='')-
A note in an Obsidian vault.
Instance variables
var backlinks-
Set of backlinks to this note (links from other notes)
var length-
Length of the note content in characters
var links-
Set of links to other notes or attachments
var name-
Name of the note (file name without extension)
var path-
Relative path within vault
-
Set of tags in the note
var vault_path-
Path to the vault
Methods
def all_tokens(self, /)-
Get content note as a list of all tokens, including those nested within other tokens (e.g. callouts, quotes, lists).
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 uses the internal
Spanof 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 uses the internal
Spanof the note to determine the insertion position. def normalized_name(self, /)-
Get the normalized name of the node.
def read(self, /)-
Read the content of the note and return it as a string
def read_bytes(self, /)-
Read the content of the note and return it as bytes
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
Spanin the note with the given text. This can be used to replace tokens within the note. def set_frontmatter(self, /, frontmatter)-
Set the frontmatter of the note from a python dictionary.
def tokens(self, /)-
Get content 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
Tokeninstances:- `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.Bold
- builtins.Token.Callout
- builtins.Token.CheckList
- builtins.Token.Code
- builtins.Token.Comment
- builtins.Token.DisplayMath
- builtins.Token.Divider
- builtins.Token.Escaped
- builtins.Token.ExternalLink
- builtins.Token.FootnoteDef
- builtins.Token.FootnoteRef
- builtins.Token.Frontmatter
- builtins.Token.Header
- builtins.Token.Highlight
- builtins.Token.InlineCode
- builtins.Token.InlineFootnote
- builtins.Token.InlineMath
- builtins.Token.InternalLink
- builtins.Token.Italic
- builtins.Token.List
- builtins.Token.NumericList
- builtins.Token.Quote
- builtins.Token.Strikethrough
- builtins.Token.Table
- builtins.Token.Tag
- builtins.Token.TemplaterCommand
- builtins.Token.Text
Class variables
var Boldvar Calloutvar CheckListvar Codevar Commentvar DisplayMathvar Dividervar Escapedvar ExternalLinkvar FootnoteDefvar FootnoteRefvar Frontmattervar Headervar Highlightvar InlineCodevar InlineFootnotevar InlineMathvar InternalLinkvar Italicvar Listvar NumericListvar Quotevar Strikethroughvar Tablevar Tagvar TemplaterCommandvar Text
Methods
def to_markdown(self, /)-
Convert the token to a Markdown string.
class Vault (path, ignore=Ellipsis, ignore_file=None, create=False)-
An Obsidian vault containing notes and attachments. The vault is indexed on creation and can be re-indexed with the
indexmethod.Instance variables
var dangling_links-
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, content, reindex=False)-
Add a note to the vault.
Use the
reindexflag or call theindexmethod to reindex the vault after adding notes. def attachment(self, /, name)-
Get attachment by its name. Eg. "image.png" or "folder/image.png"
def attachments(self, /)-
Get a list of all attachments in the vault. Order is not guaranteed.
def get_attachment_by_name(self, /, name)-
Get attachment by its name. Eg. "image.png" or "folder/image.png"
def get_attachment_by_path(self, /, path)-
Get an attachment by its path in the vault. Either absolute or relative to the vault path.
def get_note_by_name(self, /, name)-
Get note by its name.
def get_note_by_path(self, /, path)-
Get note by its path in the vault. Either absolute or relative to the vault path.
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 note(self, /, name)-
Get note by its name.
def notes(self, /)-
Get a list of all notes in the vault. Order is not guaranteed.
def rename(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.
-
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 Attachmentvar Note