Daily Note automation with Templater + python

I’ve been using Obsidian for several months and have settled into a reasonably good workflow for notes, sources, etc. The Daily Note plugin in particular has been great for keeping track of what’s going on and doing quick capture that I know I won’t lose track of later, but there were two lingering things I didn’t like:

  • Having to go back to the file tree to navigate to previous days’ notes.
  • Having to manually move uncompleted Todo items from one day’s note to the next.

I’ve seen some examples of using text expansion scripts to create navigation links for Daily Notes, but this seemed a bit clunky to me, especially when trying to account for the logic when Daily Notes aren’t actually created every single day. Similarly, there are a few plugins that are supposed to support Todo migrations, but I’ve not gotten any of them to work so far.

Then recently I saw a YouTube video from Bryan Jenks about the Templater plugin, and when he showed macro expansions invoking arbitrary scripts I was pretty sure this was a workable solution. I’ve spent a few hours banging on some Python and have a couple of scripts that, when hooked into Templater macros embedded in my Daily Note template, do a few things:

  • Create forward and backwards navigation links in the Daily Notes that are correct even if you don’t, for example, create a note over the weekend or on a day off.
  • Migrate uncompleted Todo items from the previous day’s Note into the new Daily Note.
  • Append a tick mark to the end of each Todo item each time it carries over to a new day to help identify items that are lingering too long and might need some attention to get off the list.

I am in no way a skilled or probably even competent coder, but figured it wouldn’t hurt to share what I came up with in case it’s helpful to anyone else. Obviously most of the credit goes to Templater’s author for making such a powerful addition to an already great tool. At any rate, the scripts and some basic instructions are available here:

Enjoy, and please let me know if you have ideas for improving on this concept!

EDIT (14 May): This broke with release 1.1.0 of Templater, but I have recently updated the scripts to work with the new syntax and fixed a race condition with the file modifications that was causing issues in some cases.

7 Likes

Nice. I have recently used the same idea to create a schedule depending on the weekday in my daily note template after @osgav pointed me to the Templater plugin.

2 Likes

Jason! This looks ideal to me, but I’ve no idea how to actually implement it. I muddle through obsidian as best I can but am committed to getting better. I do have templater, so would I just copy the obs_todo_migr.py as a note and put it in that folder? Sorry for my ignorance, but if you could give me any help I’d really appreciate it. Thank you so much!

Hi Stella!

TLDR: If you’ve already figured out Python scripts using Templater, please ignore, and happy holidays!

Jason’s Code

I haven’t had the chance to look at Jason’s code or how it works, but just in case you’re still in search of answers regarding running Python scripts from Templater, I’ll give you a starting point.

Workflow

User System Command Functions

You might have seen in the Templater settings where it lets you put a folder location to hold your scripts. In case you haven’t:

Enable System Command User Functions
  • The section reads, “User System Command Functions”
    • you’ll see under this section an option to “Enable System Command User Functions”
      • enable this (there’s a warning, but the print is so fine!)

A new options should have appeared below. This is where we can define our function!

Add New User Function

There is a button to add a new user function now; click the button, “Add New User Function”

Now there will be two fields you’ll fill in:

  1. Function Name
  2. System Command

In (1), you’ll write the name of the function call to Templater. Let’s say you named your function set_happy. Then, for the “System Command”, you can enter the following:

python ./Scripts/src/happy_modifier.py
About System Command Call to Python

If the folder containing python.exe isn’t in your Path variable, the above command will not work.

How to Fix?

  • replace python with full/path/to/python.exe
    • python is on Path for me, so I don’t know if this works since the executable exists outside Obsidian’s vault, but since it’s a system call, I’d be surprised if it didn’t work!
About Python Module Location

If you do not have the folder structure leading to your module indicated above, Scripts/src/happy_modifier.py, relative to your Obsidian vault, the above command will fail.

How to fix?

  • this also assumes you have a folder at the top-level of your vault named Scripts, with a subfolder, src, containing a module named happy_modifier.py.
    • if you don’t, create this directory structure and add your Python module, happy_modifier.py to the src folder to fix
Your New Templater User-Function!

Since you named your function Templater set_happy, the call to Templater would look like:

<% tp.user.set_happy() %>

If you don’t plan to pass any arguments to your module (or Templater function), you can stop here; you’re good to go! If you’re anything like me, you see set_ at the beginning of your module call, and you need to feed her!

Pass Arguments to Your Module

You might want to pass arguments to happy_modifier.py using your call to set_happy, so lets add a string and a number. We’ll call the string parameter name, myname and the number parameter name, modifier.

From the Templater function, there is a specific syntax to pass named key, value pairs via system call. Here’s how your call might look:

<% tp.user.set_happy({name:"My Name", modifier:"1"}) %>

The syntax is a little cluttered there, so I’ll refactor a bit:

tp.user.set_happy(
  {
    name:"My Name", 
    modifier:"1"
  }
)

Do you remember the parameter names we decided on earlier? As if you could forget!

The big thing to note here is that Templater is expecting 1 object to be passed to the defined function (dict-like, in this case). This applies to JavaScript user-defined functions, as well, so there’s no Python double standards here, I don’t believe.

  • the object looks like a dictionary, and I feel like I’m going to get a NameError every time I use it!

But, I need to define that… Right?

Jokes aside, this will get the arguments to your module, but maybe not the way you would expect if you’re coming from a Python world.

  • “Under the hood,” Templater is making the system call to your Python Module, but not until it has exposed your arguments, called name and modifier as environment variables!
    • I about had a stroke trying to find those suckers my first pass.

      sys.argv, don’t let me down now!!

Now that you know you’re looking for environment variables, things get a lot easier on the Python side of the house:

import os
from contextlib import suppress as tryme

def run():
  name = os.environ.get("name", "NoName")
  modifier = os.environ.get("modifier", 0)
  return name, modifier
  
if __name__ == "__main__":
  with tryme:
    do_stuff_with_name_and_modifier(*run())