##// END OF EJS Templates
elementtree.tostring is always bytes
MinRK -
Show More
@@ -1,319 +1,319 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Job and task components for writing .xml files that the Windows HPC Server
3 Job and task components for writing .xml files that the Windows HPC Server
4 2008 can use to start jobs.
4 2008 can use to start jobs.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * MinRK
9 * MinRK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import os
24 import os
25 import re
25 import re
26 import uuid
26 import uuid
27
27
28 from xml.etree import ElementTree as ET
28 from xml.etree import ElementTree as ET
29
29
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 Unicode, Integer, List, Instance,
32 Unicode, Integer, List, Instance,
33 Enum, Bool
33 Enum, Bool
34 )
34 )
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Job and Task classes
37 # Job and Task classes
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40
40
41 def as_str(value):
41 def as_str(value):
42 if isinstance(value, str):
42 if isinstance(value, str):
43 return value
43 return value
44 elif isinstance(value, bool):
44 elif isinstance(value, bool):
45 if value:
45 if value:
46 return 'true'
46 return 'true'
47 else:
47 else:
48 return 'false'
48 return 'false'
49 elif isinstance(value, (int, float)):
49 elif isinstance(value, (int, float)):
50 return repr(value)
50 return repr(value)
51 else:
51 else:
52 return value
52 return value
53
53
54
54
55 def indent(elem, level=0):
55 def indent(elem, level=0):
56 i = "\n" + level*" "
56 i = "\n" + level*" "
57 if len(elem):
57 if len(elem):
58 if not elem.text or not elem.text.strip():
58 if not elem.text or not elem.text.strip():
59 elem.text = i + " "
59 elem.text = i + " "
60 if not elem.tail or not elem.tail.strip():
60 if not elem.tail or not elem.tail.strip():
61 elem.tail = i
61 elem.tail = i
62 for elem in elem:
62 for elem in elem:
63 indent(elem, level+1)
63 indent(elem, level+1)
64 if not elem.tail or not elem.tail.strip():
64 if not elem.tail or not elem.tail.strip():
65 elem.tail = i
65 elem.tail = i
66 else:
66 else:
67 if level and (not elem.tail or not elem.tail.strip()):
67 if level and (not elem.tail or not elem.tail.strip()):
68 elem.tail = i
68 elem.tail = i
69
69
70
70
71 def find_username():
71 def find_username():
72 domain = os.environ.get('USERDOMAIN')
72 domain = os.environ.get('USERDOMAIN')
73 username = os.environ.get('USERNAME','')
73 username = os.environ.get('USERNAME','')
74 if domain is None:
74 if domain is None:
75 return username
75 return username
76 else:
76 else:
77 return '%s\\%s' % (domain, username)
77 return '%s\\%s' % (domain, username)
78
78
79
79
80 class WinHPCJob(Configurable):
80 class WinHPCJob(Configurable):
81
81
82 job_id = Unicode('')
82 job_id = Unicode('')
83 job_name = Unicode('MyJob', config=True)
83 job_name = Unicode('MyJob', config=True)
84 min_cores = Integer(1, config=True)
84 min_cores = Integer(1, config=True)
85 max_cores = Integer(1, config=True)
85 max_cores = Integer(1, config=True)
86 min_sockets = Integer(1, config=True)
86 min_sockets = Integer(1, config=True)
87 max_sockets = Integer(1, config=True)
87 max_sockets = Integer(1, config=True)
88 min_nodes = Integer(1, config=True)
88 min_nodes = Integer(1, config=True)
89 max_nodes = Integer(1, config=True)
89 max_nodes = Integer(1, config=True)
90 unit_type = Unicode("Core", config=True)
90 unit_type = Unicode("Core", config=True)
91 auto_calculate_min = Bool(True, config=True)
91 auto_calculate_min = Bool(True, config=True)
92 auto_calculate_max = Bool(True, config=True)
92 auto_calculate_max = Bool(True, config=True)
93 run_until_canceled = Bool(False, config=True)
93 run_until_canceled = Bool(False, config=True)
94 is_exclusive = Bool(False, config=True)
94 is_exclusive = Bool(False, config=True)
95 username = Unicode(find_username(), config=True)
95 username = Unicode(find_username(), config=True)
96 job_type = Unicode('Batch', config=True)
96 job_type = Unicode('Batch', config=True)
97 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
97 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
98 default_value='Highest', config=True)
98 default_value='Highest', config=True)
99 requested_nodes = Unicode('', config=True)
99 requested_nodes = Unicode('', config=True)
100 project = Unicode('IPython', config=True)
100 project = Unicode('IPython', config=True)
101 xmlns = Unicode('http://schemas.microsoft.com/HPCS2008/scheduler/')
101 xmlns = Unicode('http://schemas.microsoft.com/HPCS2008/scheduler/')
102 version = Unicode("2.000")
102 version = Unicode("2.000")
103 tasks = List([])
103 tasks = List([])
104
104
105 @property
105 @property
106 def owner(self):
106 def owner(self):
107 return self.username
107 return self.username
108
108
109 def _write_attr(self, root, attr, key):
109 def _write_attr(self, root, attr, key):
110 s = as_str(getattr(self, attr, ''))
110 s = as_str(getattr(self, attr, ''))
111 if s:
111 if s:
112 root.set(key, s)
112 root.set(key, s)
113
113
114 def as_element(self):
114 def as_element(self):
115 # We have to add _A_ type things to get the right order than
115 # We have to add _A_ type things to get the right order than
116 # the MSFT XML parser expects.
116 # the MSFT XML parser expects.
117 root = ET.Element('Job')
117 root = ET.Element('Job')
118 self._write_attr(root, 'version', '_A_Version')
118 self._write_attr(root, 'version', '_A_Version')
119 self._write_attr(root, 'job_name', '_B_Name')
119 self._write_attr(root, 'job_name', '_B_Name')
120 self._write_attr(root, 'unit_type', '_C_UnitType')
120 self._write_attr(root, 'unit_type', '_C_UnitType')
121 self._write_attr(root, 'min_cores', '_D_MinCores')
121 self._write_attr(root, 'min_cores', '_D_MinCores')
122 self._write_attr(root, 'max_cores', '_E_MaxCores')
122 self._write_attr(root, 'max_cores', '_E_MaxCores')
123 self._write_attr(root, 'min_sockets', '_F_MinSockets')
123 self._write_attr(root, 'min_sockets', '_F_MinSockets')
124 self._write_attr(root, 'max_sockets', '_G_MaxSockets')
124 self._write_attr(root, 'max_sockets', '_G_MaxSockets')
125 self._write_attr(root, 'min_nodes', '_H_MinNodes')
125 self._write_attr(root, 'min_nodes', '_H_MinNodes')
126 self._write_attr(root, 'max_nodes', '_I_MaxNodes')
126 self._write_attr(root, 'max_nodes', '_I_MaxNodes')
127 self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled')
127 self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled')
128 self._write_attr(root, 'is_exclusive', '_K_IsExclusive')
128 self._write_attr(root, 'is_exclusive', '_K_IsExclusive')
129 self._write_attr(root, 'username', '_L_UserName')
129 self._write_attr(root, 'username', '_L_UserName')
130 self._write_attr(root, 'job_type', '_M_JobType')
130 self._write_attr(root, 'job_type', '_M_JobType')
131 self._write_attr(root, 'priority', '_N_Priority')
131 self._write_attr(root, 'priority', '_N_Priority')
132 self._write_attr(root, 'requested_nodes', '_O_RequestedNodes')
132 self._write_attr(root, 'requested_nodes', '_O_RequestedNodes')
133 self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax')
133 self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax')
134 self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin')
134 self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin')
135 self._write_attr(root, 'project', '_R_Project')
135 self._write_attr(root, 'project', '_R_Project')
136 self._write_attr(root, 'owner', '_S_Owner')
136 self._write_attr(root, 'owner', '_S_Owner')
137 self._write_attr(root, 'xmlns', '_T_xmlns')
137 self._write_attr(root, 'xmlns', '_T_xmlns')
138 dependencies = ET.SubElement(root, "Dependencies")
138 dependencies = ET.SubElement(root, "Dependencies")
139 etasks = ET.SubElement(root, "Tasks")
139 etasks = ET.SubElement(root, "Tasks")
140 for t in self.tasks:
140 for t in self.tasks:
141 etasks.append(t.as_element())
141 etasks.append(t.as_element())
142 return root
142 return root
143
143
144 def tostring(self):
144 def tostring(self):
145 """Return the string representation of the job description XML."""
145 """Return the string representation of the job description XML."""
146 root = self.as_element()
146 root = self.as_element()
147 indent(root)
147 indent(root)
148 txt = ET.tostring(root, encoding="utf-8")
148 txt = ET.tostring(root, encoding="utf-8").decode('utf-8')
149 # Now remove the tokens used to order the attributes.
149 # Now remove the tokens used to order the attributes.
150 txt = re.sub(r'_[A-Z]_','',txt)
150 txt = re.sub(r'_[A-Z]_','',txt)
151 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
151 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
152 return txt
152 return txt
153
153
154 def write(self, filename):
154 def write(self, filename):
155 """Write the XML job description to a file."""
155 """Write the XML job description to a file."""
156 txt = self.tostring()
156 txt = self.tostring()
157 with open(filename, 'w') as f:
157 with open(filename, 'w') as f:
158 f.write(txt)
158 f.write(txt)
159
159
160 def add_task(self, task):
160 def add_task(self, task):
161 """Add a task to the job.
161 """Add a task to the job.
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165 task : :class:`WinHPCTask`
165 task : :class:`WinHPCTask`
166 The task object to add.
166 The task object to add.
167 """
167 """
168 self.tasks.append(task)
168 self.tasks.append(task)
169
169
170
170
171 class WinHPCTask(Configurable):
171 class WinHPCTask(Configurable):
172
172
173 task_id = Unicode('')
173 task_id = Unicode('')
174 task_name = Unicode('')
174 task_name = Unicode('')
175 version = Unicode("2.000")
175 version = Unicode("2.000")
176 min_cores = Integer(1, config=True)
176 min_cores = Integer(1, config=True)
177 max_cores = Integer(1, config=True)
177 max_cores = Integer(1, config=True)
178 min_sockets = Integer(1, config=True)
178 min_sockets = Integer(1, config=True)
179 max_sockets = Integer(1, config=True)
179 max_sockets = Integer(1, config=True)
180 min_nodes = Integer(1, config=True)
180 min_nodes = Integer(1, config=True)
181 max_nodes = Integer(1, config=True)
181 max_nodes = Integer(1, config=True)
182 unit_type = Unicode("Core", config=True)
182 unit_type = Unicode("Core", config=True)
183 command_line = Unicode('', config=True)
183 command_line = Unicode('', config=True)
184 work_directory = Unicode('', config=True)
184 work_directory = Unicode('', config=True)
185 is_rerunnaable = Bool(True, config=True)
185 is_rerunnaable = Bool(True, config=True)
186 std_out_file_path = Unicode('', config=True)
186 std_out_file_path = Unicode('', config=True)
187 std_err_file_path = Unicode('', config=True)
187 std_err_file_path = Unicode('', config=True)
188 is_parametric = Bool(False, config=True)
188 is_parametric = Bool(False, config=True)
189 environment_variables = Instance(dict, args=(), config=True)
189 environment_variables = Instance(dict, args=(), config=True)
190
190
191 def _write_attr(self, root, attr, key):
191 def _write_attr(self, root, attr, key):
192 s = as_str(getattr(self, attr, ''))
192 s = as_str(getattr(self, attr, ''))
193 if s:
193 if s:
194 root.set(key, s)
194 root.set(key, s)
195
195
196 def as_element(self):
196 def as_element(self):
197 root = ET.Element('Task')
197 root = ET.Element('Task')
198 self._write_attr(root, 'version', '_A_Version')
198 self._write_attr(root, 'version', '_A_Version')
199 self._write_attr(root, 'task_name', '_B_Name')
199 self._write_attr(root, 'task_name', '_B_Name')
200 self._write_attr(root, 'min_cores', '_C_MinCores')
200 self._write_attr(root, 'min_cores', '_C_MinCores')
201 self._write_attr(root, 'max_cores', '_D_MaxCores')
201 self._write_attr(root, 'max_cores', '_D_MaxCores')
202 self._write_attr(root, 'min_sockets', '_E_MinSockets')
202 self._write_attr(root, 'min_sockets', '_E_MinSockets')
203 self._write_attr(root, 'max_sockets', '_F_MaxSockets')
203 self._write_attr(root, 'max_sockets', '_F_MaxSockets')
204 self._write_attr(root, 'min_nodes', '_G_MinNodes')
204 self._write_attr(root, 'min_nodes', '_G_MinNodes')
205 self._write_attr(root, 'max_nodes', '_H_MaxNodes')
205 self._write_attr(root, 'max_nodes', '_H_MaxNodes')
206 self._write_attr(root, 'command_line', '_I_CommandLine')
206 self._write_attr(root, 'command_line', '_I_CommandLine')
207 self._write_attr(root, 'work_directory', '_J_WorkDirectory')
207 self._write_attr(root, 'work_directory', '_J_WorkDirectory')
208 self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable')
208 self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable')
209 self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath')
209 self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath')
210 self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath')
210 self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath')
211 self._write_attr(root, 'is_parametric', '_N_IsParametric')
211 self._write_attr(root, 'is_parametric', '_N_IsParametric')
212 self._write_attr(root, 'unit_type', '_O_UnitType')
212 self._write_attr(root, 'unit_type', '_O_UnitType')
213 root.append(self.get_env_vars())
213 root.append(self.get_env_vars())
214 return root
214 return root
215
215
216 def get_env_vars(self):
216 def get_env_vars(self):
217 env_vars = ET.Element('EnvironmentVariables')
217 env_vars = ET.Element('EnvironmentVariables')
218 for k, v in self.environment_variables.iteritems():
218 for k, v in self.environment_variables.iteritems():
219 variable = ET.SubElement(env_vars, "Variable")
219 variable = ET.SubElement(env_vars, "Variable")
220 name = ET.SubElement(variable, "Name")
220 name = ET.SubElement(variable, "Name")
221 name.text = k
221 name.text = k
222 value = ET.SubElement(variable, "Value")
222 value = ET.SubElement(variable, "Value")
223 value.text = v
223 value.text = v
224 return env_vars
224 return env_vars
225
225
226
226
227
227
228 # By declaring these, we can configure the controller and engine separately!
228 # By declaring these, we can configure the controller and engine separately!
229
229
230 class IPControllerJob(WinHPCJob):
230 class IPControllerJob(WinHPCJob):
231 job_name = Unicode('IPController', config=False)
231 job_name = Unicode('IPController', config=False)
232 is_exclusive = Bool(False, config=True)
232 is_exclusive = Bool(False, config=True)
233 username = Unicode(find_username(), config=True)
233 username = Unicode(find_username(), config=True)
234 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
234 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
235 default_value='Highest', config=True)
235 default_value='Highest', config=True)
236 requested_nodes = Unicode('', config=True)
236 requested_nodes = Unicode('', config=True)
237 project = Unicode('IPython', config=True)
237 project = Unicode('IPython', config=True)
238
238
239
239
240 class IPEngineSetJob(WinHPCJob):
240 class IPEngineSetJob(WinHPCJob):
241 job_name = Unicode('IPEngineSet', config=False)
241 job_name = Unicode('IPEngineSet', config=False)
242 is_exclusive = Bool(False, config=True)
242 is_exclusive = Bool(False, config=True)
243 username = Unicode(find_username(), config=True)
243 username = Unicode(find_username(), config=True)
244 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
244 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
245 default_value='Highest', config=True)
245 default_value='Highest', config=True)
246 requested_nodes = Unicode('', config=True)
246 requested_nodes = Unicode('', config=True)
247 project = Unicode('IPython', config=True)
247 project = Unicode('IPython', config=True)
248
248
249
249
250 class IPControllerTask(WinHPCTask):
250 class IPControllerTask(WinHPCTask):
251
251
252 task_name = Unicode('IPController', config=True)
252 task_name = Unicode('IPController', config=True)
253 controller_cmd = List(['ipcontroller.exe'], config=True)
253 controller_cmd = List(['ipcontroller.exe'], config=True)
254 controller_args = List(['--log-to-file', '--log-level=40'], config=True)
254 controller_args = List(['--log-to-file', '--log-level=40'], config=True)
255 # I don't want these to be configurable
255 # I don't want these to be configurable
256 std_out_file_path = Unicode('', config=False)
256 std_out_file_path = Unicode('', config=False)
257 std_err_file_path = Unicode('', config=False)
257 std_err_file_path = Unicode('', config=False)
258 min_cores = Integer(1, config=False)
258 min_cores = Integer(1, config=False)
259 max_cores = Integer(1, config=False)
259 max_cores = Integer(1, config=False)
260 min_sockets = Integer(1, config=False)
260 min_sockets = Integer(1, config=False)
261 max_sockets = Integer(1, config=False)
261 max_sockets = Integer(1, config=False)
262 min_nodes = Integer(1, config=False)
262 min_nodes = Integer(1, config=False)
263 max_nodes = Integer(1, config=False)
263 max_nodes = Integer(1, config=False)
264 unit_type = Unicode("Core", config=False)
264 unit_type = Unicode("Core", config=False)
265 work_directory = Unicode('', config=False)
265 work_directory = Unicode('', config=False)
266
266
267 def __init__(self, config=None):
267 def __init__(self, config=None):
268 super(IPControllerTask, self).__init__(config=config)
268 super(IPControllerTask, self).__init__(config=config)
269 the_uuid = uuid.uuid1()
269 the_uuid = uuid.uuid1()
270 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
270 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
271 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
271 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
272
272
273 @property
273 @property
274 def command_line(self):
274 def command_line(self):
275 return ' '.join(self.controller_cmd + self.controller_args)
275 return ' '.join(self.controller_cmd + self.controller_args)
276
276
277
277
278 class IPEngineTask(WinHPCTask):
278 class IPEngineTask(WinHPCTask):
279
279
280 task_name = Unicode('IPEngine', config=True)
280 task_name = Unicode('IPEngine', config=True)
281 engine_cmd = List(['ipengine.exe'], config=True)
281 engine_cmd = List(['ipengine.exe'], config=True)
282 engine_args = List(['--log-to-file', '--log-level=40'], config=True)
282 engine_args = List(['--log-to-file', '--log-level=40'], config=True)
283 # I don't want these to be configurable
283 # I don't want these to be configurable
284 std_out_file_path = Unicode('', config=False)
284 std_out_file_path = Unicode('', config=False)
285 std_err_file_path = Unicode('', config=False)
285 std_err_file_path = Unicode('', config=False)
286 min_cores = Integer(1, config=False)
286 min_cores = Integer(1, config=False)
287 max_cores = Integer(1, config=False)
287 max_cores = Integer(1, config=False)
288 min_sockets = Integer(1, config=False)
288 min_sockets = Integer(1, config=False)
289 max_sockets = Integer(1, config=False)
289 max_sockets = Integer(1, config=False)
290 min_nodes = Integer(1, config=False)
290 min_nodes = Integer(1, config=False)
291 max_nodes = Integer(1, config=False)
291 max_nodes = Integer(1, config=False)
292 unit_type = Unicode("Core", config=False)
292 unit_type = Unicode("Core", config=False)
293 work_directory = Unicode('', config=False)
293 work_directory = Unicode('', config=False)
294
294
295 def __init__(self, config=None):
295 def __init__(self, config=None):
296 super(IPEngineTask,self).__init__(config=config)
296 super(IPEngineTask,self).__init__(config=config)
297 the_uuid = uuid.uuid1()
297 the_uuid = uuid.uuid1()
298 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
298 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
299 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
299 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
300
300
301 @property
301 @property
302 def command_line(self):
302 def command_line(self):
303 return ' '.join(self.engine_cmd + self.engine_args)
303 return ' '.join(self.engine_cmd + self.engine_args)
304
304
305
305
306 # j = WinHPCJob(None)
306 # j = WinHPCJob(None)
307 # j.job_name = 'IPCluster'
307 # j.job_name = 'IPCluster'
308 # j.username = 'GNET\\bgranger'
308 # j.username = 'GNET\\bgranger'
309 # j.requested_nodes = 'GREEN'
309 # j.requested_nodes = 'GREEN'
310 #
310 #
311 # t = WinHPCTask(None)
311 # t = WinHPCTask(None)
312 # t.task_name = 'Controller'
312 # t.task_name = 'Controller'
313 # t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10"
313 # t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10"
314 # t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default"
314 # t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default"
315 # t.std_out_file_path = 'controller-out.txt'
315 # t.std_out_file_path = 'controller-out.txt'
316 # t.std_err_file_path = 'controller-err.txt'
316 # t.std_err_file_path = 'controller-err.txt'
317 # t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages"
317 # t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages"
318 # j.add_task(t)
318 # j.add_task(t)
319
319
General Comments 0
You need to be logged in to leave comments. Login now