From 74c65258eb160df34094d6ca0e8adfa752659531 Mon Sep 17 00:00:00 2001 From: Parman Date: Sun, 31 Aug 2025 14:00:21 +0330 Subject: [PATCH 1/2] fix: Clean up examples and fix implementation issues --- README.md | 2 +- examples/basic_usage.py | 145 --------- examples/contact_groups_example.py | 138 ++++---- examples/contacts_example.py | 220 +++++++------ examples/email_example.py | 58 ++-- examples/omni_channel_example.py | 72 ++--- examples/rcs_example.py | 202 +++++++----- examples/sms_example.py | 172 +++------- examples/whatsapp_example.py | 294 ++++++++---------- pyproject.toml | 2 +- setup-dev-env.ps1 | 91 ------ src/devo_global_comms_python/__init__.py | 2 +- src/devo_global_comms_python/exceptions.py | 10 - src/devo_global_comms_python/models/email.py | 20 +- src/devo_global_comms_python/models/sms.py | 170 +++++++--- .../models/whatsapp.py | 29 +- src/devo_global_comms_python/resources/rcs.py | 26 +- src/devo_global_comms_python/resources/sms.py | 14 +- src/devo_global_comms_python/services.py | 12 - 19 files changed, 720 insertions(+), 959 deletions(-) delete mode 100644 examples/basic_usage.py delete mode 100644 setup-dev-env.ps1 diff --git a/README.md b/README.md index 87c5cdc..d6940bc 100644 --- a/README.md +++ b/README.md @@ -386,7 +386,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - **Documentation**: [https://devo-global-comms-python.readthedocs.io](https://devo-global-comms-python.readthedocs.io) - **Issues**: [GitHub Issues](https://github.com/devotel/devo-global-comms-python/issues) -- **Email**: [support@devo.com](mailto:support@devo.com) +- **Email**: [support@devotel.io](mailto:support@devotel.io) ## Changelog diff --git a/examples/basic_usage.py b/examples/basic_usage.py deleted file mode 100644 index b040652..0000000 --- a/examples/basic_usage.py +++ /dev/null @@ -1,145 +0,0 @@ -import os -import subprocess -import sys - -from devo_global_comms_python import DevoClient, DevoException - - -def main(): - print("πŸš€ Devo Global Communications SDK") - print("=" * 60) - - # Check if API key is set - api_key = os.getenv("DEVO_API_KEY") - if not api_key: - print("❌ Please set DEVO_API_KEY environment variable") - print(" You can get your API key from the Devo dashboard") - return - - # Initialize the client - try: - client = DevoClient(api_key=api_key) - print("βœ… Devo SDK Client initialized successfully") - except Exception as e: - print(f"❌ Failed to initialize client: {e}") - return - - print("\nπŸ“‹ Available Resources:") - print("-" * 30) - - # Check available resources - resources = [] - if hasattr(client, "sms"): - resources.append(("πŸ“± SMS", "Implemented", "sms_example.py")) - if hasattr(client, "email"): - resources.append(("πŸ“§ Email", "Placeholder", "email_example.py")) - if hasattr(client, "whatsapp"): - resources.append(("πŸ’¬ WhatsApp", "Placeholder", "whatsapp_example.py")) - if hasattr(client, "contacts"): - resources.append(("πŸ‘₯ Contacts", "Placeholder", "contacts_example.py")) - if hasattr(client, "services") and hasattr(client.services, "contact_groups"): - resources.append(("πŸ—‚οΈ Contact Groups", "Implemented (Services)", "contact_groups_example.py")) - if hasattr(client, "rcs"): - resources.append(("🎴 RCS", "Placeholder", "rcs_example.py")) - if hasattr(client, "messages"): - resources.append(("πŸ“¬ Messages", "Implemented", "omni_channel_example.py")) - - for resource, status, example_file in resources: - print(f" {resource:<18} - {status:<20} -> {example_file}") - - # Services namespace information - if hasattr(client, "services"): - print("\n🏒 Services Namespace:") - print("-" * 30) - print(" πŸ—‚οΈ Contact Groups - client.services.contact_groups") - print(" πŸ‘₯ Contacts (Future) - client.services.contacts") - print(" πŸ“Š Analytics (Future) - client.services.analytics") - - # Quick SMS test if available - if hasattr(client, "sms"): - print("\nπŸ§ͺ Quick SMS Test:") - print("-" * 30) - try: - # Try to get senders as a connectivity test - senders = client.sms.get_senders() - print(f"βœ… SMS connection successful - {len(senders.senders)} senders available") - - if senders.senders: - print(" Sample senders:") - for i, sender in enumerate(senders.senders[:3], 1): - print(f" {i}. {sender.phone_number} ({sender.type})") - if len(senders.senders) > 3: - print(f" ... and {len(senders.senders) - 3} more") - - except DevoException as e: - print(f"⚠️ SMS connection test failed: {e}") - - # Show example usage - print("\nπŸ’‘ Getting Started:") - print("-" * 30) - print("1. Run individual resource examples:") - print(" python examples/sms_example.py # Complete SMS functionality") - print(" python examples/contact_groups_example.py # Complete Contact Groups functionality") - print(" python examples/omni_channel_example.py # Complete Omni-channel messaging") - print(" python examples/email_example.py # Email examples (placeholder)") - print(" python examples/whatsapp_example.py # WhatsApp examples (placeholder)") - print(" python examples/contacts_example.py # Contact management (placeholder)") - print(" python examples/rcs_example.py # RCS examples (placeholder)") - print() - print("2. Quick SMS example:") - print(" from devo_global_comms_python import DevoClient") - print(" client = DevoClient(api_key='your_api_key')") - print(" response = client.sms.send_sms(") - print(" recipient='+1234567890',") - print(" message='Hello from Devo!',") - print(" sender='your_sender_id'") - print(" )") - - # Interactive menu - print("\n🎯 Interactive Examples:") - print("-" * 30) - print("Would you like to run a specific example?") - print("1. SMS Example (full functionality)") - print("2. Contact Groups Example (full functionality)") - print("3. Omni-channel Messaging Example (full functionality)") - print("4. Email Example (placeholder)") - print("5. WhatsApp Example (placeholder)") - print("6. Contacts Example (placeholder)") - print("7. RCS Example (placeholder)") - print("0. Exit") - - try: - choice = input("\nEnter your choice (0-7): ").strip() - example_files = { - "1": "sms_example.py", - "2": "contact_groups_example.py", - "3": "omni_channel_example.py", - "4": "email_example.py", - "5": "whatsapp_example.py", - "6": "contacts_example.py", - "7": "rcs_example.py", - } - - if choice in example_files: - example_file = example_files[choice] - example_path = os.path.join(os.path.dirname(__file__), example_file) - - if os.path.exists(example_path): - print(f"\nπŸš€ Running {example_file}...") - print("=" * 60) - subprocess.run([sys.executable, example_path], check=True) - else: - print(f"❌ Example file {example_file} not found") - elif choice == "0": - print("πŸ‘‹ Goodbye!") - else: - print("❌ Invalid choice") - - except KeyboardInterrupt: - print("\nπŸ‘‹ Goodbye!") - except Exception as e: - print(f"❌ Error running example: {e}") - - -if __name__ == "__main__": - main() diff --git a/examples/contact_groups_example.py b/examples/contact_groups_example.py index 96d5e84..6b71d20 100644 --- a/examples/contact_groups_example.py +++ b/examples/contact_groups_example.py @@ -13,26 +13,26 @@ def main(): # Initialize the client api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Error: DEVO_API_KEY environment variable not set") + print("Error: DEVO_API_KEY environment variable not set") return client = DevoClient(api_key=api_key) - print("πŸ—‚οΈ Devo Global Communications - Contact Groups Management Example") + print("Devo Global Communications - Contact Groups Management Example") print("=" * 75) - print("πŸ“‹ Using services namespace: client.services.contact_groups") + print("Using services namespace: client.services.contact_groups") print() # Example 1: List existing contact groups print("\nπŸ“‹ Listing existing contact groups...") try: groups_list = client.services.contact_groups.list(page=1, limit=5) - print(f"βœ… Found {groups_list.total} total groups") + print(f"Found {groups_list.total} total groups") print(f" Page: {groups_list.page}/{groups_list.total_pages}") print(f" Showing: {len(groups_list.groups)} groups") for i, group in enumerate(groups_list.groups, 1): - print(f" {i}. πŸ“ {group.name}") + print(f" {i}. {group.name}") print(f" ID: {group.id}") if group.description: print(f" Description: {group.description}") @@ -41,10 +41,10 @@ def main(): print(f" Created: {group.created_at}") except Exception as e: - print(f"❌ Error listing groups: {str(e)}") + print(f"Error listing groups: {str(e)}") # Example 2: Create a new contact group - print("\nβž• Creating a new contact group...") + print("\n Creating a new contact group...") try: new_group_data = CreateContactsGroupDto( name=f"API Demo Group {datetime.now().strftime('%Y%m%d_%H%M%S')}", @@ -58,23 +58,23 @@ def main(): ) new_group = client.services.contact_groups.create(new_group_data) - print("βœ… Contact group created successfully!") - print(f" πŸ“ Name: {new_group.name}") - print(f" πŸ†” ID: {new_group.id}") - print(f" πŸ“ Description: {new_group.description}") - print(f" πŸ‘₯ Contacts: {new_group.contacts_count or 0}") + print("Contact group created successfully!") + print(f" Name: {new_group.name}") + print(f" ID: {new_group.id}") + print(f" Description: {new_group.description}") + print(f" Contacts: {new_group.contacts_count or 0}") if new_group.created_at: print(f" πŸ“… Created: {new_group.created_at}") created_group_id = new_group.id except Exception as e: - print(f"❌ Error creating group: {str(e)}") + print(f"Error creating group: {str(e)}") created_group_id = None # Example 3: Update the created group if created_group_id: - print(f"\n✏️ Updating contact group {created_group_id}...") + print(f"\nUpdating contact group {created_group_id}...") try: update_data = UpdateContactsGroupDto( name=f"Updated API Demo Group {datetime.now().strftime('%H%M%S')}", @@ -83,45 +83,45 @@ def main(): ) updated_group = client.services.contact_groups.update(created_group_id, update_data) - print("βœ… Contact group updated successfully!") - print(f" πŸ“ New name: {updated_group.name}") - print(f" πŸ“ New description: {updated_group.description}") + print("Contact group updated successfully!") + print(f" New name: {updated_group.name}") + print(f" New description: {updated_group.description}") if updated_group.updated_at: - print(f" πŸ“… Updated: {updated_group.updated_at}") + print(f" Updated: {updated_group.updated_at}") except Exception as e: - print(f"❌ Error updating group: {str(e)}") + print(f"Error updating group: {str(e)}") # Example 4: Get specific group by ID if created_group_id: print(f"\nπŸ” Retrieving specific group {created_group_id}...") try: specific_group = client.services.contact_groups.get_by_id(created_group_id) - print("βœ… Group retrieved successfully!") - print(f" πŸ“ Name: {specific_group.name}") - print(f" πŸ“ Description: {specific_group.description}") - print(f" πŸ‘₯ Contacts: {specific_group.contacts_count or 0}") - print(f" πŸ‘€ Owner: {specific_group.user_id}") + print("Group retrieved successfully!") + print(f" Name: {specific_group.name}") + print(f" Description: {specific_group.description}") + print(f" Contacts: {specific_group.contacts_count or 0}") + print(f" Owner: {specific_group.user_id}") except Exception as e: - print(f"❌ Error retrieving group: {str(e)}") + print(f"Error retrieving group: {str(e)}") # Example 5: Search contact groups - print("\nπŸ”Ž Searching contact groups...") + print("\n Searching contact groups...") try: search_results = client.services.contact_groups.search( query="demo", fields=["name", "description"], page=1, limit=10 ) - print(f"βœ… Search completed! Found {search_results.total} matching groups") + print(f"Search completed! Found {search_results.total} matching groups") for i, group in enumerate(search_results.groups, 1): - print(f" {i}. πŸ“ {group.name}") - print(f" πŸ†” ID: {group.id}") + print(f" {i}.{group.name}") + print(f" ID: {group.id}") if group.description: print(f" πŸ“ Description: {group.description}") except Exception as e: - print(f"❌ Error searching groups: {str(e)}") + print(f"Error searching groups: {str(e)}") # Example 6: Advanced listing with filters print("\nπŸ”§ Advanced group listing with filters...") @@ -129,18 +129,18 @@ def main(): filtered_groups = client.services.contact_groups.list( page=1, limit=3, search="demo", search_fields=["name", "description"] ) - print("βœ… Filtered listing completed!") + print("Filtered listing completed!") print(f" Total groups matching 'demo': {filtered_groups.total}") print(f" Showing page {filtered_groups.page} of {filtered_groups.total_pages}") for group in filtered_groups.groups: - print(f" πŸ“ {group.name} (ID: {group.id})") + print(f" {group.name} (ID: {group.id})") except Exception as e: - print(f"❌ Error with filtered listing: {str(e)}") + print(f"Error with filtered listing: {str(e)}") # Example 7: Bulk operations demonstration - print("\nπŸ“¦ Bulk operations example...") + print("\n Bulk operations example...") # First, let's create a few more groups for bulk operations bulk_group_ids = [] @@ -155,27 +155,27 @@ def main(): bulk_group = client.services.contact_groups.create(bulk_group_data) bulk_group_ids.append(bulk_group.id) - print(f" βœ… Created bulk group {i+1}: {bulk_group.name}") + print(f" Created bulk group {i+1}: {bulk_group.name}") print(f"πŸ“Š Created {len(bulk_group_ids)} groups for bulk demo") except Exception as e: - print(f"❌ Error creating bulk groups: {str(e)}") + print(f"Error creating bulk groups: {str(e)}") # Example 8: Individual group deletion if created_group_id: - print(f"\nπŸ—‘οΈ Deleting individual group {created_group_id}...") + print(f"\n Deleting individual group {created_group_id}...") try: deleted_group = client.services.contact_groups.delete_by_id(created_group_id, approve="yes") - print("βœ… Individual group deleted successfully!") - print(f" πŸ“ Deleted group: {deleted_group.name}") + print(" Individual group deleted successfully!") + print(f" Deleted group: {deleted_group.name}") except Exception as e: print(f"❌ Error deleting individual group: {str(e)}") # Example 9: Bulk deletion if bulk_group_ids: - print(f"\nπŸ—‘οΈ Performing bulk deletion of {len(bulk_group_ids)} groups...") + print(f"\n Performing bulk deletion of {len(bulk_group_ids)} groups...") try: # Create backup group first backup_group_data = CreateContactsGroupDto( @@ -187,38 +187,26 @@ def main(): bulk_delete_data = DeleteContactsGroupsDto(group_ids=bulk_group_ids, transfer_contacts_to=backup_group.id) bulk_delete_result = client.services.contact_groups.delete_bulk(bulk_delete_data, approve="yes") - print("βœ… Bulk deletion completed successfully!") - print(f" πŸ“Š Operation result: {bulk_delete_result.name}") + print(" Bulk deletion completed successfully!") + print(f" Operation result: {bulk_delete_result.name}") # Clean up backup group client.services.contact_groups.delete_by_id(backup_group.id, approve="yes") - print(" 🧹 Cleaned up backup group") + print(" Cleaned up backup group") except Exception as e: - print(f"❌ Error with bulk deletion: {str(e)}") + print(f" Error with bulk deletion: {str(e)}") # Example 10: Error handling demonstration - print("\n⚠️ Error handling demonstration...") + print("\n Error handling demonstration...") try: # Try to get a non-existent group client.services.contact_groups.get_by_id("non_existent_group_id") except Exception as e: - print(f"βœ… Properly handled expected error: {type(e).__name__}") + print(f" Properly handled expected error: {type(e).__name__}") print(f" Error message: {str(e)}") - print("\n" + "=" * 75) - print("🎯 Contact Groups management demo completed!") - print("\nKey Features Demonstrated:") - print("β€’ βœ… List groups with pagination and search") - print("β€’ βœ… Create new contact groups with metadata") - print("β€’ βœ… Update existing groups") - print("β€’ βœ… Retrieve specific groups by ID") - print("β€’ βœ… Search groups with field filtering") - print("β€’ βœ… Individual group deletion") - print("β€’ βœ… Bulk group deletion with contact transfer") - print("β€’ βœ… Comprehensive error handling") - def contact_group_management_workflow(): """ @@ -229,12 +217,12 @@ def contact_group_management_workflow(): """ print("\n" + "=" * 60) - print("πŸ“Š Contact Group Management Workflow Example") + print("Contact Group Management Workflow Example") print("=" * 60) api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Error: DEVO_API_KEY environment variable not set") + print("Error: DEVO_API_KEY environment variable not set") return client = DevoClient(api_key=api_key) @@ -266,7 +254,7 @@ def contact_group_management_workflow(): created_groups = [] # Create business contact groups - print("\n🏒 Creating business contact groups...") + print("\n Creating business contact groups...") for group_type in group_types: try: group_data = CreateContactsGroupDto( @@ -275,22 +263,22 @@ def contact_group_management_workflow(): group = client.services.contact_groups.create(group_data) created_groups.append(group) - print(f" βœ… Created: {group.name}") + print(f" Created: {group.name}") except Exception as e: - print(f" ❌ Error creating {group_type['name']}: {str(e)}") + print(f" Error creating {group_type['name']}: {str(e)}") # Demonstrate group analytics - print("\nπŸ“Š Group Analytics:") + print("\n Group Analytics:") print(f" Total groups created: {len(created_groups)}") for group in created_groups: - print(f" πŸ“ {group.name}") - print(f" πŸ“ˆ Current contacts: {group.contacts_count or 0}") - print(f" 🏷️ Category: {group.metadata.get('priority', 'standard')}") + print(f" {group.name}") + print(f" Current contacts: {group.contacts_count or 0}") + print(f" Category: {group.metadata.get('priority', 'standard')}") # Simulate group reorganization - print("\nπŸ”„ Reorganizing groups...") + print("\n Reorganizing groups...") # Update VIP group to include more metadata vip_group = next((g for g in created_groups if "VIP" in g.name), None) @@ -307,13 +295,13 @@ def contact_group_management_workflow(): ) client.services.contact_groups.update(vip_group.id, update_data) - print(" βœ… Updated VIP group with enhanced metadata") + print(" Updated VIP group with enhanced metadata") except Exception as e: - print(f" ❌ Error updating VIP group: {str(e)}") + print(f" Error updating VIP group: {str(e)}") # Clean up demonstration groups - print("\n🧹 Cleaning up demonstration groups...") + print("\n Cleaning up demonstration groups...") if created_groups: try: group_ids = [group.id for group in created_groups] @@ -328,16 +316,16 @@ def contact_group_management_workflow(): delete_data = DeleteContactsGroupsDto(group_ids=group_ids, transfer_contacts_to=temp_group.id) client.services.contact_groups.delete_bulk(delete_data, approve="yes") - print(f" βœ… Bulk deleted {len(group_ids)} demonstration groups") + print(f" Bulk deleted {len(group_ids)} demonstration groups") # Delete temporary group client.services.contact_groups.delete_by_id(temp_group.id, approve="yes") - print(" βœ… Cleaned up temporary archive group") + print(" Cleaned up temporary archive group") except Exception as e: - print(f" ❌ Error during cleanup: {str(e)}") + print(f" Error during cleanup: {str(e)}") - print("\n🎯 Workflow demonstration completed!") + print("\n Workflow demonstration completed!") if __name__ == "__main__": diff --git a/examples/contacts_example.py b/examples/contacts_example.py index 00630a6..a12ce41 100644 --- a/examples/contacts_example.py +++ b/examples/contacts_example.py @@ -1,135 +1,149 @@ -#!/usr/bin/env python3 import os from devo_global_comms_python import DevoClient from devo_global_comms_python.exceptions import DevoException +from devo_global_comms_python.models.contacts import ( + AssignToContactsGroupDto, + CreateContactDto, + CreateCustomFieldDto, + DeleteContactsDto, + UpdateContactDto, + UpdateCustomFieldDto, +) def main(): api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Please set DEVO_API_KEY environment variable") + print("Please set DEVO_API_KEY environment variable") return - print("βœ… Devo Contacts Client initialized successfully") + client = DevoClient(api_key=api_key) + print("Devo Global Communications - Contacts Management Example") print("=" * 60) try: - # Example 1: Create a contact - print("πŸ‘€ CREATE CONTACT EXAMPLE") + # Example 1: List existing contacts + print("1. LIST CONTACTS") print("-" * 30) + contacts_response = client.services.contacts.list(page=1, limit=5) + print(f"Found {contacts_response.total} total contacts") + print(f"Page: {contacts_response.page}/{contacts_response.total_pages}") + print(f"Showing: {len(contacts_response.contacts)} contacts") + + for i, contact in enumerate(contacts_response.contacts, 1): + name = f"{contact.first_name or ''} {contact.last_name or ''}".strip() + print(f" {i}. {name or 'Unnamed Contact'}") + print(f" ID: {contact.id}") + if contact.email: + print(f" Email: {contact.email}") + if contact.phone_number: + print(f" Phone: {contact.phone_number}") + if contact.company: + print(f" Company: {contact.company}") - print("πŸ“ Creating a new contact...") - print("⚠️ This is a placeholder implementation.") - print(" Update this example when Contacts API is implemented.") - - # Placeholder contact creation - update when implementing Contacts resource - print(" ```python") - print(" contact = client.contacts.create(") - print(" phone_number='+1234567890',") - print(" email='john.doe@example.com',") - print(" first_name='John',") - print(" last_name='Doe',") - print(" company='Acme Corp',") - print(" metadata={'source': 'sdk_example', 'campaign': 'Q1_2025'}") - print(" )") - print(" print(f'Contact created! ID: {contact.id}')") - print(" ```") - - # Example 2: Get contact by ID - print("\nπŸ” GET CONTACT EXAMPLE") + # Example 2: Create a new contact + print("\n2. CREATE CONTACT") + print("-" * 30) + create_data = CreateContactDto( + first_name="John", + last_name="Doe", + email="john.doe@example.com", + phone_number="+1234567890", + company="Acme Corp", + tags=["customer", "test"], + metadata={"source": "sdk_example", "campaign": "Q1_2025"}, + ) + + new_contact = client.services.contacts.create(create_data) + print("Contact created successfully!") + print(f"ID: {new_contact.id}") + print(f"Name: {new_contact.first_name} {new_contact.last_name}") + print(f"Email: {new_contact.email}") + print(f"Phone: {new_contact.phone_number}") + + # Example 3: Update the contact + print("\n3. UPDATE CONTACT") + print("-" * 30) + update_data = UpdateContactDto( + company="Acme Corporation Ltd", + tags=["customer", "vip", "updated"], + metadata={"source": "sdk_example", "updated": "2025-08-31"}, + ) + + updated_contact = client.services.contacts.update(new_contact.id, update_data) + print("Contact updated successfully!") + print(f"ID: {updated_contact.id}") + print(f"Updated company: {updated_contact.company}") + print(f"Updated tags: {updated_contact.tags}") + + # Example 4: List contacts with filtering + print("\n4. LIST CONTACTS WITH FILTERING") + print("-" * 30) + filtered_contacts = client.services.contacts.list( + page=1, limit=10, search="Acme", is_email_subscribed=True, tags=["customer"] + ) + print(f"Found {filtered_contacts.total} contacts matching filters") + for contact in filtered_contacts.contacts: + name = f"{contact.first_name or ''} {contact.last_name or ''}".strip() + print(f" - {name or 'Unnamed'} ({contact.company or 'No Company'})") + + # Example 5: Custom fields management + print("\n5. CUSTOM FIELDS MANAGEMENT") print("-" * 30) - print("πŸ“– Retrieving contact by ID...") - print(" ```python") - print(" contact = client.contacts.get('contact_id_123')") - print(" print(f'Contact: {contact.first_name} {contact.last_name}')") - print(" print(f'Phone: {contact.phone_number}')") - print(" print(f'Email: {contact.email}')") - print(" ```") + # List existing custom fields + custom_fields = client.services.contacts.list_custom_fields(page=1, limit=5) + print(f"Found {custom_fields.total} custom fields") - # Example 3: List contacts - print("\nπŸ“‹ LIST CONTACTS EXAMPLE") - print("-" * 30) + # Create a new custom field + field_data = CreateCustomFieldDto( + name="Department", field_type="text", description="Employee department", is_required=False + ) - print("πŸ“‹ Listing contacts...") - print(" ```python") - print(" contacts = client.contacts.list(") - print(" limit=10,") - print(" filter_by_company='Acme Corp'") - print(" )") - print(" print(f'Found {len(contacts)} contacts:')") - print(" for contact in contacts:") - print(" print(f' - {contact.first_name} {contact.last_name}')") - print(" ```") - - # Example 4: Update contact - print("\n✏️ UPDATE CONTACT EXAMPLE") - print("-" * 30) + new_field = client.services.contacts.create_custom_field(field_data) + print(f"Custom field created: {new_field.name} (ID: {new_field.id})") + + # Update the custom field + update_field_data = UpdateCustomFieldDto(description="Employee department (updated)", is_required=True) - print("✏️ Updating contact information...") - print(" ```python") - print(" updated_contact = client.contacts.update(") - print(" contact_id='contact_id_123',") - print(" company='Acme Corporation',") - print(" metadata={'source': 'sdk_example', 'updated': '2025-08-28'}") - print(" )") - print(" print(f'Contact updated! Company: {updated_contact.company}')") - print(" ```") - - # Example 5: Delete contact - print("\nπŸ—‘οΈ DELETE CONTACT EXAMPLE") + client.services.contacts.update_custom_field(new_field.id, update_field_data) + print("Custom field updated successfully") + + # Example 6: Contact group assignment + print("\n6. CONTACT GROUP ASSIGNMENT") print("-" * 30) - print("πŸ—‘οΈ Deleting contact...") - print(" ```python") - print(" client.contacts.delete('contact_id_123')") - print(" print('Contact deleted successfully!')") - print(" ```") + # List contact groups first + contact_groups = client.services.contact_groups.list(page=1, limit=5) + if contact_groups.groups: + first_group = contact_groups.groups[0] + print(f"Found contact group: {first_group.name} (ID: {first_group.id})") - except DevoException as e: - print(f"❌ Contacts operation failed: {e}") + # Assign contact to group + assignment_data = AssignToContactsGroupDto(contact_ids=[new_contact.id], contacts_group_id=first_group.id) - print("\n" + "=" * 60) - print("πŸ“Š CONTACTS EXAMPLE SUMMARY") - print("-" * 30) - print("⚠️ This is a placeholder example for Contacts functionality.") - client = DevoClient(api_key=api_key) + client.services.contacts.assign_to_group(assignment_data) + print(f"Contact assigned to group: {first_group.name}") - print("οΏ½ Devo Global Communications - Contacts Management Example") - print("=" * 70) - print("πŸ“‹ Using services namespace: client.services.contacts") - print() + # Unassign contact from group + client.services.contacts.unassign_from_group(assignment_data) + print(f"Contact unassigned from group: {first_group.name}") + else: + print("No contact groups available for assignment demo") - # Example 1: List existing contacts - print("\nπŸ“‹ Listing existing contacts...") - try: - contacts_list = client.services.contacts.list(page=1, limit=5) - print(f"βœ… Found {contacts_list.total} total contacts") - print(f" Page: {contacts_list.page}/{contacts_list.total_pages}") - print(f" Showing: {len(contacts_list.contacts)} contacts") - - for i, contact in enumerate(contacts_list.contacts, 1): - print(f" {i}. πŸ‘€ {contact.first_name or ''} {contact.last_name or ''}".strip()) - print(f" ID: {contact.id}") - if contact.email: - print(f" πŸ“§ Email: {contact.email}") - if contact.phone_number: - print(f" πŸ“± Phone: {contact.phone_number}") - if contact.created_at: - print(f" πŸ“… Created: {contact.created_at}") + # Example 7: Delete the test contact + print("\n7. DELETE CONTACT") + print("-" * 30) + delete_data = DeleteContactsDto(contact_ids=[new_contact.id]) + client.services.contacts.delete_bulk(delete_data) + print(f"Contact deleted successfully (ID: {new_contact.id})") + + except DevoException as e: + print(f"Contacts operation failed: {e}") except Exception as e: - print(f"❌ Error listing contacts: {str(e)}") - - print("\n🎯 Contacts management demo completed!") - print("\nKey Features Available:") - print("β€’ βœ… List contacts with advanced filtering") - print("β€’ βœ… Create and update contacts") - print("β€’ βœ… Contact group assignment/unassignment") - print("β€’ βœ… Custom field management") - print("β€’ βœ… CSV import functionality") - print("β€’ βœ… Bulk operations") + print(f"Unexpected error: {e}") if __name__ == "__main__": diff --git a/examples/email_example.py b/examples/email_example.py index 5105dee..67c2567 100644 --- a/examples/email_example.py +++ b/examples/email_example.py @@ -1,21 +1,21 @@ import os -from devo_global_comms_python import DevoCommsClient, DevoException +from devo_global_comms_python import DevoClient, DevoException def main(): api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Please set DEVO_API_KEY environment variable") + print("Please set DEVO_API_KEY environment variable") return - client = DevoCommsClient(api_key=api_key) - print("βœ… Devo Email Client initialized successfully") + client = DevoClient(api_key=api_key) + print("Devo Email Client initialized successfully") print("=" * 60) try: # Example: Send an email using the Email API - print("πŸ“§ EMAIL SEND EXAMPLE") + print("EMAIL SEND EXAMPLE") print("-" * 30) print("πŸ“€ Sending email...") @@ -26,21 +26,21 @@ def main(): recipient="recipient@example.com", ) - print("βœ… Email sent successfully!") - print(f" πŸ“§ Message ID: {email_response.message_id}") - print(f" πŸ“¦ Bulk Email ID: {email_response.bulk_email_id}") - print(f" πŸ“ Subject: {email_response.subject}") - print(f" πŸ“Š Status: {email_response.status}") - print(f" πŸ’¬ Message: {email_response.message}") - print(f" πŸ• Timestamp: {email_response.timestamp}") - print(f" βœ… Success: {email_response.success}") + print("Email sent successfully!") + print(f" Message ID: {email_response.message_id}") + print(f" Bulk Email ID: {email_response.bulk_email_id}") + print(f" Subject: {email_response.subject}") + print(f" Status: {email_response.status}") + print(f" Message: {email_response.message}") + print(f" Timestamp: {email_response.timestamp}") + print(f" Success: {email_response.success}") # Example with different content - print("\nπŸ“§ SENDING EMAIL WITH RICH CONTENT") + print("\nSENDING EMAIL WITH RICH CONTENT") print("-" * 40) rich_email_response = client.email.send_email( - subject="πŸŽ‰ Welcome to Devo Communications!", + subject="Welcome to Devo Communications!", body=( "Dear valued customer,\n\n" "Welcome to our service! We're excited to have you on board.\n\n" @@ -50,31 +50,15 @@ def main(): recipient="newcustomer@example.com", ) - print("βœ… Rich content email sent!") - print(f" πŸ“§ Message ID: {rich_email_response.message_id}") - print(f" πŸ“Š Status: {rich_email_response.status}") - print(f" βœ… Success: {rich_email_response.success}") + print("Rich content email sent!") + print(f" Message ID: {rich_email_response.message_id}") + print(f" Status: {rich_email_response.status}") + print(f" Success: {rich_email_response.success}") except DevoException as e: - print(f"❌ Email operation failed: {e}") + print(f"Email operation failed: {e}") except Exception as e: - print(f"❌ Unexpected error: {e}") - - print("\n" + "=" * 60) - print("πŸ“Š EMAIL EXAMPLE SUMMARY") - print("-" * 30) - print("βœ… Email API implementation complete!") - print("πŸ“€ Successfully demonstrated:") - print(" β€’ Basic email sending") - print(" β€’ Email with rich content and emojis") - print(" β€’ Response parsing and status checking") - print(" β€’ Error handling") - print("\nπŸ’‘ Features available:") - print(" β€’ Subject and body content") - print(" β€’ Sender and recipient validation") - print(" β€’ Message tracking with unique IDs") - print(" β€’ Status monitoring") - print(" β€’ Timestamp tracking") + print(f"Unexpected error: {e}") if __name__ == "__main__": diff --git a/examples/omni_channel_example.py b/examples/omni_channel_example.py index 23dbaa8..1e4a61b 100644 --- a/examples/omni_channel_example.py +++ b/examples/omni_channel_example.py @@ -16,38 +16,38 @@ def main(): # Initialize the client api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Error: DEVO_API_KEY environment variable not set") + print("Error: DEVO_API_KEY environment variable not set") return client = DevoClient(api_key=api_key) - print("πŸš€ Devo Global Communications - Omni-channel Messaging Example") + print("Devo Global Communications - Omni-channel Messaging Example") print("=" * 70) # Example 1: Send SMS Message - print("\nπŸ“± Sending SMS Message...") + print("\nSending SMS Message...") try: sms_message = SendMessageDto( channel="sms", to="+1234567890", **{"from": "+0987654321"}, # Use dict unpacking for 'from' field - payload={"text": "Hello from Devo! This is an SMS message sent via omni-channel API."}, + payload={"text": "Hello from Devo! This is an SMS message."}, callback_url="https://example.com/sms-webhook", metadata={"campaign": "omni-demo", "type": "sms"}, ) sms_result = client.messages.send(sms_message) - print("βœ… SMS sent successfully!") + print("SMS sent successfully!") print(f" Message ID: {sms_result.id}") print(f" Status: {sms_result.status}") print(f" Channel: {sms_result.channel}") print(f" Created: {sms_result.created_at}") except Exception as e: - print(f"❌ SMS Error: {str(e)}") + print(f"SMS Error: {str(e)}") # Example 2: Send Email Message - print("\nπŸ“§ Sending Email Message...") + print("\nSending Email Message...") try: email_message = SendMessageDto( channel="email", @@ -84,17 +84,17 @@ def main(): ) email_result = client.messages.send(email_message) - print("βœ… Email sent successfully!") + print("Email sent successfully!") print(f" Message ID: {email_result.id}") print(f" Status: {email_result.status}") print(f" Channel: {email_result.channel}") print(f" Subject: {email_result.content.get('subject', 'N/A')}") except Exception as e: - print(f"❌ Email Error: {str(e)}") + print(f"Email Error: {str(e)}") # Example 3: Send WhatsApp Message - print("\nπŸ’¬ Sending WhatsApp Message...") + print("\nSending WhatsApp Message...") try: whatsapp_message = SendMessageDto( channel="whatsapp", @@ -103,7 +103,7 @@ def main(): "type": "text", "text": { "body": ( - "πŸŽ‰ Hello from Devo! This WhatsApp message was sent " + "Hello from Devo! This WhatsApp message was sent " "using our omni-channel API. Pretty cool, right?" ) }, @@ -113,16 +113,16 @@ def main(): ) whatsapp_result = client.messages.send(whatsapp_message) - print("βœ… WhatsApp message sent successfully!") + print("WhatsApp message sent successfully!") print(f" Message ID: {whatsapp_result.id}") print(f" Status: {whatsapp_result.status}") print(f" Channel: {whatsapp_result.channel}") except Exception as e: - print(f"❌ WhatsApp Error: {str(e)}") + print(f"WhatsApp Error: {str(e)}") # Example 4: Send WhatsApp Template Message - print("\nπŸ“‹ Sending WhatsApp Template Message...") + print("\nSending WhatsApp Template Message...") try: whatsapp_template = SendMessageDto( channel="whatsapp", @@ -150,15 +150,15 @@ def main(): ) template_result = client.messages.send(whatsapp_template) - print("βœ… WhatsApp template sent successfully!") + print("WhatsApp template sent successfully!") print(f" Message ID: {template_result.id}") print(f" Status: {template_result.status}") except Exception as e: - print(f"❌ WhatsApp Template Error: {str(e)}") + print(f"WhatsApp Template Error: {str(e)}") # Example 5: Send RCS Message - print("\nπŸ’Ž Sending RCS Message...") + print("\nSending RCS Message...") try: rcs_message = SendMessageDto( channel="rcs", @@ -190,16 +190,16 @@ def main(): ) rcs_result = client.messages.send(rcs_message) - print("βœ… RCS message sent successfully!") + print("RCS message sent successfully!") print(f" Message ID: {rcs_result.id}") print(f" Status: {rcs_result.status}") print(f" Channel: {rcs_result.channel}") except Exception as e: - print(f"❌ RCS Error: {str(e)}") + print(f"RCS Error: {str(e)}") # Example 6: Send RCS Rich Card - print("\n🎴 Sending RCS Rich Card...") + print("\nSending RCS Rich Card...") try: rcs_rich_card = SendMessageDto( channel="rcs", @@ -238,15 +238,15 @@ def main(): ) rich_card_result = client.messages.send(rcs_rich_card) - print("βœ… RCS rich card sent successfully!") + print("RCS rich card sent successfully!") print(f" Message ID: {rich_card_result.id}") print(f" Status: {rich_card_result.status}") except Exception as e: - print(f"❌ RCS Rich Card Error: {str(e)}") + print(f"RCS Rich Card Error: {str(e)}") # Example 7: Bulk messaging across channels - print("\nπŸ“Š Bulk Messaging Demo...") + print("\nBulk Messaging Demo...") try: recipients = [ { @@ -292,22 +292,12 @@ def main(): result = client.messages.send(bulk_message) bulk_results.append(result) - print(f" βœ… {recipient['channel'].upper()}: {result.id} -> {result.status}") + print(f" {recipient['channel'].upper()}: {result.id} -> {result.status}") - print(f"πŸ“ˆ Bulk messaging completed! Sent {len(bulk_results)} messages") + print(f"Bulk messaging completed! Sent {len(bulk_results)} messages") except Exception as e: - print(f"❌ Bulk Messaging Error: {str(e)}") - - print("\n" + "=" * 70) - print("🎯 Omni-channel messaging demo completed!") - print("\nKey Benefits:") - print("β€’ Unified API for all communication channels") - print("β€’ Channel-specific payload flexibility") - print("β€’ Consistent response format") - print("β€’ Real-time status tracking") - print("β€’ Metadata and webhook support") - print("β€’ Type-safe models with validation") + print(f"Bulk Messaging Error: {str(e)}") def send_notification_example(): @@ -319,7 +309,7 @@ def send_notification_example(): """ print("\n" + "=" * 50) - print("πŸ“’ Notification System Example") + print("Notification System Example") print("=" * 50) # Simulated user preferences @@ -370,7 +360,7 @@ def send_notification_example(): api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Error: DEVO_API_KEY environment variable not set") + print("Error: DEVO_API_KEY environment variable not set") return client = DevoClient(api_key=api_key) @@ -429,12 +419,12 @@ def send_notification_example(): ) result = client.messages.send(message) - print(f"βœ… {user['name']} ({user['channel']}): {result.id} -> {result.status}") + print(f"{user['name']} ({user['channel']}): {result.id} -> {result.status}") except Exception as e: - print(f"❌ Failed to notify {user['name']}: {str(e)}") + print(f"Failed to notify {user['name']}: {str(e)}") - print("\nπŸ“Š Notification broadcast completed!") + print("\nNotification broadcast completed!") if __name__ == "__main__": diff --git a/examples/rcs_example.py b/examples/rcs_example.py index 1ab326e..28fe1d0 100644 --- a/examples/rcs_example.py +++ b/examples/rcs_example.py @@ -1,109 +1,155 @@ import os -from devo_global_comms_python import DevoException +from devo_global_comms_python import DevoClient, DevoException +from devo_global_comms_python.models.rcs import RcsSendMessageSerializer def main(): api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Please set DEVO_API_KEY environment variable") + print("Please set DEVO_API_KEY environment variable") return - # client = DevoClient(api_key=api_key) # Uncomment when using real API - print("βœ… Devo RCS Client initialized successfully") + client = DevoClient(api_key=api_key) + print("Devo RCS Client initialized successfully") print("=" * 60) try: - # Example 1: Account Management - print("🏒 RCS ACCOUNT MANAGEMENT") - print("-" * 40) - - print("πŸ“ Creating a new RCS account...") - print(" Account creation would be called here...") - - print("\nπŸ“‹ Getting all RCS accounts...") - print(" Account listing would be called here...") - - print("\nβœ… Verifying RCS account...") - print(" Account verification would be called here...") - - # Example 2: Brand Management - print("\n🎨 RCS BRAND MANAGEMENT") - print("-" * 40) - - print("🎨 Creating a new RCS brand...") - print(" Brand creation would be called here...") - - print("\nπŸ“‹ Getting RCS brands...") - print(" Brand listing would be called here...") - - # Example 3: Template Management - print("\nπŸ“„ RCS TEMPLATE MANAGEMENT") - print("-" * 40) - - print("πŸ“ Creating an RCS template...") - print(" Template creation would be called here...") + # Example 1: Get RCS accounts + print("RCS ACCOUNTS EXAMPLE") + print("-" * 30) + + print("Retrieving RCS accounts...") + accounts = client.rcs.get_accounts(page=1, limit=5) + + print(f"Found {len(accounts)} RCS accounts") + for i, account in enumerate(accounts, 1): + print(f" {i}. Account: {account.name}") + print(f" ID: {account.id}") + print(f" Brand: {account.brand_name}") + print(f" Email: {account.contact_email}") + print(f" Phone: {account.contact_phone}") + print(f" Approved: {account.is_approved}") + print(f" Created: {account.created_at}") - print("\nπŸ“‹ Getting RCS templates...") - print(" Template listing would be called here...") + except DevoException as e: + print(f"Error retrieving RCS accounts: {e}") + except Exception as e: + print(f"Unexpected error: {e}") - # Example 4: Tester Management - print("\nπŸ§ͺ RCS TESTER MANAGEMENT") + try: + # Example 2: Send RCS text message using legacy method + print("\nRCS TEXT MESSAGE EXAMPLE (Legacy)") print("-" * 40) - print("πŸ‘€ Adding an RCS tester...") - print(" Tester addition would be called here...") + print("Sending RCS text message...") + text_response = client.rcs.send_text( + to="+1234567890", # Replace with recipient phone number + text="Hello from Devo RCS SDK! This is a test message.", + callback_url="https://example.com/webhook", + metadata={"campaign": "test", "source": "sdk_example"}, + ) + + print("RCS text message sent successfully!") + print(f" Message ID: {text_response.id}") + print(f" Status: {text_response.status}") + print(f" To: {text_response.to}") + print(f" Direction: {text_response.direction}") + print(f" Type: {text_response.type}") + if text_response.date_created: + print(f" Created: {text_response.date_created}") + if text_response.date_sent: + print(f" Sent: {text_response.date_sent}") - print("\nπŸ“‹ Getting RCS testers...") - print(" Tester listing would be called here...") + except DevoException as e: + print(f"Error sending RCS text: {e}") + except Exception as e: + print(f"Unexpected error: {e}") - # Example 5: Send Messages - print("\nπŸ’¬ RCS MESSAGING") + try: + # Example 3: Send RCS rich card using legacy method + print("\nRCS RICH CARD EXAMPLE (Legacy)") print("-" * 40) - print("πŸ“€ Sending RCS text message...") - print(" Text message sending would be called here...") - - print("\nπŸ“€ Sending RCS rich card message...") - print(" Rich card sending would be called here...") + print("Sending RCS rich card...") + rich_card_response = client.rcs.send_rich_card( + to="+1234567890", # Replace with recipient phone number + title="Welcome to Devo", + description="Experience our premium communication services", + media_url="https://example.com/image.jpg", # Replace with actual image URL + actions=[ + {"type": "openUrl", "text": "Learn More", "url": "https://example.com/learn-more"}, + {"type": "dial", "text": "Call Us", "phoneNumber": "+1234567890"}, + ], + callback_url="https://example.com/webhook", + metadata={"campaign": "rich_card_demo"}, + ) + + print("RCS rich card sent successfully!") + print(f" Message ID: {rich_card_response.id}") + print(f" Status: {rich_card_response.status}") + print(f" Type: {rich_card_response.type}") + if rich_card_response.rich_card: + print(f" Card Title: {rich_card_response.rich_card.get('title', 'N/A')}") - print("\nπŸ“€ Sending RCS carousel message...") - print(" Carousel sending would be called here...") + except DevoException as e: + print(f"Error sending RCS rich card: {e}") + except Exception as e: + print(f"Unexpected error: {e}") - print("\nπŸ“€ Sending interactive RCS message...") - print(" Interactive message sending would be called here...") + try: + # Example 4: Send message using the general send_message method + print("\nRCS MESSAGE (General API)") + print("-" * 30) + + print("Sending RCS message via general API...") + + message_data = { + "to": "+1234567890", + "from": "+0987654321", + "account_id": "your_account_id", # Replace with actual account ID + "message_type": "text", + "text": "Hello from the general RCS API!", + "callback_url": "https://example.com/webhook", + "metadata": {"source": "general_api_example"}, + } + + send_response: RcsSendMessageSerializer = client.rcs.send_message(message_data) + + print("Message sent via general API!") + print(f" Message ID: {send_response.id}") + print(f" Account ID: {send_response.account_id}") + print(f" Status: {send_response.status}") + print(f" Message Type: {send_response.message_type}") + print(f" Created: {send_response.created_at}") + if send_response.sent_at: + print(f" Sent: {send_response.sent_at}") - print("\nπŸ“ˆ Getting message history and analytics...") - print(" Message listing and analytics would be called here...") + except DevoException as e: + print(f"Error with general RCS API: {e}") + except Exception as e: + print(f"Unexpected error: {e}") - # Example 6: Legacy Support - print("\nπŸ”„ LEGACY RCS METHODS") - print("-" * 40) + try: + # Example 5: List RCS messages + print("\nRCS MESSAGES LIST EXAMPLE") + print("-" * 30) - print("πŸ“€ Using legacy send_text method...") - print(" Legacy text sending would be called here...") + print("Retrieving RCS messages...") + messages = client.rcs.list_messages(page=1, limit=5, type="text") - print("\nπŸ“€ Using legacy send_rich_card method...") - print(" Legacy rich card sending would be called here...") + print(f"Found {len(messages)} RCS messages") + for i, message in enumerate(messages, 1): + print(f" {i}. Message ID: {message.id}") + print(f" Type: {message.message_type}") + print(f" Status: {message.status}") + print(f" To: {message.to}") + print(f" Created: {message.created_at}") except DevoException as e: - print(f"❌ RCS operation failed: {e}") - - print("\n" + "=" * 60) - print("πŸ“Š RCS IMPLEMENTATION SUMMARY") - print("-" * 40) - print("βœ… Complete RCS API Implementation") - print("πŸ“‹ Features implemented:") - print(" β€’ Account Management (create, get, verify, update)") - print(" β€’ Brand Management (create, get, update)") - print(" β€’ Template Management (create, get, update, delete)") - print(" β€’ Tester Management (add, get)") - print(" β€’ Message Sending (text, rich card, carousel)") - print(" β€’ Interactive Messages with Suggestions") - print(" β€’ Message Tracking and Analytics") - print(" β€’ Legacy Method Support") - print("\nπŸš€ All 14 RCS endpoints are now available!") - print("πŸ’‘ Uncomment the API calls above to use the real implementation") + print(f"Error listing RCS messages: {e}") + except Exception as e: + print(f"Unexpected error: {e}") if __name__ == "__main__": diff --git a/examples/sms_example.py b/examples/sms_example.py index 02340e9..6dbfcde 100644 --- a/examples/sms_example.py +++ b/examples/sms_example.py @@ -7,26 +7,26 @@ def main(): # Initialize the client with your API key api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Please set DEVO_API_KEY environment variable") - print(" You can get your API key from the Devo dashboard") + print("Please set DEVO_API_KEY environment variable") + print("You can get your API key from the Devo dashboard") return client = DevoClient(api_key=api_key) - print("βœ… Devo SMS Client initialized successfully") + print("Devo SMS Client initialized successfully") print("=" * 60) try: # Example 1: Send SMS using new quick-send API - print("πŸ“± SMS QUICK-SEND API EXAMPLE") + print("SMS QUICK-SEND API EXAMPLE") print("-" * 30) recipient = "+1234567890" # Replace with actual phone number sender = "+0987654321" # Replace with your sender number/ID message = "Hello from Devo SDK! This message was sent using the new quick-send API." - print(f"πŸ“€ Sending SMS to {recipient}...") - print(f"πŸ“ Message: {message}") - print(f"πŸ“ž From: {sender}") + print(f"Sending SMS to {recipient}...") + print(f"Message: {message}") + print(f"From: {sender}") sms_response = client.sms.send_sms( recipient=recipient, @@ -35,159 +35,77 @@ def main(): hirvalidation=True, # Enable high-quality routing validation ) - print("βœ… SMS sent successfully!") - print(f" πŸ“‹ Message ID: {sms_response.id}") - print(f" πŸ“Š Status: {sms_response.status}") - print(f" πŸ“± Recipient: {sms_response.recipient}") - print(f" πŸ”„ Direction: {sms_response.direction}") - print(f" πŸ”§ API Mode: {sms_response.apimode}") - if sms_response.send_date: - print(f" πŸ“… Send Date: {sms_response.send_date}") + print("SMS sent successfully!") + print(f" Message ID: {sms_response.id}") + print(f" Status: {sms_response.status}") + print(f" Recipient: {sms_response.recipient}") + print(f" Direction: {sms_response.direction}") + print(f" API Mode: {sms_response.apimode}") + if sms_response.sent_date: + print(f" Sent Date: {sms_response.sent_date}") except DevoException as e: - print(f"❌ Failed to send SMS: {e}") - print_error_details(e) + print(f"Failed to send SMS: {e}") print("\n" + "=" * 60) try: # Example 2: Get available senders - print("πŸ‘₯ GET AVAILABLE SENDERS EXAMPLE") + print("GET AVAILABLE SENDERS EXAMPLE") print("-" * 30) - print("πŸ” Retrieving available senders...") + print("Retrieving available senders...") senders = client.sms.get_senders() - print(f"βœ… Found {len(senders.senders)} available senders:") + print(f"Found {len(senders.senders)} available senders:") for i, sender in enumerate(senders.senders, 1): - print(f" {i}. πŸ“ž Phone: {sender.phone_number}") - print(f" 🏷️ Type: {sender.type}") - print(f" πŸ§ͺ Test Mode: {'Yes' if sender.istest else 'No'}") - print(f" πŸ†” ID: {sender.id}") + print(f" {i}. Phone: {sender.phone_number}") + print(f" Type: {sender.type}") + print(f" Test Mode: {'Yes' if sender.istest else 'No'}") + print(f" ID: {sender.id}") if sender.creation_date: - print(f" πŸ“… Created: {sender.creation_date}") + print(f" Created: {sender.creation_date}") print() except DevoException as e: - print(f"❌ Failed to get senders: {e}") - print_error_details(e) + print(f"Failed to get senders: {e}") print("=" * 60) try: # Example 3: Get available numbers for purchase - print("πŸ”’ GET AVAILABLE NUMBERS EXAMPLE") + print("GET AVAILABLE NUMBERS EXAMPLE") print("-" * 30) region = "US" number_type = "mobile" limit = 5 - print(f"πŸ” Searching for {limit} available {number_type} numbers in {region}...") + print(f"Searching for {limit} available {number_type} numbers in {region}...") numbers = client.sms.get_available_numbers(region=region, limit=limit, type=number_type) - print(f"βœ… Found {len(numbers.numbers)} available numbers:") + print(f"Found {len(numbers.numbers)} available numbers:") for i, number_info in enumerate(numbers.numbers, 1): - print(f"\n πŸ“‹ Number Group {i}:") - for j, feature in enumerate(number_info.features, 1): - print(f" {j}. πŸ“ž Number: {feature.phone_number}") - print(f" 🏷️ Type: {feature.number_type}") - print(f" 🌍 Region: {feature.region_information.region_name}") - print(f" 🌐 Country: {feature.region_information.country_code}") - print( - f" πŸ’° Monthly: {feature.cost_information.monthly_cost} {feature.cost_information.currency}" - ) - print(f" πŸ”§ Setup: {feature.cost_information.setup_cost} {feature.cost_information.currency}") + print(f"\n Number Group {i}:") + if number_info.features: + for j, feature in enumerate(number_info.features, 1): + print(f" {j}. Number: {feature.phone_number}") + print(f" Type: {feature.number_type}") + print(f" Region Info: {feature.region_information}") + print(f" Cost Info: {feature.cost_information}") + print(f" Best Effort: {feature.best_effort}") + print(f" Quickship: {feature.quickship}") + print() + else: + print(f" Phone: {number_info.phone_number}") + print(f" Type: {number_info.phone_number_type}") + print(f" Region Info: {number_info.region_information}") + print(f" Cost Info: {number_info.cost_information}") + print(f" Carrier: {number_info.carrier}") print() except DevoException as e: - print(f"❌ Failed to get available numbers: {e}") - print_error_details(e) - - print("=" * 60) - - # Example 4: Purchase a number (commented out to prevent accidental charges) - print("πŸ’³ NUMBER PURCHASE EXAMPLE (DISABLED)") - print("-" * 30) - print("⚠️ The following example is commented out to prevent accidental charges.") - print(" Uncomment and modify the code below to actually purchase a number:") - print() - print(" ```python") - print(" # Choose a number from the available numbers above") - print(" selected_number = '+1234567890' # Replace with actual available number") - print(" ") - print(" print(f'πŸ’³ Purchasing number {selected_number}...')") - print(" number_purchase = client.sms.buy_number(") - print(" region='US',") - print(" number=selected_number,") - print(" number_type='mobile',") - print(" agency_authorized_representative='Jane Doe',") - print(" agency_representative_email='jane.doe@company.com',") - print(" is_longcode=True,") - print(" is_automated_enabled=True") - print(" )") - print(" ") - print(" print('βœ… Number purchased successfully!')") - print(" print(f' πŸ“ž Number: {number_purchase.number}')") - print(" print(f' 🏷️ Type: {number_purchase.number_type}')") - print(" print(f' 🌍 Region: {number_purchase.region}')") - print(" print(f' ✨ Features: {len(number_purchase.features)}')") - print(" for feature in number_purchase.features:") - print(" print(f' - {feature.phone_number} ({feature.number_type})')") - print(" ```") - - print("\n" + "=" * 60) - - try: - # Example 5: Using legacy send method for backward compatibility - print("πŸ”„ LEGACY COMPATIBILITY EXAMPLE") - print("-" * 30) - - print("πŸ”„ Testing legacy send method for backward compatibility...") - print(" (This uses the old API structure but maps to new implementation)") - - try: - legacy_response = client.sms.send( - to=recipient, - body="Hello from legacy method! This ensures backward compatibility.", - from_=sender, - ) - print("βœ… Legacy send successful!") - print(f" πŸ“‹ Message ID: {legacy_response.id}") - print(f" πŸ“Š Status: {legacy_response.status}") - - except DevoException as e: - print(f"⚠️ Legacy send failed (this is expected if sender is not configured): {e}") - print(" πŸ’‘ Use the new send_sms() method for better control and error handling") - - except DevoException as e: - print(f"❌ Legacy compatibility test failed: {e}") - print_error_details(e) - - print("\n" + "=" * 60) - print("πŸ“Š SMS EXAMPLE SUMMARY") - print("-" * 30) - print("βœ… Covered SMS API endpoints:") - print(" 1. πŸ“€ POST /user-api/sms/quick-send - Send SMS messages") - print(" 2. πŸ‘₯ GET /user-api/me/senders - Get available senders") - print(" 3. πŸ”’ GET /user-api/numbers - Get available numbers") - print(" 4. πŸ’³ POST /user-api/numbers/buy - Purchase numbers (example only)") - print() - print("πŸ’‘ Next steps:") - print(" - Replace phone numbers with actual values") - print(" - Set up proper senders in your Devo dashboard") - print(" - Uncomment purchase example when ready to buy numbers") - print(" - Check other example files for Email, WhatsApp, etc.") - - -def print_error_details(error: DevoException): - print(f" πŸ” Error Type: {type(error).__name__}") - if hasattr(error, "status_code") and error.status_code: - print(f" πŸ“Š Status Code: {error.status_code}") - if hasattr(error, "error_code") and error.error_code: - print(f" πŸ”’ Error Code: {error.error_code}") - if hasattr(error, "response_data") and error.response_data: - print(f" πŸ“‹ Response Data: {error.response_data}") + print(f"Failed to get available numbers: {e}") if __name__ == "__main__": diff --git a/examples/whatsapp_example.py b/examples/whatsapp_example.py index 565ca2f..6ce223f 100644 --- a/examples/whatsapp_example.py +++ b/examples/whatsapp_example.py @@ -1,59 +1,77 @@ import os -from devo_global_comms_python import DevoCommsClient, DevoException +from devo_global_comms_python import DevoClient, DevoException +from devo_global_comms_python.models.whatsapp import ( + BodyComponent, + ButtonsComponent, + FooterComponent, + HeaderComponent, + LocationParameter, + OTPButton, + PhoneNumberButton, + QuickReplyButton, + TemplateExample, + TemplateMessageComponent, + TemplateMessageLanguage, + TemplateMessageParameter, + TemplateMessageTemplate, + URLButton, + WhatsAppTemplateMessageRequest, + WhatsAppTemplateRequest, +) def main(): api_key = os.getenv("DEVO_API_KEY") if not api_key: - print("❌ Please set DEVO_API_KEY environment variable") + print("Please set DEVO_API_KEY environment variable") return - client = DevoCommsClient(api_key=api_key) - print("βœ… Devo WhatsApp Client initialized successfully") + client = DevoClient(api_key=api_key) + print("Devo WhatsApp Client initialized successfully") print("=" * 60) try: # Example 1: Get WhatsApp accounts - print("πŸ“± WHATSAPP ACCOUNTS EXAMPLE") + print("WHATSAPP ACCOUNTS EXAMPLE") print("-" * 30) - print("πŸ“€ Getting WhatsApp accounts...") + print("Getting WhatsApp accounts...") accounts_response = client.whatsapp.get_accounts(page=1, limit=10, is_approved=True) - print("βœ… WhatsApp accounts retrieved successfully!") - print(f" πŸ“Š Total accounts: {accounts_response.total}") - print(f" πŸ“„ Page: {accounts_response.page}") - print(f" πŸ“ Limit: {accounts_response.limit}") - print(f" ➑️ Has next: {accounts_response.has_next}") + print("WhatsApp accounts retrieved successfully!") + print(f" Total accounts: {accounts_response.total}") + print(f" Page: {accounts_response.page}") + print(f" Limit: {accounts_response.limit}") + print(f" Has next: {accounts_response.has_next}") for i, account in enumerate(accounts_response.accounts, 1): print(f" Account {i}:") - print(f" πŸ†” ID: {account.id}") - print(f" πŸ“› Name: {account.name}") - print(f" πŸ“§ Email: {account.email}") - print(f" πŸ“ž Phone: {account.phone}") - print(f" βœ… Approved: {account.is_approved}") + print(f" ID: {account.id}") + print(f" Name: {account.name}") + print(f" Email: {account.email}") + print(f" Phone: {account.phone}") + print(f" Approved: {account.is_approved}") # Example 2: Get a template - print("\nπŸ“‹ WHATSAPP TEMPLATE EXAMPLE") + print("\nWHATSAPP TEMPLATE EXAMPLE") print("-" * 30) - print("πŸ“€ Getting WhatsApp template...") + print("Getting WhatsApp template...") template = client.whatsapp.get_template("welcome_message") - print("βœ… Template retrieved successfully!") - print(f" πŸ“› Name: {template.name}") - print(f" 🌍 Language: {template.language}") - print(f" πŸ“Š Status: {template.status}") - print(f" πŸ“‚ Category: {template.category}") - print(f" πŸ”§ Components: {len(template.components)}") + print("Template retrieved successfully!") + print(f" Name: {template.name}") + print(f" Language: {template.language}") + print(f" Status: {template.status}") + print(f" Category: {template.category}") + print(f" Components: {len(template.components)}") # Example 3: Upload a file - print("\nπŸ“Ž WHATSAPP FILE UPLOAD EXAMPLE") + print("\nWHATSAPP FILE UPLOAD EXAMPLE") print("-" * 30) - print("πŸ“€ Uploading file to WhatsApp...") + print("Uploading file to WhatsApp...") # Create a sample file content for demonstration sample_content = b"This is a sample file content for WhatsApp upload demonstration." @@ -62,68 +80,60 @@ def main(): file_content=sample_content, filename="sample_document.txt", content_type="text/plain" ) - print("βœ… File uploaded successfully!") - print(f" πŸ†” File ID: {upload_response.file_id}") - print(f" πŸ“„ Filename: {upload_response.filename}") - print(f" πŸ“ File size: {upload_response.file_size} bytes") - print(f" 🎭 MIME type: {upload_response.mime_type}") - print(f" πŸ”— URL: {upload_response.url}") + print("File uploaded successfully!") + print(f" File ID: {upload_response.file_id}") + print(f" Filename: {upload_response.filename}") + print(f" File size: {upload_response.file_size} bytes") + print(f" MIME type: {upload_response.mime_type}") + print(f" URL: {upload_response.url}") if upload_response.expires_at: - print(f" ⏰ Expires at: {upload_response.expires_at}") + print(f" Expires at: {upload_response.expires_at}") # Example 4: Search accounts - print("\nπŸ” WHATSAPP ACCOUNTS SEARCH EXAMPLE") + print("\nWHATSAPP ACCOUNTS SEARCH EXAMPLE") print("-" * 35) - print("πŸ” Searching WhatsApp accounts...") + print("Searching WhatsApp accounts...") search_response = client.whatsapp.get_accounts(search="test") - print("βœ… Search completed!") - print(f" πŸ“Š Found {search_response.total} accounts matching 'test'") + print("Search completed!") + print(f" Found {search_response.total} accounts matching 'test'") # Example 5: Send a normal message - print("\nπŸ’¬ WHATSAPP SEND MESSAGE EXAMPLE") + print("\nWHATSAPP SEND MESSAGE EXAMPLE") print("-" * 32) - print("πŸ“€ Sending WhatsApp message...") + print("Sending WhatsApp message...") message_response = client.whatsapp.send_normal_message( to="+1234567890", message="Hello from the Devo WhatsApp SDK! πŸ‘‹ This is a test message.", account_id="acc_123", # Optional - uses default if not provided ) - print("βœ… Message sent successfully!") - print(f" πŸ†” Message ID: {message_response.message_id}") - print(f" πŸ“Š Status: {message_response.status}") - print(f" πŸ“ž To: {message_response.to}") - print(f" 🏒 Account ID: {message_response.account_id}") - print(f" πŸ• Timestamp: {message_response.timestamp}") - print(f" βœ… Success: {message_response.success}") + print("Message sent successfully!") + print(f" Message ID: {message_response.message_id}") + print(f" Status: {message_response.status}") + print(f" To: {message_response.to}") + print(f" Account ID: {message_response.account_id}") + print(f" Timestamp: {message_response.timestamp}") + print(f" Success: {message_response.success}") # Example 6: Send message with emojis and Unicode - print("\n🌍 WHATSAPP UNICODE MESSAGE EXAMPLE") + print("\nWHATSAPP UNICODE MESSAGE EXAMPLE") print("-" * 35) - unicode_message = "Β‘Hola! πŸŽ‰ Welcome to Devo! 欒迎 Ω…Ψ±Ψ­Ψ¨Ψ§ πŸš€" + unicode_message = "Β‘Hola! Welcome to Devo! 欒迎 Ω…Ψ±Ψ­Ψ¨Ψ§" unicode_response = client.whatsapp.send_normal_message(to="+1234567890", message=unicode_message) - print("βœ… Unicode message sent!") - print(f" πŸ†” Message ID: {unicode_response.message_id}") - print(f" πŸ“Š Status: {unicode_response.status}") + print("Unicode message sent!") + print(f" Message ID: {unicode_response.message_id}") + print(f" Status: {unicode_response.status}") # Example 7: Create a WhatsApp template - print("\nπŸ“‹ WHATSAPP CREATE TEMPLATE EXAMPLE") + print("\nWHATSAPP CREATE TEMPLATE EXAMPLE") print("-" * 35) - from devo_global_comms_python.models.whatsapp import ( - BodyComponent, - ButtonsComponent, - FooterComponent, - QuickReplyButton, - WhatsAppTemplateRequest, - ) - - print("πŸ“€ Creating WhatsApp template...") + print("Creating WhatsApp template...") # Create a utility template for notifications template_request = WhatsAppTemplateRequest( @@ -151,43 +161,41 @@ def main(): account_id="acc_123", template=template_request # Replace with actual account ID ) - print("βœ… Template created successfully!") - print(f" πŸ†” Template ID: {template_response.id}") - print(f" πŸ“› Template Name: {template_response.name}") - print(f" πŸ“Š Status: {template_response.status}") - print(f" πŸ“‚ Category: {template_response.category}") - print(f" 🌍 Language: {template_response.language}") + print("Template created successfully!") + print(f" Template ID: {template_response.id}") + print(f" Template Name: {template_response.name}") + print(f" Status: {template_response.status}") + print(f" Category: {template_response.category}") + print(f" Language: {template_response.language}") # Example 8: Get WhatsApp templates - print("\nπŸ“‹ WHATSAPP GET TEMPLATES EXAMPLE") + print("\nWHATSAPP GET TEMPLATES EXAMPLE") print("-" * 33) - print("πŸ“€ Getting WhatsApp templates...") + print("Getting WhatsApp templates...") templates_response = client.whatsapp.get_templates( account_id="acc_123", category="UTILITY", page=1, limit=5 # Replace with actual account ID ) - print("βœ… Templates retrieved successfully!") - print(f" πŸ“Š Total templates: {templates_response.total}") - print(f" πŸ“„ Page: {templates_response.page}") - print(f" πŸ“ Limit: {templates_response.limit}") - print(f" ➑️ Has next: {templates_response.has_next}") + print("Templates retrieved successfully!") + print(f" Total templates: {templates_response.total}") + print(f" Page: {templates_response.page}") + print(f" Limit: {templates_response.limit}") + print(f" Has next: {templates_response.has_next}") for i, template in enumerate(templates_response.templates, 1): print(f" Template {i}:") - print(f" πŸ“› Name: {template.name}") - print(f" πŸ“Š Status: {template.status}") - print(f" πŸ“‚ Category: {template.category}") - print(f" 🌍 Language: {template.language}") - print(f" πŸ”§ Components: {len(template.components)}") + print(f" Name: {template.name}") + print(f" Status: {template.status}") + print(f" Category: {template.category}") + print(f" Language: {template.language}") + print(f" Components: {len(template.components)}") # Example 9: Create an authentication template - print("\nπŸ” WHATSAPP AUTHENTICATION TEMPLATE EXAMPLE") + print("\nWHATSAPP AUTHENTICATION TEMPLATE EXAMPLE") print("-" * 42) - from devo_global_comms_python.models.whatsapp import OTPButton - - print("πŸ“€ Creating authentication template...") + print("Creating authentication template...") auth_template_request = WhatsAppTemplateRequest( name="verification_code_template", @@ -204,23 +212,16 @@ def main(): auth_template_response = client.whatsapp.create_template(account_id="acc_123", template=auth_template_request) - print("βœ… Authentication template created!") - print(f" πŸ†” Template ID: {auth_template_response.id}") - print(f" πŸ” Category: {auth_template_response.category}") - print(f" πŸ“Š Status: {auth_template_response.status}") + print("Authentication template created!") + print(f" Template ID: {auth_template_response.id}") + print(f" Category: {auth_template_response.category}") + print(f" Status: {auth_template_response.status}") # Example 10: Create a marketing template with buttons - print("\n🎯 WHATSAPP MARKETING TEMPLATE EXAMPLE") + print("\nWHATSAPP MARKETING TEMPLATE EXAMPLE") print("-" * 37) - from devo_global_comms_python.models.whatsapp import ( - HeaderComponent, - PhoneNumberButton, - TemplateExample, - URLButton, - ) - - print("πŸ“€ Creating marketing template...") + print("Creating marketing template...") marketing_template_request = WhatsAppTemplateRequest( name="summer_sale_promotion", @@ -230,7 +231,7 @@ def main(): HeaderComponent( type="HEADER", format="TEXT", - text="🌞 Summer Sale Alert! {{1}}", + text="Summer Sale Alert! {{1}}", example=TemplateExample(header_text=["50% OFF"]), ), BodyComponent( @@ -259,24 +260,16 @@ def main(): account_id="acc_123", template=marketing_template_request ) - print("βœ… Marketing template created!") - print(f" πŸ†” Template ID: {marketing_template_response.id}") - print(f" 🎯 Category: {marketing_template_response.category}") - print(f" πŸ“Š Status: {marketing_template_response.status}") + print("Marketing template created!") + print(f" Template ID: {marketing_template_response.id}") + print(f" Category: {marketing_template_response.category}") + print(f" Status: {marketing_template_response.status}") # Example 11: Send template message with text parameters - print("\nπŸ“€ WHATSAPP SEND TEMPLATE MESSAGE EXAMPLE") + print("\nWHATSAPP SEND TEMPLATE MESSAGE EXAMPLE") print("-" * 40) - from devo_global_comms_python.models.whatsapp import ( - TemplateMessageComponent, - TemplateMessageLanguage, - TemplateMessageParameter, - TemplateMessageTemplate, - WhatsAppTemplateMessageRequest, - ) - - print("πŸ“€ Sending template message...") + print("Sending template message...") # Send a simple text template message template_message_request = WhatsAppTemplateMessageRequest( @@ -301,20 +294,20 @@ def main(): account_id="acc_123", template_message=template_message_request ) - print("βœ… Template message sent successfully!") - print(f" πŸ†” Message ID: {template_message_response.message_id}") - print(f" πŸ“Š Status: {template_message_response.status}") - print(f" πŸ“ž To: {template_message_response.to}") - print(f" 🏒 Account ID: {template_message_response.account_id}") - print(f" βœ… Success: {template_message_response.success}") + print("Template message sent successfully!") + print(f" Message ID: {template_message_response.message_id}") + print(f" Status: {template_message_response.status}") + print(f" To: {template_message_response.to}") + print(f" Account ID: {template_message_response.account_id}") + print(f" Success: {template_message_response.success}") # Example 12: Send template message with image header - print("\nπŸ–ΌοΈ WHATSAPP TEMPLATE MESSAGE WITH IMAGE EXAMPLE") + print("\nWHATSAPP TEMPLATE MESSAGE WITH IMAGE EXAMPLE") print("-" * 45) from devo_global_comms_python.models.whatsapp import ImageParameter - print("πŸ“€ Sending template message with image header...") + print("Sending template message with image header...") image_template_request = WhatsAppTemplateMessageRequest( to="+1234567890", @@ -352,17 +345,15 @@ def main(): account_id="acc_123", template_message=image_template_request ) - print("βœ… Image template message sent!") - print(f" πŸ†” Message ID: {image_template_response.message_id}") - print(f" πŸ“Š Status: {image_template_response.status}") + print("Image template message sent!") + print(f" Message ID: {image_template_response.message_id}") + print(f" Status: {image_template_response.status}") # Example 13: Send template message with location - print("\nπŸ“ WHATSAPP TEMPLATE MESSAGE WITH LOCATION EXAMPLE") + print("\nWHATSAPP TEMPLATE MESSAGE WITH LOCATION EXAMPLE") print("-" * 49) - from devo_global_comms_python.models.whatsapp import LocationParameter - - print("πŸ“€ Sending template message with location...") + print("Sending template message with location...") location_template_request = WhatsAppTemplateMessageRequest( to="+1234567890", @@ -405,15 +396,15 @@ def main(): account_id="acc_123", template_message=location_template_request ) - print("βœ… Location template message sent!") - print(f" πŸ†” Message ID: {location_template_response.message_id}") - print(f" πŸ“Š Status: {location_template_response.status}") + print("Location template message sent!") + print(f" Message ID: {location_template_response.message_id}") + print(f" Status: {location_template_response.status}") # Example 14: Send authentication template with OTP - print("\nπŸ” WHATSAPP AUTHENTICATION TEMPLATE MESSAGE EXAMPLE") + print("\nWHATSAPP AUTHENTICATION TEMPLATE MESSAGE EXAMPLE") print("-" * 50) - print("πŸ“€ Sending authentication template message...") + print("Sending authentication template message...") auth_template_request = WhatsAppTemplateMessageRequest( to="+1234567890", @@ -438,44 +429,15 @@ def main(): account_id="acc_123", template_message=auth_template_request ) - print("βœ… Authentication template message sent!") - print(f" πŸ†” Message ID: {auth_template_response.message_id}") - print(" πŸ” OTP Code: 123456") - print(f" πŸ“Š Status: {auth_template_response.status}") + print("Authentication template message sent!") + print(f" Message ID: {auth_template_response.message_id}") + print(" OTP Code: 123456") + print(f" Status: {auth_template_response.status}") except DevoException as e: - print(f"❌ WhatsApp operation failed: {e}") + print(f"WhatsApp operation failed: {e}") except Exception as e: - print(f"❌ Unexpected error: {e}") - - print("\n" + "=" * 60) - print("πŸ“Š WHATSAPP EXAMPLE SUMMARY") - print("-" * 30) - print("βœ… WhatsApp API implementation complete!") - print("πŸ“€ Successfully demonstrated:") - print(" β€’ Getting WhatsApp accounts with pagination") - print(" β€’ Retrieving templates by name") - print(" β€’ Uploading files with proper metadata") - print(" β€’ Searching accounts with filters") - print(" β€’ Sending normal messages") - print(" β€’ Unicode and emoji support") - print(" β€’ Creating WhatsApp templates (utility, authentication, marketing)") - print(" β€’ Getting templates with filtering and pagination") - print(" β€’ Complex template components (headers, buttons, examples)") - print(" β€’ Sending template messages with parameters") - print(" β€’ Template messages with images, locations, documents") - print(" β€’ Authentication templates with OTP codes") - print(" β€’ Marketing templates with dynamic content") - print(" β€’ Template validation and error handling") - print(" β€’ Response parsing and error handling") - print("\nπŸ’‘ Available endpoints:") - print(" β€’ GET /user-api/whatsapp/accounts - Get shared accounts") - print(" β€’ GET /user-api/whatsapp/templates/{name} - Get template by name") - print(" β€’ POST /user-api/whatsapp/upload - Upload files") - print(" β€’ POST /user-api/whatsapp/send-normal-message - Send messages") - print(" β€’ POST /user-api/whatsapp/templates - Create templates") - print(" β€’ GET /user-api/whatsapp/templates - Get all templates") - print(" β€’ POST /user-api/whatsapp/send-message-by-template - Send template messages") + print(f"Unexpected error: {e}") if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 3620373..af1a62a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" requires-python = ">=3.8" license = "MIT" authors = [ - { name = "Devo Team", email = "support@devo.com" }, + { name = "Devo Team", email = "support@devotel.io" }, ] keywords = ["api", "communication", "sms", "email", "whatsapp", "rcs"] classifiers = [ diff --git a/setup-dev-env.ps1 b/setup-dev-env.ps1 deleted file mode 100644 index 9cee028..0000000 --- a/setup-dev-env.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env powershell -# Development Environment Setup Script for Devo Global Communications Python SDK - -Write-Host "πŸš€ Setting up Devo Global Communications Python SDK development environment..." -Write-Host "" - -# Check if we're in the right directory -if (!(Test-Path "pyproject.toml")) { - Write-Host "❌ Error: pyproject.toml not found. Please run this script from the project root directory." - exit 1 -} - -# Create virtual environment if it doesn't exist -if (!(Test-Path "venv")) { - Write-Host "πŸ“¦ Creating virtual environment..." - python -m venv venv - if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Error: Failed to create virtual environment." - exit 1 - } -} else { - Write-Host "βœ“ Virtual environment already exists" -} - -# Activate virtual environment -Write-Host "πŸ”§ Activating virtual environment..." -& .\venv\Scripts\Activate.ps1 - -# Upgrade pip -Write-Host "⬆️ Upgrading pip..." -python -m pip install --upgrade pip -if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Error: Failed to upgrade pip." - exit 1 -} - -# Install package in development mode with all dependencies -Write-Host "πŸ“š Installing package and dependencies..." -pip install -e ".[dev]" -if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Error: Failed to install package and dependencies." - exit 1 -} - -# Initialize git if not already done -if (!(Test-Path ".git")) { - Write-Host "πŸ”„ Initializing git repository..." - git init - if ($LASTEXITCODE -ne 0) { - Write-Host "⚠️ Warning: Git initialization failed. You may need to install Git." - } -} - -# Install pre-commit hooks -Write-Host "πŸͺ Installing pre-commit hooks..." -pre-commit install -if ($LASTEXITCODE -ne 0) { - Write-Host "⚠️ Warning: Pre-commit hooks installation failed." -} - -# Verify installation -Write-Host "" -Write-Host "πŸ§ͺ Testing installation..." -python -c "from devo_global_comms_python import DevoClient; print('βœ“ SDK import successful')" -if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Error: SDK import test failed." - exit 1 -} - -python -c "from devo_global_comms_python import DevoClient; client = DevoClient('test-key'); print('βœ“ Client initialization successful')" -if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Error: Client initialization test failed." - exit 1 -} - -Write-Host "" -Write-Host "πŸŽ‰ Development environment setup complete!" -Write-Host "" -Write-Host "πŸ“‹ Next steps:" -Write-Host " 1. Activate virtual environment: .\venv\Scripts\Activate.ps1" -Write-Host " 2. Run tests: pytest" -Write-Host " 3. Format code: black src/ tests/" -Write-Host " 4. Check types: mypy src/" -Write-Host " 5. Run linting: flake8 src/" -Write-Host "" -Write-Host "πŸ“š Useful commands:" -Write-Host " β€’ Run tests with coverage: pytest --cov" -Write-Host " β€’ Format and sort imports: black src/ tests/ && isort src/ tests/" -Write-Host " β€’ Run all quality checks: pre-commit run --all-files" -Write-Host "" -Write-Host "Happy coding! πŸš€" diff --git a/src/devo_global_comms_python/__init__.py b/src/devo_global_comms_python/__init__.py index 0a649e9..f8ed5f5 100644 --- a/src/devo_global_comms_python/__init__.py +++ b/src/devo_global_comms_python/__init__.py @@ -3,7 +3,7 @@ __email__ = "support@devotel.io" from .client import DevoClient -from .exceptions import ( # Base exceptions; HTTP-related exceptions; Business logic exceptions; Network exceptions; Configuration exceptions +from .exceptions import ( DevoAPIException, DevoAuthenticationException, DevoBadGatewayException, diff --git a/src/devo_global_comms_python/exceptions.py b/src/devo_global_comms_python/exceptions.py index b429c30..269d56c 100644 --- a/src/devo_global_comms_python/exceptions.py +++ b/src/devo_global_comms_python/exceptions.py @@ -25,9 +25,7 @@ def __str__(self) -> str: return self.message -# ============================================================================= # HTTP-Related Exceptions -# ============================================================================= class DevoAPIException(DevoException): @@ -190,9 +188,7 @@ def __init__(self, message: str = "Gateway timeout", **kwargs): super().__init__(message, status_code=504, **kwargs) -# ============================================================================= # Business Logic Exceptions -# ============================================================================= class DevoValidationException(DevoException): @@ -308,9 +304,7 @@ def __init__(self, channel: str, **kwargs): self.channel = channel -# ============================================================================= # Network Exceptions -# ============================================================================= class DevoNetworkException(DevoException): @@ -357,9 +351,7 @@ def __init__(self, message: str = "SSL/TLS error", **kwargs): super().__init__(message, **kwargs) -# ============================================================================= # Configuration Exceptions -# ============================================================================= class DevoConfigurationException(DevoException): @@ -395,9 +387,7 @@ def __init__(self, parameter: str, value: Any, reason: str, **kwargs): self.reason = reason -# ============================================================================= # Exception Factory -# ============================================================================= def create_exception_from_response(response: requests.Response) -> DevoAPIException: diff --git a/src/devo_global_comms_python/models/email.py b/src/devo_global_comms_python/models/email.py index 78e3089..6986943 100644 --- a/src/devo_global_comms_python/models/email.py +++ b/src/devo_global_comms_python/models/email.py @@ -24,15 +24,21 @@ class EmailSendResponse(BaseModel): Response model for email send API. Returned from POST /api/v1/user-api/email/send + + Can contain either success response or error response fields. """ - success: bool = Field(..., description="Whether the email was sent successfully") - message_id: str = Field(..., description="Unique message identifier") - bulk_email_id: str = Field(..., description="Bulk email identifier") - subject: str = Field(..., description="Email subject") - status: str = Field(..., description="Message status") - message: str = Field(..., description="Status message") - timestamp: datetime = Field(..., description="Timestamp of the response") + # Success response fields + success: Optional[bool] = Field(None, description="Whether the email was sent successfully") + message_id: Optional[str] = Field(None, description="Unique message identifier") + bulk_email_id: Optional[str] = Field(None, description="Bulk email identifier") + subject: Optional[str] = Field(None, description="Email subject") + status: Optional[str] = Field(None, description="Message status") + timestamp: Optional[datetime] = Field(None, description="Timestamp of the response") + + # Error response fields (also available in success cases) + message: Optional[str] = Field(None, description="Status or error message") + statusCode: Optional[int] = Field(None, description="HTTP status code") class EmailAttachment(BaseModel): diff --git a/src/devo_global_comms_python/models/sms.py b/src/devo_global_comms_python/models/sms.py index b3efc3c..dc449b4 100644 --- a/src/devo_global_comms_python/models/sms.py +++ b/src/devo_global_comms_python/models/sms.py @@ -43,25 +43,33 @@ class SMSQuickSendResponse(BaseModel): Returned from POST /user-api/sms/quick-send """ - id: str = Field(..., description="Unique message identifier") - user_id: str = Field(..., description="User identifier") - tenant_id: str = Field(..., description="Tenant identifier") - sender_id: str = Field(..., description="Sender identifier") - recipient: str = Field(..., description="Recipient phone number") - message: str = Field(..., description="Message content") - account_id: str = Field(..., description="Account identifier") - account_type: str = Field(..., description="Account type") - status: str = Field(..., description="Message status") - message_timeline: Dict[str, Any] = Field(default_factory=dict, description="Message timeline events") - message_id: str = Field(..., description="Message identifier") - bulksmsid: str = Field(..., description="Bulk SMS identifier") - sent_date: str = Field(..., description="Date message was sent") - direction: str = Field(..., description="Message direction") - recipientcontactid: str = Field(..., description="Recipient contact identifier") - api_route: str = Field(..., description="API route used") - apimode: str = Field(..., description="API mode") - quicksendidentifier: str = Field(..., description="Quick send identifier") - hirvalidation: bool = Field(..., description="HIR validation enabled") + # Success response fields + id: Optional[str] = Field(None, description="Unique message identifier") + user_id: Optional[str] = Field(None, description="User identifier") + tenant_id: Optional[str] = Field(None, description="Tenant identifier") + sender_id: Optional[str] = Field(None, description="Sender identifier") + recipient: Optional[str] = Field(None, description="Recipient phone number") + message: Optional[str] = Field(None, description="Message content") + account_id: Optional[str] = Field(None, description="Account identifier") + account_type: Optional[str] = Field(None, description="Account type") + status: Optional[str] = Field(None, description="Message status") + message_timeline: Optional[Dict[str, Any]] = Field(None, description="Message timeline events") + message_id: Optional[str] = Field(None, description="Message identifier") + bulksmsid: Optional[str] = Field(None, description="Bulk SMS identifier") + sent_date: Optional[str] = Field(None, description="Date message was sent") + direction: Optional[str] = Field(None, description="Message direction") + recipientcontactid: Optional[str] = Field(None, description="Recipient contact identifier") + api_route: Optional[str] = Field(None, description="API route used") + apimode: Optional[str] = Field(None, description="API mode") + quicksendidentifier: Optional[str] = Field(None, description="Quick send identifier") + hirvalidation: Optional[bool] = Field(None, description="HIR validation enabled") + + # Error response fields + statusCode: Optional[int] = Field(None, description="HTTP status code for errors") + + def is_error(self) -> bool: + """Check if this response represents an error.""" + return self.statusCode is not None and self.statusCode >= 400 class SenderInfo(BaseModel): @@ -69,14 +77,20 @@ class SenderInfo(BaseModel): Model for sender information. """ - id: str = Field(..., description="Sender identifier") - sender_id: str = Field(..., description="Sender ID") - gateways_id: str = Field(..., description="Gateway identifier") - phone_number: str = Field(..., description="Phone number") - number: str = Field(..., description="Number") - istest: bool = Field(..., description="Whether this is a test sender") + id: Optional[str] = Field(None, description="Sender identifier", alias="_id") + sender_id: Optional[str] = Field(None, description="Sender ID") + gateways_id: Optional[str] = Field(None, description="Gateway identifier") + phone_number: Optional[str] = Field(None, description="Phone number") + number: Optional[str] = Field(None, description="Number") + istest: Optional[bool] = Field(None, description="Whether this is a test sender") type: str = Field(..., description="Sender type") + # Additional fields found in actual API response + name: Optional[str] = Field(None, description="Sender name") + + class Config: + allow_population_by_field_name = True + class SendersListResponse(BaseModel): """ @@ -89,22 +103,48 @@ class SendersListResponse(BaseModel): class RegionInformation(BaseModel): - """ - Model for region information. - """ + """Model for region information.""" - region_type: str = Field(..., description="Type of region") - region_name: str = Field(..., description="Name of the region") + region_type: Optional[str] = Field(None, description="Type of region") + region_name: Optional[str] = Field(None, description="Name of the region") class CostInformation(BaseModel): - """ - Model for cost information. - """ + """Model for cost information.""" + + monthly_cost: Optional[str] = Field(None, description="Monthly cost") + setup_cost: Optional[str] = Field(None, description="Setup cost") + currency: Optional[str] = Field(None, description="Currency code") - monthly_cost: str = Field(..., description="Monthly cost") - setup_cost: str = Field(..., description="Setup cost") - currency: str = Field(..., description="Currency code") + +class PurchaseFeature(BaseModel): + """Model for purchased number features.""" + + name: Optional[str] = Field(None, description="Feature name") + reservable: Optional[bool] = Field(None, description="Whether feature is reservable") + region_id: Optional[str] = Field(None, description="Region ID") + number_type: Optional[str] = Field(None, description="Number type") + quickship: Optional[bool] = Field(None, description="Quickship availability") + region_information: Optional[RegionInformation] = Field(None, description="Region information") + phone_number: Optional[str] = Field(None, description="Phone number") + cost_information: Optional[CostInformation] = Field(None, description="Cost information") + best_effort: Optional[bool] = Field(None, description="Best effort flag") + number_provider_type: Optional[str] = Field(None, description="Number provider type") + + +class AvailableNumberFeature(BaseModel): + """Model for features available with a number.""" + + name: Optional[str] = Field(None, description="Feature name") + reservable: Optional[bool] = Field(None, description="Whether feature is reservable") + region_id: Optional[str] = Field(None, description="Region ID") + number_type: Optional[str] = Field(None, description="Number type") + quickship: Optional[bool] = Field(None, description="Quickship availability") + region_information: Optional[Any] = Field(None, description="Region information") + phone_number: Optional[str] = Field(None, description="Phone number") + cost_information: Optional[Any] = Field(None, description="Cost information") + best_effort: Optional[bool] = Field(None, description="Best effort flag") + number_provider_type: Optional[str] = Field(None, description="Number provider type") class NumberFeature(BaseModel): @@ -113,15 +153,6 @@ class NumberFeature(BaseModel): """ name: str = Field(..., description="Feature name") - reservable: bool = Field(..., description="Whether the number is reservable") - region_id: str = Field(..., description="Region identifier") - number_type: str = Field(..., description="Type of number") - quickship: bool = Field(..., description="Whether quickship is available") - region_information: RegionInformation = Field(..., description="Region details") - phone_number: str = Field(..., description="Phone number") - cost_information: CostInformation = Field(..., description="Cost details") - best_effort: bool = Field(..., description="Whether this is best effort") - number_provider_type: str = Field(..., description="Number provider type") class NumberPurchaseResponse(BaseModel): @@ -131,25 +162,68 @@ class NumberPurchaseResponse(BaseModel): Returned from POST /user-api/numbers/buy """ - features: List[NumberFeature] = Field(default_factory=list, description="List of number features") + success: Optional[bool] = Field(None, description="Whether the purchase was successful") + message: Optional[str | List[str]] = Field(None, description="Response message (can be string or array)") + statusCode: Optional[int] = Field(None, description="HTTP status code") + features: Optional[List[PurchaseFeature]] = Field(None, description="Features associated with the number") + phone_number: Optional[str] = Field(None, description="Purchased phone number") + id: Optional[str] = Field(None, alias="_id", description="Unique identifier for the purchased number") + # Add other fields as discovered from actual API responses +# Legacy/compatibility classes class NumberInfo(BaseModel): """ - Model for number information in available numbers response. + Legacy model for number information in available numbers response. + Kept for backward compatibility. """ features: List[NumberFeature] = Field(default_factory=list, description="List of features for this number") +class AvailableNumber(BaseModel): + """ + Model for individual available number information. + """ + + vanity_format: Optional[str] = Field(None, description="Vanity format if applicable") + number_provider_type: Optional[Any] = Field(None, description="Number provider type identifier") + reservable: Optional[bool] = Field(None, description="Whether the number is reservable") + phone_number_type: Optional[str] = Field(None, description="Type of phone number") + region_information: Optional[Any] = Field(None, description="Region details") + quickship: Optional[bool] = Field(None, description="Whether quickship is available") + phone_number: Optional[str] = Field(None, description="Phone number") + cost_information: Optional[Any] = Field(None, description="Cost details") + record_type: Optional[str] = Field(None, description="Record type") + best_effort: Optional[bool] = Field(None, description="Whether this is best effort") + features: Optional[List[AvailableNumberFeature]] = Field(None, description="Available features") + carrier: Optional[str] = Field(None, description="Carrier name") + + class AvailableNumbersResponse(BaseModel): """ Response model for available numbers API. Returned from GET /user-api/numbers + Note: The API returns a direct array, not a wrapped object """ - numbers: List[NumberInfo] = Field(default_factory=list, description="List of available numbers") + def __init__(self, data: List[Dict] = None, **kwargs): + """Custom constructor to handle direct array response.""" + if data is not None and isinstance(data, list): + # Convert list of dicts to list of AvailableNumber objects + numbers = [AvailableNumber.parse_obj(item) for item in data] + super().__init__(numbers=numbers, **kwargs) + else: + super().__init__(**kwargs) + + numbers: List[AvailableNumber] = Field(default_factory=list, description="List of available numbers") + + @classmethod + def parse_from_list(cls, data: List[Dict]) -> "AvailableNumbersResponse": + """Parse from direct array response.""" + numbers = [AvailableNumber.parse_obj(item) for item in data] + return cls(numbers=numbers) # Legacy model for backward compatibility diff --git a/src/devo_global_comms_python/models/whatsapp.py b/src/devo_global_comms_python/models/whatsapp.py index 2564a23..6534799 100644 --- a/src/devo_global_comms_python/models/whatsapp.py +++ b/src/devo_global_comms_python/models/whatsapp.py @@ -237,13 +237,26 @@ class FileUploadRequest(BaseModel): class WhatsAppAccount(BaseModel): """WhatsApp account information model.""" - id: str = Field(..., description="Account ID") - name: str = Field(..., description="Account name") + id: Optional[str] = Field(None, description="Account ID") + name: Optional[str] = Field(None, description="Account name") email: Optional[str] = Field(None, description="Account email") phone: Optional[str] = Field(None, description="Account phone number") - is_approved: bool = Field(..., description="Whether the account is approved") + is_approved: Optional[bool] = Field(None, description="Whether the account is approved") created_at: Optional[datetime] = Field(None, description="Account creation timestamp") updated_at: Optional[datetime] = Field(None, description="Account last updated timestamp") + # Additional fields that might be present in the API response + totalTemplates: Optional[int] = Field(None, description="Total number of templates") + + class Config: + populate_by_name = True + + @classmethod + def model_validate(cls, obj): + # Handle _id -> id mapping for API compatibility + if isinstance(obj, dict) and "_id" in obj and "id" not in obj: + obj = obj.copy() + obj["id"] = obj["_id"] + return super().model_validate(obj) class GetWhatsAppAccountsResponse(BaseModel): @@ -253,11 +266,11 @@ class GetWhatsAppAccountsResponse(BaseModel): Returned from GET /api/v1/user-api/whatsapp/accounts """ - accounts: List[WhatsAppAccount] = Field(..., description="List of WhatsApp accounts") - total: int = Field(..., description="Total number of accounts") - page: int = Field(..., description="Current page number") - limit: int = Field(..., description="Page size limit") - has_next: bool = Field(..., description="Whether there are more pages") + accounts: List[WhatsAppAccount] = Field(default_factory=list, description="List of WhatsApp accounts") + total: Optional[int] = Field(None, description="Total number of accounts") + page: Optional[int] = Field(None, description="Current page number") + limit: Optional[int] = Field(None, description="Page size limit") + has_next: Optional[bool] = Field(None, description="Whether there are more pages") class WhatsAppTemplate(BaseModel): diff --git a/src/devo_global_comms_python/resources/rcs.py b/src/devo_global_comms_python/resources/rcs.py index 2ca23a1..698f84e 100644 --- a/src/devo_global_comms_python/resources/rcs.py +++ b/src/devo_global_comms_python/resources/rcs.py @@ -13,7 +13,7 @@ class RCSResource(BaseResource): # Account Management Endpoints def create_account(self, account_data: Dict[str, Any]) -> "RcsAccountSerializer": """Submit RCS Account.""" - response = self.client.post("/api/v1/user-api/rcs/accounts", json=account_data) + response = self.client.post("user-api/rcs/accounts", json=account_data) from ..models.rcs import RcsAccountSerializer @@ -37,11 +37,27 @@ def get_accounts( if is_approved is not None: params["isApproved"] = is_approved - response = self.client.get("/api/v1/user-api/rcs/accounts", params=params) + response = self.client.get("user-api/rcs/accounts", params=params) + data = response.json() + # If the response contains a nested structure (like {"rcsAccounts": [...]}), + # extract the accounts list for backward compatibility + if isinstance(data, dict) and "rcsAccounts" in data: + accounts_data = data["rcsAccounts"] + elif isinstance(data, list): + accounts_data = data + else: + # Fallback - use the data as-is + accounts_data = data + + # Ensure we have a list to work with + if not isinstance(accounts_data, list): + accounts_data = [] + + # Parse each account into RcsAccountSerializer objects from ..models.rcs import RcsAccountSerializer - return [RcsAccountSerializer.parse_obj(account) for account in response.json()] + return [RcsAccountSerializer.model_validate(account) for account in accounts_data] def verify_account(self, verification_data: Dict[str, Any]) -> "SuccessSerializer": """Verify RCS Account.""" @@ -120,7 +136,7 @@ def get_templates( if id is not None: params["id"] = id - response = self.client.get("/api/v1/user-api/rcs/templates", params=params) + response = self.client.get("user-api/rcs/templates", params=params) return response.json() def delete_template(self, delete_data: Dict[str, Any], approve: Optional[str] = None) -> Dict[str, Any]: @@ -157,7 +173,7 @@ def get_brands( if search is not None: params["search"] = search - response = self.client.get("/api/v1/user-api/rcs/brands", params=params) + response = self.client.get("user-api/rcs/brands", params=params) return response.json() def create_brand(self, brand_data: Dict[str, Any]) -> Dict[str, Any]: diff --git a/src/devo_global_comms_python/resources/sms.py b/src/devo_global_comms_python/resources/sms.py index 6f727e6..fc5f36a 100644 --- a/src/devo_global_comms_python/resources/sms.py +++ b/src/devo_global_comms_python/resources/sms.py @@ -220,7 +220,8 @@ def buy_number( from ..models.sms import NumberPurchaseResponse result = NumberPurchaseResponse.parse_obj(response.json()) - logger.info(f"Number purchased successfully with {len(result.features)} features") + feature_count = len(result.features) if result.features else 0 + logger.info(f"Number purchased successfully with {feature_count} features") return result @@ -281,10 +282,17 @@ def get_available_numbers( # Send request to the exact API endpoint response = self.client.get("user-api/numbers", params=params) - # Parse response according to API spec + # Parse response according to API spec - API returns direct array from ..models.sms import AvailableNumbersResponse - result = AvailableNumbersResponse.parse_obj(response.json()) + response_data = response.json() + if isinstance(response_data, list): + # API returns direct array, use custom parser + result = AvailableNumbersResponse.parse_from_list(response_data) + else: + # Fallback to normal parsing if API changes + result = AvailableNumbersResponse.parse_obj(response_data) + logger.info(f"Retrieved {len(result.numbers)} available numbers") return result diff --git a/src/devo_global_comms_python/services.py b/src/devo_global_comms_python/services.py index e69a332..afda0a5 100644 --- a/src/devo_global_comms_python/services.py +++ b/src/devo_global_comms_python/services.py @@ -1,10 +1,3 @@ -""" -Services namespace for organizing service-related resources. - -This module provides a namespace for accessing service-related functionality -such as contact management, contact groups, and other data management services. -""" - from typing import TYPE_CHECKING from .resources.contact_groups import ContactGroupsResource @@ -43,11 +36,6 @@ def __init__(self, client: "DevoClient"): self.contact_groups = ContactGroupsResource(client) self.contacts = ContactsResource(client) - # Future service resources will be added here: - # self.templates = TemplatesResource(client) - # self.analytics = AnalyticsResource(client) - # etc. - def __repr__(self) -> str: """String representation of the services namespace.""" return f"" From 7f1f516e30d41528f94935445475e27946ca1724 Mon Sep 17 00:00:00 2001 From: Parman Date: Sun, 31 Aug 2025 14:04:25 +0330 Subject: [PATCH 2/2] fixed pipeline errors --- src/devo_global_comms_python/models/sms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devo_global_comms_python/models/sms.py b/src/devo_global_comms_python/models/sms.py index dc449b4..2de0778 100644 --- a/src/devo_global_comms_python/models/sms.py +++ b/src/devo_global_comms_python/models/sms.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, Field @@ -163,7 +163,7 @@ class NumberPurchaseResponse(BaseModel): """ success: Optional[bool] = Field(None, description="Whether the purchase was successful") - message: Optional[str | List[str]] = Field(None, description="Response message (can be string or array)") + message: Optional[Union[str, List[str]]] = Field(None, description="Response message (can be string or array)") statusCode: Optional[int] = Field(None, description="HTTP status code") features: Optional[List[PurchaseFeature]] = Field(None, description="Features associated with the number") phone_number: Optional[str] = Field(None, description="Purchased phone number")