initialise repo
[debian/mudpuppy.git] / magiclink.py
1 #!/usr/bin/env python
2
3 '''Access the make-magic HTTP API
4
5 contains both make-magic HTTP API access and the policy for dealing with the same
6 '''
7 import requests
8 import json
9 import random
10
11 from constants import *
12 import config
13
14 class MagicError(Exception):
15         def __init__(self, error, description):
16                 self.response,self.content = response,content
17         def __str__(self):
18                 return "MudpuppyError: %s: %s" %(str(self.response), str(self.content))
19
20 class MagicAPI(object):
21         '''Talk to the make-magic API over HTTP
22         '''
23         ## HTTP Specific
24         #
25         def json_http_request(self, method, url, decode_response = True, data=None):
26                 '''Make a HTTP request, and demarshal the HTTP response
27
28                 if POST is given, the data is marshalled to JSON put in the 
29                 HTTP request body
30                 '''
31                 assert url[:4] == 'http'
32                 headers = {'User-Agent': 'mudpuppy MagicAPI'}
33                 if method == 'POST':
34                         json_data = json.dumps(data)
35                         headers['Content-Type'] = 'application/json'
36                         response = requests.request(method, url, headers=headers, data=json_data)
37                 else:
38                         response = requests.request(method, url)
39
40                 if response.status_code == None: 
41                         # Didn't manage to get a HTTP response
42                         response.raise_for_status()
43
44                 if response.status_code != 200:
45                         # Got an error, but hopefully make-magic gave us more information
46                         try:
47                                 jsondata = json.loads(response.content)
48                                 raise MagicError(jsondata['error'], jsondata['message'])
49                         except: 
50                                 # Couldn't marshal. Raise a less interesting error.
51                                 response.raise_for_status()
52
53                 # Yay! good response. Try and marshal to JSON and return
54                 if decode_response:
55                         return json.loads(response.content)
56                 else:
57                         return response.content
58
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)
64
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)
71
72         ## API to expose
73         #
74         def get_tasks(self):
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):
99                 '''create a new task
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.
103                 '''
104                 return self.json_http_post('task/create', taskdatadict)
105         def delete_task(self,uuid):
106                 '''delete a task
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
110                 '''
111                 return self.json_http_delete('task/%s' % (str(uuid),))
112
113 class MagicLink(MagicAPI):
114         '''wrap the make-magic API while adding some of the logic for dealing with it
115         '''
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
118
119                 returns True iff the state was changed from old_state to new_state and this call made the change
120                 '''
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
124
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