Skip to main content

A Python toolkit for the ConnectWise Manage API with full type support

Project description

ConnectWise Manage Python Integration

A Python toolkit for the ConnectWise Manage API with full type support and error handling.

Python 3.9+ License: MIT

Features

  • 🎯 Typed Dataclasses - Ticket, Configuration, and Note models with convenient properties
  • 🔒 Credential Protection - SecretString wrapper prevents accidental password exposure
  • 🚨 Smart Error Handling - Specific exceptions for different error types (404s return None)
  • 🔄 Full HTTP Support - GET, POST, PATCH, PUT, DELETE methods
  • 📦 Opinionated Defaults - TicketDefaults for consistent ticket creation
  • 🎨 Clean API - No environment variable magic, explicit configuration

Installation

pip install ConnectPyse-Manage

For development:

pip install -e .

Quick Start

1. Initialize the Client

from connectwise import ConnectWiseClient

cw = ConnectWiseClient(
    base_url="https://connect.example.com",
    client="YourCompany",
    username="api_user",
    password="api_password",
    client_id="your-client-id-uuid"
)

2. Work with Tickets

# Get a ticket (returns Ticket object or None)
ticket = cw.get_ticket(ticket_id=12345)
if ticket:
    print(f"{ticket.summary} - {ticket.status_name}")
    print(f"Company: {ticket.company_name}")
    print(f"Priority: {ticket.priority_name}")

# Create a ticket
new_ticket = cw.create_ticket(
    summary="Server offline",
    body="PROD-WEB-01 is unreachable",
    company_id=250,
    board_id=1,
    priority_id=8
)
print(f"Created ticket #{new_ticket.id}")

# Update ticket status
updated = cw.update_ticket_status(
    ticket_id=new_ticket.id,
    status_id=456
)

# Add a note
note = cw.add_ticket_note(
    ticket_id=new_ticket.id,
    note_text="Investigating the issue",
    internal=True
)

3. Work with Configurations

# Find configurations by name
configs = cw.get_configurations(
    conditions='name contains "PROD" AND company/id=250'
)

for config in configs:
    print(f"{config.name} - IP: {config.ipAddress}")

# Attach configuration to ticket
cw.attach_configuration(
    ticket_id=12345,
    config_id=configs[0].id
)

4. Search and Filter

# Get open tickets for a company
tickets = cw.get_tickets(
    conditions="closedFlag=false AND company/id=250",
    pagesize=1000
)

# Search by summary text
urgent_tickets = cw.get_tickets(
    conditions='summary contains "urgent" AND priority/id>=8'
)

Using Ticket Defaults

For applications that create many similar tickets:

from connectwise import ConnectWiseClient, TicketDefaults

# Define defaults
defaults = TicketDefaults(
    company_id=250,
    board_id=1,
    priority_id=8,
    source_id=42  # RMM integration
)

# Initialize with defaults
cw = ConnectWiseClient(
    base_url="https://connect.example.com",
    client="YourCompany",
    username="api_user",
    password="api_password",
    client_id="uuid",
    ticket_defaults=defaults
)

# Create tickets without repeating parameters
ticket = cw.create_ticket(
    summary="Issue summary",
    body="Issue description"
    # company_id, board_id, priority_id, source_id use defaults
)

Error Handling

The integration uses specific exceptions for different error types:

from connectwise import (
    ConnectWiseAPIError,
    ConnectWiseNotFoundError,
    ConnectWiseRateLimitError,
    ConnectWiseBadRequestError
)

try:
    ticket = cw.update_ticket_status(ticket_id=123, status_id=456)
except ConnectWiseNotFoundError:
    print("Ticket doesn't exist")
except ConnectWiseRateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after} seconds")
except ConnectWiseBadRequestError as e:
    print(f"Invalid request: {e}")
except ConnectWiseAPIError as e:
    print(f"API error [{e.status_code}]: {e}")

Note: 404s return None for get operations instead of raising exceptions:

ticket = cw.get_ticket(ticket_id=999999)
if ticket is None:
    print("Ticket not found")  # No exception raised

Dataclass Properties

All models provide convenient properties for nested data:

ticket = cw.get_ticket(ticket_id=12345)

# Clean property access
print(ticket.company_name)      # Instead of ticket.company.get('name')
print(ticket.status_name)       # Instead of ticket.status.get('name')
print(ticket.priority_name)     # Instead of ticket.priority.get('name')

# Datetime parsing included
if ticket.is_closed:
    print(f"Closed: {ticket.closed_datetime}")  # Returns datetime object

# Configuration properties
config = cw.get_configuration(config_id=67890)
print(f"{config.name} - {config.type_name}")
print(f"Company: {config.company_name}")
print(f"Active: {config.is_active}")

Advanced Usage

Custom Fields and Filtering

# Get tickets with specific fields only (returns raw dict)
result = cw.get(
    "service/tickets",
    conditions="closedFlag=false",
    fields="id,summary,status"
)

# Use base HTTP methods for custom endpoints
result = cw.post("custom/endpoint", data={...})
result = cw.patch("service/tickets", record_id=123, operations=[...])
success = cw.delete("service/tickets", record_id=123)

Complete Workflow Example

# Find server configuration
configs = cw.get_configurations(
    conditions='name="PROD-WEB-01" AND company/id=250'
)

if configs:
    config = configs[0]

    # Create ticket
    ticket = cw.create_ticket(
        summary=f"Server {config.name} Offline",
        body=f"Server unreachable. IP: {config.ipAddress}",
        company_id=250,
        board_id=1,
        priority_id=8
    )

    # Attach configuration
    cw.attach_configuration(ticket.id, config.id)

    # Add investigation note
    cw.add_ticket_note(
        ticket_id=ticket.id,
        note_text="Checking server connectivity",
        internal=True
    )

    # Get ticket URL for external systems
    ticket_url = cw.get_ticket_url(ticket.id)
    print(f"View ticket: {ticket_url}")

Rate Limiting

The client automatically retries requests that receive a 429 Too Many Requests response, using exponential backoff and honoring the Retry-After header when ConnectWise includes one.

cw = ConnectWiseClient(
    base_url="https://connect.example.com",
    client="YourCompany",
    username="api_user",
    password="api_password",
    client_id="your-client-id-uuid",
    max_retries=3,        # retries per request (default: 3)
    retry_backoff_base=2  # backoff in seconds: 2s, 4s, 8s (default: 2)
)

If all retries are exhausted, a ConnectWiseRateLimitError is raised. To avoid hitting rate limits in the first place, check record counts before fetching large result sets — see Performance Tips.

Performance Tips

  1. Check the count before fetching to avoid heavy queries:

    from datetime import datetime, timezone, timedelta
    
    seven_days_ago = datetime.now(timezone.utc) - timedelta(days=7)
    date_str = seven_days_ago.strftime("%Y-%m-%dT%H:%M:%SZ")
    conditions = f'board/name="Events" AND dateEntered>=[{date_str}]'
    
    # Single lightweight request — no records fetched
    count = cw.get_ticket_count(conditions=conditions)
    print(f"{count} tickets")
    
    # Only fetch if the volume is manageable
    if count <= 500:
        tickets = cw.get_tickets(conditions=conditions)
    
  2. Use specific conditions to limit results:

    # ❌ Slow - fetches everything
    tickets = cw.get_tickets()
    
    # ✅ Fast - filtered query
    tickets = cw.get_tickets(conditions="company/id=250 AND closedFlag=false")
    
  3. Leverage the fields parameter for partial data:

    result = cw.get("service/tickets", fields="id,summary,status")
    

Credential Security

Passwords are automatically wrapped in SecretString to prevent accidental exposure in logs:

cw = ConnectWiseClient(...)
print(cw._password)  # Output: **********

ConnectWise Conditions Syntax

ConnectWise uses a specific syntax for filtering:

# Basic operators: =, !=, <, >, <=, >=
conditions = "closedFlag=false"

# Logical operators: AND, OR
conditions = "closedFlag=false AND company/id=250"

# String operators: contains, like
conditions = 'summary contains "server"'
conditions = 'name like "PROD-%"'

# Nested properties
conditions = "company/id=250 AND status/name='Open'"

# Complex conditions
conditions = "(closedFlag=false AND priority/id>=8) OR summary contains 'urgent'"

Documentation

Requirements

  • Python 3.9+
  • requests library

Development

# Clone the repository
git clone https://github.com/Ruderali/ConnectPyse-Manage.git
cd ConnectPyse-Manage

# Install in development mode
pip install -e .

# Run tests
pytest

Contributing

Contributions are welcome! This library aims to stay a pure API wrapper without business logic.

Design Principles:

  • Keep it simple - no complex abstractions
  • Return dataclasses from high-level methods
  • Return raw JSON from base HTTP methods
  • 404s return None for get operations
  • Raise specific exceptions for errors
  • No environment variable reading
  • No business logic

License

MIT License - see LICENSE file for details

Related Projects

  • npycentral - N-Central RMM Python integration (same author)

Support

For issues and questions:


Note: This is an unofficial library and is not affiliated with or endorsed by ConnectWise.

Project details


Download files

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

Source Distribution

connectpyse_manage-0.0.5.tar.gz (19.6 kB view details)

Uploaded Source

Built Distribution

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

connectpyse_manage-0.0.5-py3-none-any.whl (28.8 kB view details)

Uploaded Python 3

File details

Details for the file connectpyse_manage-0.0.5.tar.gz.

File metadata

  • Download URL: connectpyse_manage-0.0.5.tar.gz
  • Upload date:
  • Size: 19.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for connectpyse_manage-0.0.5.tar.gz
Algorithm Hash digest
SHA256 afdbc8411e461ae435448702a12af29f4943e25b9c98ba944450c2165604232d
MD5 dbf570f97786fa72aa7b3575dad0fc14
BLAKE2b-256 3761dc886962b38f582a9e7e778bc8553a83d03512938923ba92101c4cac8446

See more details on using hashes here.

File details

Details for the file connectpyse_manage-0.0.5-py3-none-any.whl.

File metadata

File hashes

Hashes for connectpyse_manage-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 fad20f33584bc06b72f64a84ccc007ccd608c79695b90c79e40bbc8f74c7ceb8
MD5 24c38adec7b9f52b43785bae4260139d
BLAKE2b-256 cab60098a1e4155b018c00ff62b03f5eedeb356d6e7762fe469448cc1e18b832

See more details on using hashes here.

Supported by

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