1
2
3
4 """Module containing the main superclass for all models."""
5 import os
6 import sys
7 import uuid
8 import random
9 import string
10 import logging
11 import subprocess
12 from email_manager import Email
13 import shutil
14
15 from config import config
19 """Abstract base class for all user-defined models.
20
21 Users should create models that inherit from this class. It contains _all_ information
22 specific to a given model including parameters, attachments, titles and methods for
23 producing the e-mails with results.
24 """
25
26 abstractModel = "ModelTask"
27
28
29 short_name = "unspecified_name"
30 subtitle = "Unspecified Subtitle"
31 attachments = []
32
33 - def __init__(self, emailAddress, taskId, modelParameters={}, failureCount=0, visibleId=None):
34 self.emailAddress = emailAddress
35 self.taskId = taskId
36 self.failureCount = failureCount
37 self.modelParameters = []
38 self.visibleId = visibleId
39 if self.visibleId == None:
40 self.visibleId = "".join(random.choice(string.letters + string.digits)\
41 for i in xrange(8))
42
43 self.workingDirectory = "/var/tmp/npsgd/%s" % str(uuid.uuid4())
44
45 for k,v in modelParameters.iteritems():
46 param = self.parameterType(k).fromDict(v)
47 setattr(self, param.name, param)
48 self.modelParameters.append(param)
49
50
51 parameterIndexes = dict((e.name, i) for (i,e) in enumerate(self.__class__.parameters))
52 self.modelParameters.sort(key=lambda x: parameterIndexes[x.name])
53
55 try:
56 os.makedirs(self.workingDirectory, 0777)
57 except OSError, e:
58 logging.warning(e)
59
61 """Returns an empty version the parameter class for a given parameter name."""
62
63 for pClass in self.__class__.parameters:
64 if parameterName == pClass.name:
65 return pClass
66
67 return None
68
69 @classmethod
71 emailAddress = dictionary["emailAddress"]
72 taskId = dictionary["taskId"]
73 visibleId = dictionary["visibleId"]
74 failureCount = dictionary["failureCount"]
75
76 return cls(emailAddress, taskId, failureCount=failureCount,
77 modelParameters=dictionary["modelParameters"], visibleId=visibleId)
78
80 return {
81 "emailAddress" : self.emailAddress,
82 "taskId": self.taskId,
83 "visibleId": self.visibleId,
84 "failureCount": self.failureCount,
85 "modelName": self.__class__.short_name,
86 "modelFullName": self.__class__.full_name,
87 "modelVersion": self.__class__.version,
88 "modelParameters": dict((p.name, p.asDict()) for p in self.modelParameters)
89 }
90
91 - def latexBody(self):
92 """Returns the body of the LaTeX PDF used to generate result e-mails."""
93
94 return "This is a test for %s" % self.emailAddress
95
97 """Returns LaTeX markup with a table containing the values for all input parameters."""
98
99 paramRows = "\\\\\n".join(p.asLatexRow() for p in self.modelParameters)
100 return """
101 \\begin{centering}
102 \\begin{tabular*}{5in}{@{\\extracolsep{\\fill}} l l}
103 \\textbf{Parameter} & \\textbf{Value} \\\\
104 \\hline
105 %s
106 \\end{tabular*}
107 \\end{centering}""" % paramRows
108
110 """Returns an ascii representation of all parameters (e.g. for the body of e-mails)."""
111
112 return "\n".join(p.asTextRow() for p in self.modelParameters)
113
115 pdf = self.generatePDF()
116
117 attach = [('results.pdf', pdf)]
118 for attachment in self.__class__.attachments:
119 with open(os.path.join(self.workingDirectory, attachment)) as f:
120 attach.append((attachment, f.read()))
121
122 return attach
123
125 """A step in the standard model run to prepare output graphs."""
126 pass
127
129 """A step in the standard model run to prepare model execution."""
130 pass
131
133 """Generates a PDF using the LaTeX template, our model's LaTeX body and PDFLatex."""
134
135 latex = config.latexResultTemplate.generate(model_results=self.latexBody(), task=self)
136 logging.info(latex)
137
138 texPath = os.path.join(self.workingDirectory, "test_task.tex")
139 pdfOutputPath = os.path.join(self.workingDirectory, "test_task.pdf")
140
141 with open(texPath, 'w') as f:
142 f.write(latex)
143
144 logging.info("Will run PDFLatex %d times", config.latexNumRuns)
145 for i in xrange(config.latexNumRuns):
146 logging.info("Calling PDFLatex (run %d) to generate pdf output", i+1)
147 retCode = subprocess.call([config.pdfLatexPath, "-halt-on-error", texPath], cwd=self.workingDirectory)
148 logging.info("PDFLatex terminated with error code %d", retCode)
149
150 if retCode != 0:
151 raise LatexError("Bad exit code from latex")
152
153 with open(pdfOutputPath, 'rb') as f:
154 pdf = f.read()
155
156 return pdf
157
158
160 """Returns an e-mail object for notifying the user of a failure to execute this model."""
161 return Email(self.emailAddress,
162 config.failureEmailSubject.generate(task=self),
163 config.failureEmailTemplate.generate(task=self))
164
166 """Returns an e-mail object for yielding a results e-mail for the user."""
167 return Email(self.emailAddress,
168 config.resultsEmailSubject.generate(task=self),
169 config.resultsEmailBodyTemplate.generate(task=self),
170 attachments)
171
173 """Performs model-specific steps for execution."""
174 logging.warning("Called default run model - this should be overridden")
175
189