Package ovault
Version: 0.0.10
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_Frontmatter
- builtins.Token_Header
- builtins.Token_Highlight
- builtins.Token_InlineCode
- builtins.Token_InlineMath
- builtins.Token_InternalLink
- builtins.Token_Italic
- builtins.Token_List
- builtins.Token_NumericList
- builtins.Token_Quote
- builtins.Token_Strikethrough
- builtins.Token_Tag
- builtins.Token_TemplaterCommand
- builtins.Token_Text
Class variables
var Boldvar Calloutvar CheckListvar Codevar Commentvar DisplayMathvar Dividervar Escapedvar ExternalLinkvar Frontmattervar Headervar Highlightvar InlineCodevar InlineMathvar InternalLinkvar Italicvar Listvar NumericListvar Quotevar Strikethroughvar 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