3 '''Access the make-magic HTTP API
5 contains both make-magic HTTP API access and the policy for dealing with the same
11 from constants import *
14 class MagicError(Exception):
15 def __init__(self, error, description):
16 self.response,self.content = response,content
18 return "MudpuppyError: %s: %s" %(str(self.response), str(self.content))
20 class MagicAPI(object):
21 '''Talk to the make-magic API over HTTP
25 def json_http_request(self, method, url, decode_response = True, data=None):
26 '''Make a HTTP request, and demarshal the HTTP response
28 if POST is given, the data is marshalled to JSON put in the
31 assert url[:4] == 'http'
32 headers = {'User-Agent': 'mudpuppy MagicAPI'}
34 json_data = json.dumps(data)
35 headers['Content-Type'] = 'application/json'
36 response = requests.request(method, url, headers=headers, data=json_data)
38 response = requests.request(method, url)
40 if response.status_code == None:
41 # Didn't manage to get a HTTP response
42 response.raise_for_status()
44 if response.status_code != 200:
45 # Got an error, but hopefully make-magic gave us more information
47 jsondata = json.loads(response.content)
48 raise MagicError(jsondata['error'], jsondata['message'])
50 # Couldn't marshal. Raise a less interesting error.
51 response.raise_for_status()
53 # Yay! good response. Try and marshal to JSON and return
55 return json.loads(response.content)
57 return response.content
59 def full_url(self, relpath):
60 '''return the full URL from a relative API path'''
61 if config.mudpuppy_api_url[-1] == '/':
62 config.mudpuppy_api_url = config.mudpuppy_api_url[:-1]
63 return "%s/%s" % (config.mudpuppy_api_url,relpath)
65 def json_http_get(self, relpath, decode_response=True):
66 return self.json_http_request('GET', self.full_url(relpath), decode_response)
67 def json_http_post(self, relpath, data, decode_response=True):
68 return self.json_http_request('POST', self.full_url(relpath), decode_response, data)
69 def json_http_delete(self, relpath, decode_response=True):
70 return self.json_http_request('DELETE', self.full_url(relpath), decode_response)
75 '''return a list of all task UUIDs'''
76 return self.json_http_get('task')
77 def get_task(self,uuid):
78 '''return all data for a task'''
79 return self.json_http_get('task/%s' % (str(uuid),))
80 def get_task_metadata(self,uuid):
81 '''return metadata for a task'''
82 return self.json_http_get('task/%s/metadata' % (str(uuid),))
83 def update_task_metadata(self,uuid, updatedict):
84 '''update metadata for a task'''
85 return self.json_http_post('task/%s/metadata' % (str(uuid),),updatedict)
86 def get_available_items(self,uuid):
87 '''return all items in a task that are currently ready to be automated'''
88 return self.json_http_get('task/%s/available' % (str(uuid),))
89 def update_item(self,uuid,itemname, updatedict):
90 '''update data for a specific item'''
91 return self.json_http_post('task/%s/%s' % (str(uuid),str(itemname)),updatedict)
92 def get_item(self,uuid,itemname):
93 '''return data for a specific item'''
94 return self.json_http_get('task/%s/%s' % (str(uuid),str(itemname)))
95 def get_item_state(self,uuid,itemname):
96 '''return item state for a specific item'''
97 return self.json_http_get('task/%s/%s/state' % (str(uuid),str(itemname)),decode_response=False)
98 def create_task(self,taskdatadict):
100 mudpuppy shouldn't have to do this ever but it is here for
101 completeness and testing. Unless you want to automatically
102 create tasks from a task, in which case, power to you.
104 return self.json_http_post('task/create', taskdatadict)
105 def delete_task(self,uuid):
107 mudpuppy REALLY shoudn't be doing this, but is here for testing
108 and completeness. Unless you want to automatically delete
109 tasks from a task, in which case I'll be hiding behind this rock
111 return self.json_http_delete('task/%s' % (str(uuid),))
113 class MagicLink(MagicAPI):
114 '''wrap the make-magic API while adding some of the logic for dealing with it
116 def update_item_state(self, uuid, item, old_state, new_state):
117 '''atomically update the item state, failing if we don't manage to
119 returns True iff the state was changed from old_state to new_state and this call made the change
121 token = random.randint(1,2**48)
122 item_state_update = {"state": new_state, "onlyif": dict(state=old_state)}
123 item_state_update[CHANGE_STATE_TOKEN] = token
125 new_item = self.update_item(uuid, item, item_state_update)
126 return new_item.get('state') == new_state and new_item.get(CHANGE_STATE_TOKEN) == token