In the previous chapter, Domain Models & Schema, we designed the blueprints for our data. We decided what a "Note" and a "Notebook" look like.
Now, we need a way to actually put data into and get data out of the database.
Imagine a library where anyone is allowed to walk into the restricted stacks, grab any book, scribble on it, or move it to a different shelf. Chaos would ensue! Books would be lost, damaged, or misfiled.
In software, allowing every part of your app to talk directly to the database creates similar chaos.
We solve this by hiring a Librarian. In software architecture, this is called the Repository Pattern.
The Repository is a specific file (database/repository.py) that acts as the gatekeeper. If you want to save a note, you don't talk to the database; you hand the note to the Repository functions, and they handle the complex filing logic.
In open-notebook, we don't write raw SQL inside our API routes. We use these helper functions.
repo_create: Storing New ItemsUse this when you have a new object (like a Note) to save.
# Example Usage
note_data = {
"title": "My First Idea",
"content": "Hello world!"
}
# The repository handles the SQL INSERT for you
saved_record = await repo_create("note", note_data)
"note") and a dictionary of data.created and updated timestamps, and returns the saved record.repo_query: Asking QuestionsUse this when you need to search for something specific or run complex logic.
# Example Usage
# The '$id' is a safety variable (parameter)
query = "SELECT * FROM note WHERE title = $title"
vars = {"title": "My First Idea"}
results = await repo_query(query, vars)
repo_relate: Connecting the DotsSince we are using a Graph Database, connecting items is just as important as creating them. This function draws the "arrow" between two items.
# Example Usage: Put a Note INSIDE a Notebook
# Source -> Relationship -> Target
await repo_relate(
source="note:123",
relationship="artifact",
target="notebook:456"
)
artifact), and the ID of the destination.How does this actually work? When you call one of these functions, a specific lifecycle occurs to ensure the database connection is opened safely and closed immediately after.
Let's look at database/repository.py. The magic happens in a function called db_connection.
It uses a Python concept called a Context Manager (asynccontextmanager). Think of this as an automatic door closer.
# open_notebook/database/repository.py
@asynccontextmanager
async def db_connection():
# 1. Setup: Create the client and sign in
db = AsyncSurreal(get_database_url())
await db.signin({...}) # (Credentials hidden for brevity)
await db.use(...) # Select the namespace
try:
yield db # <--- PAUSE: This is where your query runs!
finally:
# 3. Teardown: ALWAYS runs, even if your query crashed
await db.close()
Why is this important?
The try...finally block guarantees that we always close the connection. If we didn't do this, our application would eventually run out of connections and freeze, like a library with too many people inside and the doors locked.
repo_query
The repo_query function wraps that connection manager so you don't have to type the setup code every time.
async def repo_query(query_str: str, vars: dict = None):
# Automatically open/close the connection
async with db_connection() as connection:
try:
# Run the actual command
result = await connection.query(query_str, vars)
# Clean up weird ID formats before returning
return parse_record_ids(result)
except Exception as e:
logger.exception(e) # Log errors so we can fix them
raise
Key Benefit:
Notice parse_record_ids? Database IDs often come back as complex objects (e.g., RecordID('note', '123')). The repository converts these into simple strings ("note:123"), so the rest of your app doesn't need to struggle with database-specific formats.
Let's revisit the use case from the start: Creating a Note.
In the old way (without a Repository), you would have to:
With the Repository Pattern, the code in our application logic (which we will write in future chapters) looks like this:
# Simple, clean, and readable
new_note = await repo_create("note", {
"title": "Repository Pattern",
"content": "It abstracts away the database complexity."
})
By using the Repository pattern, we have made our code:
repository.py, not the whole app.In this chapter, we learned:
repo_create, repo_query, and repo_relate to manipulate data.Now that we can model our data (Chapter 1) and store it safely (Chapter 2), we are ready to introduce the "Brain" of the operation.
In the next chapter, we will learn how to set up the AI models that will eventually read and understand our notes.
Next Chapter: Universal AI Provisioning
Generated by Code IQ