diff --git a/docs/README.template.MD b/docs/README.template.MD index 60a5c4e..66566d7 100644 --- a/docs/README.template.MD +++ b/docs/README.template.MD @@ -21,12 +21,19 @@ A simple and powerful Python client for interacting with [NocoDB](https://nocodb - **Easy to Use**: Intuitive API design with comprehensive documentation - **Full CRUD Operations**: Create, read, update, and delete records with ease +- **Bulk Operations**: High-performance bulk insert, update, and delete operations - **File Management**: Upload, download, and manage file attachments -- **Advanced Querying**: Complex filtering, sorting, and pagination support +- **Advanced Querying**: Complex filtering, sorting, and pagination support with SQL-like query builder +- **Meta API Support**: Full access to NocoDB Meta API for structure management + - Workspace, Base, and Table operations + - Column/Field management with type-specific helpers + - View management (Grid, Gallery, Form, Kanban, Calendar) + - Webhook automation (URL, Email, Slack, Teams) + - Link/Relation management +- **Flexible Configuration**: Multiple configuration methods (environment variables, files, direct parameters) - **Type Hints**: Full type annotation support for better IDE experience - **Error Handling**: Comprehensive exception handling with specific error types - **Context Manager**: Automatic resource management with context manager support -- **Flexible Configuration**: Support for custom timeouts, redirects, and authentication - **Production Ready**: Robust error handling and resource management ## šŸš€ Quick Start @@ -305,6 +312,618 @@ records = table.get_records(sort="-CreatedAt") # Descending records = table.get_records(sort="-Status,Name") # Status desc, then Name asc ``` +### Bulk Operations + +Perform high-performance bulk operations for better efficiency: + +```python +# Bulk insert multiple records +records_to_insert = [ + {"Name": "Alice", "Email": "alice@example.com", "Age": 28}, + {"Name": "Bob", "Email": "bob@example.com", "Age": 32}, + {"Name": "Charlie", "Email": "charlie@example.com", "Age": 45} +] +record_ids = table.bulk_insert_records(records_to_insert) +print(f"Inserted {len(record_ids)} records") + +# Bulk update multiple records +records_to_update = [ + {"Id": 1, "Status": "Active"}, + {"Id": 2, "Status": "Active"}, + {"Id": 3, "Status": "Inactive"} +] +updated_ids = table.bulk_update_records(records_to_update) +print(f"Updated {len(updated_ids)} records") + +# Bulk delete multiple records +record_ids_to_delete = [1, 2, 3] +deleted_ids = table.bulk_delete_records(record_ids_to_delete) +print(f"Deleted {len(deleted_ids)} records") +``` + +### Query Builder + +Use SQL-like syntax for building complex queries: + +```python +from nocodb_simple_client import NocoDBTable, NocoDBClient + +client = NocoDBClient(base_url="...", db_auth_token="...") +table = NocoDBTable(client, table_id="your-table-id") + +# Build a complex query +records = (table.query() + .select('Name', 'Email', 'Status', 'Age') + .where('Status', 'eq', 'Active') + .where_and('Age', 'gt', 18) + .where_or('Role', 'eq', 'Admin') + .order_by('CreatedAt', 'desc') + .limit(50) + .offset(10) + .execute()) + +# Use convenience methods +first_record = (table.query() + .where('Email', 'eq', 'john@example.com') + .first()) + +# Check if records exist +has_active_users = (table.query() + .where('Status', 'eq', 'Active') + .exists()) + +# Count matching records +active_count = (table.query() + .where('Status', 'eq', 'Active') + .count()) + +# Advanced filtering +records = (table.query() + .select('Name', 'Email') + .where_in('Status', ['Active', 'Pending']) + .where_not_null('Email') + .where_between('Age', 25, 65) + .where_like('Name', '%John%') + .order_by_desc('CreatedAt') + .page(2, 25) # Page 2 with 25 records per page + .execute()) +``` + +### Filter and Sort Builders + +For advanced filtering and sorting without the full query builder: + +```python +from nocodb_simple_client.filter_builder import FilterBuilder, SortBuilder + +# Build complex filters +filter_builder = FilterBuilder() +filter_str = (filter_builder + .where('Status', 'eq', 'Active') + .and_('Age', 'gt', 21) + .or_('Role', 'eq', 'Admin') + .build()) + +# Use with get_records +records = table.get_records(where=filter_str, limit=100) + +# Build sorts +sort_builder = SortBuilder() +sort_str = (sort_builder + .desc('CreatedAt') + .asc('Name') + .build()) + +records = table.get_records(sort=sort_str) +``` + +### Configuration Management + +Multiple ways to configure the client: + +```python +from nocodb_simple_client import NocoDBClient, NocoDBConfig +from pathlib import Path + +# Method 1: Direct parameters +client = NocoDBClient( + base_url="https://app.nocodb.com", + db_auth_token="your-api-token" +) + +# Method 2: From environment variables +# Set environment variables: NOCODB_BASE_URL, NOCODB_API_TOKEN +config = NocoDBConfig.from_env() +client = NocoDBClient(config) + +# Method 3: From configuration file (JSON, YAML, or TOML) +config = NocoDBConfig.from_file(Path("config.json")) +client = NocoDBClient(config) + +# Advanced configuration +config = NocoDBConfig( + base_url="https://app.nocodb.com", + api_token="your-token", + timeout=60.0, + max_retries=5, + backoff_factor=0.5, + pool_connections=20, + pool_maxsize=40, + verify_ssl=True, + debug=False, + log_level="INFO" +) +client = NocoDBClient(config) +``` + +## šŸ”§ Meta API Operations + +The Meta API allows you to programmatically manage your NocoDB structure, including workspaces, bases, tables, columns, views, and webhooks. + +### Meta Client Initialization + +```python +from nocodb_simple_client import NocoDBMetaClient, NocoDBConfig + +# Initialize Meta API client +meta_client = NocoDBMetaClient( + base_url="https://app.nocodb.com", + api_token="your-api-token" +) + +# Or with config +config = NocoDBConfig(base_url="...", api_token="...") +meta_client = NocoDBMetaClient(config) + +# Meta client inherits all data operations from NocoDBClient +# So you can use it for both meta operations AND data operations +records = meta_client.get_records("table_id") # Data operation +tables = meta_client.list_tables("base_id") # Meta operation +``` + +### Workspace Operations + +```python +# List all workspaces +workspaces = meta_client.list_workspaces() +for workspace in workspaces: + print(f"Workspace: {workspace['title']} (ID: {workspace['id']})") + +# Get workspace details +workspace = meta_client.get_workspace("workspace_id") + +# Create a new workspace +workspace_data = { + "title": "My Workspace", + "description": "Team workspace for projects" +} +new_workspace = meta_client.create_workspace(workspace_data) + +# Update workspace +meta_client.update_workspace("workspace_id", {"title": "Updated Name"}) + +# Delete workspace (careful - deletes all bases!) +meta_client.delete_workspace("workspace_id") +``` + +### Base Operations + +```python +# List all bases +bases = meta_client.list_bases() +for base in bases: + print(f"Base: {base['title']} (ID: {base['id']})") + +# Get base details +base = meta_client.get_base("base_id") + +# Create a new base +base_data = { + "title": "My Project Database", + "description": "Project management database" +} +new_base = meta_client.create_base("workspace_id", base_data) + +# Update base +meta_client.update_base("base_id", {"title": "Updated Project"}) + +# Delete base (careful - deletes all tables!) +meta_client.delete_base("base_id") +``` + +### Table Structure Operations + +```python +# List all tables in a base +tables = meta_client.list_tables("base_id") +for table in tables: + print(f"Table: {table['title']} (ID: {table['id']})") + +# Get detailed table information (including columns and relationships) +table_info = meta_client.get_table_info("table_id") +print(f"Table has {len(table_info['columns'])} columns") + +# Create a new table +table_data = { + "title": "Users", + "table_name": "users", + "columns": [ + {"title": "Name", "column_name": "name", "uidt": "SingleLineText"}, + {"title": "Email", "column_name": "email", "uidt": "Email"}, + {"title": "Age", "column_name": "age", "uidt": "Number"} + ] +} +new_table = meta_client.create_table("base_id", table_data) + +# Update table metadata +meta_client.update_table("table_id", {"title": "Active Users"}) + +# Delete table (careful - deletes all data!) +meta_client.delete_table("table_id") +``` + +### Column Management + +```python +from nocodb_simple_client import NocoDBMetaClient +from nocodb_simple_client.columns import NocoDBColumns + +meta_client = NocoDBMetaClient(base_url="...", api_token="...") +columns_manager = NocoDBColumns(meta_client) + +# List all columns in a table +columns = columns_manager.get_columns("table_id") +for col in columns: + print(f"Column: {col['title']} - Type: {col['uidt']}") + +# Create different types of columns + +# Text column +text_col = columns_manager.create_text_column( + "table_id", + title="Description", + max_length=500, + default_value="N/A" +) + +# Number column +number_col = columns_manager.create_number_column( + "table_id", + title="Price", + precision=10, + scale=2 +) + +# Checkbox column +checkbox_col = columns_manager.create_checkbox_column( + "table_id", + title="Is Active", + default_value=True +) + +# Single select column +select_col = columns_manager.create_singleselect_column( + "table_id", + title="Status", + options=[ + {"title": "Active", "color": "#00ff00"}, + {"title": "Inactive", "color": "#ff0000"}, + {"title": "Pending", "color": "#ffaa00"} + ] +) + +# Date column +date_col = columns_manager.create_date_column( + "table_id", + title="Start Date", + date_format="YYYY-MM-DD" +) + +# Email column +email_col = columns_manager.create_email_column( + "table_id", + title="Contact Email", + validate=True +) + +# URL column +url_col = columns_manager.create_url_column( + "table_id", + title="Website", + validate=True +) + +# Rating column +rating_col = columns_manager.create_rating_column( + "table_id", + title="User Rating", + max_rating=5, + icon="star", + color="#fcb401" +) + +# Formula column +formula_col = columns_manager.create_formula_column( + "table_id", + title="Full Name", + formula="{FirstName} & ' ' & {LastName}" +) + +# Link/Relation column +link_col = columns_manager.create_link_column( + "table_id", + title="Orders", + related_table_id="orders_table_id", + relation_type="hm" # has many +) + +# Update a column +columns_manager.update_column( + "table_id", + "column_id", + title="Updated Name", + cdf="new default value" +) + +# Delete a column +columns_manager.delete_column("table_id", "column_id") + +# Find column by name +column = columns_manager.get_column_by_name("table_id", "Email") +if column: + print(f"Found column: {column['id']}") +``` + +### View Management + +```python +from nocodb_simple_client import NocoDBMetaClient +from nocodb_simple_client.views import NocoDBViews + +meta_client = NocoDBMetaClient(base_url="...", api_token="...") +views_manager = NocoDBViews(meta_client) + +# List all views for a table +views = views_manager.get_views("table_id") +for view in views: + print(f"View: {view['title']} - Type: {view['type']}") + +# Create different types of views +grid_view = views_manager.create_view( + "table_id", + title="Active Users", + view_type="grid", + options={"show_system_fields": False} +) + +gallery_view = views_manager.create_view( + "table_id", + title="User Gallery", + view_type="gallery" +) + +form_view = views_manager.create_view( + "table_id", + title="User Registration Form", + view_type="form" +) + +kanban_view = views_manager.create_view( + "table_id", + title="Project Kanban", + view_type="kanban" +) + +# Add filters to a view +filter_data = views_manager.create_view_filter( + "table_id", + "view_id", + column_id="column_id", + comparison_op="eq", + value="Active", + logical_op="and" +) + +# Add sorting to a view +sort_data = views_manager.create_view_sort( + "table_id", + "view_id", + column_id="column_id", + direction="desc" +) + +# Update view column visibility and order +views_manager.update_view_column( + "table_id", + "view_id", + "column_id", + {"show": True, "order": 1, "width": 200} +) + +# Get data from a view (with filters/sorts applied) +view_data = views_manager.get_view_data( + "table_id", + "view_id", + fields=["Name", "Email", "Status"], + limit=50 +) + +# Duplicate a view +duplicated_view = views_manager.duplicate_view( + "table_id", + "view_id", + "Copy of Active Users" +) + +# Delete a view +views_manager.delete_view("table_id", "view_id") +``` + +### Webhook Automation + +```python +from nocodb_simple_client import NocoDBMetaClient +from nocodb_simple_client.webhooks import NocoDBWebhooks + +meta_client = NocoDBMetaClient(base_url="...", api_token="...") +webhooks_manager = NocoDBWebhooks(meta_client) + +# List all webhooks for a table +webhooks = webhooks_manager.get_webhooks("table_id") +for webhook in webhooks: + print(f"Webhook: {webhook['title']} - Active: {webhook.get('active')}") + +# Create a URL webhook +webhook = webhooks_manager.create_webhook( + "table_id", + title="New User Notification", + event_type="after", + operation="insert", + url="https://api.example.com/webhook", + method="POST", + headers={"Authorization": "Bearer token123"}, + body='{"user": "{{Name}}", "email": "{{Email}}"}', + active=True +) + +# Create an Email webhook +email_webhook = webhooks_manager.create_email_webhook( + "table_id", + title="Alert on Update", + event_type="after", + operation="update", + emails=["admin@example.com", "team@example.com"], + subject="Record Updated: {{Name}}", + body="Record {{Id}} has been updated.", + active=True +) + +# Create a Slack webhook +slack_webhook = webhooks_manager.create_slack_webhook( + "table_id", + title="Slack Notification", + event_type="after", + operation="insert", + webhook_url="https://hooks.slack.com/services/YOUR/WEBHOOK/URL", + message="New record created: {{Name}}", + active=True +) + +# Create a Microsoft Teams webhook +teams_webhook = webhooks_manager.create_teams_webhook( + "table_id", + title="Teams Notification", + event_type="after", + operation="delete", + webhook_url="https://outlook.office.com/webhook/YOUR_WEBHOOK", + message="Record deleted: {{Name}}", + active=True +) + +# Update a webhook +webhooks_manager.update_webhook( + "table_id", + "webhook_id", + title="Updated Notification", + url="https://new-api.example.com/webhook", + active=False +) + +# Test a webhook +test_result = webhooks_manager.test_webhook("table_id", "webhook_id") +print(f"Test result: {test_result}") + +# Get webhook execution logs +logs = webhooks_manager.get_webhook_logs("table_id", "webhook_id", limit=10) +for log in logs: + print(f"Execution at {log.get('created_at')}: {log.get('status')}") + +# Toggle webhook active status +webhooks_manager.toggle_webhook("table_id", "webhook_id") + +# Delete a webhook +webhooks_manager.delete_webhook("table_id", "webhook_id") +``` + +### Link/Relation Management + +```python +from nocodb_simple_client import NocoDBClient +from nocodb_simple_client.links import NocoDBLinks + +client = NocoDBClient(base_url="...", db_auth_token="...") +links_manager = NocoDBLinks(client) + +# Get linked records +linked_records = links_manager.get_linked_records( + "table_id", + record_id=123, + link_field_id="column_id", + fields=["Name", "Email"], + sort="-CreatedAt", + limit=25 +) + +# Count linked records +count = links_manager.count_linked_records( + "table_id", + record_id=123, + link_field_id="column_id" +) +print(f"This record has {count} linked records") + +# Link records to a record +links_manager.link_records( + "table_id", + record_id=123, + link_field_id="column_id", + linked_record_ids=[456, 789, 101] +) + +# Unlink specific records +links_manager.unlink_records( + "table_id", + record_id=123, + link_field_id="column_id", + linked_record_ids=[456, 789] +) + +# Unlink all records +links_manager.unlink_all_records( + "table_id", + record_id=123, + link_field_id="column_id" +) + +# Replace all links with new ones +links_manager.replace_links( + "table_id", + record_id=123, + link_field_id="column_id", + new_linked_record_ids=[111, 222, 333] +) + +# Bulk link operations +operations = [ + { + "table_id": "table1", + "record_id": 1, + "link_field_id": "col1", + "linked_record_ids": [10, 20], + "action": "link" + }, + { + "table_id": "table1", + "record_id": 2, + "link_field_id": "col1", + "linked_record_ids": [30], + "action": "unlink" + } +] +results = links_manager.bulk_link_records(operations) +print(f"Operations successful: {sum(results)}/{len(results)}") +``` + ## šŸ›”ļø Error Handling The client provides specific exceptions for different error scenarios: @@ -333,8 +952,13 @@ Check out the [`examples/`](examples/) directory for comprehensive examples: - **[Basic Usage](examples/basic_usage.py)**: CRUD operations and fundamentals - **[File Operations](examples/file_operations.py)**: File upload/download examples -- **[Advanced Querying](examples/advanced_querying.py)**: Complex filtering and sorting +- **[Advanced Querying](examples/advanced_querying.py)**: Complex filtering and sorting with Query Builder - **[Context Manager Usage](examples/context_manager_usage.py)**: Proper resource management +- **[Bulk Operations](examples/bulk_operations.py)**: High-performance bulk insert, update, and delete +- **[Meta API Operations](examples/meta_operations.py)**: Workspace, Base, Table, Column, View management +- **[Webhook Automation](examples/webhook_examples.py)**: Creating and managing webhooks +- **[Link Management](examples/link_examples.py)**: Managing relationships between tables +- **[Configuration](examples/config_examples.py)**: Different configuration methods ## šŸ“‹ Requirements diff --git a/examples/advanced_querying.py b/examples/advanced_querying.py index cf8e7d7..bc1bc43 100644 --- a/examples/advanced_querying.py +++ b/examples/advanced_querying.py @@ -2,12 +2,13 @@ Advanced querying examples for NocoDB Simple Client. This example demonstrates advanced filtering, sorting, and querying -techniques with the NocoDB Simple Client. +techniques including the SQL-like Query Builder and Filter/Sort builders. """ from datetime import datetime, timedelta from nocodb_simple_client import NocoDBClient, NocoDBException, NocoDBTable +from nocodb_simple_client.filter_builder import FilterBuilder, SortBuilder # Configuration NOCODB_BASE_URL = "https://your-nocodb-instance.com" @@ -181,6 +182,176 @@ def demonstrate_complex_queries(table: NocoDBTable): print(f" {status}: {count} records") +def demonstrate_query_builder(table: NocoDBTable): + """Demonstrate SQL-like Query Builder.""" + print("\nšŸ—ļø Query Builder Examples:") + + # Example 1: Simple query with method chaining + print("\n1. Simple query with Query Builder:") + records = ( + table.query() + .select("Name", "Email", "Status") + .where("Status", "eq", "Active") + .order_by("Name", "asc") + .limit(5) + .execute() + ) + print(f" Found {len(records)} active records") + for record in records: + print(f" - {record.get('Name')} ({record.get('Email')})") + + # Example 2: Complex filtering with AND/OR + print("\n2. Complex filtering with AND/OR:") + records = ( + table.query() + .select("Name", "Age", "Status") + .where("Status", "eq", "Active") + .where_and("Age", "gt", 21) + .where_or("Role", "eq", "Admin") + .order_by_desc("Age") + .limit(10) + .execute() + ) + print(f" Found {len(records)} records") + + # Example 3: Using convenience methods + print("\n3. Query Builder convenience methods:") + + # Get first matching record + first_record = table.query().where("Email", "like", "%@example.com").first() + if first_record: + print(f" First record: {first_record.get('Name')}") + + # Check if records exist + has_vip_users = table.query().where("UserType", "eq", "VIP").exists() + print(f" Has VIP users: {has_vip_users}") + + # Count matching records + active_count = table.query().where("Status", "eq", "Active").count() + print(f" Active users count: {active_count}") + + # Example 4: Advanced filtering methods + print("\n4. Advanced filtering methods:") + records = ( + table.query() + .select("Name", "Email", "Status", "Age") + .where_in("Status", ["Active", "Pending"]) + .where_not_null("Email") + .where_between("Age", 25, 65) + .where_like("Name", "%Smith%") + .order_by_desc("CreatedAt") + .limit(15) + .execute() + ) + print(f" Found {len(records)} records with advanced filters") + + # Example 5: Pagination with Query Builder + print("\n5. Pagination with Query Builder:") + page_2_records = ( + table.query() + .select("Name", "Email") + .where("Status", "eq", "Active") + .order_by("Name") + .page(2, 10) # Page 2 with 10 records per page + .execute() + ) + print(f" Page 2 contains {len(page_2_records)} records") + + # Example 6: Query debugging + print("\n6. Query debugging:") + query = ( + table.query() + .select("Name", "Status") + .where("Age", "gt", 18) + .where_and("Status", "eq", "Active") + .order_by_desc("CreatedAt") + .limit(50) + ) + + print(f" Query string: {str(query)}") + print(f" Query params: {query.to_params()}") + + +def demonstrate_filter_builder(): + """Demonstrate advanced Filter Builder.""" + print("\nšŸ”§ Filter Builder Examples:") + + # Example 1: Building complex filters + print("\n1. Building complex filters:") + filter_builder = FilterBuilder() + filter_str = ( + filter_builder.where("Status", "eq", "Active") + .and_("Age", "gt", 21) + .and_("Email", "isnotblank") + .or_("Role", "eq", "Admin") + .build() + ) + print(f" Filter string: {filter_str}") + + # Example 2: Using different operators + print("\n2. Different comparison operators:") + + # Equality + fb1 = FilterBuilder() + print(f" Equal: {fb1.where('Status', 'eq', 'Active').build()}") + + # Numeric comparisons + fb2 = FilterBuilder() + print(f" Greater than: {fb2.where('Age', 'gt', 21).build()}") + + # Text search + fb3 = FilterBuilder() + print(f" Like: {fb3.where('Name', 'like', '%John%').build()}") + + # IN operator + fb4 = FilterBuilder() + print(f" In: {fb4.where('Status', 'in', ['Active', 'Pending']).build()}") + + # Between operator + fb5 = FilterBuilder() + print(f" Between: {fb5.where('Age', 'btw', [18, 65]).build()}") + + # Example 3: NULL checks + print("\n3. NULL and empty checks:") + fb_null = FilterBuilder() + print(f" Is null: {fb_null.where('DeletedAt', 'null').build()}") + + fb_not_null = FilterBuilder() + print(f" Not null: {fb_not_null.where('Email', 'notnull').build()}") + + fb_blank = FilterBuilder() + print(f" Is blank: {fb_blank.where('Description', 'isblank').build()}") + + +def demonstrate_sort_builder(): + """Demonstrate Sort Builder.""" + print("\nšŸ“ Sort Builder Examples:") + + # Example 1: Simple sorting + print("\n1. Simple sorting:") + sort_builder = SortBuilder() + sort_str = sort_builder.desc("CreatedAt").build() + print(f" Sort string: {sort_str}") + + # Example 2: Multiple sort fields + print("\n2. Multiple sort fields:") + sort_builder = SortBuilder() + sort_str = sort_builder.desc("Status").asc("Name").desc("CreatedAt").build() + print(f" Sort string: {sort_str}") + + # Example 3: Using with get_records + print("\n3. Combining builders:") + filter_builder = FilterBuilder() + filter_str = filter_builder.where("Status", "eq", "Active").and_("Age", "gt", 18).build() + + sort_builder = SortBuilder() + sort_str = sort_builder.desc("Priority").asc("Name").build() + + print(f" Filter: {filter_str}") + print(f" Sort: {sort_str}") + print(" Ready to use with: table.get_records(where=filter_str, sort=sort_str)") + + def main(): """Run all advanced querying examples.""" @@ -199,6 +370,9 @@ def main(): demonstrate_field_selection(table) demonstrate_pagination(table) demonstrate_complex_queries(table) + demonstrate_query_builder(table) + demonstrate_filter_builder() # No table needed for this demo + demonstrate_sort_builder() # No table needed for this demo print("\n" + "=" * 50) print("āœ… All examples completed successfully!") diff --git a/examples/bulk_operations.py b/examples/bulk_operations.py new file mode 100644 index 0000000..3af51ad --- /dev/null +++ b/examples/bulk_operations.py @@ -0,0 +1,115 @@ +""" +Bulk operations examples for NocoDB Simple Client. + +This example demonstrates high-performance bulk operations for inserting, +updating, and deleting multiple records at once. +""" + +from nocodb_simple_client import NocoDBClient, NocoDBException, NocoDBTable + +# Configuration +NOCODB_BASE_URL = "https://your-nocodb-instance.com" +API_TOKEN = "your-api-token-here" +TABLE_ID = "your-table-id-here" + + +def main(): + """Demonstrate bulk operations.""" + + client = NocoDBClient(base_url=NOCODB_BASE_URL, db_auth_token=API_TOKEN) + table = NocoDBTable(client, table_id=TABLE_ID) + + try: + # Example 1: Bulk Insert + print("1. Bulk inserting records...") + records_to_insert = [ + {"Name": "Alice Smith", "Email": "alice@example.com", "Age": 28, "Active": True}, + {"Name": "Bob Johnson", "Email": "bob@example.com", "Age": 32, "Active": True}, + {"Name": "Charlie Brown", "Email": "charlie@example.com", "Age": 45, "Active": False}, + {"Name": "Diana Prince", "Email": "diana@example.com", "Age": 29, "Active": True}, + {"Name": "Eve Wilson", "Email": "eve@example.com", "Age": 37, "Active": True}, + ] + + record_ids = table.bulk_insert_records(records_to_insert) + print(f" āœ“ Inserted {len(record_ids)} records") + print(f" Record IDs: {record_ids}") + + # Example 2: Bulk Update + print("\n2. Bulk updating records...") + records_to_update = [ + {"Id": record_ids[0], "Active": False, "Status": "Updated"}, + {"Id": record_ids[1], "Active": False, "Status": "Updated"}, + {"Id": record_ids[2], "Active": True, "Status": "Updated"}, + ] + + updated_ids = table.bulk_update_records(records_to_update) + print(f" āœ“ Updated {len(updated_ids)} records") + print(f" Updated IDs: {updated_ids}") + + # Example 3: Verify updates + print("\n3. Verifying updates...") + for record_id in updated_ids: + record = table.get_record(record_id, fields=["Id", "Name", "Active", "Status"]) + print( + f" Record {record_id}: {record.get('Name')} - Active: {record.get('Active')}, Status: {record.get('Status')}" + ) + + # Example 4: Bulk Delete + print("\n4. Bulk deleting records...") + deleted_ids = table.bulk_delete_records(record_ids) + print(f" āœ“ Deleted {len(deleted_ids)} records") + print(f" Deleted IDs: {deleted_ids}") + + # Example 5: Performance comparison + print("\n5. Performance comparison (inserting 100 records)...") + + # Single inserts (for comparison - commented out for brevity) + # import time + # start = time.time() + # for i in range(100): + # table.insert_record({"Name": f"User {i}", "Email": f"user{i}@example.com"}) + # single_time = time.time() - start + + # Bulk insert + import time + + bulk_records = [ + {"Name": f"User {i}", "Email": f"user{i}@example.com", "Age": 20 + (i % 50)} + for i in range(100) + ] + start = time.time() + bulk_ids = table.bulk_insert_records(bulk_records) + bulk_time = time.time() - start + + print(f" Bulk insert time: {bulk_time:.2f} seconds for {len(bulk_ids)} records") + # print(f" Single insert time: {single_time:.2f} seconds") + # print(f" Performance improvement: {single_time / bulk_time:.1f}x faster") + + # Cleanup + print("\n6. Cleaning up test records...") + table.bulk_delete_records(bulk_ids) + print(" āœ“ Cleanup complete") + + # Example 6: Error handling in bulk operations + print("\n7. Demonstrating error handling...") + try: + # Attempt to update non-existent records + invalid_updates = [ + {"Id": 99999999, "Name": "Invalid"}, + {"Id": 88888888, "Name": "Also Invalid"}, + ] + table.bulk_update_records(invalid_updates) + except NocoDBException as e: + print(f" Expected error caught: {e.message}") + + except NocoDBException as e: + print(f"āŒ NocoDB error: {e.error} - {e.message}") + except Exception as e: + print(f"āŒ Unexpected error: {e}") + finally: + client.close() + print("\nāœ“ Client session closed") + + +if __name__ == "__main__": + main() diff --git a/examples/config_examples.py b/examples/config_examples.py new file mode 100644 index 0000000..1c297b6 --- /dev/null +++ b/examples/config_examples.py @@ -0,0 +1,272 @@ +""" +Configuration examples for NocoDB Simple Client. + +This example demonstrates different ways to configure the NocoDB client +using direct parameters, environment variables, and configuration files. +""" + +import os +from pathlib import Path + +from nocodb_simple_client import NocoDBClient, NocoDBConfig + + +def example_1_direct_parameters(): + """Example 1: Direct parameter configuration (simplest).""" + print("\n" + "=" * 60) + print("Example 1: Direct Parameters") + print("=" * 60) + + client = NocoDBClient( + base_url="https://app.nocodb.com", + db_auth_token="your-api-token-here", + timeout=30, + max_redirects=3, + ) + + print("āœ“ Client initialized with direct parameters") + client.close() + + +def example_2_environment_variables(): + """Example 2: Configuration from environment variables.""" + print("\n" + "=" * 60) + print("Example 2: Environment Variables") + print("=" * 60) + + # Set environment variables (in practice, these would be set in your shell or .env file) + os.environ["NOCODB_BASE_URL"] = "https://app.nocodb.com" + os.environ["NOCODB_API_TOKEN"] = "your-api-token-here" + os.environ["NOCODB_TIMEOUT"] = "60.0" + os.environ["NOCODB_MAX_RETRIES"] = "5" + os.environ["NOCODB_DEBUG"] = "false" + + # Load configuration from environment + config = NocoDBConfig.from_env() + client = NocoDBClient(config) + + print("āœ“ Client initialized from environment variables") + print(f" Base URL: {config.base_url}") + print(f" Timeout: {config.timeout}s") + print(f" Max Retries: {config.max_retries}") + client.close() + + +def example_3_config_object(): + """Example 3: Using NocoDBConfig object.""" + print("\n" + "=" * 60) + print("Example 3: Config Object") + print("=" * 60) + + # Create a config object with all options + config = NocoDBConfig( + base_url="https://app.nocodb.com", + api_token="your-api-token-here", + access_protection_auth="optional-protection-token", + access_protection_header="X-BAUERGROUP-Auth", + timeout=60.0, + max_retries=5, + backoff_factor=0.5, + pool_connections=20, + pool_maxsize=40, + verify_ssl=True, + user_agent="my-app/1.0", + debug=False, + log_level="INFO", + ) + + # Validate configuration + config.validate() + print("āœ“ Configuration validated") + + # Setup logging based on configuration + config.setup_logging() + + # Initialize client with config + client = NocoDBClient(config) + + print("āœ“ Client initialized with config object") + print(f" Configuration: {config.to_dict()}") + client.close() + + +def example_4_config_from_json(): + """Example 4: Load configuration from JSON file.""" + print("\n" + "=" * 60) + print("Example 4: JSON Configuration File") + print("=" * 60) + + # Create example JSON config file + config_data = """{ + "base_url": "https://app.nocodb.com", + "api_token": "your-api-token-here", + "timeout": 60.0, + "max_retries": 5, + "debug": false +}""" + + config_path = Path("nocodb_config.json") + config_path.write_text(config_data) + + # Load configuration from JSON + config = NocoDBConfig.from_file(config_path) + client = NocoDBClient(config) + + print("āœ“ Client initialized from JSON configuration file") + print(f" Config file: {config_path}") + + client.close() + + # Cleanup + config_path.unlink() + print(" Cleaned up config file") + + +def example_5_custom_headers(): + """Example 5: Configuration with custom headers.""" + print("\n" + "=" * 60) + print("Example 5: Custom Headers") + print("=" * 60) + + config = NocoDBConfig( + base_url="https://app.nocodb.com", + api_token="your-api-token-here", + access_protection_auth="custom-auth-token", + access_protection_header="X-Custom-Protection", + extra_headers={"X-Request-ID": "123456", "X-Client-Version": "1.0.0"}, + ) + + client = NocoDBClient(config) + + print("āœ“ Client initialized with custom headers") + print(f" Headers: {client.headers}") + + client.close() + + +def example_6_production_config(): + """Example 6: Production-ready configuration.""" + print("\n" + "=" * 60) + print("Example 6: Production Configuration") + print("=" * 60) + + config = NocoDBConfig( + base_url="https://production.nocodb.com", + api_token="production-api-token", + # Timeouts and retry configuration + timeout=120.0, # Longer timeout for production + max_retries=3, + backoff_factor=1.0, # Exponential backoff between retries + # Connection pooling for better performance + pool_connections=50, + pool_maxsize=100, + # SSL verification (important for production!) + verify_ssl=True, + # Logging configuration + debug=False, + log_level="WARNING", # Less verbose in production + # Custom user agent for tracking + user_agent="MyApp-Production/2.0", + ) + + # Validate before using in production + config.validate() + + client = NocoDBClient(config) + + print("āœ“ Production client initialized") + print(f" Timeout: {config.timeout}s") + print(f" Connection Pool: {config.pool_connections}/{config.pool_maxsize}") + print(f" SSL Verification: {config.verify_ssl}") + + client.close() + + +def example_7_development_config(): + """Example 7: Development configuration.""" + print("\n" + "=" * 60) + print("Example 7: Development Configuration") + print("=" * 60) + + config = NocoDBConfig( + base_url="http://localhost:8080", # Local NocoDB instance + api_token="development-token", + # Development settings + timeout=30.0, + max_retries=1, # Fail fast in development + debug=True, # Enable debug mode + log_level="DEBUG", # Verbose logging + verify_ssl=False, # May be needed for local development + user_agent="MyApp-Development/1.0-dev", + ) + + # Setup debug logging + config.setup_logging() + + client = NocoDBClient(config) + + print("āœ“ Development client initialized") + print(f" Base URL: {config.base_url}") + print(f" Debug Mode: {config.debug}") + print(f" Log Level: {config.log_level}") + + client.close() + + +def example_8_multiple_environments(): + """Example 8: Managing multiple environments.""" + print("\n" + "=" * 60) + print("Example 8: Multiple Environments") + print("=" * 60) + + environments = { + "development": NocoDBConfig( + base_url="http://localhost:8080", api_token="dev-token", debug=True + ), + "staging": NocoDBConfig( + base_url="https://staging.nocodb.com", api_token="staging-token", debug=False + ), + "production": NocoDBConfig( + base_url="https://production.nocodb.com", + api_token="prod-token", + debug=False, + timeout=120.0, + ), + } + + # Select environment (e.g., from environment variable) + current_env = os.getenv("APP_ENV", "development") + config = environments[current_env] + + client = NocoDBClient(config) + + print(f"āœ“ Client initialized for '{current_env}' environment") + print(f" Base URL: {config.base_url}") + print(f" Debug: {config.debug}") + + client.close() + + +def main(): + """Run all configuration examples.""" + print("\n" + "=" * 60) + print("NOCODB SIMPLE CLIENT - CONFIGURATION EXAMPLES") + print("=" * 60) + + # Run examples + example_1_direct_parameters() + example_2_environment_variables() + example_3_config_object() + example_4_config_from_json() + example_5_custom_headers() + example_6_production_config() + example_7_development_config() + example_8_multiple_environments() + + print("\n" + "=" * 60) + print("All configuration examples completed!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/examples/link_examples.py b/examples/link_examples.py new file mode 100644 index 0000000..42cc213 --- /dev/null +++ b/examples/link_examples.py @@ -0,0 +1,277 @@ +""" +Link/Relation management examples for NocoDB Simple Client. + +This example demonstrates how to manage relationships between +records in different tables. +""" + +from nocodb_simple_client import NocoDBClient +from nocodb_simple_client.links import NocoDBLinks + +# Configuration +NOCODB_BASE_URL = "https://your-nocodb-instance.com" +API_TOKEN = "your-api-token-here" + +# Example: Projects table linking to Tasks table +PROJECTS_TABLE_ID = "your-projects-table-id" +TASKS_TABLE_ID = "your-tasks-table-id" +LINK_FIELD_ID = "your-link-field-id" # The LinkToAnotherRecord column ID + + +def main(): + """Demonstrate link/relation management.""" + + client = NocoDBClient(base_url=NOCODB_BASE_URL, db_auth_token=API_TOKEN) + links_manager = NocoDBLinks(client) + + try: + print("=" * 60) + print("LINK/RELATION MANAGEMENT EXAMPLES") + print("=" * 60) + + # Note: For this example to work, you need: + # 1. Two tables (e.g., Projects and Tasks) + # 2. A LinkToAnotherRecord field in one of them + # 3. Some existing records + + # =================================================================== + # GET LINKED RECORDS + # =================================================================== + print("\n1. Getting linked records...") + project_id = 1 # Replace with actual project ID + + linked_tasks = links_manager.get_linked_records( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + fields=["Id", "Title", "Status", "Priority"], + sort="-Priority", + limit=25, + ) + + print(f" Project {project_id} has {len(linked_tasks)} linked tasks:") + for task in linked_tasks: + print(f" • Task {task.get('Id')}: {task.get('Title')} ({task.get('Status')})") + + # =================================================================== + # COUNT LINKED RECORDS + # =================================================================== + print("\n2. Counting linked records...") + total_tasks = links_manager.count_linked_records( + PROJECTS_TABLE_ID, record_id=project_id, link_field_id=LINK_FIELD_ID + ) + print(f" Total tasks: {total_tasks}") + + # Count with filter + completed_tasks = links_manager.count_linked_records( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + where="(Status,eq,Completed)", + ) + print(f" Completed tasks: {completed_tasks}") + + # =================================================================== + # LINK RECORDS + # =================================================================== + print("\n3. Linking records...") + # Link tasks 10, 11, 12 to project 1 + task_ids_to_link = [10, 11, 12] + + links_manager.link_records( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + linked_record_ids=task_ids_to_link, + ) + print(f" āœ“ Linked {len(task_ids_to_link)} tasks to project {project_id}") + + # =================================================================== + # UNLINK SPECIFIC RECORDS + # =================================================================== + print("\n4. Unlinking specific records...") + task_ids_to_unlink = [10] + + links_manager.unlink_records( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + linked_record_ids=task_ids_to_unlink, + ) + print(f" āœ“ Unlinked {len(task_ids_to_unlink)} task(s) from project {project_id}") + + # =================================================================== + # REPLACE ALL LINKS + # =================================================================== + print("\n5. Replacing all linked records...") + new_task_ids = [15, 16, 17, 18] + + links_manager.replace_links( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + new_linked_record_ids=new_task_ids, + ) + print(f" āœ“ Replaced all links with {len(new_task_ids)} new tasks") + + # Verify + new_count = links_manager.count_linked_records( + PROJECTS_TABLE_ID, record_id=project_id, link_field_id=LINK_FIELD_ID + ) + print(f" New link count: {new_count}") + + # =================================================================== + # UNLINK ALL RECORDS + # =================================================================== + print("\n6. Unlinking all records...") + links_manager.unlink_all_records( + PROJECTS_TABLE_ID, record_id=project_id, link_field_id=LINK_FIELD_ID + ) + print(f" āœ“ Unlinked all tasks from project {project_id}") + + # =================================================================== + # BULK LINK OPERATIONS + # =================================================================== + print("\n7. Performing bulk link operations...") + operations = [ + { + "table_id": PROJECTS_TABLE_ID, + "record_id": 1, + "link_field_id": LINK_FIELD_ID, + "linked_record_ids": [20, 21], + "action": "link", + }, + { + "table_id": PROJECTS_TABLE_ID, + "record_id": 2, + "link_field_id": LINK_FIELD_ID, + "linked_record_ids": [22, 23], + "action": "link", + }, + { + "table_id": PROJECTS_TABLE_ID, + "record_id": 3, + "link_field_id": LINK_FIELD_ID, + "linked_record_ids": [24], + "action": "unlink", + }, + ] + + results = links_manager.bulk_link_records(operations) + successful = sum(results) + print(f" āœ“ Bulk operations: {successful}/{len(results)} successful") + + # =================================================================== + # PRACTICAL EXAMPLES + # =================================================================== + print("\n" + "=" * 60) + print("PRACTICAL USE CASES") + print("=" * 60) + + print("\n8. Practical scenarios...") + + # Scenario 1: Assign multiple tasks to a project + print("\n Scenario 1: Assigning tasks to a new project") + new_project_id = 100 # Hypothetical new project + task_ids_for_project = [30, 31, 32, 33, 34] + + links_manager.link_records( + PROJECTS_TABLE_ID, + record_id=new_project_id, + link_field_id=LINK_FIELD_ID, + linked_record_ids=task_ids_for_project, + ) + print(f" āœ“ Assigned {len(task_ids_for_project)} tasks to project {new_project_id}") + + # Scenario 2: Transfer tasks from one project to another + print("\n Scenario 2: Transferring tasks between projects") + source_project = 100 + target_project = 101 + + # Get tasks from source project + tasks_to_transfer = links_manager.get_linked_records( + PROJECTS_TABLE_ID, record_id=source_project, link_field_id=LINK_FIELD_ID, fields=["Id"] + ) + task_ids = [task["Id"] for task in tasks_to_transfer] + + # Unlink from source + links_manager.unlink_records( + PROJECTS_TABLE_ID, + record_id=source_project, + link_field_id=LINK_FIELD_ID, + linked_record_ids=task_ids, + ) + + # Link to target + links_manager.link_records( + PROJECTS_TABLE_ID, + record_id=target_project, + link_field_id=LINK_FIELD_ID, + linked_record_ids=task_ids, + ) + print( + f" āœ“ Transferred {len(task_ids)} tasks from project {source_project} to {target_project}" + ) + + # Scenario 3: Finding all linked records with specific criteria + print("\n Scenario 3: Finding high-priority tasks in a project") + high_priority_tasks = links_manager.get_linked_records( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + fields=["Id", "Title", "Priority"], + where="(Priority,eq,High)", + sort="-CreatedAt", + ) + print(f" Found {len(high_priority_tasks)} high-priority tasks:") + for task in high_priority_tasks: + print(f" • {task.get('Title')}") + + # Scenario 4: Link validation before operations + print("\n Scenario 4: Validating links before adding") + max_tasks_per_project = 50 + + current_count = links_manager.count_linked_records( + PROJECTS_TABLE_ID, record_id=project_id, link_field_id=LINK_FIELD_ID + ) + + new_tasks_to_add = [40, 41, 42] + if current_count + len(new_tasks_to_add) <= max_tasks_per_project: + links_manager.link_records( + PROJECTS_TABLE_ID, + record_id=project_id, + link_field_id=LINK_FIELD_ID, + linked_record_ids=new_tasks_to_add, + ) + print( + f" āœ“ Added {len(new_tasks_to_add)} tasks (total: {current_count + len(new_tasks_to_add)})" + ) + else: + print(f" ⚠ Cannot add tasks: would exceed maximum of {max_tasks_per_project}") + + except Exception as e: + print(f"āŒ Error: {e}") + import traceback + + traceback.print_exc() + finally: + client.close() + print("\nāœ“ Client session closed") + + +if __name__ == "__main__": + print("\n" + "=" * 60) + print("NOTE: This example requires proper setup of linked tables") + print("=" * 60) + print("\nBefore running this example, you need to:") + print("1. Create two tables (e.g., Projects and Tasks)") + print("2. Add a LinkToAnotherRecord field to link them") + print("3. Create some test records") + print("4. Update the configuration constants at the top of this file") + print("\n" + "=" * 60) + + response = input("\nHave you completed the setup? (y/n): ") + if response.lower() == "y": + main() + else: + print("\nPlease complete the setup first, then run this example again.") diff --git a/examples/meta_operations.py b/examples/meta_operations.py new file mode 100644 index 0000000..35859aa --- /dev/null +++ b/examples/meta_operations.py @@ -0,0 +1,209 @@ +""" +Meta API operations examples for NocoDB Simple Client. + +This example demonstrates how to manage NocoDB structure including +workspaces, bases, tables, columns, and views programmatically. +""" + +from nocodb_simple_client import NocoDBMetaClient +from nocodb_simple_client.columns import NocoDBColumns +from nocodb_simple_client.views import NocoDBViews + +# Configuration +NOCODB_BASE_URL = "https://your-nocodb-instance.com" +API_TOKEN = "your-api-token-here" + + +def main(): + """Demonstrate Meta API operations.""" + + # Initialize Meta API client + meta_client = NocoDBMetaClient(base_url=NOCODB_BASE_URL, api_token=API_TOKEN) + + try: + # =================================================================== + # WORKSPACE OPERATIONS + # =================================================================== + print("=" * 60) + print("WORKSPACE OPERATIONS") + print("=" * 60) + + # List workspaces + print("\n1. Listing workspaces...") + workspaces = meta_client.list_workspaces() + for workspace in workspaces: + print(f" • {workspace.get('title')} (ID: {workspace.get('id')})") + + # =================================================================== + # BASE OPERATIONS + # =================================================================== + print("\n" + "=" * 60) + print("BASE OPERATIONS") + print("=" * 60) + + # List bases + print("\n2. Listing bases...") + bases = meta_client.list_bases() + for base in bases: + print(f" • {base.get('title')} (ID: {base.get('id')})") + + if bases: + base_id = bases[0]["id"] + print(f"\n Using base: {bases[0].get('title')} ({base_id})") + + # =================================================================== + # TABLE OPERATIONS + # =================================================================== + print("\n" + "=" * 60) + print("TABLE OPERATIONS") + print("=" * 60) + + # List tables + print("\n3. Listing tables...") + tables = meta_client.list_tables(base_id) + for table in tables: + print(f" • {table.get('title')} (ID: {table.get('id')})") + + # Create a new table + print("\n4. Creating a new table...") + table_data = { + "title": "Demo Table", + "table_name": "demo_table", + "columns": [ + {"title": "Title", "column_name": "title", "uidt": "SingleLineText"}, + { + "title": "Status", + "column_name": "status", + "uidt": "SingleSelect", + "dtxp": [ + {"title": "New", "color": "#3498db"}, + {"title": "In Progress", "color": "#f39c12"}, + {"title": "Done", "color": "#2ecc71"}, + ], + }, + ], + } + + new_table = meta_client.create_table(base_id, table_data) + table_id = new_table["id"] + print(f" āœ“ Created table: {new_table.get('title')} (ID: {table_id})") + + # Get table info + print("\n5. Getting table information...") + table_info = meta_client.get_table_info(table_id) + print(f" • Table: {table_info.get('title')}") + print(f" • Columns: {len(table_info.get('columns', []))}") + + # =================================================================== + # COLUMN OPERATIONS + # =================================================================== + print("\n" + "=" * 60) + print("COLUMN OPERATIONS") + print("=" * 60) + + columns_manager = NocoDBColumns(meta_client) + + # Add various column types + print("\n6. Adding columns...") + + # Text column + text_col = columns_manager.create_text_column( + table_id, title="Description", max_length=1000 + ) + print(f" āœ“ Added text column: {text_col.get('title')}") + + # Number column + number_col = columns_manager.create_number_column( + table_id, title="Priority", precision=3 + ) + print(f" āœ“ Added number column: {number_col.get('title')}") + + # Checkbox column + checkbox_col = columns_manager.create_checkbox_column( + table_id, title="Is Important", default_value=False + ) + print(f" āœ“ Added checkbox column: {checkbox_col.get('title')}") + + # Date column + date_col = columns_manager.create_date_column( + table_id, title="Due Date", date_format="YYYY-MM-DD" + ) + print(f" āœ“ Added date column: {date_col.get('title')}") + + # List all columns + print("\n7. Listing all columns...") + columns = columns_manager.get_columns(table_id) + for col in columns: + print(f" • {col.get('title')} ({col.get('uidt')})") + + # =================================================================== + # VIEW OPERATIONS + # =================================================================== + print("\n" + "=" * 60) + print("VIEW OPERATIONS") + print("=" * 60) + + views_manager = NocoDBViews(meta_client) + + # Create views + print("\n8. Creating views...") + + # Grid view + grid_view = views_manager.create_view(table_id, title="All Items", view_type="grid") + print(f" āœ“ Created grid view: {grid_view.get('title')}") + + # Gallery view + gallery_view = views_manager.create_view( + table_id, title="Gallery View", view_type="gallery" + ) + print(f" āœ“ Created gallery view: {gallery_view.get('title')}") + + # Form view + form_view = views_manager.create_view(table_id, title="Submit Form", view_type="form") + print(f" āœ“ Created form view: {form_view.get('title')}") + + # List all views + print("\n9. Listing all views...") + views = views_manager.get_views(table_id) + for view in views: + print(f" • {view.get('title')} ({view.get('type')})") + + # Add filter to a view (if we have the column IDs) + if columns: + status_column = next((c for c in columns if c.get("title") == "Status"), None) + if status_column and views: + print("\n10. Adding filter to grid view...") + views_manager.create_view_filter( + table_id, + grid_view["id"], + column_id=status_column["id"], + comparison_op="eq", + value="Done", + logical_op="and", + ) + print(" āœ“ Added filter to view") + + # =================================================================== + # CLEANUP + # =================================================================== + print("\n" + "=" * 60) + print("CLEANUP") + print("=" * 60) + + # Delete the demo table + print("\n11. Cleaning up (deleting demo table)...") + meta_client.delete_table(table_id) + print(" āœ“ Deleted demo table") + + else: + print("\n No bases found. Please create a base in NocoDB first.") + + except Exception as e: + print(f"āŒ Error: {e}") + finally: + meta_client.close() + print("\nāœ“ Meta client session closed") + + +if __name__ == "__main__": + main() diff --git a/examples/webhook_examples.py b/examples/webhook_examples.py new file mode 100644 index 0000000..5ca1791 --- /dev/null +++ b/examples/webhook_examples.py @@ -0,0 +1,263 @@ +""" +Webhook automation examples for NocoDB Simple Client. + +This example demonstrates how to create and manage webhooks for +automating actions based on database events. +""" + +from nocodb_simple_client import NocoDBMetaClient +from nocodb_simple_client.webhooks import NocoDBWebhooks + +# Configuration +NOCODB_BASE_URL = "https://your-nocodb-instance.com" +API_TOKEN = "your-api-token-here" +TABLE_ID = "your-table-id-here" + + +def main(): + """Demonstrate webhook automation.""" + + meta_client = NocoDBMetaClient(base_url=NOCODB_BASE_URL, api_token=API_TOKEN) + webhooks_manager = NocoDBWebhooks(meta_client) + + try: + print("=" * 60) + print("WEBHOOK AUTOMATION EXAMPLES") + print("=" * 60) + + # =================================================================== + # LIST EXISTING WEBHOOKS + # =================================================================== + print("\n1. Listing existing webhooks...") + webhooks = webhooks_manager.get_webhooks(TABLE_ID) + if webhooks: + for webhook in webhooks: + status = "Active" if webhook.get("active") else "Inactive" + print(f" • {webhook.get('title')} ({status})") + else: + print(" No webhooks configured yet") + + # =================================================================== + # URL WEBHOOK + # =================================================================== + print("\n2. Creating a URL webhook...") + url_webhook = webhooks_manager.create_webhook( + TABLE_ID, + title="API Notification", + event_type="after", # after the event + operation="insert", # on insert operation + url="https://api.example.com/notifications", + method="POST", + headers={"Authorization": "Bearer your-api-token", "Content-Type": "application/json"}, + body='{"event": "new_record", "name": "{{Name}}", "email": "{{Email}}", "id": "{{Id}}"}', + active=True, + ) + print(f" āœ“ Created URL webhook: {url_webhook.get('title')}") + url_webhook_id = url_webhook.get("id") + + # =================================================================== + # EMAIL WEBHOOK + # =================================================================== + print("\n3. Creating an email webhook...") + email_webhook = webhooks_manager.create_email_webhook( + TABLE_ID, + title="Email Alert", + event_type="after", + operation="update", + emails=["admin@example.com", "team@example.com"], + subject="Record Updated: {{Name}}", + body=""" + Record has been updated: + + ID: {{Id}} + Name: {{Name}} + Email: {{Email}} + Updated At: {{UpdatedAt}} + + Please review the changes. + """, + active=True, + ) + print(f" āœ“ Created email webhook: {email_webhook.get('title')}") + + # =================================================================== + # SLACK WEBHOOK + # =================================================================== + print("\n4. Creating a Slack webhook...") + slack_webhook = webhooks_manager.create_slack_webhook( + TABLE_ID, + title="Slack Notification", + event_type="after", + operation="insert", + webhook_url="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK", + message=":tada: New record created!\n*Name:* {{Name}}\n*Email:* {{Email}}", + active=True, + ) + print(f" āœ“ Created Slack webhook: {slack_webhook.get('title')}") + + # =================================================================== + # TEAMS WEBHOOK + # =================================================================== + print("\n5. Creating a Microsoft Teams webhook...") + teams_webhook = webhooks_manager.create_teams_webhook( + TABLE_ID, + title="Teams Notification", + event_type="after", + operation="delete", + webhook_url="https://outlook.office.com/webhook/YOUR_WEBHOOK_URL", + message="Record deleted: {{Name}} ({{Email}})", + active=True, + ) + print(f" āœ“ Created Teams webhook: {teams_webhook.get('title')}") + + # =================================================================== + # CONDITIONAL WEBHOOK + # =================================================================== + print("\n6. Creating a conditional webhook...") + # Webhook only triggers if specific conditions are met + conditional_webhook = webhooks_manager.create_webhook( + TABLE_ID, + title="VIP Customer Alert", + event_type="after", + operation="insert", + url="https://api.example.com/vip-alert", + method="POST", + body='{"customer": "{{Name}}", "tier": "{{CustomerTier}}"}', + condition={"column_name": "CustomerTier", "comparison_op": "eq", "value": "VIP"}, + active=True, + ) + print(f" āœ“ Created conditional webhook: {conditional_webhook.get('title')}") + + # =================================================================== + # TEST WEBHOOK + # =================================================================== + print("\n7. Testing a webhook...") + try: + test_result = webhooks_manager.test_webhook(TABLE_ID, url_webhook_id) + print(" āœ“ Webhook test completed") + print(f" Response: {test_result}") + except Exception as e: + print(f" ⚠ Webhook test failed (expected if endpoint doesn't exist): {e}") + + # =================================================================== + # UPDATE WEBHOOK + # =================================================================== + print("\n8. Updating a webhook...") + webhooks_manager.update_webhook( + TABLE_ID, + url_webhook_id, + title="Updated API Notification", + url="https://api.example.com/v2/notifications", + active=False, # Temporarily disable + ) + print(" āœ“ Updated webhook (deactivated)") + + # =================================================================== + # GET WEBHOOK LOGS + # =================================================================== + print("\n9. Getting webhook execution logs...") + try: + logs = webhooks_manager.get_webhook_logs(TABLE_ID, url_webhook_id, limit=5) + if logs: + print(f" Found {len(logs)} recent executions:") + for log in logs: + print(f" • {log.get('created_at')}: {log.get('status')}") + else: + print(" No execution logs yet") + except Exception as e: + print(f" ⚠ Could not retrieve logs: {e}") + + # =================================================================== + # TOGGLE WEBHOOK + # =================================================================== + print("\n10. Toggling webhook status...") + webhooks_manager.toggle_webhook(TABLE_ID, url_webhook_id) + print(" āœ“ Toggled webhook active status") + + # =================================================================== + # PRACTICAL EXAMPLES + # =================================================================== + print("\n" + "=" * 60) + print("PRACTICAL USE CASES") + print("=" * 60) + + print("\n11. Creating practical webhooks...") + + # Order notification + order_webhook = webhooks_manager.create_webhook( + TABLE_ID, + title="New Order Alert", + event_type="after", + operation="insert", + url="https://api.yourcompany.com/orders/notify", + method="POST", + headers={"X-API-Key": "your-api-key"}, + body='{"order_id": "{{Id}}", "customer": "{{CustomerName}}", "total": "{{OrderTotal}}"}', + active=True, + ) + print(" āœ“ Order notification webhook") + + # Status change webhook + status_webhook = webhooks_manager.create_webhook( + TABLE_ID, + title="Status Change Webhook", + event_type="after", + operation="update", + url="https://api.yourcompany.com/status-changed", + method="POST", + body='{"record_id": "{{Id}}", "old_status": "{{__old__.Status}}", "new_status": "{{Status}}"}', + active=True, + ) + print(" āœ“ Status change tracking webhook") + + # Backup webhook + backup_webhook = webhooks_manager.create_webhook( + TABLE_ID, + title="Backup Important Changes", + event_type="before", # before the change + operation="delete", + url="https://api.yourcompany.com/backup", + method="POST", + body='{"backup": {{__data__}}}', # Full record data + active=True, + ) + print(" āœ“ Backup webhook for deleted records") + + # =================================================================== + # CLEANUP + # =================================================================== + print("\n" + "=" * 60) + print("CLEANUP") + print("=" * 60) + + print("\n12. Cleaning up demo webhooks...") + + # List all webhooks we created + demo_webhooks = [ + url_webhook_id, + email_webhook.get("id"), + slack_webhook.get("id"), + teams_webhook.get("id"), + conditional_webhook.get("id"), + order_webhook.get("id"), + status_webhook.get("id"), + backup_webhook.get("id"), + ] + + for webhook_id in demo_webhooks: + if webhook_id: + try: + webhooks_manager.delete_webhook(TABLE_ID, webhook_id) + print(f" āœ“ Deleted webhook {webhook_id}") + except Exception as e: + print(f" ⚠ Could not delete webhook {webhook_id}: {e}") + + except Exception as e: + print(f"āŒ Error: {e}") + finally: + meta_client.close() + print("\nāœ“ Meta client session closed") + + +if __name__ == "__main__": + main()