Skip to content

Commit

Permalink
Operator and Argument registry
Browse files Browse the repository at this point in the history
Summary: This adds a registry for both operators and arguments. This is necessary to provide a centralized place for clients to get arguments and operators specified in serialized switches.

Test Plan: new tests

Reviewers: kalail, NorthIsUp

Reviewed By: NorthIsUp

Differential Revision: http://phabricator.local.disqus.net/D12534
  • Loading branch information
Fluxx committed Jul 22, 2014
1 parent 383947b commit 59fb1f4
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 3 deletions.
14 changes: 14 additions & 0 deletions .arcconfig
@@ -0,0 +1,14 @@

{
"project_id": "gutter",
"copyright_holder": "Disqus, Inc.",
"phutil_libraries": {"disqus": "/usr/local/include/php/libdisqus/src"},
"arcanist_configuration": "DisqusConfiguration",
"conduit_uri": "http://phabricator.local.disqus.net/",
"base": "git:merge-base(origin/master), arc:upstream, git:HEAD^",
"differential.field-selector": "DisqusDifferentialFieldSelector",
"history.immutable": false,
"unit.engine": "GenericXUnitTestEngine",
"unit.genericxunit.script": "make test-xunit XUNIT_FILE=arcanist.xml",
"unit.genericxunit.result_path": "test_results/xunit"
}
29 changes: 29 additions & 0 deletions .arclint
@@ -0,0 +1,29 @@
{
"linters" : {
"flake8" : {
"type" : "flake8",
"include" : "(\\.py$)",
"exclude" : "(^.git/|^ci/|^env/|/vendor/|_pb2\\.py$)",
"severity.rules" : {
"(^(W292|W391))" : "disabled",
"(^E501)" : "warning"
}
},
"filename" : {
"type" : "filename",
"include" : "(.*)"
},
"text" : {
"type" : "text",
"include" : "(\\.txt$)"
},
"nolint" : {
"type" : "nolint",
"include" : "(.*)"
},
"generated" : {
"type" : "generated",
"include" : "(.*)"
}
}
}
1 change: 0 additions & 1 deletion .gitignore
Expand Up @@ -5,7 +5,6 @@ dist/
docs/
build/
*.egg
.arcconfig
*.sublime-workspace
.coverage
test_results/
14 changes: 12 additions & 2 deletions Makefile
@@ -1,9 +1,19 @@
VERSION = $(shell python setup.py --version)

TEST_RESULTS_DIR=test_results
XUNIT_DIR=${TEST_RESULTS_DIR}/xunit
XUNIT_FILE=nosetests.xml
XUNIT_FILENAME=${XUNIT_DIR}/${XUNIT_FILE}

test:
python setup.py flake8
python setup.py nosetests

test-xunit: ${XUNIT_DIR}
python setup.py nosetests --verbosity=2 --with-xunit --xunit-file=${XUNIT_FILENAME}

${XUNIT_DIR}:
mkdir -p ${XUNIT_DIR}

release:
git tag $(VERSION)
git push origin $(VERSION)
Expand All @@ -13,4 +23,4 @@ release:
watch:
bundle exec guard

.PHONY: test release watch
.PHONY: test test-xunit release watch
9 changes: 9 additions & 0 deletions gutter/client/operators/comparable.py
@@ -1,4 +1,5 @@
from gutter.client.operators import Base
from gutter.client.registry import operators


class Equals(Base):
Expand Down Expand Up @@ -81,3 +82,11 @@ def applies_to(self, argument):

def __str__(self):
return 'more than or equal to "%s"' % self.lower_limit


operators.register(Equals)
operators.register(Between)
operators.register(LessThan)
operators.register(LessThanOrEqualTo)
operators.register(MoreThan)
operators.register(MoreThanOrEqualTo)
4 changes: 4 additions & 0 deletions gutter/client/operators/identity.py
@@ -1,4 +1,5 @@
from gutter.client.operators import Base
from gutter.client.registry import operators


class Truthy(Base):
Expand All @@ -12,3 +13,6 @@ def applies_to(self, argument):

def __str__(self):
return 'true'


operators.register(Truthy)
5 changes: 5 additions & 0 deletions gutter/client/operators/misc.py
@@ -1,6 +1,7 @@
from decimal import Context as decimal_Context, Decimal, DecimalException

from gutter.client.operators import Base
from gutter.client.registry import operators


class PercentRange(Base):
Expand Down Expand Up @@ -60,3 +61,7 @@ def variables(self):

def __str__(self):
return 'in %s%% of values' % self.upper_limit


operators.register(PercentRange)
operators.register(Percent)
46 changes: 46 additions & 0 deletions gutter/client/registry.py
@@ -0,0 +1,46 @@
from gutter.client.operators import Base
from gutter.client.arguments.base import Container


class Registry(object):

def __init__(self, cls):
self.__items = {}
self.__cls = cls

items = property(lambda self: self.__items)

def __getitem__(self, key):
try:
return self.__items[key]
except KeyError:
raise KeyError("Key '%s' not registered" % key)

def register(self, key, obj):
try:
if not issubclass(obj, self.__cls):
raise ValueError("%s is not a %s" % (obj, self.__cls))
except TypeError:
raise ValueError("%s is not a %s" % (obj, self.__cls))

self.__items[key] = obj


def extract_key_from_name(func):
def helpful_register(key_or_operator=None, operator=None):
if not operator:
operator = key_or_operator
key = operator.name
else:
key = key_or_operator

return func(key, operator)

return helpful_register


arguments = Registry(Container)
operators = Registry(Base)


operators.register = extract_key_from_name(operators.register)
88 changes: 88 additions & 0 deletions tests/test_registry.py
@@ -0,0 +1,88 @@
import itertools
import unittest2
from copy import copy

from exam.cases import Exam
from exam.decorators import around

from gutter.client.operators import (
Base,
comparable,
identity,
misc,
)
from gutter.client.arguments.base import Container
from gutter.client import registry

def all_operators_in(module):
for _, obj in vars(module).iteritems():
try:
if issubclass(obj, Base) and obj is not Base:
yield obj
except TypeError:
pass


ALL_OPERATORS = itertools.chain(
*map(all_operators_in, (comparable, identity, misc))
)


class TestOperator(Base):
name = 'test_name'


class TestArgument(Container):
pass


class TestOperatorRegistry(Exam, unittest2.TestCase):

@around
def preserve_registry(self):
original = registry.operators
registry.operators = copy(registry.operators)
yield
registry.operators = original

def test_has_default_operators_registered_by_default(self):
for operator in ALL_OPERATORS:
self.assertEqual(operator, registry.operators[operator.name])

def test_stores_registered_operator_via_provided_name(self):
registry.operators.register('hello', TestOperator)
self.assertEqual(registry.operators['hello'], TestOperator)

def test_uses_operator_name_if_not_provided_one(self):
registry.operators.register(TestOperator)
self.assertEqual(registry.operators['test_name'], TestOperator)

def test_raises_exception_if_object_is_not_an_operator(self):
self.assertRaises(
ValueError,
registry.operators.register,
'junk',
'thing'
)


class TestArgumentRegistry(Exam, unittest2.TestCase):

@around
def preserve_registry(self):
original = registry.arguments
registry.arguments = copy(registry.arguments)
yield
registry.arguments = original

def test_stores_registered_argument_via_provided_name(self):
registry.arguments.register('test', TestArgument)
self.assertEqual(registry.arguments['test'], TestArgument)

def test_raises_exception_if_object_is_not_an_argument(self):
self.assertRaises(
ValueError,
registry.arguments.register,
'junk',
'thing'
)

0 comments on commit 59fb1f4

Please sign in to comment.