add vcs-* fields to debian/control
[debian/make-magic.git] / lib / magic.py
1 #! /usr/bin/env python
2
3 '''main python API for make-magic
4
5 This is where most of the magic happens. Any APIs you write (e.g. for HTTP)
6 may very well just call this much of the time
7 '''
8
9 import config
10
11 from core.store import Store
12 from core.marshal import ItemConverter,TaskConverter
13 from lib.loaders import JSONItemLoader
14 from core.deptools import DigraphDependencyStrategy
15 from core.bits import Item
16
17 class Magic(object):
18         def __init__(self,store_factory=Store,dependency_strategy=DigraphDependencyStrategy):
19                 # Load items
20                 self.load_items()
21                 self.store = store_factory()
22                 self.dependency_strategy = dependency_strategy
23
24         def load_items(self):
25                 itemsf = open(config.items_file)
26                 self.taskfactory = JSONItemLoader.load_item_classes_from_file(itemsf)
27                 itemsf.close()
28
29         reload_items = load_items
30
31         #
32         # This stuff is pretty easy. Get information about existing tasks
33         # and update them: Just pass it off to the storage module
34         #
35         def get_tasks(self):
36                 return self.store.get_tasks()
37         def get_task(self, uuid):
38                 metadata = self.store.metadata(uuid)
39                 items = self.store.items(uuid)
40                 if not metadata and not items:
41                         raise KeyError('uuid '+str(uuid)+' not found')
42                 return {'items': items, 'metadata': metadata}
43         def get_item(self, uuid, itemname):
44                 item = self.store.item(uuid, itemname)
45                 if item is None:
46                         raise KeyError(uuid+'/'+itemname)
47                 return item
48         def get_metadata(self, uuid):
49                 metadata = self.store.metadata(uuid)
50                 if not metadata:
51                         raise KeyError('uuid '+str(uuid)+' not found')
52                 return metadata
53         def update_item(self, uuid, name, updatedict, onlyif={}):
54                 cannot_update = set(('name','depends','if'))
55                 onlyif = dict(onlyif)
56                 for k,v in updatedict.items():
57                         if k in cannot_update:
58                                 raise ValueError('cannot modify item attribute "%s"' %(k,))
59                 if 'onlyif' in updatedict:
60                         if not getattr(updatedict['onlyif'], 'items', None): 
61                                 raise ValueError('can only set "onlyif" to a dictionary')
62                         onlyif.update(updatedict['onlyif'])
63                         updatedict.pop('onlyif')
64                 if 'state' in updatedict:
65                         if updatedict['state'] not in Item.allowed_states:
66                                 raise ValueError('can only change state to '+','.join(Item.allowed_states))
67                 return self.store.update_item(uuid,name,updatedict,onlyif)
68         def update_item_state(self, uuid, name, oldstate, newstate):
69                 '''helper to update a state with a guard against the old one
70                 WARNING: This actually sucks quite a bit. It doesn't guard against race conditions
71                 from multiple agents.  This is deliberately not exposed to the HTTP interface for
72                 that reason.
73                 '''
74                 return self.update_item(uuid, name, {'state': newstate}, {'state': oldstate})
75         def update_task_metadata(self, uuid, updatedict, onlyif={}):
76                 if 'uuid' in updatedict and uuid != updatedict['uuid']:
77                         raise ValueError('cannot change uuid for a task')
78                 return self.store.update_metadata(uuid,updatedict,onlyif)
79         def delete_task(self, uuid):
80                 self.store.delete_task(uuid)
81
82         #
83         # Creating a new task is almost as easy!
84         #
85         def create_task(self, task_data):
86                 if 'requirements' not in task_data:
87                         raise ValueError('No requirements supplied to create task')
88                 task = self.taskfactory.task_from_requirements(task_data['requirements'])
89
90                 # FIXME: Should be in core.marshal
91                 # This is also an awful hack
92                 task.data.update(task_data)
93                 ic = ItemConverter()
94                 items = [ic.item_to_itemdict(item) for item in task.items]
95
96                 # Save!
97                 self.store.new_task(task.uuid, items, metadata=task.data)
98                 return self.get_task(task.uuid)
99
100         #
101         #  What can we do?
102         #
103         def ready_to_run(self, uuid):
104                 '''return all the items that we can run'''
105                 task = self.get_task(uuid)
106                 converter = TaskConverter()
107                 task = converter.taskdict_to_task(task)
108                 ready =  self.dependency_strategy.ready_to_run(task.items)
109                 # FIXME: Evil, Evil hack
110                 if 'TaskComplete' in (r.name for r in ready):
111                         self.update_item(uuid, 'TaskComplete', {'data': {'state': 'COMPLETED'}})
112                         return self.ready_to_run(uuid)
113                 ready =  self.dependency_strategy.ready_to_run(task.items)
114                 return [converter.item_to_itemdict(item) for item in ready]