Skip to main content

Viebo Notion ORM

Project description

Notion ORM Library - Complete Tutorial

Welcome to the comprehensive tutorial for the Notion ORM Library - a powerful Python library that provides both low-level client access and high-level ORM capabilities for working with Notion databases.

Table of Contents

  1. Installation & Setup
  2. Quick Start
  3. Core Concepts
  4. Database Operations
  5. ORM-Style Models
  6. Querying & Filtering
  7. Advanced Features
  8. Real-World Examples
  9. Best Practices
  10. Troubleshooting
  11. API Reference

Installation & Setup

Requirements

  • Python 3.10 or higher
  • Notion Integration Token
  • Parent Page ID in your Notion workspace

Installation

pip install notion-client
# Install the viemind_notion_orm library (replace with actual installation method)

Getting Your Notion Credentials

  1. Create a Notion Integration:

  2. Get Parent Page ID:

    • Create or navigate to a Notion page where you want to create databases
    • Copy the page ID from the URL: https://notion.so/workspace/PAGE_ID
    • The page ID is the 32-character string after the last /
  3. Grant Access:

    • Share the parent page with your integration
    • Click "Share" → "Invite" → Select your integration

Basic Setup

from viemind_notion_orm import NotionClient

# Initialize the client
AUTH_TOKEN = "ntn_your_integration_token_here"
PARENT_PAGE_ID = "your_parent_page_id_here"

client = NotionClient(auth=AUTH_TOKEN)

Quick Start

Here's a 5-minute example to get you up and running:

from enum import Enum
from viemind_notion_orm import (
    NotionClient, NotionModel, TitleField, TextField, 
    SelectField, BooleanField
)

# 1. Define an enum for status
class TaskStatus(Enum):
    TODO = "To Do"
    IN_PROGRESS = "In Progress"
    DONE = "Done"

# 2. Define your model
class Task(NotionModel):
    title = TitleField()
    description = TextField()
    status = SelectField(TaskStatus)
    completed = BooleanField()

# 3. Setup
AUTH_TOKEN = "your_token_here"
PARENT_PAGE_ID = "your_page_id_here"
client = NotionClient(auth=AUTH_TOKEN)

# 4. Create the database
Task.create_table(client=client, parent_page_id=PARENT_PAGE_ID)

# 5. Create and save a task
task = Task(
    title="Learn Notion ORM",
    description="Go through the tutorial",
    status=TaskStatus.IN_PROGRESS.value,
    completed=False
)
task.save()

# 6. Query tasks
all_tasks = Task.objects().all()
for task in all_tasks:
    print(f"📋 {task.title} - {task.status}")

Core Concepts

Architecture Overview

The Notion ORM provides two main interfaces:

  1. Low-Level Client API: Direct database operations
  2. ORM-Style Models: Django/SQLAlchemy-like class-based interface

Property Types

The library supports all major Notion property types:

from viemind_notion_orm import NotionPropertyType, PropertyDefinition

# Basic types
NotionPropertyType.TITLE          # Required for every database
NotionPropertyType.TEXT           # Rich text
NotionPropertyType.NUMBER         # Numbers with formatting
NotionPropertyType.BOOLEAN        # Checkbox
NotionPropertyType.DATE           # Date and datetime
NotionPropertyType.URL            # URL validation
NotionPropertyType.EMAIL          # Email validation
NotionPropertyType.PHONE          # Phone number

# Advanced types
NotionPropertyType.SELECT         # Single choice dropdown
NotionPropertyType.MULTI_SELECT   # Multiple choice
NotionPropertyType.STATUS         # Kanban-style status
NotionPropertyType.PEOPLE         # User references
NotionPropertyType.FILES          # File attachments
NotionPropertyType.RELATION       # Database relations
NotionPropertyType.UNIQUE_ID      # Auto-incrementing IDs

Database Operations

Creating Databases

Method 1: Low-Level Client API

from viemind_notion_orm import NotionClient, NotionPropertyType, PropertyDefinition, SelectOption

client = NotionClient(auth=AUTH_TOKEN)

# Define schema
schema = {
    "Name": NotionPropertyType.TITLE,
    "Description": NotionPropertyType.TEXT,
    "Priority": PropertyDefinition(
        type=NotionPropertyType.SELECT,
        options=[
            SelectOption("High", "red"),
            SelectOption("Medium", "yellow"),
            SelectOption("Low", "green")
        ]
    ),
    "Budget": PropertyDefinition(
        type=NotionPropertyType.NUMBER,
        number_format="dollar"
    ),
    "Due Date": NotionPropertyType.DATE,
    "Active": NotionPropertyType.BOOLEAN,
    "Project ID": PropertyDefinition(
        type=NotionPropertyType.UNIQUE_ID,
        unique_id_prefix="PROJ"
    )
}

# Create database
database = client.create_database(
    parent_page_id=PARENT_PAGE_ID,
    db_title="Projects",
    schema=schema,
    if_exists='return_existing'  # Options: 'error', 'skip', 'return_existing'
)

print(f"Database created with ID: {database['id']}")

Method 2: ORM Model Auto-Creation

from viemind_notion_orm import NotionModel, TitleField, TextField, SelectField, NumberField

class Project(NotionModel):
    name = TitleField()
    description = TextField()
    priority = SelectField(["High", "Medium", "Low"])
    budget = NumberField(number_format="dollar")
    active = BooleanField()

# Auto-create from model definition
database = Project.create_table(
    client=client,
    parent_page_id=PARENT_PAGE_ID,
    table_name="Projects",  # Optional custom name
    if_exists='return_existing'
)

CRUD Operations

Creating Records

# Method 1: Direct client API
properties = {
    "Name": {"title": [{"text": {"content": "New Project"}}]},
    "Description": {"rich_text": [{"text": {"content": "Project description"}}]},
    "Priority": {"select": {"name": "High"}},
    "Budget": {"number": 10000},
    "Active": {"checkbox": True}
}

page = client.create_page_in_database(
    database_id=database['id'],
    properties=properties
)

# Method 2: ORM style
project = Project(
    name="New Project",
    description="Project description",
    priority="High",
    budget=10000,
    active=True
)
project.save()

Reading Records

# Method 1: Client query
results = client.query(database_id=database['id'])
for page in results:
    print(page['properties'])

# Method 2: ORM query
projects = Project.objects().all()
for project in projects:
    print(f"{project.name}: {project.priority}")

Updating Records

# Method 1: Client update
client.update_page_properties(
    page_id=page['id'],
    properties={"Priority": {"select": {"name": "Low"}}}
)

# Method 2: ORM update
project.priority = "Low"
project.save()

Deleting Records

# Archive (soft delete)
client.archive_page(page_id=page['id'])

# ORM delete (archives the page)
project.delete()

ORM-Style Models

Field Types

from viemind_notion_orm import (
    TitleField, TextField, NumberField, BooleanField, DateField,
    EmailField, URLField, PhoneField, PeopleField, FilesField,
    SelectField, MultiSelectField, StatusField, UniqueIDField,
    RelationField
)

class ComprehensiveModel(NotionModel):
    # Required title field
    title = TitleField()
    
    # Text fields
    description = TextField()
    email = EmailField()
    website = URLField()
    phone = PhoneField()
    
    # Number and date fields
    price = NumberField(number_format="dollar")
    quantity = NumberField()
    created_date = DateField()
    
    # Boolean
    is_active = BooleanField()
    
    # Choice fields
    category = SelectField(["A", "B", "C"])
    tags = MultiSelectField(["tag1", "tag2", "tag3"])
    status = StatusField(["Draft", "Review", "Published"])
    
    # Special fields
    record_id = UniqueIDField(prefix="REC")
    assignees = PeopleField()
    attachments = FilesField()

Using Enums for Better Type Safety

from enum import Enum

class Priority(Enum):
    CRITICAL = "Critical"
    HIGH = "High"
    MEDIUM = "Medium"
    LOW = "Low"

class ProjectStatus(Enum):
    PLANNING = "Planning"
    ACTIVE = "Active"
    ON_HOLD = "On Hold"
    COMPLETED = "Completed"

class Project(NotionModel):
    name = TitleField()
    priority = SelectField(Priority)
    status = StatusField(ProjectStatus)
    
    # Usage
    project = Project(
        name="Important Project",
        priority=Priority.HIGH.value,
        status=ProjectStatus.ACTIVE.value
    )

Model Relations

class Author(NotionModel):
    name = TitleField()
    email = EmailField()

class Book(NotionModel):
    title = TitleField()
    author = RelationField(Author)  # Foreign key to Author
    pages = NumberField()

# Create tables (order matters for relations)
Author.create_table(client=client, parent_page_id=PARENT_PAGE_ID)
Book.create_table(client=client, parent_page_id=PARENT_PAGE_ID)

# Create related records
author = Author(name="John Doe", email="john@example.com")
author.save()

book = Book(title="Great Book", pages=300)
# Note: Setting relations requires page IDs in current implementation
book.save()

Querying & Filtering

Basic Queries

# Get all records
all_projects = Project.objects().all()

# Count records
total_count = Project.objects().count()

# Check if records exist
has_projects = Project.objects().exists()

# Get first record
first_project = Project.objects().first()

# Limit results
recent_projects = Project.objects().limit(5).all()

Filtering with Field Conditions

from viemind_notion_orm import Select, Number, Checkbox, Date, Text

# Simple field filters
active_projects = Project.objects().filter(is_active=True).all()
high_priority = Project.objects().filter(priority="High").all()

# Advanced filter objects
from viemind_notion_orm.filters import Select, Number, Checkbox

# Text contains
name_filter = Project.objects().filter(
    Text("description").contains("important")
).all()

# Number comparisons
expensive_projects = Project.objects().filter(
    Number("budget").greater_than(50000)
).all()

# Date filters
recent_projects = Project.objects().filter(
    Date("created_date").past_month()
).all()

# Boolean filters
active_projects = Project.objects().filter(
    Checkbox("is_active").equals(True)
).all()

Complex Filtering with AND/OR

from viemind_notion_orm.filters import AndFilter, OrFilter, Select, Number

# AND condition
high_value_active = Project.objects().filter(
    Select("priority").equals("High") & 
    Number("budget").greater_than(10000) &
    Checkbox("is_active").equals(True)
).all()

# OR condition
important_projects = Project.objects().filter(
    Select("priority").equals("Critical") |
    Select("priority").equals("High")
).all()

# Mixed conditions
complex_filter = Project.objects().filter(
    (Select("status").equals("Active") | Select("status").equals("Planning")) &
    Number("budget").greater_than(5000)
).all()

Sorting

# Single field ascending
projects_by_name = Project.objects().order_by('name').all()

# Single field descending (prefix with -)
projects_by_budget_desc = Project.objects().order_by('-budget').all()

# Multiple fields
projects_sorted = Project.objects().order_by('priority', '-budget', 'name').all()

# Combined with filtering
filtered_sorted = (Project.objects()
                  .filter(is_active=True)
                  .order_by('-priority', 'name')
                  .limit(10)
                  .all())

Query Chaining

# Build complex queries step by step
query = Project.objects()
query = query.filter(is_active=True)
query = query.filter(Number("budget").greater_than(1000))
query = query.order_by('-priority', 'name')
query = query.limit(20)

results = query.all()

Advanced Features

Joins and Relations

# Join two databases
join_results = client.join(
    left_database_id=projects_db['id'],
    right_database_id=tasks_db['id'],
    on_relation="project",  # Relation field name in tasks
    left_filters=Select("is_active").equals(True),
    right_filters=Select("status").equals("In Progress")
)

# Process joined results
for project_with_tasks in join_results:
    project_name = project_with_tasks['properties']['title']['title'][0]['plain_text']
    tasks = project_with_tasks.get('_joined_project', [])
    print(f"Project: {project_name} has {len(tasks)} active tasks")

Batch Operations

# Bulk create using ORM
projects_data = [
    {"name": "Project 1", "priority": "High", "budget": 10000},
    {"name": "Project 2", "priority": "Medium", "budget": 5000},
    {"name": "Project 3", "priority": "Low", "budget": 2000},
]

for data in projects_data:
    project = Project(**data)
    project.save()

# Bulk update
high_priority_projects = Project.objects().filter(priority="High").all()
for project in high_priority_projects:
    project.is_active = True
    project.save()

Unique ID Configuration

class Task(NotionModel):
    title = TitleField()
    
    # Auto-incrementing IDs with different prefixes
    task_id = UniqueIDField(prefix="TSK")      # TSK-1, TSK-2, TSK-3...
    bug_id = UniqueIDField(prefix="BUG")       # BUG-1, BUG-2, BUG-3...
    feature_id = UniqueIDField()               # 1, 2, 3... (no prefix)

# Different ID formats in practice
task = Task(title="Fix login bug")
task.save()
# Results in: task_id = "TSK-1", bug_id = "BUG-1", feature_id = "1"

Working with Files and People

class Document(NotionModel):
    title = TitleField()
    author = PeopleField()
    attachments = FilesField()
    reviewers = PeopleField()

# Note: Files and People fields require special handling
# Files: Upload to Notion and get URLs
# People: Use user IDs from Notion workspace

Real-World Examples

Example 1: Project Management System

from enum import Enum
from datetime import datetime, date
from viemind_notion_orm import *

# Define enums
class ProjectStatus(Enum):
    PLANNING = "Planning"
    ACTIVE = "Active"
    ON_HOLD = "On Hold"
    COMPLETED = "Completed"

class Priority(Enum):
    CRITICAL = "Critical"
    HIGH = "High"
    MEDIUM = "Medium"
    LOW = "Low"

class TaskStatus(Enum):
    TODO = "To Do"
    IN_PROGRESS = "In Progress"
    REVIEW = "In Review"
    DONE = "Done"

# Define models
class Project(NotionModel):
    name = TitleField()
    description = TextField()
    status = StatusField(ProjectStatus)
    priority = SelectField(Priority)
    budget = NumberField(number_format="dollar")
    start_date = DateField()
    end_date = DateField()
    is_active = BooleanField()
    project_id = UniqueIDField(prefix="PROJ")
    tags = MultiSelectField(["Frontend", "Backend", "Mobile", "DevOps"])

class Task(NotionModel):
    title = TitleField()
    description = TextField()
    project = RelationField(Project)
    status = SelectField(TaskStatus)
    priority = SelectField(Priority)
    estimated_hours = NumberField()
    due_date = DateField()
    is_blocked = BooleanField()
    task_id = UniqueIDField(prefix="TSK")

# Setup and create tables
client = NotionClient(auth=AUTH_TOKEN)

projects_db = Project.create_table(client=client, parent_page_id=PARENT_PAGE_ID)
tasks_db = Task.create_table(client=client, parent_page_id=PARENT_PAGE_ID)

# Create sample project
project = Project(
    name="E-commerce Platform Redesign",
    description="Complete overhaul of the e-commerce platform",
    status=ProjectStatus.ACTIVE.value,
    priority=Priority.HIGH.value,
    budget=75000,
    start_date="2024-01-15",
    is_active=True,
    tags=["Frontend", "Backend"]
)
project.save()

# Create related tasks
tasks_data = [
    {
        "title": "Design new UI components",
        "description": "Create reusable UI components",
        "status": TaskStatus.IN_PROGRESS.value,
        "priority": Priority.HIGH.value,
        "estimated_hours": 40,
        "due_date": "2024-02-15",
        "is_blocked": False,
    },
    {
        "title": "Implement payment gateway",
        "description": "Integrate Stripe payment processing",
        "status": TaskStatus.TODO.value,
        "priority": Priority.CRITICAL.value,
        "estimated_hours": 60,
        "due_date": "2024-03-01",
        "is_blocked": False,
    }
]

for task_data in tasks_data:
    task = Task(**task_data)
    task.save()

# Query and analyze
print("📊 Project Analytics")
print("-" * 40)

# Active projects
active_projects = Project.objects().filter(
    Checkbox("is_active").equals(True)
).all()
print(f"Active projects: {len(active_projects)}")

# High priority tasks
high_priority_tasks = Task.objects().filter(
    Select("priority").equals(Priority.HIGH.value)
).all()
print(f"High priority tasks: {len(high_priority_tasks)}")

# Tasks due this month
import datetime
current_month = datetime.datetime.now().strftime("%Y-%m")
due_this_month = Task.objects().filter(
    Date("due_date").on_or_after(f"{current_month}-01") &
    Date("due_date").before(f"{current_month}-31")
).all()
print(f"Tasks due this month: {len(due_this_month)}")

# Blocked tasks
blocked_tasks = Task.objects().filter(
    Checkbox("is_blocked").equals(True)
).all()
print(f"Blocked tasks: {len(blocked_tasks)}")

Example 2: Customer Relationship Management (CRM)

from enum import Enum
from viemind_notion_orm import *

class CustomerStatus(Enum):
    LEAD = "Lead"
    PROSPECT = "Prospect"
    CUSTOMER = "Customer"
    INACTIVE = "Inactive"

class DealStage(Enum):
    QUALIFICATION = "Qualification"
    PROPOSAL = "Proposal"
    NEGOTIATION = "Negotiation"
    CLOSED_WON = "Closed Won"
    CLOSED_LOST = "Closed Lost"

class Customer(NotionModel):
    company_name = TitleField()
    contact_email = EmailField()
    website = URLField()
    phone = PhoneField()
    status = StatusField(CustomerStatus)
    industry = SelectField(["Technology", "Healthcare", "Finance", "Education", "Retail"])
    annual_revenue = NumberField(number_format="dollar")
    employee_count = NumberField()
    customer_id = UniqueIDField(prefix="CUST")
    is_active = BooleanField()

class Deal(NotionModel):
    deal_name = TitleField()
    customer = RelationField(Customer)
    stage = StatusField(DealStage)
    amount = NumberField(number_format="dollar")
    probability = NumberField()  # Percentage
    close_date = DateField()
    deal_id = UniqueIDField(prefix="DEAL")
    notes = TextField()

# CRM Analytics
def crm_analytics():
    # Sales pipeline
    pipeline_value = 0
    deals_by_stage = {}
    
    all_deals = Deal.objects().all()
    
    for deal in all_deals:
        stage = deal.stage
        amount = deal.amount if deal.amount else 0
        
        if stage not in deals_by_stage:
            deals_by_stage[stage] = {"count": 0, "value": 0}
        
        deals_by_stage[stage]["count"] += 1
        deals_by_stage[stage]["value"] += amount
        
        # Add to pipeline if not closed
        if stage not in ["Closed Won", "Closed Lost"]:
            pipeline_value += amount
    
    print("💰 Sales Pipeline Analytics")
    print("-" * 40)
    print(f"Total pipeline value: ${pipeline_value:,.2f}")
    
    for stage, data in deals_by_stage.items():
        print(f"{stage}: {data['count']} deals, ${data['value']:,.2f}")
    
    # Customer analysis
    customers_by_status = {}
    total_revenue = 0
    
    customers = Customer.objects().all()
    
    for customer in customers:
        status = customer.status
        revenue = customer.annual_revenue if customer.annual_revenue else 0
        
        if status not in customers_by_status:
            customers_by_status[status] = 0
        customers_by_status[status] += 1
        
        if status == "Customer":
            total_revenue += revenue
    
    print("\n👥 Customer Analytics")
    print("-" * 40)
    print(f"Total customer revenue: ${total_revenue:,.2f}")
    
    for status, count in customers_by_status.items():
        print(f"{status}: {count} companies")

Example 3: Inventory Management

from enum import Enum
from viemind_notion_orm import *

class ProductCategory(Enum):
    ELECTRONICS = "Electronics"
    CLOTHING = "Clothing"
    BOOKS = "Books"
    HOME = "Home & Garden"
    SPORTS = "Sports"

class StockStatus(Enum):
    IN_STOCK = "In Stock"
    LOW_STOCK = "Low Stock"
    OUT_OF_STOCK = "Out of Stock"
    DISCONTINUED = "Discontinued"

class Product(NotionModel):
    name = TitleField()
    description = TextField()
    sku = UniqueIDField(prefix="SKU")
    category = SelectField(ProductCategory)
    price = NumberField(number_format="dollar")
    cost = NumberField(number_format="dollar")
    quantity_in_stock = NumberField()
    reorder_level = NumberField()
    supplier_email = EmailField()
    status = StatusField(StockStatus)
    is_active = BooleanField()

class StockMovement(NotionModel):
    movement_id = UniqueIDField(prefix="MOV")
    product = RelationField(Product)
    movement_type = SelectField(["Purchase", "Sale", "Adjustment", "Return"])
    quantity = NumberField()
    date = DateField()
    notes = TextField()

# Inventory analytics
def inventory_analytics():
    print("📦 Inventory Analytics")
    print("-" * 40)
    
    # Low stock alerts
    low_stock_products = Product.objects().filter(
        Select("status").equals(StockStatus.LOW_STOCK.value) |
        Select("status").equals(StockStatus.OUT_OF_STOCK.value)
    ).all()
    
    print(f"🚨 Products needing attention: {len(low_stock_products)}")
    
    # Inventory value
    all_products = Product.objects().all()
    total_inventory_value = 0
    total_products = len(all_products)
    
    for product in all_products:
        if product.quantity_in_stock and product.cost:
            total_inventory_value += product.quantity_in_stock * product.cost
    
    print(f"💰 Total inventory value: ${total_inventory_value:,.2f}")
    print(f"📊 Total products: {total_products}")
    
    # Category breakdown
    category_counts = {}
    for product in all_products:
        category = product.category
        if category not in category_counts:
            category_counts[category] = 0
        category_counts[category] += 1
    
    print("\n📋 Products by category:")
    for category, count in category_counts.items():
        print(f"  {category}: {count}")

Best Practices

1. Model Design

# ✅ Good: Clear, descriptive names
class CustomerOrder(NotionModel):
    order_number = TitleField()
    customer_email = EmailField()
    order_date = DateField()
    status = StatusField(OrderStatus)

# ❌ Avoid: Vague names
class Data(NotionModel):
    name = TitleField()
    value = TextField()

2. Enum Usage

# ✅ Good: Use enums for consistency
class OrderStatus(Enum):
    PENDING = "Pending"
    PROCESSING = "Processing"
    SHIPPED = "Shipped"
    DELIVERED = "Delivered"

class Order(NotionModel):
    status = StatusField(OrderStatus)

# ❌ Avoid: Hard-coded strings
class Order(NotionModel):
    status = SelectField(["pending", "processing", "shipped"])  # Inconsistent casing

3. Error Handling

from viemind_notion_orm.exceptions import NotionORMError, DatabaseExistsError

try:
    # Create database
    db = Project.create_table(
        client=client,
        parent_page_id=PARENT_PAGE_ID,
        if_exists='error'
    )
except DatabaseExistsError:
    print("Database already exists, using existing one")
    db = Project.create_table(
        client=client,
        parent_page_id=PARENT_PAGE_ID,
        if_exists='return_existing'
    )
except NotionORMError as e:
    print(f"Notion ORM error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

4. Query Optimization

# ✅ Good: Use specific filters
active_high_priority = Project.objects().filter(
    Checkbox("is_active").equals(True) &
    Select("priority").equals("High")
).limit(10).all()

# ✅ Good: Chain operations efficiently
query = (Project.objects()
         .filter(is_active=True)
         .order_by('-priority')
         .limit(20))

results = query.all()  # Execute once

# ❌ Avoid: Multiple separate queries
all_projects = Project.objects().all()  # Loads everything
filtered = [p for p in all_projects if p.is_active]  # Filter in Python

5. Relation Handling

# ✅ Good: Create parent tables first
Author.create_table(client=client, parent_page_id=PARENT_PAGE_ID)
Book.create_table(client=client, parent_page_id=PARENT_PAGE_ID)

# ✅ Good: Use meaningful relation names
class Task(NotionModel):
    title = TitleField()
    assigned_project = RelationField(Project)  # Clear relation name
    
# ❌ Avoid: Creating child tables before parents
Book.create_table(client=client, parent_page_id=PARENT_PAGE_ID)
Author.create_table(client=client, parent_page_id=PARENT_PAGE_ID)  # May fail

6. Data Validation

# ✅ Good: Validate data before saving
def create_project(name, budget, start_date):
    if not name or len(name.strip()) == 0:
        raise ValueError("Project name cannot be empty")
    
    if budget < 0:
        raise ValueError("Budget cannot be negative")
    
    try:
        from datetime import datetime
        datetime.strptime(start_date, '%Y-%m-%d')
    except ValueError:
        raise ValueError("Invalid date format. Use YYYY-MM-DD")
    
    project = Project(
        name=name.strip(),
        budget=budget,
        start_date=start_date,
        is_active=True
    )
    project.save()
    return project

Troubleshooting

Common Issues and Solutions

1. Authentication Errors

Problem: notion_client.errors.APIResponseError: Unauthorized

Solutions:

  • Verify your integration token is correct
  • Ensure the integration has access to the parent page
  • Check that the token hasn't expired
# Test connection
try:
    client = NotionClient(auth=AUTH_TOKEN)
    # Try a simple operation
    databases = client.query(database_id="test_id")
    print("✅ Connection successful")
except Exception as e:
    print(f"❌ Connection failed: {e}")

2. Page/Database Not Found

Problem: Database or page ID not found

Solutions:

  • Verify the parent page ID is correct
  • Ensure the integration has access to the page
  • Check if the database was created successfully
# Verify page access
try:
    response = client.client.pages.retrieve(page_id=PARENT_PAGE_ID)
    print(f"✅ Page accessible: {response['properties']}")
except Exception as e:
    print(f"❌ Cannot access page: {e}")

3. Schema Validation Errors

Problem: SchemaValidationError: Model must have exactly one TitleField

Solutions:

  • Ensure every model has exactly one TitleField
  • Check that field types match Notion's capabilities
  • Verify enum values are strings
# ✅ Correct model definition
class MyModel(NotionModel):
    title = TitleField()  # Required
    description = TextField()

# ❌ Incorrect - no title field
class BadModel(NotionModel):
    description = TextField()

# ❌ Incorrect - multiple title fields
class BadModel2(NotionModel):
    title1 = TitleField()
    title2 = TitleField()

4. Relation Issues

Problem: Relations not working correctly

Solutions:

  • Create parent tables before child tables
  • Ensure relation database IDs are correct
  • Check that both databases exist
# Debug relation creation
try:
    # Create parent first
    parent_db = ParentModel.create_table(client=client, parent_page_id=PARENT_PAGE_ID)
    print(f"Parent DB ID: {parent_db['id']}")
    
    # Then create child
    child_db = ChildModel.create_table(client=client, parent_page_id=PARENT_PAGE_ID)
    print(f"Child DB ID: {child_db['id']}")
    
except Exception as e:
    print(f"Relation creation failed: {e}")

5. Query Performance Issues

Problem: Slow or failing queries

Solutions:

  • Use specific filters instead of loading all data
  • Limit result sets with .limit()
  • Check for rate limiting
# ✅ Optimized query
specific_results = MyModel.objects().filter(
    Checkbox("is_active").equals(True)
).limit(100).all()

# ❌ Slow query
all_data = MyModel.objects().all()  # Loads everything
filtered = [item for item in all_data if item.is_active]

Debug Mode

# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)

# Test individual components
def debug_database_creation():
    try:
        # Test schema generation
        schema = MyModel.get_schema()
        print(f"Generated schema: {schema}")
        
        # Test database creation
        db = client.create_database(
            parent_page_id=PARENT_PAGE_ID,
            db_title="Debug Test",
            schema=schema
        )
        print(f"Database created: {db['id']}")
        
    except Exception as e:
        print(f"Debug failed: {e}")
        import traceback
        traceback.print_exc()

API Reference

Core Classes

NotionClient

Main client for database operations.

client = NotionClient(auth: str)

# Methods
client.create_database(parent_page_id, db_title, schema, if_exists='error')
client.query(database_id, filters=None, sorts=None, page_size=100)
client.join(left_database_id, right_database_id, on_relation, left_filters=None, right_filters=None)
client.create_page_in_database(database_id, properties)
client.update_page_properties(page_id, properties)
client.archive_page(page_id)

NotionModel

Base class for ORM models.

class MyModel(NotionModel):
    # Field definitions
    
# Class methods
MyModel.create_table(client, parent_page_id, table_name=None, if_exists='error')
MyModel.get_schema()
MyModel.objects()

# Instance methods
instance.save()
instance.delete()

QueryManager

Handles database queries.

# Access through Model.objects()
query_manager = MyModel.objects()

# Methods
query_manager.all()
query_manager.filter(filter_obj=None, **kwargs)
query_manager.order_by(*field_names)
query_manager.limit(count)
query_manager.first()
query_manager.count()
query_manager.exists()
query_manager.create(**kwargs)
query_manager.get_or_create(defaults=None, **kwargs)
query_manager.update(**kwargs)
query_manager.delete()

Field Types

# Basic fields
TitleField()                    # Required for every model
TextField()                     # Rich text
NumberField(number_format=None) # Numbers with optional formatting
BooleanField()                  # Checkbox
DateField()                     # Date and datetime

# Contact fields
EmailField()                    # Email validation
URLField()                      # URL validation
PhoneField()                    # Phone number

# Advanced fields
SelectField(choices)            # Single choice dropdown
MultiSelectField(choices)       # Multiple choice
StatusField(choices)            # Kanban-style status
UniqueIDField(prefix=None)      # Auto-incrementing IDs
RelationField(to, two_way_property=None)  # Database relations
PeopleField()                   # User references
FilesField()                    # File attachments

Filter Classes

from viemind_notion_orm.filters import *

# Text filters
Title("field_name").contains("value")
Text("field_name").equals("value")
Text("field_name").starts_with("prefix")

# Number filters
Number("field_name").equals(100)
Number("field_name").greater_than(50)
Number("field_name").less_than(200)

# Boolean filters
Checkbox("field_name").equals(True)

# Choice filters
Select("field_name").equals("option")
MultiSelect("field_name").contains("option")
Status("field_name").equals("status")

# Date filters
Date("field_name").equals("2024-01-15")
Date("field_name").before("2024-12-31")
Date("field_name").past_week()

# Logical combinations
filter1 & filter2  # AND
filter1 | filter2  # OR

Enums and Types

from viemind_notion_orm import NotionPropertyType

# Property types
NotionPropertyType.TITLE
NotionPropertyType.TEXT
NotionPropertyType.NUMBER
NotionPropertyType.BOOLEAN
NotionPropertyType.DATE
NotionPropertyType.SELECT
NotionPropertyType.MULTI_SELECT
NotionPropertyType.STATUS
NotionPropertyType.URL
NotionPropertyType.EMAIL
NotionPropertyType.PHONE
NotionPropertyType.PEOPLE
NotionPropertyType.FILES
NotionPropertyType.RELATION
NotionPropertyType.UNIQUE_ID

# Helper classes
SelectOption(name, color=None)
StatusOption(name, color=None)
PropertyDefinition(type, options=None, relation_database_id=None, ...)

Exception Classes

from viemind_notion_orm.exceptions import *

NotionORMError          # Base exception
DatabaseExistsError     # Database already exists
PropertyNotFoundError   # Property not found in schema
InvalidPropertyTypeError # Invalid property type
SchemaValidationError   # Schema validation failed

Conclusion

The Notion ORM library provides a powerful and flexible way to work with Notion databases in Python. Whether you prefer the low-level client API for direct control or the high-level ORM interface for clean, object-oriented code, the library has you covered.

Key benefits:

  • Type safety with enum support
  • Clean syntax with dot notation
  • Powerful querying with filter chaining
  • Relation support for complex data models
  • Auto-incrementing IDs with custom prefixes
  • Comprehensive field types matching Notion's capabilities

Start with the quick start example and gradually explore the advanced features as your needs grow. The ORM interface makes it easy to build complex applications while maintaining clean, readable code.

For additional support, check the troubleshooting section or refer to the comprehensive API reference above.

Happy coding with Notion ORM! 🚀

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

viemind_notion_orm-0.1.1.tar.gz (40.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

viemind_notion_orm-0.1.1-py3-none-any.whl (35.7 kB view details)

Uploaded Python 3

File details

Details for the file viemind_notion_orm-0.1.1.tar.gz.

File metadata

  • Download URL: viemind_notion_orm-0.1.1.tar.gz
  • Upload date:
  • Size: 40.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.11

File hashes

Hashes for viemind_notion_orm-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3ddf3b760d0ec400395ae08f04de4004a2d74549fe57b3238a196c650db372e5
MD5 043f5721fadd1f4e630d8ab5b421c4cd
BLAKE2b-256 731341548db30799532529c45a01e0f6c6e0ca8cf7528f6956408f267f6ad79d

See more details on using hashes here.

File details

Details for the file viemind_notion_orm-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for viemind_notion_orm-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 97100054bd26f9fc988195224e85dcc3711e51dc0a4227060145f87e542dad71
MD5 14f0478efde77c3298376b996960702c
BLAKE2b-256 e6e72a2fd57918caef6cc7de76afaafd8a4af3fe9a9fad04da20386b03f95aaa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page