Josh Finnie PRO
Full stack staff engineer. Lives in the DMV.
Easy with jsonify
No need for additional packages
The idea behind the DSL was to allow for users to calculate complex metrics without programmatically interfering:
Want to calculate "total Facebook likes"?
Want to calculate the "average Facebook likes & Twitter retweets per post & tweet"?
<URL>?calculate=total.facebook.*.likes
<URL>?calculate=div(sum(total.facebook.*.likes,total.twitter.*retweets),sum(count.facebook.*,count.twitter.*))
@app.route('/workspace/<workspace_id>', methods=['GET'])
def index(workspace_id):
calc = request.args.get('calculate', None)
bucket = request.args.get('bucket', 'day')
start_date = request.args.get('start_date', None)
end_date = request.args.get('end_date', None)
# Get variables from user submitted DSL
es_vars = VariableParser.parse(calc)
# Build equation from user submitted DSL
equation = EquationBuilder(calc, [x.string for x in es_vars])
# Generate ElasticSearch aggregation and call it
time_series_agg = TimeSeriesAggregation(interval=bucket)
sub_agg_dict = AggregationBuilder(es_vars=es_vars).as_dict()
time_series_agg.add_subaggregations(sub_agg_dict)
total_results = time_series_agg.call(workspace_id, request.args)
"""
total_results ~= {
"total.facebook.*.likes": {
"total.facebook.*.likes": {
"value": 7
}
},
"count.facebook.*": {
"doc_count": 9
},
...
}
"""
# Compute equation from user submitted DSL with ES aggregation
final_results = []
for time_bucket in total_results['data']['main']['buckets']:
result = equation.calculate_result(time_bucket)
final_results += [{'x': time_bucket['key_as_string'], 'y': result}]
# Return the data in JSON format
return jsonify({"data": final_results})
class EquationBuilder(object):
...
@staticmethod
def _split_calculation(calculation):
"""
`_split_calculation` is a private function to separate a formula
represented as a string into words and ')'s
`div(add(x,y),z)` =>
`['div','add','x','y',')','z',')']`
"""
...
def _evaluate(self, operator):
operand_list = []
i = self.stack.pop()
while i is not ")":
operand_list.append(i)
i = self.stack.pop()
answer = self._perform_operation(operand_list, operator)
self.stack.append(answer)
return answer
def calculate_result(self, var_dict):
OPERATORS = ["add", "div", "sub", "mult"]
self.stack = []
for item in reversed(self.tokens):
if item in [')']:
self.stack.append(item)
elif is_number(item):
self.stack.append(float(item))
elif item in self.variables:
if "count" in item:
self.stack.append(var_dict[item]['doc_count'])
else:
self.stack.append(var_dict[item][item]['value'])
elif item in OPERATORS:
self._evaluate(item)
return self.stack[0]
Any questions?
Come work for TrackMaven, we're pretty awesome!
@joshfinnie
http://www.joshfinnie.com
By Josh Finnie