GitHub PullRequestReviewCommentEvent at 2014-04-11 15:55:16
Repository: business-rules
Actor: simon-weber
Payload: {"comment":{"url":"https://api.github.com/repos/venmo/business-rules/pulls/comments/11538806","id":11538806,"diff_hunk":"@@ -0,0 +1,220 @@\n+import inspect\n+import re\n+from functools import wraps\n+from .six import string_types\n+\n+from .fields import (FIELD_TEXT, FIELD_NUMERIC, FIELD_NO_INPUT,\n+                     FIELD_SELECT, FIELD_SELECT_MULTIPLE)\n+from .utils import fn_name_to_pretty_label\n+\n+\n+class BaseType(object):\n+    def __init__(self, value):\n+        self.value = self._assert_valid_value_and_cast(value)\n+\n+    def _assert_valid_value_and_cast(self, value):\n+        raise NotImplemented()\n+\n+    @classmethod\n+    def get_all_operators(cls):\n+        methods = inspect.getmembers(cls)\n+        return [{'name': m[0],\n+                 'label': m[1].label,\n+                 'input_type': m[1].input_type}\n+                for m in methods if getattr(m[1], 'is_operator', False)]\n+\n+\n+def type_operator(input_type, label=None,\n+                  assert_type_for_arguments=True):\n+    \"\"\" Decorator to make a function into a type operator.\n+\n+    - assert_type_for_arguments - if True this patches the operator function\n+      so that arguments passed to it will have _assert_valid_value_and_cast\n+      called on them to make type errors explicit.\n+    \"\"\"\n+    def wrapper(func):\n+        func.is_operator = True\n+        func.label = label \\\n+            or fn_name_to_pretty_label(func.__name__)\n+        func.input_type = input_type\n+\n+        @wraps(func)\n+        def inner(self, *args, **kwargs):\n+            if assert_type_for_arguments:\n+                args = [self._assert_valid_value_and_cast(arg) for arg in args]\n+                kwargs = dict((k, self._assert_valid_value_and_cast(v))\n+                              for k, v in kwargs.items())\n+            return func(self, *args, **kwargs)\n+        return inner\n+    return wrapper\n+\n+\n+class StringType(BaseType):\n+\n+    name = \"string\"\n+\n+    def _assert_valid_value_and_cast(self, value):\n+        value = value or \"\"\n+        if not isinstance(value, string_types):\n+            raise AssertionError(\"{0} is not a valid string type.\".\n+                                 format(value))\n+        return value\n+\n+    @type_operator(FIELD_TEXT)\n+    def equal_to(self, other_string):\n+        return self.value == other_string\n+\n+    @type_operator(FIELD_TEXT, label=\"Equal To (case insensitive)\")\n+    def equal_to_case_insensitive(self, other_string):\n+        return self.value.lower() == other_string.lower()\n+\n+    @type_operator(FIELD_TEXT)\n+    def starts_with(self, other_string):\n+        return self.value.startswith(other_string)\n+\n+    @type_operator(FIELD_TEXT)\n+    def ends_with(self, other_string):\n+        return self.value.endswith(other_string)\n+\n+    @type_operator(FIELD_TEXT)\n+    def contains(self, other_string):\n+        return other_string in self.value\n+\n+    @type_operator(FIELD_TEXT)\n+    def matches_regex(self, regex):\n+        return re.search(regex, self.value)\n+\n+    @type_operator(FIELD_NO_INPUT)\n+    def non_empty(self):\n+        return bool(self.value)\n+\n+\n+class NumericType(BaseType):\n+    EPSILON = 0.000001\n+\n+    name = \"numeric\"\n+\n+    @staticmethod\n+    def _assert_valid_value_and_cast(value):\n+        if not isinstance(value, (float, int)):\n+            raise AssertionError(\"{0} is not a valid numeric type.\".\n+                                 format(value))\n+        return float(value)\n+\n+    @type_operator(FIELD_NUMERIC)\n+    def equal_to(self, other_numeric):\n+        return abs(self.value - other_numeric) <= self.EPSILON\n+\n+    @type_operator(FIELD_NUMERIC)\n+    def greater_than(self, other_numeric):\n+        return (self.value - other_numeric) > self.EPSILON\n+\n+    @type_operator(FIELD_NUMERIC)\n+    def greater_than_or_equal_to(self, other_numeric):\n+        return self.greater_than(other_numeric) or self.equal_to(other_numeric)\n+\n+    @type_operator(FIELD_NUMERIC)\n+    def less_than(self, other_numeric):\n+        return (other_numeric - self.value) > self.EPSILON\n+\n+    @type_operator(FIELD_NUMERIC)\n+    def less_than_or_equal_to(self, other_numeric):\n+        return self.less_than(other_numeric) or self.equal_to(other_numeric)\n+\n+\n+class BooleanType(BaseType):\n+\n+    name = \"boolean\"\n+\n+    def _assert_valid_value_and_cast(self, value):\n+        if type(value) != bool:\n+            raise AssertionError(\"{0} is not a valid boolean type\".\n+                                 format(value))\n+        return value\n+\n+    @type_operator(FIELD_NO_INPUT)\n+    def is_true(self):\n+        return self.value\n+\n+    def is_false(self):\n+        return not self.value\n+\n+\n+class SelectType(BaseType):\n+\n+    name = \"select\"\n+\n+    def _assert_valid_value_and_cast(self, value):\n+        if not hasattr(value, '__iter__'):\n+            raise AssertionError(\"{0} is not a valid select type\".\n+                                 format(value))\n+        return value\n+\n+    @staticmethod\n+    def _case_insensitive_equal_to(value_from_list, other_value):\n+        if isinstance(value_from_list, string_types) and \\\n+                isinstance(other_value, string_types):\n+                    return value_from_list.lower() == other_value.lower()\n+        else:\n+            return value_from_list == other_value\n+\n+    @type_operator(FIELD_SELECT, assert_type_for_arguments=False)\n+    def contains(self, other_value):\n+        for val in self.value:\n+            if self._case_insensitive_equal_to(val, other_value):\n+                return True\n+        return False\n+\n+    @type_operator(FIELD_SELECT, assert_type_for_arguments=False)\n+    def does_not_contain(self, other_value):\n+        for val in self.value:\n+            if self._case_insensitive_equal_to(val, other_value):\n+                return False\n+        return True\n+\n+\n+class SelectMultipleType(BaseType):\n+\n+    name = \"select_multiple\"\n+\n+    def _assert_valid_value_and_cast(self, value):\n+        if not hasattr(value, '__iter__'):\n+            raise AssertionError(\"{0} is not a valid select multiple type\".\n+                                 format(value))\n+        return value\n+\n+    @type_operator(FIELD_SELECT_MULTIPLE)\n+    def contains_all(self, other_value):\n+        select = SelectType(self.value)\n+        for other_val in other_value:\n+            if not select.contains(other_val):\n+                return False\n+        return True\n+\n+    @type_operator(FIELD_SELECT_MULTIPLE)\n+    def is_contained_by(self, other_value):\n+        other_select_multiple = SelectMultipleType(other_value)\n+        return other_select_multiple.contains_all(self.value)\n+\n+    @type_operator(FIELD_SELECT_MULTIPLE)\n+    def shares_at_least_one_element_with(self, other_value):\n+        select = SelectType(self.value)\n+        for other_val in other_value:\n+            if select.contains(other_val):\n+                return True\n+        return False\n+\n+    @type_operator(FIELD_SELECT_MULTIPLE)\n+    def shares_exactly_one_element_with(self, other_value):\n+        found_one = False\n+        select = SelectType(self.value)\n+        for other_val in other_value:\n+            if select.contains(other_val):\n+                if found_one:\n+                    return False\n+                found_one = True","path":"business_rules/operators.py","position":215,"original_position":215,"commit_id":"b8b9e6fac9e430b375d231e8ba70b82d7b385d5d","original_commit_id":"b8b9e6fac9e430b375d231e8ba70b82d7b385d5d","user":{"login":"simon-weber","id":950506,"avatar_url":"https://avatars.githubusercontent.com/u/950506?","gravatar_id":"5ac24ef595f7d1cd016bc3de24f78e60","url":"https://api.github.com/users/simon-weber","html_url":"https://github.com/simon-weber","followers_url":"https://api.github.com/users/simon-weber/followers","following_url":"https://api.github.com/users/simon-weber/following{/other_user}","gists_url":"https://api.github.com/users/simon-weber/gists{/gist_id}","starred_url":"https://api.github.com/users/simon-weber/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simon-weber/subscriptions","organizations_url":"https://api.github.com/users/simon-weber/orgs","repos_url":"https://api.github.com/users/simon-weber/repos","events_url":"https://api.github.com/users/simon-weber/events{/privacy}","received_events_url":"https://api.github.com/users/simon-weber/received_events","type":"User","site_admin":false},"body":"a little verbose. Maybe something like `return 1 == len([select.contains(other_val) for val in other_value])` unless shortcircuiting is needed for performance reasons","created_at":"2014-04-11T15:55:16Z","updated_at":"2014-04-11T15:55:16Z","html_url":"https://github.com/venmo/business-rules/pull/1#discussion_r11538806","pull_request_url":"https://api.github.com/repos/venmo/business-rules/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/venmo/business-rules/pulls/comments/11538806"},"html":{"href":"https://github.com/venmo/business-rules/pull/1#discussion_r11538806"},"pull_request":{"href":"https://api.github.com/repos/venmo/business-rules/pulls/1"}}}}