honeybee-energy 1.116.106__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. honeybee_energy/__init__.py +24 -0
  2. honeybee_energy/__main__.py +4 -0
  3. honeybee_energy/_extend_honeybee.py +145 -0
  4. honeybee_energy/altnumber.py +21 -0
  5. honeybee_energy/baseline/__init__.py +2 -0
  6. honeybee_energy/baseline/create.py +608 -0
  7. honeybee_energy/baseline/data/__init__.py +1 -0
  8. honeybee_energy/baseline/data/constructions.csv +64 -0
  9. honeybee_energy/baseline/data/fen_ratios.csv +15 -0
  10. honeybee_energy/baseline/data/lpd_building.csv +21 -0
  11. honeybee_energy/baseline/data/pci_2016.csv +22 -0
  12. honeybee_energy/baseline/data/pci_2019.csv +22 -0
  13. honeybee_energy/baseline/data/pci_2022.csv +22 -0
  14. honeybee_energy/baseline/data/shw.csv +21 -0
  15. honeybee_energy/baseline/pci.py +512 -0
  16. honeybee_energy/baseline/result.py +371 -0
  17. honeybee_energy/boundarycondition.py +128 -0
  18. honeybee_energy/cli/__init__.py +69 -0
  19. honeybee_energy/cli/baseline.py +475 -0
  20. honeybee_energy/cli/edit.py +327 -0
  21. honeybee_energy/cli/lib.py +1154 -0
  22. honeybee_energy/cli/result.py +810 -0
  23. honeybee_energy/cli/setconfig.py +124 -0
  24. honeybee_energy/cli/settings.py +569 -0
  25. honeybee_energy/cli/simulate.py +380 -0
  26. honeybee_energy/cli/translate.py +1714 -0
  27. honeybee_energy/cli/validate.py +224 -0
  28. honeybee_energy/config.json +11 -0
  29. honeybee_energy/config.py +842 -0
  30. honeybee_energy/construction/__init__.py +1 -0
  31. honeybee_energy/construction/_base.py +374 -0
  32. honeybee_energy/construction/air.py +325 -0
  33. honeybee_energy/construction/dictutil.py +89 -0
  34. honeybee_energy/construction/dynamic.py +607 -0
  35. honeybee_energy/construction/opaque.py +460 -0
  36. honeybee_energy/construction/shade.py +319 -0
  37. honeybee_energy/construction/window.py +1096 -0
  38. honeybee_energy/construction/windowshade.py +847 -0
  39. honeybee_energy/constructionset.py +1655 -0
  40. honeybee_energy/dictutil.py +56 -0
  41. honeybee_energy/generator/__init__.py +5 -0
  42. honeybee_energy/generator/loadcenter.py +204 -0
  43. honeybee_energy/generator/pv.py +535 -0
  44. honeybee_energy/hvac/__init__.py +21 -0
  45. honeybee_energy/hvac/_base.py +124 -0
  46. honeybee_energy/hvac/_template.py +270 -0
  47. honeybee_energy/hvac/allair/__init__.py +22 -0
  48. honeybee_energy/hvac/allair/_base.py +349 -0
  49. honeybee_energy/hvac/allair/furnace.py +168 -0
  50. honeybee_energy/hvac/allair/psz.py +131 -0
  51. honeybee_energy/hvac/allair/ptac.py +163 -0
  52. honeybee_energy/hvac/allair/pvav.py +109 -0
  53. honeybee_energy/hvac/allair/vav.py +128 -0
  54. honeybee_energy/hvac/detailed.py +337 -0
  55. honeybee_energy/hvac/doas/__init__.py +28 -0
  56. honeybee_energy/hvac/doas/_base.py +345 -0
  57. honeybee_energy/hvac/doas/fcu.py +127 -0
  58. honeybee_energy/hvac/doas/radiant.py +329 -0
  59. honeybee_energy/hvac/doas/vrf.py +81 -0
  60. honeybee_energy/hvac/doas/wshp.py +91 -0
  61. honeybee_energy/hvac/heatcool/__init__.py +23 -0
  62. honeybee_energy/hvac/heatcool/_base.py +177 -0
  63. honeybee_energy/hvac/heatcool/baseboard.py +61 -0
  64. honeybee_energy/hvac/heatcool/evapcool.py +72 -0
  65. honeybee_energy/hvac/heatcool/fcu.py +92 -0
  66. honeybee_energy/hvac/heatcool/gasunit.py +53 -0
  67. honeybee_energy/hvac/heatcool/radiant.py +269 -0
  68. honeybee_energy/hvac/heatcool/residential.py +77 -0
  69. honeybee_energy/hvac/heatcool/vrf.py +54 -0
  70. honeybee_energy/hvac/heatcool/windowac.py +70 -0
  71. honeybee_energy/hvac/heatcool/wshp.py +62 -0
  72. honeybee_energy/hvac/idealair.py +699 -0
  73. honeybee_energy/internalmass.py +310 -0
  74. honeybee_energy/lib/__init__.py +1 -0
  75. honeybee_energy/lib/_loadconstructions.py +194 -0
  76. honeybee_energy/lib/_loadconstructionsets.py +117 -0
  77. honeybee_energy/lib/_loadmaterials.py +83 -0
  78. honeybee_energy/lib/_loadprogramtypes.py +125 -0
  79. honeybee_energy/lib/_loadschedules.py +87 -0
  80. honeybee_energy/lib/_loadtypelimits.py +64 -0
  81. honeybee_energy/lib/constructions.py +207 -0
  82. honeybee_energy/lib/constructionsets.py +95 -0
  83. honeybee_energy/lib/materials.py +67 -0
  84. honeybee_energy/lib/programtypes.py +125 -0
  85. honeybee_energy/lib/schedules.py +61 -0
  86. honeybee_energy/lib/scheduletypelimits.py +31 -0
  87. honeybee_energy/load/__init__.py +1 -0
  88. honeybee_energy/load/_base.py +190 -0
  89. honeybee_energy/load/daylight.py +397 -0
  90. honeybee_energy/load/dictutil.py +47 -0
  91. honeybee_energy/load/equipment.py +771 -0
  92. honeybee_energy/load/hotwater.py +543 -0
  93. honeybee_energy/load/infiltration.py +460 -0
  94. honeybee_energy/load/lighting.py +480 -0
  95. honeybee_energy/load/people.py +497 -0
  96. honeybee_energy/load/process.py +472 -0
  97. honeybee_energy/load/setpoint.py +816 -0
  98. honeybee_energy/load/ventilation.py +550 -0
  99. honeybee_energy/material/__init__.py +1 -0
  100. honeybee_energy/material/_base.py +166 -0
  101. honeybee_energy/material/dictutil.py +59 -0
  102. honeybee_energy/material/frame.py +367 -0
  103. honeybee_energy/material/gas.py +1087 -0
  104. honeybee_energy/material/glazing.py +854 -0
  105. honeybee_energy/material/opaque.py +1351 -0
  106. honeybee_energy/material/shade.py +1360 -0
  107. honeybee_energy/measure.py +472 -0
  108. honeybee_energy/programtype.py +723 -0
  109. honeybee_energy/properties/__init__.py +1 -0
  110. honeybee_energy/properties/aperture.py +333 -0
  111. honeybee_energy/properties/door.py +342 -0
  112. honeybee_energy/properties/extension.py +244 -0
  113. honeybee_energy/properties/face.py +274 -0
  114. honeybee_energy/properties/model.py +2640 -0
  115. honeybee_energy/properties/room.py +1747 -0
  116. honeybee_energy/properties/shade.py +314 -0
  117. honeybee_energy/properties/shademesh.py +262 -0
  118. honeybee_energy/reader.py +48 -0
  119. honeybee_energy/result/__init__.py +1 -0
  120. honeybee_energy/result/colorobj.py +648 -0
  121. honeybee_energy/result/emissions.py +290 -0
  122. honeybee_energy/result/err.py +101 -0
  123. honeybee_energy/result/eui.py +100 -0
  124. honeybee_energy/result/generation.py +160 -0
  125. honeybee_energy/result/loadbalance.py +890 -0
  126. honeybee_energy/result/match.py +202 -0
  127. honeybee_energy/result/osw.py +90 -0
  128. honeybee_energy/result/rdd.py +59 -0
  129. honeybee_energy/result/zsz.py +190 -0
  130. honeybee_energy/run.py +1577 -0
  131. honeybee_energy/schedule/__init__.py +1 -0
  132. honeybee_energy/schedule/day.py +626 -0
  133. honeybee_energy/schedule/dictutil.py +59 -0
  134. honeybee_energy/schedule/fixedinterval.py +1012 -0
  135. honeybee_energy/schedule/rule.py +619 -0
  136. honeybee_energy/schedule/ruleset.py +1867 -0
  137. honeybee_energy/schedule/typelimit.py +310 -0
  138. honeybee_energy/shw.py +315 -0
  139. honeybee_energy/simulation/__init__.py +1 -0
  140. honeybee_energy/simulation/control.py +214 -0
  141. honeybee_energy/simulation/daylightsaving.py +185 -0
  142. honeybee_energy/simulation/dictutil.py +51 -0
  143. honeybee_energy/simulation/output.py +646 -0
  144. honeybee_energy/simulation/parameter.py +606 -0
  145. honeybee_energy/simulation/runperiod.py +443 -0
  146. honeybee_energy/simulation/shadowcalculation.py +295 -0
  147. honeybee_energy/simulation/sizing.py +546 -0
  148. honeybee_energy/ventcool/__init__.py +5 -0
  149. honeybee_energy/ventcool/_crack_data.py +91 -0
  150. honeybee_energy/ventcool/afn.py +289 -0
  151. honeybee_energy/ventcool/control.py +269 -0
  152. honeybee_energy/ventcool/crack.py +126 -0
  153. honeybee_energy/ventcool/fan.py +493 -0
  154. honeybee_energy/ventcool/opening.py +365 -0
  155. honeybee_energy/ventcool/simulation.py +314 -0
  156. honeybee_energy/writer.py +1078 -0
  157. honeybee_energy-1.116.106.dist-info/METADATA +113 -0
  158. honeybee_energy-1.116.106.dist-info/RECORD +162 -0
  159. honeybee_energy-1.116.106.dist-info/WHEEL +5 -0
  160. honeybee_energy-1.116.106.dist-info/entry_points.txt +2 -0
  161. honeybee_energy-1.116.106.dist-info/licenses/LICENSE +661 -0
  162. honeybee_energy-1.116.106.dist-info/top_level.txt +1 -0
@@ -0,0 +1,472 @@
1
+ # coding=utf-8
2
+ """Module for parsing OpenStudio measures and setting measure arguments."""
3
+ from __future__ import division
4
+
5
+ import os
6
+ import xml.etree.ElementTree as ElementTree
7
+
8
+
9
+ class Measure(object):
10
+ """Object to hold all properties of an OpenStudio measure, including arguments.
11
+
12
+ Args:
13
+ folder: Path to the folder in which the measure exists. This folder
14
+ must contain a measure.rb and a measure.xml file. Other files are
15
+ optional.
16
+
17
+ Properties:
18
+ * folder
19
+ * metadata_file
20
+ * program_file
21
+ * resources_folder
22
+ * identifier
23
+ * display_name
24
+ * description
25
+ * type
26
+ * arguments
27
+ """
28
+ __slots__ = ('_folder', '_metadata_file', '_program_file', '_resources_folder',
29
+ '_identifier', '_display_name', '_description', '_type', '_arguments')
30
+
31
+ def __init__(self, folder):
32
+ """Initialize Measure."""
33
+ # check to be sure that the required files are all there
34
+ assert os.path.isdir(folder), \
35
+ 'No directory was found at {}'.format(folder)
36
+ self._folder = os.path.abspath(folder)
37
+ self._metadata_file = os.path.join(self._folder, 'measure.xml')
38
+ assert os.path.isfile(self._metadata_file), \
39
+ 'No Measure XML file was found at {}'.format(self._metadata_file)
40
+ self._program_file = os.path.join(self._folder, 'measure.rb')
41
+ assert os.path.isfile(self._program_file), \
42
+ 'No Measure Ruby file was found at {}'.format(self._program_file)
43
+ resources_folder = os.path.join(self._folder, 'resources')
44
+ self._resources_folder = None
45
+ if os.path.isdir(resources_folder):
46
+ self._resources_folder = resources_folder
47
+
48
+ # parse the XML file to extract the measure properties and arguments
49
+ self._parse_metadata_file()
50
+
51
+ @property
52
+ def folder(self):
53
+ """Get the path to the folder in which the measure exists."""
54
+ return self._folder
55
+
56
+ @property
57
+ def metadata_file(self):
58
+ """Get the path to the measure.xml file within the measure folder.
59
+
60
+ This file contains metadata about the measure and this is where many of
61
+ the properties on this object originate from.
62
+ """
63
+ return self._metadata_file
64
+
65
+ @property
66
+ def program_file(self):
67
+ """Get the path to the measure.rb file within the measure folder.
68
+
69
+ This file contains the Ruby code that is executed whenever the measure
70
+ is run by the OpenStudio CLI.
71
+ """
72
+ return self._program_file
73
+
74
+ @property
75
+ def resources_folder(self):
76
+ """Get the path to the folder for resource Ruby file if it exists.
77
+
78
+ This folder contains Ruby file dependencies that are used in the program_file.
79
+ """
80
+ return self._resources_folder
81
+
82
+ @property
83
+ def identifier(self):
84
+ """Get text for the identifier of the measure.
85
+
86
+ This is also called the "name" in the measure.xml file.
87
+ """
88
+ return self._identifier
89
+
90
+ @property
91
+ def display_name(self):
92
+ """Get text for the human-readable display name of the measure.
93
+
94
+ This is called the "display_name" in the measure.xml file.
95
+ """
96
+ return self._display_name
97
+
98
+ @property
99
+ def description(self):
100
+ """Get text for describing what the measure does."""
101
+ return self._description
102
+
103
+ @property
104
+ def type(self):
105
+ """Get text for the type of measure this is. This is always one of 3 values.
106
+
107
+ * ModelMeasure - for measures that operate on the .osm model.
108
+ * EnergyPlusMeasure - for measures that operate on the .idf file.
109
+ * ReportingMeasure - for measures that run after the simulation is finished.
110
+ """
111
+ return self._type
112
+
113
+ @property
114
+ def arguments(self):
115
+ """Get a tuple of MeasureArgument objects for the measure input arguments.
116
+
117
+ The value property of these objects can be set in order to specify input
118
+ arguments for the measure.
119
+ """
120
+ return tuple(self._arguments)
121
+
122
+ @classmethod
123
+ def from_dict(cls, data, folder='.'):
124
+ """Initialize a Measure from a dictionary.
125
+
126
+ Args:
127
+ data: A dictionary in the format below.
128
+ folder: Path to a destination folder to save the measure files. (Default '.')
129
+
130
+ .. code-block:: python
131
+
132
+ {
133
+ "type": "Measure",
134
+ "identifier": string, # Measure identifier
135
+ "xml_data": string, # XML file data as string
136
+ "rb_data": string, # Ruby file data as string
137
+ "resource_data": {}, # Dictionary of strings for any resource ruby files
138
+ "argument_values": [], # List of values for each of the measure arguments
139
+ }
140
+ """
141
+ assert data['type'] == 'Measure', \
142
+ 'Expected Measure dictionary. Got {}.'.format(data['type'])
143
+ fp = os.path.join(folder, data['identifier'])
144
+ if not os.path.isdir(fp):
145
+ os.makedirs(fp)
146
+
147
+ # write out the contents of the measure
148
+ xml_fp = os.path.join(fp, 'measure.xml')
149
+ cls._decompress_to_file(data['xml_data'], xml_fp)
150
+ rb_fp = os.path.join(fp, 'measure.rb')
151
+ cls._decompress_to_file(data['rb_data'], rb_fp)
152
+ if 'resource_data' in data and data['resource_data'] is not None:
153
+ resource_path = os.path.join(fp, 'resources')
154
+ os.makedirs(resource_path)
155
+ for f_name, res in data['resource_data'].items():
156
+ res_fp = os.path.join(resource_path, f_name)
157
+ cls._decompress_to_file(res, res_fp)
158
+
159
+ # create the measure object and assign the arguments
160
+ new_measure = cls(fp)
161
+ for arg, val in zip(new_measure.arguments, data['argument_values']):
162
+ if val is not None:
163
+ arg.value = val
164
+ return new_measure
165
+
166
+ def to_dict(self):
167
+ """Convert Measure to a dictionary."""
168
+ # create a base dictionary with the XML and Ruby file data, and the arguments
169
+ base = {
170
+ 'type': 'Measure',
171
+ 'identifier': self.identifier,
172
+ 'xml_data': self._compress_file(self.metadata_file),
173
+ 'rb_data': self._compress_file(self.program_file),
174
+ 'argument_values': [arg._value for arg in self._arguments]
175
+ }
176
+
177
+ # add any resource files to the dictionary if they exist
178
+ if self.resources_folder:
179
+ base['resource_data'] = {}
180
+ for rb_file in os.listdir(self.resources_folder):
181
+ path = os.path.join(self.folder, rb_file)
182
+ base['resource_data'][rb_file] = self._compress_file(path)
183
+
184
+ return base
185
+
186
+ def to_osw_dict(self, full_path=False):
187
+ """Get a Python dictionary that can be written to an OSW JSON.
188
+
189
+ Specifically, this dictionary can be appended to the "steps" key of the
190
+ OpenStudio Workflow (.osw) JSON dictionary in order to include the measure
191
+ in the workflow.
192
+
193
+ Note that this method does not perform any checks to validate that the
194
+ Measure has all required values and only arguments with values will be
195
+ included in the dictionary. Validation should be done separately with
196
+ the validate method.
197
+
198
+ Args:
199
+ full_path: Boolean to note whether the full path to the measure should
200
+ be written under the 'measure_dir_name' key or just the measure
201
+ base name. (Default: False)
202
+ """
203
+ meas_dir = self.folder if full_path else os.path.basename(self.folder)
204
+ base = {'measure_dir_name': meas_dir, 'arguments': {}}
205
+ for arg in self._arguments:
206
+ if arg.value is not None:
207
+ base['arguments'][arg.identifier] = arg.value
208
+ return base
209
+
210
+ def validate(self, raise_exception=True):
211
+ """Check if all required arguments have values needed for simulation.
212
+
213
+ Args:
214
+ raise_exception: If True, an exception will be raised if there's a
215
+ required argument and there is no value. Otherwise, False will
216
+ be returned for this case and True will be returned if all
217
+ is correct.
218
+ """
219
+ for arg in self._arguments:
220
+ if not arg.validate(raise_exception):
221
+ return False
222
+ return True
223
+
224
+ @staticmethod
225
+ def sort_measures(measures):
226
+ """Sort measures according to the order they will be executed by OpenStudio CLI.
227
+
228
+ ModelMeasures will be first, followed by EnergyPlusMeasures, followed by
229
+ ReportingMeasures.
230
+ """
231
+ m_dict = {'ModelMeasure': [], 'EnergyPlusMeasure': [], 'ReportingMeasure': []}
232
+ for measure in measures:
233
+ m_dict[measure.type].append(measure)
234
+ return m_dict['ModelMeasure'] + m_dict['EnergyPlusMeasure'] + \
235
+ m_dict['ReportingMeasure']
236
+
237
+ def _parse_metadata_file(self):
238
+ """Parse measure properties from the measure.xml file."""
239
+ # create an element tree object
240
+ tree = ElementTree.parse(self._metadata_file)
241
+ root = tree.getroot()
242
+
243
+ # parse the measure properties from the element tree
244
+ self._identifier = root.find('name').text
245
+ self._display_name = root.find('display_name').text
246
+ self._description = root.find('description').text
247
+ self._type = None
248
+ for atr in root.find('attributes'):
249
+ if atr.find('name').text == 'Measure Type':
250
+ self._type = atr.find('value').text
251
+
252
+ # parse the measure arguments
253
+ self._arguments = []
254
+ arg_info = root.find('arguments')
255
+ if arg_info is not None:
256
+ for arg in arg_info:
257
+ arg_obj = MeasureArgument(arg)
258
+ if arg_obj.model_dependent:
259
+ # TODO: Figure out how to implement model-dependent arguments
260
+ raise NotImplementedError(
261
+ 'Model dependent arguments are not yet supported and measure '
262
+ 'argument is "{}" model dependent.'.format(arg_obj.identifier))
263
+ self._arguments.append(arg_obj)
264
+
265
+ @staticmethod
266
+ def _compress_file(filepath):
267
+ """Compress file contents to a string."""
268
+ # TODO: Research better ways to compress the file
269
+ with open(filepath, 'r') as input_file:
270
+ content = input_file.read()
271
+ return content
272
+
273
+ @staticmethod
274
+ def _decompress_to_file(value, filepath):
275
+ """Write file contents to a file."""
276
+ with open(filepath, 'w') as output_file:
277
+ output_file.write(value)
278
+
279
+ def __len__(self):
280
+ return len(self._arguments)
281
+
282
+ def __getitem__(self, key):
283
+ return self._arguments[key]
284
+
285
+ def __iter__(self):
286
+ return iter(self._arguments)
287
+
288
+ def ToString(self):
289
+ return self.__repr__()
290
+
291
+ def __repr__(self):
292
+ return 'Measure: {}'.format(self.display_name)
293
+
294
+
295
+ class MeasureArgument(object):
296
+ """Object representing a single measure argument.
297
+
298
+ Args:
299
+ xml_element: A Python XML Element object taken from the <arguments> section
300
+ of the measure.xml file.
301
+
302
+ Properties:
303
+ * identifier
304
+ * display_name
305
+ * value
306
+ * default_value
307
+ * type
308
+ * type_text
309
+ * required
310
+ * description
311
+ * model_dependent
312
+ * valid_choices
313
+ """
314
+ PYTHON_TYPES = {
315
+ 'Double': float,
316
+ 'Integer': int,
317
+ 'Boolean': bool,
318
+ 'String': str,
319
+ 'Choice': str
320
+ }
321
+
322
+ __slots__ = ('_identifier', '_display_name', '_value', '_default_value',
323
+ '_type', '_type_text', '_required', '_description',
324
+ '_model_dependent', '_valid_choices')
325
+
326
+ def __init__(self, xml_element):
327
+ """Initialize MeasureArgument."""
328
+ # parse the required properties of the argument
329
+ self._identifier = xml_element.find('name').text
330
+ self._type_text = xml_element.find('type').text
331
+ self._type = self.PYTHON_TYPES[self._type_text]
332
+ required = xml_element.find('required').text
333
+ self._required = True if required == 'true' else False
334
+
335
+ # set up the argument value and default value
336
+ self._value = None # will be set by user
337
+ self._default_value = None # will be overridden if it is present
338
+ if xml_element.find('default_value') is not None and \
339
+ xml_element.find('default_value').text is not None:
340
+ d_val = xml_element.find('default_value').text
341
+ if self._type_text == 'Boolean':
342
+ self._default_value = True if d_val.lower() == 'true' else False
343
+ else: # just use the type to cast the text
344
+ self._default_value = self._type(d_val)
345
+
346
+ # parse the optional properties of the argument
347
+ self._display_name = xml_element.find('display_name').text \
348
+ if xml_element.find('display_name') is not None else None
349
+ self._description = xml_element.find('description').text \
350
+ if xml_element.find('description') is not None else None
351
+ model_dependent = xml_element.find('model_dependent').text
352
+ self._model_dependent = True if model_dependent == 'true' else False
353
+
354
+ # parse any choice arguments if they exist
355
+ self._valid_choices = None
356
+ if self._type_text == 'Choice':
357
+ try:
358
+ self._valid_choices = tuple(choice.find('value').text
359
+ for choice in xml_element.find('choices'))
360
+ except TypeError as e:
361
+ raise ValueError(
362
+ 'The measure is invalid. Choice argument was found without any '
363
+ 'available choices.\n{}'.format(e))
364
+
365
+ @property
366
+ def identifier(self):
367
+ """Get text for the identifier of the argument.
368
+
369
+ This is also called the "name" in the measure.xml file.
370
+ """
371
+ return self._identifier
372
+
373
+ @property
374
+ def display_name(self):
375
+ """Get text for the human-readable display name of the argument.
376
+
377
+ This is called the "display_name" in the measure.xml file.
378
+ """
379
+ return self._display_name
380
+
381
+ @property
382
+ def value(self):
383
+ """Get or set the value for the argument.
384
+
385
+ If not set, this will be equal to the default_value and, if no default
386
+ value is included for this argument, it will be None.
387
+ """
388
+ if self._value is not None:
389
+ return self._value
390
+ return self._default_value
391
+
392
+ @value.setter
393
+ def value(self, val):
394
+ if val is not None:
395
+ try:
396
+ val = self._type(val)
397
+ except Exception:
398
+ raise TypeError('Value for measure argument "{}" must be a {}. '
399
+ 'Got {}'.format(self.identifier, self._type, type(val)))
400
+ if self._valid_choices:
401
+ assert val in self._valid_choices, 'Choice measure argument "{}" ' \
402
+ 'must be one of the following:\n{}\nGot {}'.format(
403
+ self.identifier, self._valid_choices, val)
404
+ self._value = val
405
+
406
+ @property
407
+ def default_value(self):
408
+ """Get the default value for the argument.
409
+
410
+ This may be None if no default value has been included.
411
+ """
412
+ return self._default_value
413
+
414
+ @property
415
+ def type(self):
416
+ """Get the Python type of argument this is (eg. float, str, int)."""
417
+ return self._type
418
+
419
+ @property
420
+ def type_text(self):
421
+ """Get a text string for the argument type as it appears in the measure.xml.
422
+
423
+ (eg. 'Double', 'String', 'Boolean').
424
+ """
425
+ return self._type_text
426
+
427
+ @property
428
+ def required(self):
429
+ """Get a boolean for whether this argument is required to run the measure."""
430
+ return self._required
431
+
432
+ @property
433
+ def description(self):
434
+ """Get text for describing what the measure does if it exists."""
435
+ return self._description
436
+
437
+ @property
438
+ def model_dependent(self):
439
+ """Get a boolean for whether this argument is dependent on the model."""
440
+ return self._model_dependent
441
+
442
+ @property
443
+ def valid_choices(self):
444
+ """Get a list of text for valid inputs for choice arguments.
445
+
446
+ This will be None if the argument type is not Choice.
447
+ """
448
+ return self._valid_choices
449
+
450
+ def validate(self, raise_exception=True):
451
+ """If this argument is required, check that it has a value.
452
+
453
+ If this argument is not required, this method will always return True.
454
+
455
+ Args:
456
+ raise_exception: If True, an exception will be raised if this argument
457
+ is required and there is no value. Otherwise, False will be returned
458
+ for this case and True will be returned if all is correct.
459
+ """
460
+ if self.required and self.value is None:
461
+ if self._valid_choices != (None,):
462
+ if raise_exception:
463
+ raise ValueError('Measure argument "{}" is required and missing '
464
+ 'a value'.format(self.identifier))
465
+ return False
466
+ return True
467
+
468
+ def ToString(self):
469
+ return self.__repr__()
470
+
471
+ def __repr__(self):
472
+ return '{} <{}> value: {}'.format(self.display_name, self.type_text, self.value)