Search code examples
pythonpython-2.7zendesk-api

Why are my ZenDesk macros being updated, but no change actually going through?


I was trying to bulk edit the signature of my personal macros on ZenDesk, and the only way to do that is via the API. So I wrote this quick Python script to try to do it:

import sys
import time
import logging
import requests
import re

start_time = time.time()

# Set up logging
logger = logging.getLogger()
log_handler = logging.StreamHandler(sys.stdout)
log_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(funcName)s - line %(lineno)d"))
log_handler.setLevel(logging.DEBUG)
logger.addHandler(log_handler)
logger.setLevel(logging.DEBUG)

def doTheGet(url, user, pwd):
        response = requests.get(url, auth=(user + "/token", pwd))

        if response.status_code != 200:
                logger.error("Status: %s (%s) Problem with the request. Exiting. %f seconds elapsed" % (response.status_code, response.reason, time.time() - start_time))
                exit()

        data = response.json()
        return data

def doThePut(url, updated_data, user, pwd):
        response = requests.put(url, json="{'macro': {'actions': %r}}" % updated_data, headers={"Content-Type": "application/json"}, auth=(user + "/token", pwd))

        if response.status_code != 200:
                logger.error("Status: %s (%s) Problem with the request. Exiting. %f seconds elapsed" % (response.status_code, response.reason, time.time() - start_time))
                exit()

        data = response.json()
        return data

def getMacros():
        macros = {}

        data = doTheGet("https://mydomain.zendesk.com/api/v2/macros.json", "[email protected]", "111tokenZZZ")

        def getMacros(macro_list, page, page_count):
                if not page:
                        for macro in macro_list:
                                if macro["restriction"] and macro["active"]:
                                        if macro["restriction"]["type"] == "User":
                                                macros[macro["id"]] = macro["actions"]
                else:
                        for macro in macro_list:
                                if macro["restriction"] and macro["active"]:
                                        if macro["restriction"]["type"] == "User":
                                                macros[macro["id"]] = macro["actions"]

                        page_count += 1
                        new_data = doTheGet(page, "[email protected]", "111tokenZZZ")
                        new_macs = new_data["macros"]
                        new_next_page = new_data["next_page"]
                        getMacros(new_macs, new_next_page, page_count)


        macs = data["macros"]
        current_page = 1
        next_page = data["next_page"]
        getMacros(macs, next_page, current_page)
        return macros

def updateMacros():
        macros = getMacros()

        regular = "RegEx to match signature to be replaced$" #since some macros already have the updated signature

        for macro in macros:
                for action in macros[macro]:
                        if action["field"] == "comment_value":
                                if re.search(regular, action["value"][1]):
                                        ind = action["value"][1].rfind("\n")
                                        action["value"][1] = action["value"][1][:ind] + "\nNew signature"

        return macros

macs = updateMacros()

for mac in macs:
        doThePut("https://mydomain.zendesk.com/api/v2/macros/%d.json" % (mac), macs[mac], "[email protected]", "111tokenZZZ")

Now, everything's running as expected, and I get no errors. When I go to my macros on ZenDesk and sort them by last updated, I do see that the script did something, since they show as being last updated today. However, nothing changes on them. I made sure the data I'm sending over is edited (updateMacros is doing its job). I made sure the requests send back an OK response. So I'm sending updated data, getting back a 200 response, but the response sent back shows me the macros as they were before, with zero changes.

The only thing that occurs to me as maybe being wrong in some way is the format of the data I'm sending over, or something of the sort. But even then, I'd expect the response to not be a 200, then...

What am I missing here?


Solution

  • Looks like you're double-encoding the JSON data in your PUT request:

    response = requests.put(url, json="{'macro': {'actions': %r}}" % updated_data, headers={"Content-Type": "application/json"}, auth=(user + "/token", pwd))
    

    The json parameter expects an object, which it then dutifully encodes as JSON and sends as the body of the request; this is merely a convenience; the implementation is simply,

        if not data and json is not None:
            # urllib3 requires a bytes-like body. Python 2's json.dumps
            # provides this natively, but Python 3 gives a Unicode string.
            content_type = 'application/json'
            body = complexjson.dumps(json)
            if not isinstance(body, bytes):
                body = body.encode('utf-8')
    

    (source: https://github.com/kennethreitz/requests/blob/master/requests/models.py#L424)

    Since the value is always passed through json.dumps(), if you pass a string representing already-encoded JSON it will itself be encoded:

    "{\'macro\': {\'actions\': [{\'field\': \'comment_value\', \'value\': [\'channel:all\', \'Spiffy New Sig that will Never Be Saved\']}]}}"
    

    ZenDesk, upon being given JSON it doesn't expect, updates the updated_at field and... Does nothing else. You can verify this by passing an empty string - same result.

    Note that you're also relying on Python's repr formatting to fill in your JSON; that's probably a bad idea too. Instead, let's just reconstruct our macro object and let requests encode it:

    response = requests.put(url, json={'macro': {'actions': updated_data}}, headers={"Content-Type": "application/json"}, auth=(user + "/token", pwd))
    

    This should do what you expect.