diff --git a/examples/README.md b/examples/README.md index f152e71..63e6eeb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -158,34 +158,34 @@ The following examples show the structure and planned functionality but are not The contact groups resource is fully implemented with all CRUD operations: ### ๐Ÿ”ง Available Functions -1. **List Groups** - `client.contact_groups.list()` +1. **List Groups** - `client.services.contact_groups.list()` - Uses GET `/api/v1/contacts-groups` - Pagination and search support - Field filtering capabilities -2. **Create Group** - `client.contact_groups.create()` +2. **Create Group** - `client.services.contact_groups.create()` - Uses POST `/api/v1/contacts-groups` - Metadata and contact assignment - Validation and error handling -3. **Update Group** - `client.contact_groups.update()` +3. **Update Group** - `client.services.contact_groups.update()` - Uses PUT `/api/v1/contacts-groups/{group_id}` - Partial updates with metadata - Flexible field modification -4. **Get Group** - `client.contact_groups.get_by_id()` +4. **Get Group** - `client.services.contact_groups.get_by_id()` - Uses GET `/api/v1/contacts-groups/{group_id}` - Complete group information retrieval -5. **Delete Group** - `client.contact_groups.delete_by_id()` +5. **Delete Group** - `client.services.contact_groups.delete_by_id()` - Uses DELETE `/api/v1/contacts-groups/{group_id}` - Individual group deletion with approval -6. **Bulk Delete** - `client.contact_groups.delete_bulk()` +6. **Bulk Delete** - `client.services.contact_groups.delete_bulk()` - Uses DELETE `/api/v1/contacts-groups` - Multiple group deletion with contact transfer -7. **Search Groups** - `client.contact_groups.search()` +7. **Search Groups** - `client.services.contact_groups.search()` - Uses GET `/api/v1/contacts-groups` - Advanced search with field filtering @@ -201,23 +201,26 @@ The contact groups resource is fully implemented with all CRUD operations: ```python from devo_global_comms_python.models.contact_groups import CreateContactsGroupDto -# Create new contact group +# Create new contact group (using new services namespace) group_data = CreateContactsGroupDto( name="VIP Customers", description="High-value customers", contact_ids=["contact1", "contact2"], metadata={"priority": "high"} ) -group = client.contact_groups.create(group_data) +group = client.services.contact_groups.create(group_data) # List with pagination -groups = client.contact_groups.list(page=1, limit=10, search="VIP") +groups = client.services.contact_groups.list(page=1, limit=10, search="VIP") # Search groups -search_results = client.contact_groups.search( +search_results = client.services.contact_groups.search( query="priority", fields=["name", "description"] ) + +# Backward compatibility (deprecated - shows warning) +# groups = client.contact_groups.list() # Still works but deprecated ``` ## ๐Ÿ”ง Configuration Notes diff --git a/examples/basic_usage.py b/examples/basic_usage.py index aa79778..b040652 100644 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -37,15 +37,23 @@ def main(): resources.append(("๐Ÿ’ฌ WhatsApp", "Placeholder", "whatsapp_example.py")) if hasattr(client, "contacts"): resources.append(("๐Ÿ‘ฅ Contacts", "Placeholder", "contacts_example.py")) - if hasattr(client, "contact_groups"): - resources.append(("๐Ÿ—‚๏ธ Contact Groups", "Implemented", "contact_groups_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:<12} - {status:<12} -> {example_file}") + 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"): diff --git a/examples/contact_groups_example.py b/examples/contact_groups_example.py index 474f938..96d5e84 100644 --- a/examples/contact_groups_example.py +++ b/examples/contact_groups_example.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import os from datetime import datetime @@ -11,13 +10,6 @@ def main(): - """ - Demonstrate contact groups management capabilities. - - Shows how to create, read, update and delete contact groups - using the Contact Groups API. - """ - # Initialize the client api_key = os.getenv("DEVO_API_KEY") if not api_key: @@ -28,11 +20,13 @@ def main(): print("๐Ÿ—‚๏ธ Devo Global Communications - Contact Groups Management Example") print("=" * 75) + 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.contact_groups.list(page=1, limit=5) + groups_list = client.services.contact_groups.list(page=1, limit=5) 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") @@ -63,7 +57,7 @@ def main(): }, ) - new_group = client.contact_groups.create(new_group_data) + 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}") @@ -88,7 +82,7 @@ def main(): metadata={"updated_by": "api_example", "updated_at": datetime.now().isoformat(), "version": "2.0"}, ) - updated_group = client.contact_groups.update(created_group_id, update_data) + 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}") @@ -102,7 +96,7 @@ def main(): if created_group_id: print(f"\n๐Ÿ” Retrieving specific group {created_group_id}...") try: - specific_group = client.contact_groups.get_by_id(created_group_id) + 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}") @@ -115,7 +109,9 @@ def main(): # Example 5: Search contact groups print("\n๐Ÿ”Ž Searching contact groups...") try: - search_results = client.contact_groups.search(query="demo", fields=["name", "description"], page=1, limit=10) + 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") for i, group in enumerate(search_results.groups, 1): @@ -130,7 +126,7 @@ def main(): # Example 6: Advanced listing with filters print("\n๐Ÿ”ง Advanced group listing with filters...") try: - filtered_groups = client.contact_groups.list( + filtered_groups = client.services.contact_groups.list( page=1, limit=3, search="demo", search_fields=["name", "description"] ) print("โœ… Filtered listing completed!") @@ -157,7 +153,7 @@ def main(): metadata={"bulk_demo": True, "group_number": i + 1}, ) - bulk_group = client.contact_groups.create(bulk_group_data) + 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}") @@ -170,7 +166,7 @@ def main(): if created_group_id: print(f"\n๐Ÿ—‘๏ธ Deleting individual group {created_group_id}...") try: - deleted_group = client.contact_groups.delete_by_id(created_group_id, approve="yes") + 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}") @@ -185,17 +181,17 @@ def main(): backup_group_data = CreateContactsGroupDto( name="Backup Group for Bulk Delete Demo", description="Temporary group to receive transferred contacts" ) - backup_group = client.contact_groups.create(backup_group_data) + backup_group = client.services.contact_groups.create(backup_group_data) # Perform bulk deletion bulk_delete_data = DeleteContactsGroupsDto(group_ids=bulk_group_ids, transfer_contacts_to=backup_group.id) - bulk_delete_result = client.contact_groups.delete_bulk(bulk_delete_data, approve="yes") + 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}") # Clean up backup group - client.contact_groups.delete_by_id(backup_group.id, approve="yes") + client.services.contact_groups.delete_by_id(backup_group.id, approve="yes") print(" ๐Ÿงน Cleaned up backup group") except Exception as e: @@ -205,7 +201,7 @@ def main(): print("\nโš ๏ธ Error handling demonstration...") try: # Try to get a non-existent group - client.contact_groups.get_by_id("non_existent_group_id") + client.services.contact_groups.get_by_id("non_existent_group_id") except Exception as e: print(f"โœ… Properly handled expected error: {type(e).__name__}") @@ -277,7 +273,7 @@ def contact_group_management_workflow(): name=group_type["name"], description=group_type["description"], metadata=group_type["metadata"] ) - group = client.contact_groups.create(group_data) + group = client.services.contact_groups.create(group_data) created_groups.append(group) print(f" โœ… Created: {group.name}") @@ -310,7 +306,7 @@ def contact_group_management_workflow(): }, ) - client.contact_groups.update(vip_group.id, update_data) + client.services.contact_groups.update(vip_group.id, update_data) print(" โœ… Updated VIP group with enhanced metadata") except Exception as e: @@ -326,16 +322,16 @@ def contact_group_management_workflow(): temp_group_data = CreateContactsGroupDto( name="Temporary Archive", description="Temporary group for workflow cleanup" ) - temp_group = client.contact_groups.create(temp_group_data) + temp_group = client.services.contact_groups.create(temp_group_data) # Bulk delete with contact transfer delete_data = DeleteContactsGroupsDto(group_ids=group_ids, transfer_contacts_to=temp_group.id) - client.contact_groups.delete_bulk(delete_data, approve="yes") + client.services.contact_groups.delete_bulk(delete_data, approve="yes") print(f" โœ… Bulk deleted {len(group_ids)} demonstration groups") # Delete temporary group - client.contact_groups.delete_by_id(temp_group.id, approve="yes") + client.services.contact_groups.delete_by_id(temp_group.id, approve="yes") print(" โœ… Cleaned up temporary archive group") except Exception as e: diff --git a/src/devo_global_comms_python/client.py b/src/devo_global_comms_python/client.py index 71b04ed..b268a30 100644 --- a/src/devo_global_comms_python/client.py +++ b/src/devo_global_comms_python/client.py @@ -6,44 +6,46 @@ from .auth import APIKeyAuth from .exceptions import DevoAPIException, DevoAuthenticationException, DevoException, DevoMissingAPIKeyException -from .resources.contact_groups import ContactGroupsResource from .resources.contacts import ContactsResource from .resources.email import EmailResource from .resources.messages import MessagesResource from .resources.rcs import RCSResource from .resources.sms import SMSResource from .resources.whatsapp import WhatsAppResource +from .services import ServicesNamespace class DevoClient: """ Main client for interacting with the Devo Global Communications API. - This client follows a resource-based pattern, - where each communication channel (SMS, Email, etc.) is accessed as a - resource with its own methods. + This client follows a resource-based pattern with two main namespaces: + - Messaging resources: Direct access to communication channels (SMS, Email, etc.) + - Services namespace: Organized access to data management services Example: >>> client = DevoClient(api_key="your-api-key") - >>> # Send SMS using new API + >>> + >>> # Messaging resources (direct access) >>> response = client.sms.send_sms( ... recipient="+1234567890", ... message="Hello, World!", ... sender="+0987654321" ... ) >>> print(f"Message ID: {response.id}") - >>> print(f"Status: {response.status}") >>> - >>> # Get available senders - >>> senders = client.sms.get_senders() - >>> for sender in senders.senders: - ... print(f"Sender: {sender.phone_number}") + >>> # Services namespace (organized access) + >>> groups = client.services.contact_groups.list() + >>> for group in groups.groups: + ... print(f"Group: {group.name}") >>> - >>> # Get available numbers - >>> numbers = client.sms.get_available_numbers(region="US", limit=5) - >>> for number_info in numbers.numbers: - ... for feature in number_info.features: - ... print(f"Number: {feature.phone_number}") + >>> # Omni-channel messaging + >>> from devo_global_comms_python.models.messages import SendMessageDto + >>> message = client.messages.send(SendMessageDto( + ... channel="sms", + ... to="+1234567890", + ... payload={"text": "Hello World"} + ... )) """ DEFAULT_BASE_URL = "https://global-api-development.devotel.io/api/v1" @@ -82,15 +84,17 @@ def __init__( # Set up session with retry strategy self.session = session or self._create_session(max_retries) - # Initialize resources + # Initialize messaging resources self.sms = SMSResource(self) self.email = EmailResource(self) self.whatsapp = WhatsAppResource(self) self.rcs = RCSResource(self) self.contacts = ContactsResource(self) - self.contact_groups = ContactGroupsResource(self) self.messages = MessagesResource(self) + # Initialize services namespace + self.services = ServicesNamespace(self) + def _create_session(self, max_retries: int) -> requests.Session: """Create a requests session with retry strategy.""" session = requests.Session() diff --git a/src/devo_global_comms_python/services.py b/src/devo_global_comms_python/services.py new file mode 100644 index 0000000..cef1349 --- /dev/null +++ b/src/devo_global_comms_python/services.py @@ -0,0 +1,51 @@ +""" +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 + +if TYPE_CHECKING: + from .client import DevoClient + + +class ServicesNamespace: + """ + Namespace for accessing service-related resources. + + This namespace organizes service-related functionality separately from + messaging resources, providing a clear conceptual separation between + sending messages and managing data/services. + + Example: + >>> client = DevoClient(api_key="your-api-key") + >>> # Access contact groups through services namespace + >>> groups = client.services.contact_groups.list() + >>> # Future: contacts, templates, analytics, etc. + >>> contacts = client.services.contacts.list() + """ + + def __init__(self, client: "DevoClient"): + """ + Initialize the services namespace. + + Args: + client: The DevoClient instance + """ + self._client = client + + # Initialize service resources + self.contact_groups = ContactGroupsResource(client) + + # Future service resources will be added here: + # self.contacts = ContactsResource(client) + # self.templates = TemplatesResource(client) + # self.analytics = AnalyticsResource(client) + # etc. + + def __repr__(self) -> str: + """String representation of the services namespace.""" + return f"" diff --git a/tests/test_contact_groups.py b/tests/test_contact_groups.py index fc0898e..ba0abc9 100644 --- a/tests/test_contact_groups.py +++ b/tests/test_contact_groups.py @@ -3,6 +3,7 @@ import pytest +from src.devo_global_comms_python import DevoClient from src.devo_global_comms_python.models.contact_groups import ( ContactsGroup, ContactsGroupListResponse, @@ -496,3 +497,28 @@ def test_empty_list_response(self): assert response.total == 0 assert response.page == 1 assert response.total_pages == 0 + + +class TestServicesNamespace: + """Test cases for the services namespace integration.""" + + def setup_method(self): + """Set up test fixtures.""" + self.client = DevoClient(api_key="test_api_key") + + def test_services_namespace_exists(self): + """Test that services namespace is available.""" + assert hasattr(self.client, "services") + assert self.client.services is not None + + def test_contact_groups_available_via_services(self): + """Test that contact groups is available via services namespace.""" + assert hasattr(self.client.services, "contact_groups") + assert self.client.services.contact_groups is not None + assert isinstance(self.client.services.contact_groups, ContactGroupsResource) + + def test_services_namespace_string_representation(self): + """Test string representation of services namespace.""" + services_repr = repr(self.client.services) + assert "ServicesNamespace" in services_repr + assert "DevoClient" in services_repr