markdown.obsidian.vault

Functions for inspecting and modifying Obsidian.md vaults and their files and folders

This module generally manages Obsidian.md vaults and their files and folders.

The VaultNote class in this module is one of the most essentially classes used in trouver. Generally, one uses Obsidian.md to read and write “notes”, which are .md files. The VaultNote class manages such notes in an Obsidian.md vault.

See Also markdown.obsidian.personal.vault.

import shutil
import tempfile
from unittest import mock

from fastcore.test import *
from nbdev.showdoc import show_doc

from trouver.helper import path_name_no_ext, _test_directory

Some examples for this module come from nbs/_tests/vault_1.

Errors


source

NoteNotUniqueError

 NoteNotUniqueError (*args, **kwargs)

A NoteNotUniqueError is raised when a VaultNote is specified by name and not by relative path in the vault, but the vault is found to have multiple notes of the name.

Attributes

  • note_name - str
    • The name of the note which is not unique in the vault.
  • notes - list[str]
    • The paths of the notes whose names are note_name.

source

NoteNotUniqueError.from_note_names

 NoteNotUniqueError.from_note_names (note_name:str, notes:list[str])

Construct a NoteNotUniqueError object from note names


source

NoteDoesNotExistError

 NoteDoesNotExistError (*args, **kwargs)

A NoteDoesNotExistError is raised when a VaultNote is specified by either name and or by relative path in the vault, but the vault is found to have no notes of the name.


source

NoteDoesNotExistError.from_note_name

 NoteDoesNotExistError.from_note_name (note_name:str)

Construct a NoteDoesNotExistError object from note name


source

NotePathIsNotIdentifiedError

 NotePathIsNotIdentifiedError (*args, **kwargs)

A NotePathIsNotIdentifiedError is raised when the rel_path attribute of a VaultNote object is expected to be identified (i.e. a path and not None) but this expectation is not fulfilled.


source

NotePathIsNotIdentifiedError.from_note

 NotePathIsNotIdentifiedError.from_note (note)

Construct a NotePatahIsNotIdentifiedErrro object from a VaultNote.

Convert path to Obsidian ID


source

path_to_obs_id

 path_to_obs_id (rel_path:os.PathLike)

Convert a relative path of an Obsidian note to the Obsidian identifying str.

This identification is for a vault-internal Wikilink.

Note that this function does not have a vault as a parameter.

Type Details
rel_path PathLike A path representation the path of an Obsidian note relative to its vault. This does not have to be an existing path.
Returns str The obsidian url of the hypothetical note within its vault. Note that this does not end with the file extension .md.

Obsidian formats its file paths with ‘/’; the path_to_obs_id function converts a relative path of an Obsidian note to the Obsidian-recognized path.

test_eq(path_to_obs_id(
    r'some_folder\some_subfolder\some_subsubfolder\some_file.md'),
    'some_folder/some_subfolder/some_subsubfolder/some_file')

Obsidian notes might contain spaces in their paths.

test_eq(path_to_obs_id(
    Path('some folder\some subfolder\some file.md')),
    'some folder/some subfolder/some file')

Example vault

Get all notes


source

all_paths_to_notes_in_vault

 all_paths_to_notes_in_vault (vault:os.PathLike, as_dict:bool=False)

Return the paths, relative to the Obsidian vault, of notes in the Obsidian vault.

This may not actually return all of the paths to the notes, see the parameter as_dict.

Parameters

  • vault - PathLike
    • The path to the Obsidian vault directory
  • as_dict - bool
    • If True, then returns a dict. If False, then returns a list. Defaults to False. If there are multiple notes with the same name in the vault, and as_dict is set to True, then the dictionary will contain only one of the (relative) paths to those notes among its values. If as_dict is set to False, then the list will contain all the paths to the notes even when notes with non-unique names exist.

Returns

  • Union[list[str], dict[str, str]]
    • Each str represents the path relative to vault. If as_dict is True, then returns a dict whose keys are str, which are (unique) names of the notes in the vault, and the values are the paths.

The all_paths_to_notes_in_vault function returns all of the paths to notes in the Obsidian vault; only .md files are recognized as notes.

By default, the function returns a list whose items are strings of paths to notes relative to the vault path.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    # os.startfile(os.getcwd())

    notes = all_paths_to_notes_in_vault(temp_vault)
    test_eq(len(notes), 7)
    print(notes)
    # test_shuffled(, list)
['README.md', '_index.md', 'algebra\\ring.md', 'algebra\\reference_1\\ring.md', 'analysis\\exponential_function.md', 'category_theory\\category.md', 'topology\\category.md']

Passing as_dict=True returns a dictionary whose keys are note names and whose values are lists of paths to notes of the name relative to the vault.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)
    # os.startfile(os.getcwd())

    notes = all_paths_to_notes_in_vault(temp_vault, as_dict=True)
    test_eq(len(notes), 5)
    test_eq(len(notes['ring']), 2)
    print(notes)
{'README': ['README.md'], '_index': ['_index.md'], 'ring': ['algebra\\ring.md', 'algebra\\reference_1\\ring.md'], 'exponential_function': ['analysis\\exponential_function.md'], 'category': ['category_theory\\category.md', 'topology\\category.md']}

Searching notes by name


source

all_note_paths_by_name

 all_note_paths_by_name (name:str, vault:os.PathLike,
                         subdirectory:Optional[os.PathLike]=None)

Return the relative paths to all notes in the Obsidian vault with the specified name in the specified subdirectory.

This function does not assume that the specified subdirectory in the vault has at most one note of the specified name.

Type Default Details
name str Name of the note(s) to find
vault PathLike The path to the Obsidian vault directory
subdirectory typing.Optional[os.PathLike] None The path to a subdirectory in the Obsidian vault, relative to vault. If None, then denotes the root of the vault.
Returns list Each item is a path to a note of the given name, relative to vault.

Basic usage:

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    notes = all_note_paths_by_name('ring', temp_vault)
    print('Searched for notes of name `ring`:')
    print(notes, '\n')
    assert len(notes) == 2

    notes = all_note_paths_by_name('exponential_function', temp_vault)
    print('Searched for notes of name `exponential_function`:')
    print(notes, '\n')
    assert len(notes) == 1

    empty_list = all_note_paths_by_name('non-existent-note-name', temp_vault)
    print('Searching for a non-existent note name yields an empty list:')
    print(empty_list)
    assert len(empty_list) == 0
Searched for notes of name `ring`:
[Path('algebra/ring.md'), Path('algebra/reference_1/ring.md')] 

Searched for notes of name `exponential_function`:
[Path('analysis/exponential_function.md')] 

Searching for a non-existent note name yields an empty list:
[]

We can specify a subdirectory inside the vault to restrict the search to.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    all_notes_named_curve_in_vault = all_note_paths_by_name('category', temp_vault)
    print('All notes named `category`:\n', all_notes_named_curve_in_vault, '\n')
    assert len(all_notes_named_curve_in_vault) == 2
    for note_path in all_notes_named_curve_in_vault:
        test_eq(path_name_no_ext(note_path), 'category')
    
    print('All notes named `category` in the subdirectory `topology`')
    notes_named_topology_in_subdirectory = all_note_paths_by_name(
        'category', temp_vault, 'topology')
    print(notes_named_topology_in_subdirectory)
    assert len(notes_named_topology_in_subdirectory) == 1
    assert path_name_no_ext(notes_named_topology_in_subdirectory[0]) == 'category'
All notes named `category`:
 [Path('category_theory/category.md'), Path('topology/category.md')] 

All notes named `category` in the subdirectory `topology`
[Path('topology/category.md')]

source

note_path_by_name

 note_path_by_name (name:str, vault:os.PathLike,
                    subdirectory:Optional[os.PathLike]=None,
                    hints:Optional[list[os.PathLike]]=None)

Return the path, relative to a subdirectory in the vault, of the note of the specified name.

Raises

  • NoteNotUniqueError
    • If the note of the specified name is not unique in the subdirectory.
  • NoteDoesNotExistError
    • If the note of the specified name does not exist in the subdirectory.

See Also

  • The constructor of the VaultNote class
    • passing an argument to the name parameter of this constructor method essentially does the same thing as this function, except the constructor method uses a cache.
Type Default Details
name str The path to the Obsidian vault directory.
vault PathLike The path to a subdirectory in the Obsidian vault. If None, then denotes the root of the vault.
subdirectory typing.Optional[os.PathLike] None The path to a subdirectory in the Obsidian vault. If None, then denotes the root of the vault.
hints typing.Optional[list[os.PathLike]] None Hints of which directories, relative to subdirectory that the note may likely be in. This is for speedup. The directories will be searched in the order listed.
Returns Path The note of the specified name in the specified subdirectory of the vault.

Assuming that there exists a unique note of a specified name in a vault, we can identify it.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    note_path = note_path_by_name('exponential_function', temp_vault)
    print(note_path)
    test_eq(path_name_no_ext(note_path), 'exponential_function')
analysis\exponential_function.md

If there is more than one note in the vault of the specified name, then a NoteNotUniqueError is raised.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    print(f'The vault has more than one note named `category`:\n',
            all_note_paths_by_name('category', temp_vault))
    with (ExceptionExpected(ex=NoteNotUniqueError, regex='not unique')):
        note_path_by_name('category', temp_vault)
The vault has more than one note named `category`:
 [Path('category_theory/category.md'), Path('topology/category.md')]

Passing an argument to the parameter subdirectory restricts the search to the subdirectory. A NoteNotUniqueError can be avoided if a subdirectory is specified and if the note of the specified name is unique in the subdirectory.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    note = note_path_by_name('category', temp_vault, subdirectory='topology')
    print(note)
    assert 'topology' in str(note) and path_name_no_ext(note) == 'category'
topology\category.md

If there is no note in the vault of the specified name, then a NoteDoesNotExistError is raised.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    with (ExceptionExpected(ex=NoteDoesNotExistError, regex='does not exist')):
        note_path_by_name('this_note_does_not_exit', temp_vault)

The hints parameter

TODO: finish example


source

note_name_unique

 note_name_unique (name:str, vault:os.PathLike)

Return True if a note of the specified name exists and is unique in the Obsidian vault.

Type Details
name str Name of the note.
vault PathLike Path to the vault.
Returns bool

Example use:

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vault_has_unique_note_named_ring = note_name_unique('ring', temp_vault)
    assert not vault_has_unique_note_named_ring

    vault_has_unique_note_named_exponential_function = note_name_unique('exponential_function', temp_vault)
    assert vault_has_unique_note_named_exponential_function

Getting note name from its path


source

note_name_from_path

 note_name_from_path (note_path:str)

Return the name of a note from its path.

Type Details
note_path str The path of the note. The note does not need to exist.
Returns str The name of the note.
assert note_name_from_path('algebra/ring.md') == 'ring'

VaultNote class

Just as how paths in Python can be dealt either via strings of paths or via pathlib.Path objects, It is useful to have a class to encapsulate together the name of a note, and its path/Obsidian vault identifier.


source

VaultNote

 VaultNote (vault:os.PathLike, rel_path:os.PathLike=None, name:str=None,
            subdirectory:Optional[os.PathLike]='',
            hints:list[os.PathLike]=[])

Represents a note in an Obsidian vault, without regards to the contents.

The note does not have to exist, except in circumstances stating otherwise.

TODO go through the methods of this class to see which methods assume that the note exists and which do not.

TODO finish the sentence below. A VaultNote can be specified by either the rel_path or the name argument in its constructor. If name is specified, then the

TODO implement subdirectory hint

Attributes

  • vault - Path
    • The (relative or absolute) path of the Obsidian vault that the note is located in.
  • name - str
    • The name of the note in the vault.
  • rel_path - str
    • The note’s path relative to the vault. If
  • cache - dict[str, dict[str, list[str]]], class attribute
    • The keys are string, which are paths to vaults. The corresponding values are dict whose keys are string, which are names in the vault of the (unique) note of that name, and whose values are list of string, which are paths to the note relative to the vault. The cache is not automatically updated when notes are moved.

Parameters

  • vault - PathLike
  • rel_path - PathLike
  • name - str
    • The name of the note in the vault. Defaults to the empty str.
      • If None, then the rel_path parameter should be used to determine self.name instead.
      • If not None, then the note must uniquely exist in the vault.
  • subdirectory - Union[PathLike, None]
  • hints - list[PathLike]

Raises

  • ValueError
    • if rel_path and name are both None.
Type Default Details
vault PathLike The (relative or absolute) path of the Obsidian vault that the note is located in.
rel_path PathLike None The note’s path relative to the vault. If None, then the name parameter is used to determine the note instead. Defaults to None.
name str None The name of the note. If None, then the rel_path parameter is used to determine the note instead. Defaults to None
subdirectory typing.Optional[os.PathLike] The relative path to a subdirectory in the Obsidian vault. If None, then denotes the root of the vault. Defaults to the empty str.
hints list [] Paths, relative to subdirectory, to directories where the note file may be found. This is for speedup. Defaults to the empty list, in which case the vault note is searched in all of subdirectory.

Functions/Methods of the VaultNote class


source

VaultNote.rel_path_identified

 VaultNote.rel_path_identified ()

Return True if self.rel_path is identified, i.e. is a path that is not None.


source

VaultNote.obsidian_identifier

 VaultNote.obsidian_identifier ()

Return the Obsidian identifier of the VaultNote object.

This is the note’s unqiue Obsidian id in the vault. This is like a path str with forward slashes / (as opposed to backwards \ slashes) and without a file extension (.md).


source

VaultNote.identify_rel_path

 VaultNote.identify_rel_path (update_cache=False)

Sets self.rel_path to a path, if not already done so.

If self.rel_path is not already set as a path, then the cache is searched to find a note whose name is self.name (which is necessarily specified).

Type Default Details
update_cache bool False If True, if the cache is searched, and if a note of the specified name is not found in the cache, then the cache is updated and searched again. Defaults to False.
Returns None

source

VaultNote.exists

 VaultNote.exists (update_cache=False)

Returns True if self.rel_path is identified and if the note exists in the vault.

Type Default Details
update_cache bool False If True, then update the cache and try to identify self.rel_path before verifying whether the note exists in the vault.
Returns bool

source

VaultNote.path

 VaultNote.path (relative=False)

Returns the path to the note.

Assumes that self.rel_path has been identified.

Raises - NotePathIsNotIdentifiedError - If the relative path of self is not identified.

Type Default Details
relative bool False If True, then return the path relative to the vault.
Returns typing.Optional[pathlib.Path] Path to the note if self.rel_path is deterined. None otherwise.

source

VaultNote.directory

 VaultNote.directory (relative=False)

Return the directory that the note is in.

Type Default Details
relative bool False If True, then return the path of the directory relative to the vault.
Returns Path The path of the directory that the note is in.

source

VaultNote.create

 VaultNote.create ()

Create the note if it does not exist.

The directory of the file needs to be created separately beforehand.

If the file exists, then a FileExistsError is raised and the file modification time is not changed.

Raises

  • FileExistsError
    • If the file already exists.
  • FileNotFoundError
    • If the directory of the file does not already exist.

source

VaultNote.delete

 VaultNote.delete ()

Delete the note if it exists.

This updates the cache if necessary


source

VaultNote.move_to

 VaultNote.move_to (rel_path:os.PathLike)

Move/rename the note to the specified location in the vault, assuming that it exists.

Type Details
rel_path PathLike The path in which to rename the path to self as, relative to self.vault.
Returns None

source

VaultNote.move_to_folder

 VaultNote.move_to_folder (rel_dir:os.PathLike)

Move the note to the specified folder in the vault, assuming that if exists.

Type Details
rel_dir PathLike The path of the directory in which to move self to, relative to self.vault.
Returns None

source

VaultNote.text

 VaultNote.text ()

Returns the text contained in the note.

Raises

  • NoteDoesNotExistError
    • If self does not point to an existing note.

source

VaultNote.update_cache

 VaultNote.update_cache (vault:os.PathLike)

Class method to update cache for vault by inspecting all files in subdirectories of vault.

Type Details
vault PathLike The vault.
Returns None

source

VaultNote.clear_cache

 VaultNote.clear_cache ()

Class method to clear out the entire cache for all vaults.


source

VaultNote.unique_name

 VaultNote.unique_name (name:str, vault:os.PathLike)

A class method to return a name for a note that is unique in the vault based on a specified name.

Type Details
name str The base name for the note.
vault PathLike The vault
Returns str A str obtained by appending _{some number} to the end of name.

Constructing VaultNote instances

By name

The most convenient way to construct an existing VaultNote is to specify the vault in which it exists and the note’s name, assuming that the note of the specified name exists and is unique in the specified vault.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vault_note = VaultNote(temp_vault, name='exponential_function')
    assert vault_note.exists()
    print(vault_note.name)
    print(f'`vault_note` is located, relative to `vault`, at {vault_note.rel_path}.')
exponential_function
`vault_note` is located, relative to `vault`, at analysis\exponential_function.md.
# TODO: Delete the below example
# If an argument is passed to the `name` parameter and if the note's name is not unique in the vault, then a `NoteNotUniqueError` is raised. 

# If an argument is passed to the `name` parameter and if the note's name does not exist in the vault, then a `NoteDoesNotExistError` is raised.
# with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as vault:
#     make_example_vault(vault)

#     with ExceptionExpected(ex=NoteNotUniqueError):
#         vault_note = VaultNote(vault, name='ring')
#     with ExceptionExpected(ex=NoteDoesNotExistError):
#         vault_note = VaultNote(vault, name='does_not_exist')

By relative path

Alternatively, a VaultNote object can be created by passing an argument to the rel_path parameter. In this case, the note of the specified path does not need to exist.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vault_note = VaultNote(temp_vault, rel_path='non_existent_folder/non_existent_note.md')
    assert not vault_note.exists()
    # Note that there is not a unique note of name `ring`.
    vault_note = VaultNote(temp_vault, rel_path='algebra/ring.md')  
    assert vault_note.exists()

The rel_path parameter takes precedence over the name parameter.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vault_note = VaultNote(temp_vault, rel_path='non_existent_folder/non_existent_note.md', name='ring')
    assert vault_note.name == 'non_existent_note'

If the arguments for both the name and the rel_path parameters are None, then a ValueError is raised:

test_vault = _test_directory() / 'test_vault_1'
with ExceptionExpected(ValueError):
    vault_note = VaultNote(test_vault, rel_path=None, name=None)

Cache of the VaultNote class

The VaultNote class keeps a cache of the notes (files with extension .md) in the vault. In pracctice, this cache is updated when a note of the specified name is not found when constructing a VaultNote instance via the name parameter.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    VaultNote.update_cache(temp_vault)
    
    assert VaultNote._check_name_exists_and_unique_in_vault_cache(temp_vault, 'exponential_function') is None
    with ExceptionExpected(ex=NoteDoesNotExistError):
        VaultNote._check_name_exists_and_unique_in_vault_cache(temp_vault, 'this_note_does_not_exist')
        # TODO: test this works correctly when a note does not exist by being removed.
    with ExceptionExpected(ex=NoteNotUniqueError):
        VaultNote._check_name_exists_and_unique_in_vault_cache(temp_vault, 'ring')
VaultNote.clear_cache()
with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    assert VaultNote._check_if_cache_needs_to_update(temp_vault, name='ring')
    VaultNote.update_cache(temp_vault)
    assert VaultNote._check_if_cache_needs_to_update(temp_vault, name='does_not_exist')

Identifying the VaultNote object

When the name parameter (as opposed to the rel_path parameter) is specified in the constructor of a VaultNote object, the constructor looks into the cache of the VaultNote class to identify a note with the specified name in the specified vault. If the cache contains no such note, then the cache is updated and searched again. If the cache still contains no such note, then the rel_path attribute of the VaultNote object is left unidentified (i.e. is set to None).

Assuming that a note of the specified name is created later, the relative path of the VaultNote object can be identified using the identify_rel_path(update_cache=True) method. Moreover, the rel_path_identified method returns True if the relative path is identified.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vn = VaultNote(temp_vault, name='does_not_exist_at_first')
    assert not vn.rel_path_identified()
    # Create a note at the root of the vault.
    open(temp_vault / 'does_not_exist_at_first.md', 'w').close()
    vn.identify_rel_path(update_cache=True)
    assert vn.rel_path_identified()
    assert vn.exists()
    test_eq(vn.rel_path, 'does_not_exist_at_first.md')

Getting information about the note

Here are some convenient ways to get information about the VaultNote:

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vault_note = VaultNote(temp_vault, name='exponential_function')
    print(f'Obsidian vault identifier:\t{vault_note.obsidian_identifier()}')
    print(f'relative path:\t{vault_note.rel_path}')
    print(f'Part of the absolute path of the note:\t{str(vault_note.path(relative=False))[:7]}')
    print(f'note name:\t{vault_note.name}')
    print(f'directory that the note is in relative to the vault:\t{vault_note.directory(relative=True)}')
Obsidian vault identifier:  analysis/exponential_function
relative path:  analysis\exponential_function.md
Part of the absolute path of the note:  c:\User
note name:  exponential_function
directory that the note is in relative to the vault:    analysis

The VaultNote.path method raises a NotePathIsNotIdentifiedError if the VaultNote object’s relative path is not idetnfied:

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    vn = VaultNote(temp_vault, name='does_not_exist')
    with ExceptionExpected(NotePathIsNotIdentifiedError):
        vn.path()

Reading the contents of the note

The VaultNote.text() function reads the contents of the file that the VaultNote object represents, assuming that this file exists. If the file does not exist, then a NoteDoesNotExistError is raised.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)
    vault_note = VaultNote(temp_vault, name='exponential_function')
    print(vault_note.text())
    assert len(vault_note.text()) > 10

    vault_note = VaultNote(temp_vault, rel_path='does_not_exist.md')
    with ExceptionExpected(ex=NoteDoesNotExistError):
        vault_note.text()
The **exponential function** is the function sending a complex number $z$ to **$$e^{z} = \sum_{n=0}^\infty \frac{z^n}{n!}$$**. It converges for all $z \in \mathbb{C}$.

Creating/deleting/moving the note

If a VaultNote object represents a non-existent file, then the file can be created with empty content. The cache is updated with this single new entry, but the rest of the cache remains the same.

If the file exists, then a FileExistsError is raised.

If the specified directory for the file does not exist, then a FileNotFoundError is raised.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)
    vault_note = VaultNote(temp_vault, rel_path='new_file.md')
    vault_note.create()
    assert vault_note.exists()
    assert vault_note.rel_path in VaultNote.cache[str(temp_vault)]['new_file']

    with ExceptionExpected(ex=FileExistsError):
        vault_note.create() 

    vault_note = VaultNote(temp_vault, rel_path='none_existent_folder/new_file.md')
    with ExceptionExpected(ex=FileNotFoundError):
        vault_note.create()
with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)
    vault_note = VaultNote(temp_vault, name='exponential_function')
    vault_note.delete()
    assert not vault_note.exists()
    assert vault_note.rel_path not in VaultNote.cache[str(temp_vault)]['exponential_function']

If a VaultNote object represents an existing file, then the file can be renamed or moved.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)

    VaultNote.clear_cache()
    vault_note = VaultNote(temp_vault, name='exponential_function')
    vault_note.move_to_folder('')
    assert vault_note.rel_path == 'exponential_function.md'
    assert 'exponential_function.md' in VaultNote.cache[str(temp_vault)]['exponential_function']

Getting a unique note name

It is troublesome to create a note with a non-unique name. The unique_name method of the VaultNote class takes a tentative name for a note to be created and adds a number to the name so that the note name will be unique in the vault.

with tempfile.TemporaryDirectory(prefix='temp_dir', dir=os.getcwd()) as temp_dir:
    temp_vault = Path(temp_dir) / 'test_vault_1'
    shutil.copytree(_test_directory() / 'test_vault_1', temp_vault)
    # There is a note named category in the test vault.
    sample_name = VaultNote.unique_name('category', temp_vault)  
    assert not VaultNote(temp_vault, name=sample_name).exists()

    sample_name = VaultNote.unique_name('non_existent_note_name', temp_vault)
    assert not VaultNote(temp_vault, name=sample_name).exists()
    assert sample_name == 'non_existent_note_name'
## Copying files in an `Obsidian.md` vault to and from a subvault
# # TODO: use these methods during vault construction for a reference
# def copy_vault_file_into_subvault(
#         vault: PathLike, # The Path to the vault from which to copy the files.
#         subvaults: Union[PathLike, list[PathLike]], # The Paths to the subvaults to which to copy the files.
#         files: Union[PathLike, list[PathLike]], # The Path to the files, relative to `vault` to copy.
#         replace: bool = True, # If `True`, replace existing files in `subvaults` if necessary. Defaults to `True`
#         backup: bool = True, # If `True` and if `replace=True`, create a backup for any replaced files in a subvault in a folder named `.back` in the root directory of the subvault.
#         ) -> None: 
#     """Copy the specified files in `vault` into subvaults.

#     The files are copied within the subvaults to the same relative paths as
#     they are found in `vault`.

#     Here, "files" include directories. If a directory is copied, then all
#     files and subdirectories of that directory are also copied.

#     **Parameters**
#     - vault - PathLike
#         - The path to the Obsidian vault from which to copy files from.
#     - subvaults - PathLike or list[PathLike]
#         - The paths to the subvaults to which to copy the files.
#     - files - PathLike or list[PathLike]
#         - The files to copy.

#     **Raises**
#     - FileExistsError
#         - If `replace` is `False` and some subvault already has a file at
#           the path in which a file-copy is attempted. In this case, no
#           files are copied.
#     - FileNotFoundError
#         - If a path specified in `files` does not exist in `vault`. In this
#           case, no files are copied.

#     """
#     vault = Path(vault)
#     if isinstance(subvaults, PathLike):
#         subvaults = [subvaults]
#     if isinstance(files, PathLike):

#         files = [files]


#     # TODO: Implement this as a private function 
#     for file in files:
#         if not os.path.exists(vault / file):
#             raise FileNotFoundError(
#                 f"Attempted to copy files/folders from a vault into subvaults"
#                 f", but there is at least one non-existent files"
#                 f". No files have been copied."
#                 f"vault: {vault}"
#                 f"file: {file}")

#     if not replace: 
#         for subvault, file in itertools.product(subvaults, files):
#             if os.path.exists(subvault / file):
#                 raise FileExistsError(
#                     f"Attempted to copy files/folders from a vault into subvaults"
#                     f", but at least one subvault already has a file that is"
#                     f" supposed to be copied from the vault"
#                     f". No files have been copied."
#                     f"subvault: {subvault}"
#                     f"file: {file}")
    
#     # TODO: copy and backup files

#     return