Permalink
Browse files

Merged some parts of sbuss/pypercube thanks to @sbuss.

  • Loading branch information...
1 parent 990e9fc commit 5a9815bddc396eb3e2325ead1eb657e35993510a @tsileo committed Oct 1, 2013
Showing with 680 additions and 0 deletions.
  1. +1 −0 cube/__init__.py
  2. +407 −0 cube/expression.py
  3. +103 −0 cube/filters.py
  4. +93 −0 cube/tests/test_compound_metric_expression.py
  5. +76 −0 cube/tests/test_filters.py
View
1 cube/__init__.py
@@ -1,3 +1,4 @@
+# -*- encoding: utf-8 -*-
from datetime import datetime
try:
View
407 cube/expression.py
@@ -0,0 +1,407 @@
+# -*- encoding: utf-8 -*-
+
+"""
+Copyright (c) 2012 Steven Buss
+Originally from:
+https://github.com/sbuss/pypercube/blob/master/pypercube/expression.py
+"""
+
+import types
+
+from cube import filters
+
+
+class CompoundMetricExpression(object):
+ """CompoundMetricExpressions have two MetricExpressions and an operator.
+
+ Used to do calculated metrics like sum(request(elapsed_ms)) / sum(request)
+ """
+ def __init__(self, metric1, operator=None, metric2=None):
+ """Create a CompoundMetricExpression.
+
+ :param metric1: The metric on the left side of the operator
+ :type metric1: `MetricExpression` or `CompoundMetricExpression`
+ :param operator: The operator to use on metric1 and metric2
+ :type operator: `str`
+ :param metric2: The metric on the right side of the operator
+ :type metric2: `MetricExpression` or `CompoundMetricExpression`
+
+ Note that CompoundMetricExpression does respect standard order of
+ operations (*/+-).
+
+ >>> e = EventExpression('request')
+ >>> m = MetricExpression('sum', e)
+ >>> print(m + m)
+ (sum(request) + sum(request))
+ >>> print(m + m - m)
+ ((sum(request) + sum(request)) - sum(request))
+ >>> print(m + m * 2)
+ (sum(request) + (sum(request) * 2))
+ >>> print(m + m * m / m)
+ (sum(request) + ((sum(request) * sum(request)) / sum(request)))
+ """
+ if not operator and metric2:
+ raise ValueError("You must have an operator if metric2 is"
+ "defined.")
+ self.metric1 = metric1
+ self.operator = operator
+ self.metric2 = metric2
+
+ def __eq__(self, other):
+ """Note that this tests for *equality* not *equivalence*, eg
+ m + (m + m) != (m + m) + m, though the two expressions are equivalent.
+ """
+ return self.metric1 == other.metric1 and \
+ self.operator == other.operator and \
+ self.metric2 == other.metric2
+
+ def __str__(self):
+ response = "%s" % self.metric1
+ if self.operator and self.metric2:
+ response = "(" + response
+ response += " {op} {right})".format(
+ op=self.operator,
+ right=self.metric2)
+ return response
+
+ def __add__(self, right):
+ """
+ >>> e = EventExpression('request')
+ >>> m = MetricExpression('sum', e)
+ >>> print(m + m)
+ (sum(request) + sum(request))
+ """
+ return CompoundMetricExpression(self, "+", right)
+
+ def __sub__(self, right):
+ """
+ >>> e = EventExpression('request')
+ >>> m = MetricExpression('sum', e)
+ >>> print(m - m)
+ (sum(request) - sum(request))
+ """
+ return CompoundMetricExpression(self, "-", right)
+
+ def __mul__(self, right):
+ """
+ >>> e = EventExpression('request')
+ >>> m = MetricExpression('sum', e)
+ >>> print(m * m)
+ (sum(request) * sum(request))
+ """
+ return CompoundMetricExpression(self, "*", right)
+
+ def __div__(self, right):
+ """
+ >>> e = EventExpression('request')
+ >>> m = MetricExpression('sum', e)
+ >>> print(m / m)
+ (sum(request) / sum(request))
+ """
+ return CompoundMetricExpression(self, "/", right)
+
+ def __truediv__(self, right):
+ return self.__div__(right)
+
+
+class MetricExpression(object):
+ """A single MetricExpression."""
+ def __init__(self, metric_type, event_expression):
+ """Calculate a Cube Metric.
+
+ :param metric_type: The type of the metric, like 'sum' or 'min'
+ :type metric_type: `str`
+ :param event_expression: The EventExpression over which to calculate
+ :type event_expression: `EventExpression`
+
+ Note that the EventExpressions for Cube metrics may only have *one*
+ event_property.
+
+ >>> e = EventExpression('request')
+ >>> print(MetricExpression('sum', e))
+ sum(request)
+ >>> e = EventExpression('request', 'elapsed_ms')
+ >>> print(MetricExpression('sum', e))
+ sum(request(elapsed_ms))
+ """
+ if len(event_expression.event_properties) > 1:
+ raise ValueError("Events for Metrics may only select a single "
+ "event property")
+ self.metric_type = metric_type
+ self.event_expression = event_expression
+
+ def __str__(self):
+ return "{type}({value})".format(
+ type=self.metric_type,
+ value=self.event_expression)
+
+ def __add__(self, right):
+ return CompoundMetricExpression(self) + right
+
+ def __sub__(self, right):
+ return CompoundMetricExpression(self) - right
+
+ def __mul__(self, right):
+ return CompoundMetricExpression(self) * right
+
+ def __div__(self, right):
+ return CompoundMetricExpression(self).__div__(right)
+
+ def __truediv__(self, right):
+ return CompoundMetricExpression(self).__truediv__(right)
+
+ def __eq__(self, other):
+ """
+ >>> e1 = EventExpression('request')
+ >>> m1 = MetricExpression('sum', e1)
+ >>> m2 = MetricExpression('sum', e1)
+ >>> m1 == m2
+ True
+ """
+ return self.metric_type == other.metric_type and \
+ self.event_expression == other.event_expression
+
+
+class Sum(MetricExpression):
+ """A "sum" metric."""
+ def __init__(self, event):
+ super(Sum, self).__init__("sum", event)
+
+
+class Min(MetricExpression):
+ """A "min" metric."""
+ def __init__(self, event):
+ super(Min, self).__init__("min", event)
+
+
+class Max(MetricExpression):
+ """A "max" metric."""
+ def __init__(self, event):
+ super(Max, self).__init__("max", event)
+
+
+class Median(MetricExpression):
+ """A "median" metric."""
+ def __init__(self, event):
+ super(Median, self).__init__("median", event)
+
+
+class Distinct(MetricExpression):
+ """A "distinct" metric."""
+ def __init__(self, event):
+ super(Distinct, self).__init__("distinct", event)
+
+
+class EventExpression(object):
+ def __init__(self, event_type, event_properties=None):
+ """Create an Event expression.
+
+ :param event_type: The type of the event to query for.
+ :type event_type: str
+ :param event_properties: Any properties to fetch from the event.
+ :type event_properties: `str` or `list(str)`
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> print(request_time.event_type)
+ request
+ >>> print(request_time.event_properties)
+ ['elapsed_ms']
+ >>> print(request_time.eq('path', '/').gt('elapsed_ms', 100).lt(
+ ... 'elapsed_ms', 1000)) # doctest:+NORMALIZE_WHITESPACE
+ request(elapsed_ms).eq(path, "/").gt(elapsed_ms, 100).lt(elapsed_ms,
+ 1000)
+ """
+ self.event_type = event_type
+ if event_properties:
+ if isinstance(event_properties, types.StringTypes):
+ event_properties = [event_properties]
+ else:
+ event_properties = []
+ self.event_properties = event_properties
+ self.filters = []
+
+ def copy(self):
+ c = EventExpression(self.event_type, self.event_properties[:])
+ c.filters = self.filters[:]
+ return c
+
+ def __eq__(self, other):
+ """
+ >>> e1 = EventExpression('request')
+ >>> e2 = EventExpression('request')
+ >>> e1 == e2
+ True
+ >>> e1 = EventExpression('request', 'path')
+ >>> e1 == e2
+ False
+ """
+ return self.event_type == other.event_type and \
+ len(self.event_properties) == len(other.event_properties) and \
+ all((x == y) for (x, y) in \
+ zip(self.event_properties, other.event_properties)) and \
+ self.event_properties == other.event_properties and \
+ len(self.filters) == len(other.filters) and \
+ all((x == y) for (x, y) in zip(self.filters, other.filters))
+
+ def eq(self, event_property, value):
+ """An equals filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.eq('path', '/')
+ >>> print(filtered)
+ request(elapsed_ms).eq(path, "/")
+ """
+ c = self.copy()
+ c.filters.append(filters.EQ(event_property, value))
+ return c
+
+ def ne(self, event_property, value):
+ """A not-equal filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.ne('path', '/')
+ >>> print(filtered)
+ request(elapsed_ms).ne(path, "/")
+ """
+ c = self.copy()
+ c.filters.append(filters.NE(event_property, value))
+ return c
+
+ def lt(self, event_property, value):
+ """A less-than filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.lt('elapsed_ms', 500)
+ >>> print(filtered)
+ request(elapsed_ms).lt(elapsed_ms, 500)
+ """
+ c = self.copy()
+ c.filters.append(filters.LT(event_property, value))
+ return c
+
+ def le(self, event_property, value):
+ """A less-than-or-equal-to filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.le('elapsed_ms', 500)
+ >>> print(filtered)
+ request(elapsed_ms).le(elapsed_ms, 500)
+ """
+ c = self.copy()
+ c.filters.append(filters.LE(event_property, value))
+ return c
+
+ def gt(self, event_property, value):
+ """A greater-than filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.gt('elapsed_ms', 500)
+ >>> print(filtered)
+ request(elapsed_ms).gt(elapsed_ms, 500)
+ """
+ c = self.copy()
+ c.filters.append(filters.GT(event_property, value))
+ return c
+
+ def ge(self, event_property, value):
+ """A greater-than-or-equal-to filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.ge('elapsed_ms', 500)
+ >>> print(filtered)
+ request(elapsed_ms).ge(elapsed_ms, 500)
+ """
+ c = self.copy()
+ c.filters.append(filters.GE(event_property, value))
+ return c
+
+ def re(self, event_property, value):
+ """A regular expression filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.re('path', '[^A-Za-z0-9+]')
+ >>> print(filtered)
+ request(elapsed_ms).re(path, "[^A-Za-z0-9+]")
+ """
+ c = self.copy()
+ c.filters.append(filters.RE(event_property, value))
+ return c
+
+ def startswith(self, event_property, value):
+ """A starts-with filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.startswith('path', '/cube')
+ >>> print(filtered)
+ request(elapsed_ms).re(path, "^/cube")
+ """
+ c = self.copy()
+ c.filters.append(filters.RE(event_property, "^{value}".format(
+ value=value)))
+ return c
+
+ def endswith(self, event_property, value):
+ """An ends-with filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.endswith('path', 'event/get')
+ >>> print(filtered)
+ request(elapsed_ms).re(path, ".*event/get$")
+ """
+ c = self.copy()
+ c.filters.append(filters.RE(event_property, ".*{value}$".format(
+ value=value)))
+ return c
+
+ def contains(self, event_property, value):
+ """A string-contains filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.contains('path', 'event')
+ >>> print(filtered)
+ request(elapsed_ms).re(path, ".*event.*")
+ """
+ c = self.copy()
+ c.filters.append(filters.RE(event_property, ".*{value}.*".format(
+ value=value)))
+ return c
+
+ def in_array(self, event_property, value):
+ """An in-array filter chain.
+
+ >>> request_time = EventExpression('request', 'elapsed_ms')
+ >>> filtered = request_time.in_array('path', '/event')
+ >>> print(filtered)
+ request(elapsed_ms).in(path, ["/", "e", "v", "e", "n", "t"])
+ >>> filtered = request_time.in_array('path', ['/event', '/'])
+ >>> print(filtered)
+ request(elapsed_ms).in(path, ["/event", "/"])
+ """
+ c = self.copy()
+ c.filters.append(filters.IN(event_property, value))
+ return c
+
+ def get_expression(self):
+ event_type = self.event_type
+ event_property = self.event_properties
+ filters = self.filters
+
+ expression = "{event_type}".format(event_type=event_type)
+
+ if event_property:
+ if isinstance(event_property, types.StringTypes):
+ p = event_property
+ else:
+ p = ", ".join(str(x) for x in event_property)
+
+ expression += "({properties})".format(properties=p)
+
+ if filters:
+ expression += "".join(str(filter) for filter in filters)
+ return expression
+
+ def __repr__(self):
+ return "<EventExpression: {value}>".format(value=self)
+
+ def __str__(self):
+ return self.get_expression()
View
103 cube/filters.py
@@ -0,0 +1,103 @@
+# -*- encoding: utf-8 -*-
+
+"""
+Copyright (c) 2012 Steven Buss
+Originally from:
+https://github.com/sbuss/pypercube/blob/master/pypercube/filters.py
+"""
+
+import json
+
+
+class Filter(object):
+ """A filter for a cube event query."""
+ def __init__(self, type, property_name, value):
+ """Create a Filter.
+
+ :param type: The type of the filter, eg "eq" or "gt".
+ :type type: str
+ :param property_name: The name of property on which to filter.
+ :type property_name: str
+ :param value: The value to which the property will be compared.
+ :type value: str or list(str)
+ """
+ self.type = type
+ self.property_name = property_name
+ self.value = value
+
+ def __repr__(self):
+ return "<{name}: {value}>".format(name=self.__class__.__name__,
+ value=self)
+
+ def __str__(self):
+ return ".{type}({property}, {value})".format(
+ type=self.type, property=self.property_name,
+ value=json.dumps(self.value))
+
+ def __eq__(self, other):
+ return self.type == other.type and \
+ self.property_name == other.property_name and \
+ self.value == other.value
+
+
+class EQ(Filter):
+ """An "equals" filter"""
+ def __init__(self, property_name, value):
+ return super(EQ, self).__init__("eq", property_name, value)
+
+
+class LT(Filter):
+ """A "less than" filter"""
+ def __init__(self, property_name, value):
+ return super(LT, self).__init__("lt", property_name, value)
+
+
+class LE(Filter):
+ """A "less than or equal to" filter"""
+ def __init__(self, property_name, value):
+ return super(LE, self).__init__("le", property_name, value)
+
+
+class GT(Filter):
+ """A "greater than" filter"""
+ def __init__(self, property_name, value):
+ return super(GT, self).__init__("gt", property_name, value)
+
+
+class GE(Filter):
+ """A "greater than or equal to" filter"""
+ def __init__(self, property_name, value):
+ return super(GE, self).__init__("ge", property_name, value)
+
+
+class NE(Filter):
+ """A "not equals" filter"""
+ def __init__(self, property_name, value):
+ return super(NE, self).__init__("ne", property_name, value)
+
+
+class RE(Filter):
+ """A "regular expression" filter"""
+ def __init__(self, property_name, value):
+ return super(RE, self).__init__("re", property_name, value)
+
+
+class IN(Filter):
+ """An "in array" filter"""
+ def __init__(self, property_name, value):
+ return super(IN, self).__init__("in", property_name,
+ [x for x in value])
+
+
+class StartsWith(RE):
+ """A "starts with" filter"""
+ def __init__(self, property_name, value):
+ v = "^{value}".format(value=value)
+ return super(StartsWith, self).__init__(property_name, v)
+
+
+class EndsWith(RE):
+ """An "ends with" filter"""
+ def __init__(self, property_name, value):
+ v = ".*{value}$".format(value=value)
+ return super(EndsWith, self).__init__(property_name, v)
View
93 cube/tests/test_compound_metric_expression.py
@@ -0,0 +1,93 @@
+# -*- encoding: utf-8 -*-
+
+"""
+Copyright (c) 2012 Steven Buss
+Originally from:
+https://github.com/sbuss/pypercube/blob/master/tests/test_compound_metric_expression.py
+"""
+
+import unittest
+
+from cube.expression import CompoundMetricExpression
+from cube.expression import EventExpression
+from cube.expression import Max
+from cube.expression import MetricExpression
+from cube.expression import Min
+from cube.expression import Sum
+
+
+class TestCompoundMetricExpressions(unittest.TestCase):
+ def setUp(self):
+ self.e = EventExpression('test', 'ing')
+ self.sum = Sum(self.e)
+ self.max = Max(self.e)
+ self.min = Min(self.e)
+
+ def test_addition(self):
+ self.assertEqual("%s" % (self.sum + self.min),
+ "(sum(test(ing)) + min(test(ing)))")
+ self.assertEqual("%s" % (self.sum + self.min + self.max),
+ "((sum(test(ing)) + min(test(ing))) + max(test(ing)))")
+ self.assertEqual("%s" %
+ ((self.sum + self.min) + (self.max + self.max)),
+ "((sum(test(ing)) + min(test(ing))) + "\
+ "(max(test(ing)) + max(test(ing))))")
+
+ def test_subtraction(self):
+ self.assertEqual("%s" % (self.sum - self.min - self.max),
+ "((sum(test(ing)) - min(test(ing))) - max(test(ing)))")
+ self.assertEqual("%s" %
+ ((self.sum - self.min) + (self.max - self.sum)),
+ "((sum(test(ing)) - min(test(ing))) + " \
+ "(max(test(ing)) - sum(test(ing))))")
+
+ def test_multiplication(self):
+ self.assertEqual("%s" % (self.sum - self.min * self.max),
+ "(sum(test(ing)) - (min(test(ing)) * max(test(ing))))")
+ self.assertEqual("%s" %
+ ((self.max - self.min) * (self.sum + self.min)),
+ "((max(test(ing)) - min(test(ing))) * "\
+ "(sum(test(ing)) + min(test(ing))))")
+ self.assertEqual("%s" %
+ (self.max - self.min * self.sum + self.min),
+ "((max(test(ing)) - (min(test(ing)) * "\
+ "sum(test(ing)))) + min(test(ing)))")
+
+ def test_division(self):
+ self.assertEqual("%s" % (self.max / self.sum * self.min),
+ "((max(test(ing)) / sum(test(ing))) * min(test(ing)))")
+ self.assertEqual("%s" %
+ ((self.min / self.sum) / (self.min + self.max)),
+ "((min(test(ing)) / sum(test(ing))) / "\
+ "(min(test(ing)) + max(test(ing))))")
+ self.assertEqual("%s" %
+ (self.min / self.sum * self.max + self.min - self.max),
+ "((((min(test(ing)) / sum(test(ing))) * max(test(ing))) + "\
+ "min(test(ing))) - max(test(ing)))")
+
+ def test_types(self):
+ self.assertTrue(isinstance(self.min, MetricExpression))
+ self.assertTrue(isinstance(self.max, MetricExpression))
+ self.assertTrue(isinstance(self.sum + self.sum,
+ CompoundMetricExpression))
+ self.assertTrue(isinstance(self.sum + self.min,
+ CompoundMetricExpression))
+ self.assertTrue(isinstance(self.max + self.min,
+ CompoundMetricExpression))
+
+ m1 = self.sum
+ m2 = self.min
+ self.assertTrue(isinstance(m1 + m2, CompoundMetricExpression))
+ self.assertTrue(isinstance(m1 - m2, CompoundMetricExpression))
+ self.assertTrue(isinstance(m1 * m2, CompoundMetricExpression))
+ self.assertTrue(isinstance(m1.__div__(m2), CompoundMetricExpression))
+ self.assertTrue(isinstance(
+ m1.__truediv__(m2), CompoundMetricExpression))
+
+ def test_filters(self):
+ e1 = EventExpression('request', 'elapsed_ms').eq('path', '/')
+ e2 = e1.gt('elapsed_ms', 500)
+ self.assertEqual("%s" % (Sum(e1) - Min(e2)),
+ "(sum(request(elapsed_ms).eq(path, \"/\")) - "\
+ "min(request(elapsed_ms).eq(path, \"/\").gt("\
+ "elapsed_ms, 500)))")
View
76 cube/tests/test_filters.py
@@ -0,0 +1,76 @@
+# -*- encoding: utf-8 -*-
+
+"""
+Copyright (c) 2012 Steven Buss
+Originally from:
+https://github.com/sbuss/pypercube/blob/master/tests/test_filters.py
+"""
+
+import unittest
+
+from cube.filters import Filter
+from cube.filters import EQ
+from cube.filters import LT
+from cube.filters import LE
+from cube.filters import GT
+from cube.filters import GE
+from cube.filters import NE
+from cube.filters import RE
+from cube.filters import IN
+from cube.filters import StartsWith
+from cube.filters import EndsWith
+
+
+class TestFilter(unittest.TestCase):
+ def test_equality(self):
+ f1 = Filter('eq', 'name', 'test')
+ f2 = EQ('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('lt', 'name', 'test')
+ self.assertNotEqual(f1, f2)
+ f2 = LT('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('le', 'name', 'test')
+ f2 = LE('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('gt', 'name', 'test')
+ f2 = GT('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('ge', 'name', 'test')
+ f2 = GE('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('ne', 'name', 'test')
+ f2 = NE('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('re', 'name', 'test')
+ f2 = RE('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('in', 'name', ['t', 'e', 's', 't'])
+ f2 = IN('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('in', 'name', ['test'])
+ f2 = IN('name', ['test'])
+ self.assertEqual(f1, f2)
+ f1 = Filter('re', 'name', '^test')
+ f2 = StartsWith('name', 'test')
+ self.assertEqual(f1, f2)
+ f1 = Filter('re', 'name', '.*test$')
+ f2 = EndsWith('name', 'test')
+ self.assertEqual(f1, f2)
+
+ def test_starts_with(self):
+ f = StartsWith('name', 'test')
+ self.assertEqual("%s" % f, '.re(name, "^test")')
+
+ def test_ends_with(self):
+ f = EndsWith('name', 'test')
+ self.assertEqual("%s" % f, '.re(name, ".*test$")')
+
+# def test_re(self):
+# # FIXME: Regular expressions are broken
+# f = RE('name', r"\s+([A-Za-z0-9]+)")
+# self.assertEqual("%s" % f, r'.re(name, "\s+([A-Za-z0-9]+)")')
+
+ def test_in(self):
+ f = IN('name', ['a', 'b', 'c'])
+ self.assertEqual("%s" % f, '.in(name, ["a", "b", "c"])')

0 comments on commit 5a9815b

Please sign in to comment.