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.
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
-
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)
-
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")
-
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
- API Reference - Complete API documentation
- AI Agent Guide - Guide for AI assistants using this library
Requirements
- Python 3.9+
requestslibrary
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:
- GitHub Issues: Ruderali/ConnectPyse-Manage/issues
- Documentation: See
docs/directory
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
afdbc8411e461ae435448702a12af29f4943e25b9c98ba944450c2165604232d
|
|
| MD5 |
dbf570f97786fa72aa7b3575dad0fc14
|
|
| BLAKE2b-256 |
3761dc886962b38f582a9e7e778bc8553a83d03512938923ba92101c4cac8446
|
File details
Details for the file connectpyse_manage-0.0.5-py3-none-any.whl.
File metadata
- Download URL: connectpyse_manage-0.0.5-py3-none-any.whl
- Upload date:
- Size: 28.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fad20f33584bc06b72f64a84ccc007ccd608c79695b90c79e40bbc8f74c7ceb8
|
|
| MD5 |
24c38adec7b9f52b43785bae4260139d
|
|
| BLAKE2b-256 |
cab60098a1e4155b018c00ff62b03f5eedeb356d6e7762fe469448cc1e18b832
|