class ExpressionIndicatorSpec(IndicatorSpecBase): type = TypeProperty('expression') datatype = DataTypeProperty(required=True) is_nullable = BooleanProperty(default=True) is_primary_key = BooleanProperty(default=False) create_index = BooleanProperty(default=False) expression = DefaultProperty(required=True) transform = DictProperty(required=False) def parsed_expression(self, context): from corehq.apps.userreports.expressions.factory import ExpressionFactory expression = ExpressionFactory.from_spec(self.expression, context) datatype_transform = transform_for_datatype(self.datatype) if self.transform: generic_transform = TransformFactory.get_transform( self.transform).get_transform_function() inner_getter = TransformedGetter(expression, generic_transform) else: inner_getter = expression return TransformedGetter(inner_getter, datatype_transform) def readable_output(self, context): from corehq.apps.userreports.expressions.factory import ExpressionFactory expression_object = ExpressionFactory.from_spec( self.expression, context) return str(expression_object)
class PropertyPathGetterSpec(JsonObject): """ This expression returns ``doc["child"]["age"]``: .. code:: json { "type": "property_path", "property_path": ["child", "age"] } An optional ``"datatype"`` attribute may be specified, which will attempt to cast the property to the given data type. The options are "date", "datetime", "string", "integer", and "decimal". If no datatype is specified, "string" will be used. """ type = TypeProperty('property_path') property_path = ListProperty(str, required=True) datatype = DataTypeProperty(required=False) def __call__(self, item, context=None): transform = transform_from_datatype(self.datatype) return transform(safe_recursive_lookup(item, self.property_path)) def __str__(self): value = "/".join(self.property_path) if self.datatype: "({datatype}){value}".format(datatype=self.datatype, value=value) return value
class PropertyNameGetterSpec(JsonObject): """ This expression returns ``doc["age"]``: .. code:: json { "type": "property_name", "property_name": "age" } An optional ``"datatype"`` attribute may be specified, which will attempt to cast the property to the given data type. The options are "date", "datetime", "string", "integer", and "decimal". If no datatype is specified, "string" will be used. """ type = TypeProperty('property_name') property_name = DefaultProperty(required=True) datatype = DataTypeProperty(required=False) def configure(self, property_name_expression): self._property_name_expression = property_name_expression def __call__(self, item, context=None): raw_value = item.get(self._property_name_expression( item, context)) if isinstance(item, dict) else None return transform_from_datatype(self.datatype)(raw_value) def __str__(self): value = self.property_name if self.datatype: "({datatype}){value}".format(datatype=self.datatype, value=value) return value
class EvalExpressionSpec(JsonObject): type = TypeProperty('evaluator') statement = StringProperty(required=True) context_variables = DictProperty() datatype = DataTypeProperty(required=False) def configure(self, context_variables): self._context_variables = context_variables def __call__(self, item, context=None): var_dict = self.get_variables(item, context) try: untransformed_value = eval_statements(self.statement, var_dict) return transform_from_datatype(self.datatype)(untransformed_value) except (InvalidExpression, SyntaxError, TypeError, ZeroDivisionError): return None def get_variables(self, item, context): var_dict = { slug: variable_expression(item, context) for slug, variable_expression in self._context_variables.items() } return var_dict def __str__(self): value = self.statement for name, exp in self._context_variables.items(): value.replace(name, str(exp)) if self.datatype: value = "({}){}".format(self.datatype, value) return value
class RawIndicatorSpec(PropertyReferenceIndicatorSpecBase): type = TypeProperty('raw') datatype = DataTypeProperty(required=True) is_nullable = BooleanProperty(default=True) is_primary_key = BooleanProperty(default=False) create_index = BooleanProperty(default=False) @property def getter(self): transform = transform_for_datatype(self.datatype) getter = getter_from_property_reference(self) return TransformedGetter(getter, transform)
class DynamicChoiceListFilterSpec(FilterSpec): type = TypeProperty('dynamic_choice_list') show_all = BooleanProperty(default=True) datatype = DataTypeProperty(default='string') choice_provider = DictProperty() ancestor_expression = DictProperty(default={}, required=False) def get_choice_provider_spec(self): return self.choice_provider or {'type': DATA_SOURCE_COLUMN} @property def choices(self): return []
class PropertyPathGetterSpec(JsonObject): type = TypeProperty('property_path') property_path = ListProperty(six.text_type, required=True) datatype = DataTypeProperty(required=False) def __call__(self, item, context=None): transform = transform_from_datatype(self.datatype) return transform(safe_recursive_lookup(item, self.property_path)) def __str__(self): value = "/".join(self.property_path) if self.datatype: "({datatype}){value}".format(datatype=self.datatype, value=value) return value
class PropertyNameGetterSpec(JsonObject): type = TypeProperty('property_name') property_name = DefaultProperty(required=True) datatype = DataTypeProperty(required=False) def configure(self, property_name_expression): self._property_name_expression = property_name_expression def __call__(self, item, context=None): raw_value = item.get(self._property_name_expression(item, context)) if isinstance(item, dict) else None return transform_from_datatype(self.datatype)(raw_value) def __str__(self): value = self.property_name if self.datatype: "({datatype}){value}".format(datatype=self.datatype, value=value) return value
class FilterSpec(JsonObject): """ This is the spec for a report filter - a thing that should show up as a UI filter element in a report (like a date picker or a select list). """ type = StringProperty(required=True, choices=[ 'date', 'quarter', 'numeric', 'pre', 'choice_list', 'dynamic_choice_list', 'multi_field_dynamic_choice_list', 'location_drilldown', 'village_choice_list' ]) # this shows up as the ID in the filter HTML. slug = StringProperty(required=True) field = StringProperty( required=True) # this is the actual column that is queried display = DefaultProperty() datatype = DataTypeProperty(default='string') def get_display(self): return self.display or self.slug
class FilterSpec(JsonObject): """ This is the spec for a report filter - a thing that should show up as a UI filter element in a report (like a date picker or a select list). """ type = StringProperty( required=True, choices=[ 'date', 'quarter', 'numeric', 'pre', 'choice_list', 'dynamic_choice_list', 'multi_field_dynamic_choice_list', 'location_drilldown', 'village_choice_list' ] ) # this shows up as the ID in the filter HTML. slug = StringProperty(required=True) field = StringProperty(required=True) # this is the actual column that is queried display = DefaultProperty() datatype = DataTypeProperty(default='string') def get_display(self): return self.display or self.slug @classmethod def wrap(cls, *args, **kwargs): # Because pre_value is set to DefaultProperty, pre_value is automatically # interpreted to be a datatype when it's fetched by jsonobject. # This fails hard when the spec expects a string # and gets something else...like a date. doc = args[0] if 'pre_value' in doc: pre_value = doc['pre_value'] data_type = doc.get('datatype') if data_type == 'string': doc['pre_value'] = str(pre_value) return super().wrap(*args, **kwargs)
class ChoiceListFilterSpec(FilterSpec): type = TypeProperty('choice_list') show_all = BooleanProperty(default=True) datatype = DataTypeProperty(default='string') choices = ListProperty(FilterChoice)
class SqlColumnProperties(jsonobject.JsonObject): datatype = DataTypeProperty(required=True) statement = jsonobject.StringProperty(required=True) statement_params = jsonobject.DictProperty()
class EvalExpressionSpec(JsonObject): """ ``evaluator`` expression can be used to evaluate statements that contain arithmetic (and simple python like statements). It evaluates the statement specified by ``statement`` which can contain variables as defined in ``context_variables``. .. code:: json { "type": "evaluator", "statement": "a + b - c + 6", "context_variables": { "a": 1, "b": 20, "c": 2 } } This returns 25 (1 + 20 - 2 + 6). ``statement`` can be any statement that returns a valid number. All python math `operators <https://en.wikibooks.org/wiki/Python_Programming/Basic_Math#Mathematical_Operators>`__ except power operator are available for use. ``context_variables`` is a dictionary of Expressions where keys are names of variables used in the ``statement`` and values are expressions to generate those variables. Variables can be any valid numbers (Python datatypes ``int``, ``float`` and ``long`` are considered valid numbers.) or also expressions that return numbers. In addition to numbers the following types are supported: - ``date`` - ``datetime`` Only the following functions are permitted: - ``rand()``: generate a random number between 0 and 1 - ``randint(max)``: generate a random integer between 0 and ``max`` - ``int(value)``: convert ``value`` to an int. Value can be a number or a string representation of a number - ``float(value)``: convert ``value`` to a floating point number - ``str(value)``: convert ``value`` to a string - ``timedelta_to_seconds(time_delta)``: convert a TimeDelta object into seconds. This is useful for getting the number of seconds between two dates. - e.g. ``timedelta_to_seconds(time_end - time_start)`` - ``range(start, [stop], [skip])``: the same as the python ```range`` function <https://docs.python.org/2/library/functions.html#range>`__. Note that for performance reasons this is limited to 100 items or less. """ type = TypeProperty('evaluator') statement = StringProperty(required=True) context_variables = DictProperty() datatype = DataTypeProperty(required=False) def configure(self, context_variables): self._context_variables = context_variables def __call__(self, item, context=None): var_dict = self.get_variables(item, context) try: untransformed_value = eval_statements(self.statement, var_dict) return transform_from_datatype(self.datatype)(untransformed_value) except (InvalidExpression, SyntaxError, TypeError, ZeroDivisionError): return None def get_variables(self, item, context): var_dict = { slug: variable_expression(item, context) for slug, variable_expression in self._context_variables.items() } return var_dict def __str__(self): value = self.statement for name, exp in self._context_variables.items(): value.replace(name, str(exp)) if self.datatype: value = "({}){}".format(self.datatype, value) return value