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.
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"])
# 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.
Composite Tags
dbzero-procommercial editionComposite tags are ordered, structured tags made from two or more elements. They are useful when a plain string or a single object tag is not specific enough to describe the relationship.
Create composite tags with dbzero.as_tag. You can pass the elements as separate arguments or as one tuple:
read_grant = db0.as_tag("GRANT-READ", workspace, user)
same_tag = db0.as_tag(("GRANT-READ", workspace, user))The first element is often a relationship name, while the remaining elements provide scope. Composite tags can have more than two elements, which makes them practical for multi-tenant or hierarchical relationships:
# Access control: user can read documents inside one workspace.
db0.tags(document).add(db0.as_tag("GRANT-READ", workspace, user))
# Tenant-scoped category: overdue invoices for a tenant.
db0.tags(invoice).add(db0.as_tag("CATEGORY", tenant, "invoice", "overdue"))
# Workflow ownership: work assigned within a project and sprint.
db0.tags(task).add(db0.as_tag("ASSIGNED", project, sprint, user))You can query composite tags directly or combine them with a type filter:
grant = db0.as_tag("GRANT-READ", workspace, user)
# Find every object tagged with this exact relationship.
readable = db0.find(grant)
# Narrow results to one memo type.
readable_documents = db0.find(Document, grant)Removing a composite tag uses the same tag value:
db0.tags(document).remove(db0.as_tag("GRANT-READ", workspace, user))Passive Tags
dbzero-procommercial editionPassive tags behave like normal tags for matching and removal, but they do not keep the tagged object alive. Use them for high-churn labels such as permissions, access predicates, temporary audit markers, or generated classifications where object lifetime should still be controlled by regular references and regular tags.
db0.tags(document, passive=True).add("visible-to-current-user")
# Passive tags must be queried with a positive non-passive predicate,
# such as a memo type or a regular tag.
visible_documents = db0.find(Document, "visible-to-current-user")A passive-only query is rejected because passive tag entries may outlive the object they point to:
# Raises an exception.
list(db0.find("visible-to-current-user"))Regular removal works for passive tags; you do not need to pass passive=True when removing:
db0.tags(document).remove("visible-to-current-user")Passive tags also work with query targets and composite tags:
grant = db0.as_tag("GRANT-READ", workspace, user)
# Apply a passive composite access tag to all active documents.
db0.tags(db0.find(Document, "active"), passive=True).add(grant)
# Query it with an explicit type anchor.
readable_active_documents = db0.find(Document, grant)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")) == 100Set-Based Tagging
dbzero-procommercial editionSet-based tagging is the dbzero-pro form of batch tagging for query results. Instead of iterating over a find() result in Python and tagging each object one by one, pass the query directly to dbzero.tags. dbzero applies the tag operation to every object matched by that specific query result using the optimized query engine.
# Tag every active overdue invoice for review.
overdue_active = db0.find(Invoice, "active", "overdue")
db0.tags(overdue_active).add("needs-review")
review_queue = list(db0.find(Invoice, "needs-review"))The same pattern works for removals:
# Clear a temporary import marker from every object that still has it.
db0.tags(db0.find("import-session-2026-06")).remove("import-session-2026-06")You can also combine explicit objects and query targets in one call:
# Include one manually selected document plus every source-tagged document.
db0.tags(priority_document, db0.find(Document, "source")).add("audit-scope")Set-based tagging composes with passive tags and composite tags:
grant = db0.as_tag("GRANT-READ", workspace, user)
# Assign a passive composite access tag to every active document.
db0.tags(db0.find(Document, "active"), passive=True).add(grant)Query-result tag targets are live dbzero queries. Empty query results are valid and simply update nothing. Snapshot query targets are read-only and cannot be used for set-based tagging.