Giter Site home page Giter Site logo

healthitau / pyconnectwise Goto Github PK

View Code? Open in Web Editor NEW
39.0 7.0 6.0 4.69 MB

A library for simplifying interactions with the ConnectWise Manage API in Python

Home Page: https://healthit.com.au

License: GNU General Public License v3.0

Python 99.81% Jinja 0.19%
annotated api automate client connectwise manage msp python typed psa

pyconnectwise's Introduction

Health IT Logo

pyConnectWise - An API library for ConnectWise Manage and ConnectWise Automate, written in Python

pre-commit.ci status

pyConnectWise is a full-featured, type annotated API client written in Python for the ConnectWise APIs based off their OpenAPI schemas.

This library has been developed with the intention of making the ConnectWise APIs simple and accessible to non-coders while allowing experienced coders to utilize all features the API has to offer without the boilerplate.

pyConnectWise currently supports both ConnectWise Manage and ConnectWise Automate.

Features:

  • 100% API Coverage. All endpoints and response models have had their code generated from the ConnectWise Manage and ConnectWise Automate OpenAPI schemas.
  • Non-coder friendly. 100% annotated for full IDE auto-completion. Clients handle requests and authentication - just plug the right details in and go!
  • Fully annotated. This library has a strong focus on type safety and type hinting. Models are declared and parsed using Pydantic

pyConnectWise is currently in pre-release. This means that while it does work, you may come across issues and inconsistencies.

As all Endpoint and Model code has been generated, not all of it has been tested. YMMV.

Endpoint generation is custom-built, but Pydantic models have been generated using a customised fork of datamodel-code-generator

Known Issues:

  • Currently only parses Response models. No input models yet.
  • As this project is still a WIP, documentation or code commentary may not always align.

Roadmap:

  • Automate API Support - โœ… Done
  • Robust error handling - โœ… Done
  • Tests - ๐Ÿšง In Progress
  • Input model validation - ๐Ÿ“ˆ Planned
  • ScreenConnect (Control) API Support - ๐Ÿ“ˆ Planned
  • Batch requests - ๐Ÿ“ˆ Planned

How-to:

Install

Open a terminal and run pip install pyconnectwise or poetry add pyconnectwise

Initializing the API Clients

ConnectWise Manage

from pyconnectwise import ConnectWiseManageAPIClient

# init client
manage_api_client = ConnectWiseManageAPIClient(
  # your company name,
  # manage instance url,
  # your api client id,
  # your api public key,
  # your api private key,
  # optionally, a Config object for customizing API interactions. See [Additional Configuration].
)

ConnectWise Automate

from pyconnectwise import ConnectWiseAutomateAPIClient

# init client
automate_api_client = ConnectWiseAutomateAPIClient(
  # your automate url
  # your client id
  # automate api username
  # automate api password,
  # optionally, a Config object for customizing API interactions. See [Additional Configuration].
)

Working with Endpoints

Endpoints are 1:1 to what's available for both the ConnectWise Manage and ConnectWise Automate as code is generated from their OpenAPI spec.

For more information, check out the following resources:

Get many

### Manage ###

# sends GET request to /company/companies endpoint
companies = manage_api_client.company.companies.get()

### Automate ###

# sends GET request to /clients endpoint
clients = automate_api_client.clients.get()

Get one

### Manage ###

# sends GET request to /company/companies/{id} endpoint
company = manage_api_client.company.companies.id(250).get()

### Automate ###

# sends GET request to /clients/{id} endpoint
client = automate_api_client.clients.id(250).get()

Get with params

### Manage ###

# sends GET request to /company/companies with a conditions query string
conditional_company = manage_api_client.company.companies.get(params={
  'conditions': 'company/id=250'
})

### Automate ###
# sends GET request to /clients endpoint with a condition query string
# note that the Automate API expects the string 'condition' where-as the Manage API expects the string 'conditions'
conditional_client = automate_api_client.clients.get(params={
  'condition': 'company/id=250'
})

Child Endpoints

The ConnectWise APIs have many instances of endpoints with path parameters - for example, /company/companies/{company_id}/sites

This also exists in the library. Endpoints provide an id method for setting the ID and traversing down the path.

Example using /company/companies/{company_id}/sites
# equivalent to GET /company/companies/250/sites
sites = manage_api_client.company.companies.id(250).sites.get()

Pagination

The ConnectWise Manage API paginates data for performance reasons through the page and pageSize query parameters. pageSize is limited to a maximum of 1000.

To make working with paginated data easy, Endpoints that implement a GET response with an array also supply a paginated() method. Under the hood this wraps a GET request, but does a lot of neat stuff to make working with pages easier.

Working with pagination

# initialize a PaginatedResponse instance for /company/companies, starting on page 1 with a pageSize of 100
paginated_companies = manage_api_client.company.companies.paginated(1,100)

# access the data from the current page using the .data field
page_one_data = paginated_companies.data

# if there's a next page, retrieve the next page worth of data
paginated_companies.get_next_page()

# if there's a previous page, retrieve the previous page worth of data
paginated_companies.get_previous_page()

# iterate over all companies on the current page
for company in paginated_companies:
  # ... do things ...

# iterate over all companies in all pages
# this works by yielding every item on the page, then fetching the next page and continuing until there's no data left
for company in paginated_companies.all():
  # ... do things ...

Additional Configuration

As of version 0.4.6, pyConnectWise clients now accept a new Config object for additional API interaction configuration.

Implementation

from pyconnectwise import ConnectWiseManageAPIClient, ConnectWiseAutomateAPIClient
from pyconnectwise.config import Config

# create an instance of Config with your own changes...
config = Config(max_retries = 5)

# ... and hand off to the clients during initialization
manage_api_client = ConnectWiseManageAPIClient(config = config)
automate_api_client = ConnectWiseAutomateAPIClient(config = config)

Supported Options

As of version 0.4.6, the following Config options are supported:

  • max_retries - The number of times to re-attempt a request if a HTTP 500 error occurs. Defaults to 3.

Examples

Get all agreements, then all additions for an agreement

agreements = api.finance.agreements.paginated(1, 1000)
for agreement in agreements.all():
    additions = api.finance.agreements.id(agreement.id).additions.get()

Get all service tickets with an ID > 1000

tickets = api.service.tickets.get(params={
    'conditions': 'id > 1000'
})

Contributing

Contributions to the project are welcome. If you find any issues or have suggestions for improvement, please feel free to open an issue or submit a pull request.

When working on the project, please note that there's a few requirements you'll need to install in order to run the project locally. These requirements are stored in requirements.txt

You can install these requirements by opening a terminal, navigating to the repo's directory and running pip install -r requirements.txt

Preparing your development environment

Prerequisites:

Quick dependencies install on apt-based systems (using pipx to manage python tools)

sudo apt install pipx
pipx install poetry
pipx install pre-commit

Setting up your development environment

poetry install --with=dev
pre-commit install

Testing

poetry run pytest

Running code generation

poetry run python -m pyconnectwise_generator <path to schema file>

Supporting the project

โค๏ธ the project and would like to show your support? Please consider donating to the following charities:

pyconnectwise's People

Contributors

emoonarchitech avatar pedro-101 avatar pre-commit-ci[bot] avatar sherbang avatar yoshify avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyconnectwise's Issues

Logging

Hi All, I think it would be handy to implement some basic logging, just using the basic python logging module. I'm happy to do this myself and sprinkle some logging around the code if you guys aren't working on anything in the background.

Support Extra Data Fields in Automate

Endpoints for EDF's exist for Automate as per documentation here, but are undocumented within their OpenAPI schemas.

This means the library never generated them because it doesn't know they exist.

This is a pretty important one to be missing (at least in our personal use cases), so whether we workaround this ourselves or bring it to ConnectWise's attention to get it fixed themselves is up to decision.

The 2 options for DIYing support:

  • We create our own OpenAPI documentation for the extrafields endpoints (would require a bit of work to discern response structure)
  • We hand-make the endpoints and models

ConnectWiseEndpoint._make_request() argument "as_json" not implemented

Hi all, while digging around in this cool piece of software you've made, I found an unimplemented argument in the _make_request() function. specifically, "as_json" is mentioned in the docstring but from what I can see does not seem to be implemented in the function itself.

I also looked in the requests.api module and it does not appear to be present as a parameter there either. However, I have not been able to test this myself so apologies if this is onerous.

Should this be implemented in code or removed from the docs?

as_json (bool, optional): Whether to return the JSON response or the Response object. Defaults to True.

Issue with patch requests

Hi All, I'm attempting a patch request to the CompanyConfigurationsIdEndpoint endpoint, but am encountering an issue with the typing of what data I'm allowed to pass through. The ConnectWise API specifies that patch requests have to come through as an array containing the data ([ ] brackets around the json {} dict) for the patch request to be accepted, but due to the chain of type annotations from CompanyConfigurationsIdEndpoint .patch() down to the base _make_request() function, data cannot be passed through to the requests function as a List.

See below for CW docs on patch requests & the format they need to follow:
https://developer.connectwise.com/Best_Practices/PSA_API_Requests?mt-learningpath=manage#Patch

Here's someone from reddit solving this issue quite a while ago, passing an array of dicts into the json field, which it also looks like the _make_request function is using to pass 'data' args into.

What would be the best way to approach fixing this? just changing the input types on _make_requests to also accept List[Dict[...]]?

pydantic error

D:\Documents\programming\cw-automations\venv\lib\site-packages\pydantic\_internal\_config.py:257: UserWarning: Valid config keys have changed in V2:
* 'allow_population_by_field_name' has been renamed to 'populate_by_name'
  warnings.warn(message, UserWarning)
D:\Documents\programming\cw-automations\venv\lib\site-packages\pydantic\_internal\_fields.py:126: UserWarning: Field "model_number" has conflict with protected namespace "model_".

You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`.
  warnings.warn(

Getting this error when trying a simple request:

api.company.companies.get()

Configurations with custom fields cause pydantic to choke

Hi,

When calling my configuration list for domain names it has custom fields in, and this seems to cause pydantic to choke.

Calling configuration = cwm.company.configurations.paginated(1,1000).params={'conditions': 'type/id=55'}

Traceback (most recent call last):
  File "domain-expiry-refresh.py", line 38, in <module>
    configuration = cwm.company.configurations.paginated(1,1000).params={'conditions': 'type/id=55'}
  File "C:\Program Files (x86)\Python311-32\Lib\site-packages\pyconnectwise\endpoints\manage\CompanyConfigurationsEndpoint.py", line 91, in paginated
    return PaginatedResponse(
  File "C:\Program Files (x86)\Python311-32\Lib\site-packages\pyconnectwise\responses\paginated_response.py", line 55, in __init__
    self._initialize(response, response_model, endpoint, page, page_size, params)
  File "C:\Program Files (x86)\Python311-32\Lib\site-packages\pyconnectwise\responses\paginated_response.py", line 101, in _initialize
    self.data: list[TModel] = [
  File "C:\Program Files (x86)\Python311-32\Lib\site-packages\pyconnectwise\responses\paginated_response.py", line 102, in <listcomp>
    response_model.model_validate(d) for d in response.json()
  File "C:\Program Files (x86)\Python311-32\Lib\site-packages\pydantic\main.py", line 509, in model_validate
    return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 2 validation errors for CompanyConfiguration
questions.0.answer
  Input should be a valid dictionary [type=dict_type, input_value='configuration_custom_secret_value_1', input_type=str]
    For further information visit https://errors.pydantic.dev/2.6/v/dict_type
questions.1.answer
  Input should be a valid dictionary [type=dict_type, input_value='configuration_custom_secret_value_2, input_type=str]
    For further information visit https://errors.pydantic.dev/2.6/v/dict_type

Hoping this isn't something too weird! I have removed the actual values that were returned as they were confidential and replaced it with configuration_custom_secret_value

pydantic model causes error for system.members endpoint

other endpoints work fine.

im simply trying to use the get method without any pagination or paramaters.

errant line:
members = api.system.members.get()

python trace:
System.Private.CoreLib: Exception while executing function: Functions.timesheet_audit. System.Private.CoreLib: Result: Failure Exception: ValidationError: 1 validation error for Member lastName Field required [type=missing, input_value={'id': 149, 'identifier':...MemberTemplateRecId': 0}, input_type=dict] For further information visit https://errors.pydantic.dev/2.0.2/v/missing Stack: File "C:\ProgramData\chocolatey\lib\azure-functions-core-tools\tools\workers\python\3.10\WINDOWS\X64\azure_functions_worker\dispatcher.py", line 495, in _handle__invocation_request call_result = await self._loop.run_in_executor( File "c:\python310\lib\concurrent\futures\thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) File "C:\ProgramData\chocolatey\lib\azure-functions-core-tools\tools\workers\python\3.10\WINDOWS\X64\azure_functions_worker\dispatcher.py", line 768, in _run_sync_func return ExtensionManager.get_sync_invocation_wrapper(context, File "C:\ProgramData\chocolatey\lib\azure-functions-core-tools\tools\workers\python\3.10\WINDOWS\X64\azure_functions_worker\extension.py", line 215, in _raw_invocation_wrapper result = function(**args) File "project_dir\timesheet_audit\__init__.py", line 8, in main processed = timesheet_audit.process() File "project_dir\timesheet_audit\timesheet_audit.py", line 16, in process members = api.system.members.get() File "venv_dir\.venv\lib\site-packages\pyconnectwise\endpoints\manage\SystemMembersEndpoint.py", line 73, in get return self._parse_many(Member, super()._make_request("GET", data=data, params=params).json()) File "venv_dir\.venv\lib\site-packages\pyconnectwise\endpoints\base\connectwise_endpoint.py", line 166, in _parse_many return [model_type.model_validate(d) for d in data] File "venv_dir\.venv\lib\site-packages\pyconnectwise\endpoints\base\connectwise_endpoint.py", line 166, in <listcomp> return [model_type.model_validate(d) for d in data] File "venv_dir\.venv\lib\site-packages\pydantic\main.py", line 480, in model_validate return cls.__pydantic_validator__.validate_python( .

Configurations extraction

I'm trying to use the library to export the full list of active configs, but I'm only getting a subset. How can I get a complete dump of all configurations for a specified company?

paginated tickets does not return expected records for original 'conditions' params

When testing against the below code using paginated response, the first page returns the expected results, but subsequent calls to tickets.get_next_page() return pages starting at the oldest tickets in Connectwise.

params={
    'conditions':'id > 222000 and board/name="Help Desk"',
    'fields' : 'id,summary'
}
tickets = cw.service.tickets.paginated(1,10,params=params)

222000
222001
222002
...

tickets.get_next_page()

1000 (oldest ticket)
1001
1002
...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.