Working with Tags
Tagging is a powerful feature in dbzero that lets you categorize, group, and reference your objects. You can think of tags like flexible labels that create a link between a tag and an object being tagged. However, they do more than just label things. Tags are a core part of dbzero. They can model complex relations between objects and be used with our powerful query engine to efficiently retrieve objects.
Tags as References: Tags are integrated with dbzero's memory management model. Adding a tag to an object increases its internal reference count. As long as an object has at least one tag (or is referenced by another persisted object), dbzero will not garbage collect it. Removing the last tag from an unreferenced object will cause it to be deleted automatically.
The Basics
You can easily add or remove tags from any memo object.
Adding Tags
To manage an object's tags, you first get a tags manager using dbzero.tags. Then, you can use the .add() method to tag the object.
You can add a single tag, multiple tags at once from a list, or even multiple tags as separate arguments. You can also use the += operator for a more concise syntax.
import dbzero as db0
@db0.memo
class Book:
def __init__(self, title):
self.title = title
# Create an object to tag
book = Book(title="The Hitchhiker's Guide")
# Get the tag controller for the object
tags = dbzero.tags(book)
# Add a single tag
tags.add("sci-fi")
# Add multiple tags from a list
tags.add(["classic", "comedy"])
# You can also use the += operator
tags += "favorite"
tags += ["published-1979", "british-author"]
# The object is now findable by any of its tags
assert len(dbzero.find("sci-fi")) == 1Removing Tags
Removing tags is just as simple. You can use the .remove() method or the -= operator with a single tag or a list of tags.
import dbzero as db0
book = next(iter(db0.find("published-1979")))
tags = db0.tags(book)
# Remove a single tag
tags.remove("published-1979")
# Remove multiple tags using the -= operator
tags -= ["classic", "comedy"]
# Now the object can't be found by the removed tags
assert len(db0.find("classic")) == 0Finding Objects with Tags
The primary purpose of tagging objects is to be able to retrieve them later using dbzero.find function.
@db0.memo
class Person:
def __init__(self, name: str):
self.name = name
# Create and tag objects
person = Person("Susan")
db0.tags(person).add("employee", "manager")
person = Person("Michael")
db0.tags(person).add("employee", "developer")
# Find every Person by type
result = db0.find(Person)
# Combine type and tags (AND logic) to find employees
employees = db0.find(Person, "employee")
# OR logic using a list to find managers and developers
staff = db0.find(["manager", "developer"])
# NOT logic using db0.no() to find employees which aren't managers
non_managers = db0.find("employee", db0.no("manager"))Learn more about queries here.
Advanced Tagging
Tags don't have to be just simple strings. You can use other memo objects or classes as tags to create explicit relationships.
Using Objects as Tags
You can use one memo object as a tag for another. This is great for linking related data. To create a tag from a memo instance, you have to use dbzero.as_tag function.
author = Author(name="Douglas Adams")
book = Book(title="The Hitchhiker's Guide")
# Use the author object as a tag for the book
db0.tags(book).add(db0.as_tag(author))
# Now you can find all books by that author
adams_books = db0.find(Book, db0.as_tag(author))Using Classes as Tags
Sometimes you might want to tag an object with a class itself. To do this, you must wrap the class with dbzero.as_tag.
# Create an object
project = Project(name="Project X")
# Tag it with the "HighPriority" class
high_priority_tag = db0.as_tag(HighPriority)
db0.tags(project).add(high_priority_tag)
# Find all projects with this classification
high_prio_projects = db0.find(Project, high_priority_tag)It's important to differentiate the use of a class as a queried object type and the use of a class as a tag. Object class is treated as a special kind of tag assigned by dbzero and it cannot be assigned manually or overridden.
Automatic Type Tags
Normally, dbzero automatically assigns a tag of an object's class to it upon creation. This means you can immediately find all instances of a class without doing any manual tagging. It is convenient, as this also helps to ensure that the instance you find are of type you'd expect.
# Create two instances of the Book class
book1 = Book(title="Dune")
book2 = Book(title="Foundation")
# dbzero automatically tags them with the `Book` class.
all_books = db0.find(Book)
assert len(list(all_books)) == 2Opting Out of Automatic Tags
If you're certain that you won't need to query objects of a specific class, you can use the no_default_tags parameter of the @memo decorator to skip the automatic class tag. This can bring performance benefits in some cases, especially when creating huge amounts of objects.
import dbzero as db0
# This class could be a component used in many other memo objects
@db0.memo(no_default_tags=True)
class MemoComponent:
def __init__(self, properties):
self.properties = properties
# Creating an instance...
component = MemoComponent("api_key")
# ...does not make it findable by its type
assert len(db0.find(MemoComponent)) == 0
# However, as soon as you add any other tag...
db0.tags(component).add("secret")
# ...it becomes findable by its type as well!
assert len(db0.find(MemoComponent)) == 1You can still manually manage tags of opted-out classes. As soon as you assign a first tag, dbzero adds a class tag to it as well.
Working with Mixed Types
A key strength of the tagging system is its type-agnostic nature. The same tag, such as "to-do", can be applied to objects of completely unrelated classes. For instance, a to-do list might contain a Task that needs to be done, a Contact that needs to be called, and a Bill that needs to be paid. When you later call dbzero.find("to-do"), the result will be a query iterator over this mix of different object types. One way to handle such collections is to simply loop through the results and check each item against expected classes. This lets you apply type-specific logic, such as rendering each item differently in a UI.
# Add a "to-do" tag to various unrelated objects
db0.tags(Task(description="Write report")).add("to-do")
db0.tags(Contact(name="John Doe")).add("to-do")
db0.tags(Bill(amount=99.99, vendor="Cloud Services")).add("to-do")
# Retrieve all "to-do" items
for item in db0.find("to-do"):
if isinstance(item, Task):
print(f"[ ] Task: {item.description}")
elif isinstance(item, Contact):
print(f"[ ] Call: {item.name}")
elif isinstance(item, Bill):
print(f"[ ] Pay: ${item.amount} to {item.vendor}")
# Output:
# [ ] Task: Write report
# [ ] Call: John Doe
# [ ] Pay: $99.99 to Cloud ServicesBatch Operations
When tagging large amounts of objects, you can consider grouping them to apply tags in a single operation. While dbzero buffers tagging operations by itself, this still can give slight performance benefits and make the code cleaner.
# Create some objects
books_to_tag = [Book(title=f"Book {i}") for i in range(100)]
# Add the "new-release" tag to all of them at once
db0.tags(*books_to_tag).add("new-release")
assert len(db0.find("new-release")) == 100