Python SDK for Trilium Notes. More features are planned, such as a CLI toolkit with advanced synchronization capability.
Read the full documentation here: https://mm21.github.io/trilium-alchemy/
This guide assumes you have some familiarity with Trilium itself; namely the basic concepts of notes, attributes, and branches.
Install from PyPI:
pip install trilium-alchemy
To connect to a Trilium server, you need to supply either an ETAPI token or password. A token is the recommended mechanism; create a new token in Trilium's UI from Options → ETAPI. If you provide a password, a temporary token is created for you.
In TriliumAlchemy, the Session
class is the fundamental interface to interact with Trilium. It implements a unit of work pattern, much like SQLAlchemy's Session
. (In fact, the design for this project was based heavily on SQLAlchemy and therefore inspired its name as well.)
As you make changes to Trilium objects, their state is maintained in the Session
to which they belong. When you're done making changes and invoke Session.flush()
, the unit of work dependency solver determines the order in which to commit changes to Trilium and commits them. For example, new notes need to be created before their attributes.
Below is an example of how to create a Session
:
from trilium_alchemy import Session
# your host here
HOST = "http://localhost:8080"
# your token here
TOKEN = "my-token"
session = Session(HOST, token=TOKEN)
Once you're done making changes, simply commit them to Trilium using Session.flush()
:
session.flush()
The Session
implements a context manager which automatically invokes flush()
upon exit. For example:
with Session(HOST, token=TOKEN) as session:
# create a new note under root
note = Note(title="My note", parents=session.root)
# session.flush() will be invoked automatically
See the full documentation here: https://mm21.github.io/trilium-alchemy/sdk/guide/working-with-notes/index.html
There are 3 kinds of objects in Trilium, represented in TriliumAlchemy as the following classes:
Note
Attribute
base class, with concrete classesLabel
andRelation
Branch
, linking a parent and child note
Once you have a Session
, you can begin to interact with Trilium. The first Session
created is registered as the default for any subsequent Trilium objects created.
The following shows an example of creating a new note under today's day note:
with Session(HOST, token=TOKEN) as session:
# get today's day note
today = session.get_today_note()
# create a new note under today
note = Note(title="New note about today", parents=today)
# add some content
note.content = "<p>Hello, world!</p>"
This project implements idiomatic interfaces for working with notes.
Values of single-valued attributes can be accessed by indexing into the note itself. For example:
note["myLabel"] = "myValue"
assert note["myLabel"] == "myValue"
This creates the label myLabel
if it doesn't already exist.
The same approach works with relations. For example, to set ~template=Task
:
# assumes you have a template with label #task
task_template = session.search("#template #task")[0]
task = Note(title="My task")
task["template"] = task_template
The ~template
relation can be equivalently set during note creation itself:
task = Note(title="My task", template=task_template)
Use +=
to add a Label
, Relation
, or Branch
.
Add a label with an optional value:
note += Label("myLabel", "myValue")
assert note.attributes["myLabel"][0].value == "myValue"
Add a relation to root note:
note += Relation("myRelation", session.root)
assert note.attributes.owned["myRelation"][0].target is session.root
Add a child branch implicitly, with empty branch prefix:
note += Note(title="Child note")
assert note.children[0].title == "Child note"
Add a child branch with a branch prefix:
child = Note(title="Child note")
note += Branch(child=child, prefix="My prefix")
assert note.branches.children[0].prefix == "My prefix"
Add a parent branch, using the root note as the parent:
note += Branch(parent=session.root, prefix="My prefix")
assert note.branches.parents[0].prefix == "My prefix"
Alternatively, pass a tuple
of (Note
, str
) to set the branch prefix:
child = Note(title="Child note")
note += (child, "My prefix")
assert note.branches.children[0].prefix == "My prefix"
Use ^=
to add another note as a parent, cloning it:
# get today's day note
today = session.get_today_note()
# clone to today
note ^= today
assert note in today.children
assert today in note.parents
Pass a tuple
of (Note
, str
) to set the branch prefix:
note ^= (today, "My prefix")
assert note.branches.parents[0].prefix == "My prefix"
One of the goals of this project is to enable building, maintaining, and sharing complex note hierarchies using Python. This approach is declarative in nature, inspired by SQLAlchemy's declarative mapping approach.
The general idea of declarative programming is that you specify the desired end state, not the steps needed to reach it.
For a fully-featured example of a note hierarchy designed using this approach, see Event tracker.
The basic technique is to subclass Note
:
class MyNote(Note): pass
Sometimes you want to logically group attributes or children together in a reusable way, but don't need a fully-featured Note
. In those cases you can use a Mixin
.
The basic technique is to subclass Mixin
:
class MyMixin(Mixin): pass
Note
inherits from Mixin
, so the following semantics can be applied to Note
subclasses and Mixin
subclasses equally.
You can set the following fields by setting attribute values:
Note.title
orMixin.title
Note.note_type
orMixin.note_type
Note.mime
orMixin.mime
Note.content
class MyNote(Note):
title = "My title"
note_type = "text"
mime = "text/html"
content = "<p>Hello, world!</p>"
Set note content from a file by setting Note.content_file
or Mixin.content_file
:
class MyFrontendScript(Note):
note_type = "code"
mime = "application/javascript;env=frontend"
content_file = "assets/myFrontendScript.js"
The filename is relative to the package or subpackage the class is defined in. Currently accessing parent paths (".."
) is not supported.
Use the decorator label
to add a label to a Note
or Mixin
subclass:
@label("sorted")
class SortedMixin(Mixin): pass
Now you can simply inherit from this mixin if you want a note's children to be sorted:
@label("iconClass", "bx bx-group")
class Contacts(Note, SortedMixin): pass
This approach enables reuse of groups of related attributes.
The above is equivalent to the following imperative approach:
contacts = Note(title="Contacts")
contacts += [Label("iconClass", "bx bx-group"), Label("sorted")]