Commits

Andriy Kornatskyy committed d2c0d61

Added base64, scientific, relative_unixtime, relative_utcunixtime and int_adapter rules.

  • Participants
  • Parent commits 265750a

Comments (0)

Files changed (5)

doc/userguide.rst

   :py:class:`~wheezy.validation.rules.SlugRule`.
 * ``email``. Ensures a valid email. See
   :py:class:`~wheezy.validation.rules.EmailRule`.
+* ``scientific``. Ensures a valid scientific string input. See
+  :py:class:`~wheezy.validation.rules.ScientificRule`.
+* ``base64``. Ensures a valid base64 string input. See
+  :py:class:`~wheezy.validation.rules.Base64Rule`.
 * ``range``. Ensures value is in range defined by this rule. Works with any
   numbers including int, float, decimal, etc. Supported range attributes
   include: ``min``, ``max``. See
   :py:class:`~wheezy.validation.rules.OneOfRule`.
 * ``relative_date``, ``relative_utcdate``, ``relative_tzdate``,
   ``relative_datetime``, ``relative_utcdatetime``,
-  ``relative_tzdatetime``. Check if value is in relative
-  date/datetime range per local, UTC or tz time. See
+  ``relative_tzdatetime``. Check if value is in relative date/datetime
+  range per local, UTC or tz time. See
   :py:class:`~wheezy.validation.rules.RelativeDateDeltaRule`,
   :py:class:`~wheezy.validation.rules.RelativeUTCDateDeltaRule`,
   :py:class:`~wheezy.validation.rules.RelativeTZDateDeltaRule` and
   :py:class:`~wheezy.validation.rules.RelativeDateTimeDeltaRule`,
   :py:class:`~wheezy.validation.rules.RelativeUTCDateTimeDeltaRule`,
   :py:class:`~wheezy.validation.rules.RelativeTZDateTimeDeltaRule`.
+* ``relative_timestamp``, ``relative_unixtime``, ``relative_utctimestamp``,
+  ``relative_utcunixtime``. Check if value is in relative unix timestamp
+  range per local, UTC. See
+  :py:class:`~wheezy.validation.rules.RelativeUnixTimeDeltaRule`.
+* ``adapter``, ``int_adapter``. Adapts a value according to converter.
+  This is useful when you need to keep string input in model but validate
+  as an integer. See
+  :py:class:`~wheezy.validation.rules.AdapterRule`,
+  :py:class:`~wheezy.validation.rules.IntAdapterRule`.
 * ``ignore``. The idea behind this rule is to be able to substitute
   any validation rule by this one that always succeeds. See
-  :py:class:`~wheezy.validation.rules.IgnoreRule`
+  :py:class:`~wheezy.validation.rules.IgnoreRule`.
 
 Custom Message
 ~~~~~~~~~~~~~~

i18n/en/LC_MESSAGES/validation.po

 "Project-Id-Version: wheezy.validation\n"
 "Report-Msgid-Bugs-To: Andriy Kornatskyy <andriy.kornatskyy@live.com>\n"
 "POT-Creation-Date: 2011-11-02 14:13+0200\n"
-"PO-Revision-Date: 2012-05-23 11:14+0300\n"
+"PO-Revision-Date: 2013-10-10 12:03+0300\n"
 "Last-Translator: Andriy Kornatskyy <andriy.kornatskyy@live.com>\n"
 "Language: US English\n"
 "MIME-Version: 1.0\n"
 msgid "Input was not in a correct format."
 msgstr "Input was not in a correct format."
 
-#: src/wheezy/validation/rules.py:26 src/wheezy/validation/rules.py:28
+#: src/wheezy/validation/rules.py:30
 msgid "Required field cannot be left blank."
 msgstr "Required field cannot be left blank."
 
-#: src/wheezy/validation/rules.py:49
+#: src/wheezy/validation/rules.py:51
 msgid "Field cannot have a value."
 msgstr "Field cannot have a value."
 
-#: src/wheezy/validation/rules.py:80
+#: src/wheezy/validation/rules.py:82
 #, python-format
 msgid "Required to be a minimum of %(min)d characters in length."
 msgstr "Required to be a minimum of %(min)d characters in length."
 
-#: src/wheezy/validation/rules.py:85
+#: src/wheezy/validation/rules.py:87
 #, python-format
 msgid "The length must be exactly %(len)d characters."
 msgstr "The length must be exactly %(len)d characters."
 
-#: src/wheezy/validation/rules.py:91
+#: src/wheezy/validation/rules.py:93
 #, python-format
 msgid "The length must fall within the range %(min)d - %(max)d characters."
 msgstr "The length must fall within the range %(min)d - %(max)d characters."
 
-#: src/wheezy/validation/rules.py:97
+#: src/wheezy/validation/rules.py:99
 #, python-format
 msgid "Exceeds maximum length of %(max)d."
 msgstr "Exceeds maximum length of %(max)d."
 
-#: src/wheezy/validation/rules.py:155
+#: src/wheezy/validation/rules.py:157
 #, python-format
 msgid "The value failed equality comparison with \"%(comparand)s\"."
 msgstr "The value failed equality comparison with \"%(comparand)s\"."
 
-#: src/wheezy/validation/rules.py:161
+#: src/wheezy/validation/rules.py:163
 #, python-format
 msgid "The value failed not equal comparison with \"%(comparand)s\"."
 msgstr "The value failed not equal comparison with \"%(comparand)s\"."
 
-#: src/wheezy/validation/rules.py:200
+#: src/wheezy/validation/rules.py:202
 msgid "Required to satisfy validation predicate condition."
 msgstr "Required to satisfy validation predicate condition."
 
-#: src/wheezy/validation/rules.py:221
+#: src/wheezy/validation/rules.py:223
 msgid "Required to satisfy validation value predicate condition."
 msgstr "Required to satisfy validation value predicate condition."
 
-#: src/wheezy/validation/rules.py:249
+#: src/wheezy/validation/rules.py:251
 msgid "Required to not match validation pattern."
 msgstr "Required to not match validation pattern."
 
-#: src/wheezy/validation/rules.py:253
+#: src/wheezy/validation/rules.py:255
 msgid "Required to match validation pattern."
 msgstr "Required to match validation pattern."
 
-#: src/wheezy/validation/rules.py:281
+#: src/wheezy/validation/rules.py:283
 msgid ""
 "Invalid slug. The value must consist of letters, digits, underscopes and/or "
 "hyphens."
 "Invalid slug. The value must consist of letters, digits, underscopes and/or "
 "hyphens."
 
-#: src/wheezy/validation/rules.py:300
+#: src/wheezy/validation/rules.py:302
 msgid "Required to be a valid email address."
 msgstr "Required to be a valid email address."
 
-#: src/wheezy/validation/rules.py:325
+#: src/wheezy/validation/rules.py:320
+msgid "Required to be a valid number is scientific format."
+msgstr "Required to be a valid number is scientific format."
+
+#: src/wheezy/validation/rules.py:338
+msgid "Required to be a valid base64 string."
+msgstr "Required to be a valid base64 string."
+
+#: src/wheezy/validation/rules.py:363
 #, python-format
 msgid "Required to be greater or equal to %(min)s."
 msgstr "Required to be greater or equal to %(min)s."
 
-#: src/wheezy/validation/rules.py:330
+#: src/wheezy/validation/rules.py:368
 #, python-format
 msgid "The value must fall within the range %(min)s - %(max)s"
 msgstr "The value must fall within the range %(min)s - %(max)s"
 
-#: src/wheezy/validation/rules.py:337
+#: src/wheezy/validation/rules.py:375
 #, python-format
 msgid "Exceeds maximum allowed value of %(max)s."
 msgstr "Exceeds maximum allowed value of %(max)s."
 
-#: src/wheezy/validation/rules.py:463
+#: src/wheezy/validation/rules.py:501
 msgid "The value does not belong to the list of known items."
 msgstr "The value does not belong to the list of known items."
 
-#: src/wheezy/validation/rules.py:494
+#: src/wheezy/validation/rules.py:532
 msgid "Required to be above a minimum allowed."
 msgstr "Required to be above a minimum allowed."
 
-#: src/wheezy/validation/rules.py:499
+#: src/wheezy/validation/rules.py:537
 msgid "Must fall within a valid range."
 msgstr "Must fall within a valid range."
 
-#: src/wheezy/validation/rules.py:505
+#: src/wheezy/validation/rules.py:543
 msgid "Exceeds maximum allowed."
 msgstr "Exceeds maximum allowed."
+
+#: src/wheezy/validation/rules.py:694
+msgid "Required to satisfy a converter format."
+msgstr "Required to satisfy a converter format."
+
+#: src/wheezy/validation/rules.py:714
+msgid "Required to satisfy an integer format."
+msgstr "Required to satisfy an integer format."

i18n/ru/LC_MESSAGES/validation.po

 "Content-Transfer-Encoding: 8bit\n"
 
 #. thousands separator
-#: src/wheezy/validation/format.py:7
 msgid ","
 msgstr ","
 
 #. decimal point separator
-#: src/wheezy/validation/format.py:9
 msgid "."
 msgstr "."
 
 #. default date input format: 18.5.2008.
-#: src/wheezy/validation/format.py:11
 msgid "%Y/%m/%d"
 msgstr "%d.%m.%Y"
 
 #. fallback date input formats: 5/18/2008. Use | to separate multiple values.
-#: src/wheezy/validation/format.py:13 src/wheezy/validation/format.py:15
 msgid "%m/%d/%Y|%Y-%m-%d|%m/%d/%y"
 msgstr "%d.%m.%y|%Y-%m-%d"
 
 #. default time input format: 16:34.
-#: src/wheezy/validation/format.py:16 src/wheezy/validation/format.py:17
 msgid "%H:%M"
 msgstr "%H:%M"
 
 #. fallback time input formats: 16:34:52. Use | to separate multiple values.
-#: src/wheezy/validation/format.py:18 src/wheezy/validation/format.py:20
-#: src/wheezy/validation/format.py:21
 msgid "%H:%M:%S"
 msgstr "%H:%M:%S"
 
 #. default datetime input format: 2008/5/18 16:34
-#: src/wheezy/validation/format.py:20 src/wheezy/validation/format.py:22
-#: src/wheezy/validation/format.py:23
 msgid "%Y/%m/%d %H:%M"
 msgstr "%d.%m.%Y %H:%M"
 
 #. fallback datetime input formats: 2008/5/18 16:34:52. Use | to separate.
-#: src/wheezy/validation/format.py:22 src/wheezy/validation/format.py:23
-#: src/wheezy/validation/format.py:26 src/wheezy/validation/format.py:27
 msgid ""
 "%Y/%m/%d %H:%M:%S|%m/%d/%Y %H:%M|%m/%d/%Y %H:%M:%S|%Y-%m-%d %H:%M|%Y-%m-%d "
 "%H:%M:%S|%m/%d/%y %H:%M|%m/%d/%y %H:%M:%S"
 "%d.%m.%Y %H:%M:%S|%d.%m.%y %H:%M|%d.%m.%y %H:%M:%S|%Y-%m-%d %H:%M|"
 "%Y-%m-%d %H:%M:%S"
 
-#: src/wheezy/validation/model.py:112
 msgid "Input was not in a correct format."
 msgstr "Ввод не в правильном формате."
 
-#: src/wheezy/validation/model.py:130
 msgid "Multiple input was not in a correct format."
 msgstr "Ввод с несколькими значениями был не в правильном формате."
 
-#: src/wheezy/validation/rules.py:12
 msgid "Required field cannot be left blank."
 msgstr "Обязательное поле не может быть пустым."
 
-#: src/wheezy/validation/rules.py:77
 msgid "Field cannot have a value."
 msgstr "Поле не может иметь значения."
 
-#: src/wheezy/validation/rules.py:79
 #, python-format
 msgid "Required to be a minimum of %(min)d characters in length."
 msgstr "Должно содержать, как минимум, %(min)d символов в длинну."
 
-#: src/wheezy/validation/rules.py:155
 #, python-format
 msgid "The length must be exactly %(len)d characters."
 msgstr "Должно содержать, ровно, %(len)d символов в длинну."
 
-#: src/wheezy/validation/rules.py:84
 #, python-format
 msgid "The length must fall within the range %(min)d - %(max)d characters."
 msgstr "Длина строки должна находиться в пределах диапазона %(min)d - %(max)d символов."
 
-#: src/wheezy/validation/rules.py:87 src/wheezy/validation/rules.py:91
 #, python-format
 msgid "Exceeds maximum length of %(max)d."
 msgstr "Превышена максимально допустимая длина %(max)d."
 
-#: src/wheezy/validation/rules.py:214 src/wheezy/validation/rules.py:217
 #, python-format
 msgid "The value failed equality comparison with \"%(comparand)s\"."
 msgstr "Значение не равно в сравнении с \"%(comparand)s\"."
 
-#: src/wheezy/validation/rules.py:238
 #, python-format
 msgid "The value failed not equal comparison with \"%(comparand)s\"."
 msgstr "Значение равно в сравнении с \"%(comparand)s\"."
 
-#: src/wheezy/validation/rules.py:389
 msgid "Required to satisfy validation predicate condition."
 msgstr "Должно удовлетворять условие проверки предиката."
 
-#: src/wheezy/validation/rules.py:221
 msgid "Required to satisfy validation value predicate condition."
 msgstr "Должно удовлетворять условие проверки значения предикатом."
 
-#: src/wheezy/validation/rules.py:290
 msgid "Required to match validation pattern."
 msgstr "Обязательно должно соответствовать проверяемому шаблону."
 
-#: src/wheezy/validation/rules.py:290
 msgid "Required to not match validation pattern."
 msgstr "Обязательно не должно соответствовать проверяемому шаблону."
 
-#: src/wheezy/validation/rules.py:322
 msgid ""
 "Invalid slug. The value must consist of letters, digits, underscopes and/or "
 "hyphens."
 "Неверный слаг. Значение должно содержать буквы, цыфры, знак подчеркивания "
 "и/или тире."
 
-#: src/wheezy/validation/rules.py:354
 msgid "Required to be a valid email address."
 msgstr "Обязательно должно соответствовать правильному адресу электронной "
 "почты."
 
-#: src/wheezy/validation/rules.py:402
+msgid "Required to be a valid number is scientific format."
+msgstr ""
+
+msgid "Required to be a valid base64 string."
+msgstr ""
+
 #, python-format
 msgid "Required to be greater or equal to %(min)s."
 msgstr "Должно быть больше или равно %(min)s."
 
-#: src/wheezy/validation/rules.py:406
 #, python-format
 msgid "The value must fall within the range %(min)s - %(max)s"
 msgstr "Значение должно попадать в диапазон %(min)s - %(max)s"
 
-#: src/wheezy/validation/rules.py:413
 #, python-format
 msgid "Exceeds maximum allowed value of %(max)s."
 msgstr "Превышает максимально допустимое значение %(max)s."
 
-#: src/wheezy/validation/rules.py:624 src/wheezy/validation/rules.py:625
 msgid "The value does not belong to the list of known items."
 msgstr "Значение не входит в список известных элементов."
 
-#: src/wheezy/validation/rules.py:655
 msgid "Required to be above a minimum allowed."
 msgstr "Обязательно должно быть выше минимально допустимого значения."
 
-#: src/wheezy/validation/rules.py:659
 msgid "Must fall within a valid range."
 msgstr "Должно находиться в пределах допустимого диапазона."
 
-#: src/wheezy/validation/rules.py:665
 msgid "Exceeds maximum allowed."
 msgstr "Превышает допустимый максимум."
+
+msgid "Required to satisfy a converter format."
+msgstr ""
+
+msgid "Required to satisfy an integer format."
+msgstr ""

src/wheezy/validation/rules.py

 from datetime import date
 from datetime import datetime
 from datetime import time
+from time import altzone
+from time import time as unixtime
 
 from wheezy.validation.comp import ref_getter
 from wheezy.validation.comp import regex_pattern
         return EmailRule(message_template)
 
 
+class ScientificRule(RegexRule):
+    """ Ensures a valid scientific string input.
+    """
+    __slots__ = ()
+
+    def __init__(self, message_template=None):
+        super(ScientificRule, self).__init__(
+            re.compile(r'^[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?$'),
+            False,
+            message_template or
+            _('Required to be a valid number is scientific format.'))
+
+    def __call__(self, message_template):
+        """ Let you customize message template.
+        """
+        return ScientificRule(message_template)
+
+
+class Base64Rule(RegexRule):
+    """ Ensures a valid base64 string input.
+    """
+    __slots__ = ()
+
+    def __init__(self, message_template=None):
+        super(Base64Rule, self).__init__(
+            re.compile(r'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|'
+                       '[A-Za-z0-9+/]{3}=)?$'), False,
+            message_template or
+            _('Required to be a valid base64 string.'))
+
+    def __call__(self, message_template):
+        """ Let you customize message template.
+        """
+        return Base64Rule(message_template)
+
+
 class RangeRule(object):
     """ Ensures value is in range defined by this rule.
 
         return datetime.now(self.tz)
 
 
+class RelativeUnixTimeDeltaRule(RelativeDeltaRule):
+    """ Check if value is in relative unix range local time.
+    """
+    __slots__ = ()
+
+    def now(self):
+        return int(unixtime())
+
+
+class RelativeUTCUnixTimeDeltaRule(RelativeDeltaRule):
+    """ Check if value is in relative unix range UTC time.
+    """
+    __slots__ = ()
+
+    def now(self):
+        return int(unixtime()) + altzone
+
+
 class IgnoreRule(object):
     """ The idea behind this rule is to be able to substitute
         any validation rule by this one that always succeed:
         return True
 
 
+class AdapterRule(object):
+    """ Adapts value according to converter. This is useful when you
+        need keep string input in model but validate as an integer.
+    """
+
+    def __init__(self, converter, rule, message_template=None):
+        self.converter = converter
+        self.rule = rule
+        self.message_template = message_template or _(
+            'Required to satisfy a converter format.')
+
+    def validate(self, value, name, model, result, gettext):
+        if value is None:
+            return True
+        try:
+            value = self.converter(value)
+        except (ArithmeticError, ValueError):
+            result.append(gettext(self.message_template))
+            return False
+        return self.rule.validate(value, name, model, result, gettext)
+
+
+class IntAdapterRule(AdapterRule):
+    """ Adapts value to an integer.
+    """
+
+    def __init__(self, rule, message_template=None):
+        super(IntAdapterRule, self).__init__(
+            int, rule, message_template or _(
+                'Required to satisfy an integer format.'))
+
+
+adapter = AdapterRule
+and_ = AndRule
+base64 = Base64Rule()
+compare = CompareRule
+email = EmailRule()
+ignore = IgnoreRule
+int_adapter = IntAdapterRule
+iterator = IteratorRule
+length = LengthRule
+missing = optional = empty = MissingRule()
+model_predicate = predicate = PredicateRule
+one_of = OneOfRule
+or_ = OrRule
+range = RangeRule
+regex = RegexRule
+relative_date = RelativeDateDeltaRule
+relative_datetime = RelativeDateTimeDeltaRule
+relative_tzdate = RelativeTZDateDeltaRule
+relative_tzdatetime = RelativeTZDateTimeDeltaRule
+relative_unixtime = RelativeUnixTimeDeltaRule
+relative_timestamp = RelativeUnixTimeDeltaRule
+relative_utcdate = RelativeUTCDateDeltaRule
+relative_utcdatetime = RelativeUTCDateTimeDeltaRule
+relative_utcunixtime = RelativeUTCUnixTimeDeltaRule
+relative_utctimestamp = RelativeUTCUnixTimeDeltaRule
 required = RequiredRule()
-missing = optional = empty = MissingRule()
-length = LengthRule
-compare = CompareRule
-model_predicate = predicate = PredicateRule
+scientific = ScientificRule()
+slug = SlugRule()
 value_predicate = must = ValuePredicateRule
-regex = RegexRule
-slug = SlugRule()
-email = EmailRule()
-range = RangeRule
-and_ = AndRule
-or_ = OrRule
-iterator = IteratorRule
-one_of = OneOfRule
-relative_date = RelativeDateDeltaRule
-relative_utcdate = RelativeUTCDateDeltaRule
-relative_tzdate = RelativeTZDateDeltaRule
-relative_datetime = RelativeDateTimeDeltaRule
-relative_utcdatetime = RelativeUTCDateTimeDeltaRule
-relative_tzdatetime = RelativeTZDateTimeDeltaRule
-ignore = IgnoreRule

src/wheezy/validation/tests/test_rules.py

         assert r != email
         assert 'customized' == r.message_template
 
+    def test_scientific(self):
+        """ Test `scientific` rule.
+        """
+        from wheezy.validation.rules import ScientificRule
+        from wheezy.validation.rules import scientific
+
+        # shortcut
+        assert isinstance(scientific, ScientificRule)
+
+        errors = []
+        r = scientific
+        v = lambda i: r.validate(i, None, None, errors, lambda s: s)
+
+        assert v('12.5e-3')
+        assert not errors
+
+        assert not v('12.x')
+        assert errors
+
+        r = scientific(message_template='customized')
+        assert r != scientific
+        assert 'customized' == r.message_template
+
+    def test_base64(self):
+        """ Test `base64` rule.
+        """
+        from wheezy.validation.rules import Base64Rule
+        from wheezy.validation.rules import base64
+
+        # shortcut
+        assert isinstance(base64, Base64Rule)
+
+        errors = []
+        r = base64
+        v = lambda i: r.validate(i, None, None, errors, lambda s: s)
+
+        assert v('d2hlZXp5')
+        assert not errors
+
+        assert not v('d.x')
+        assert errors
+
+        r = base64(message_template='customized')
+        assert r != base64
+        assert 'customized' == r.message_template
+
     def test_range_strategies(self):
         """ Test `range` rule strategies.
         """
 
         self.assertRaises(AssertionError, lambda: one_of([]))
 
+    def test_relative_rule(self):
+        """ Test `RelativeDeltaRule` now raises error.
+        """
+        from wheezy.validation.rules import RelativeDeltaRule
+        r = RelativeDeltaRule()
+        self.assertRaises(NotImplementedError, r.now)
+
     def test_ignore(self):
         """ Test `ignore` rule.
         """
         assert v('x')
         assert not errors
 
+    def test_adapter(self):
+        """ Test `adapter` and `int_adapter` rules.
+        """
+        from wheezy.validation.rules import IntAdapterRule
+        from wheezy.validation.rules import int_adapter
+        from wheezy.validation.rules import range
 
-class RelativeDeltaRuleMixin:
+        # shortcut
+        assert int_adapter == IntAdapterRule
+
+        errors = []
+        r = int_adapter(range(min=1))
+        v = lambda i: r.validate(i, None, None, errors, lambda s: s)
+
+        assert v(None)
+        assert v('100')
+        assert v('1')
+        assert not errors
+
+        assert not v('0')
+        assert errors
+        del errors[:]
+        assert not v('X')
+        assert errors
+
+
+class RelativeDeltaRuleMixin(object):
+
+    def test_shortcut(self):
+        """ Test rule shortcut.
+        """
+        assert self.shortcut == self.Rule
 
     def test_strategies(self):
         """ Test rule strategies.
         """
-        # shortcut
-        assert self.shortcut == self.Rule
-
         r = self.shortcut()
         assert r.validate == r.succeed
         r = self.shortcut(min=2)
         from wheezy.validation.rules import relative_tzdatetime
         self.shortcut = relative_tzdatetime
         self.Rule = RelativeTZDateTimeDeltaRule
+
+
+class RelativeUnixTimeDeltaRule(unittest.TestCase, RelativeDeltaRuleMixin):
+
+    def setUp(self):
+        from wheezy.validation.rules import RelativeUnixTimeDeltaRule
+        from datetime import datetime
+
+        class Proxy(RelativeUnixTimeDeltaRule):
+            def now(self):
+                t = super(Proxy, self).now()
+                return datetime.fromtimestamp(t)
+
+        self.shortcut = Proxy
+        self.Rule = RelativeUnixTimeDeltaRule
+
+    def test_shortcut(self):
+        """ Test rule shortcut.
+        """
+        from wheezy.validation.rules import RelativeUnixTimeDeltaRule
+        from wheezy.validation.rules import relative_unixtime
+        from wheezy.validation.rules import relative_timestamp
+        assert relative_unixtime == RelativeUnixTimeDeltaRule
+        assert relative_unixtime == relative_timestamp
+
+
+class RelativeUTCUnixTimeDeltaRule(unittest.TestCase, RelativeDeltaRuleMixin):
+
+    def setUp(self):
+        from wheezy.validation.rules import RelativeUTCUnixTimeDeltaRule
+        from datetime import datetime
+
+        class Proxy(RelativeUTCUnixTimeDeltaRule):
+            def now(self):
+                t = super(Proxy, self).now()
+                return datetime.fromtimestamp(t)
+
+        self.shortcut = Proxy
+        self.Rule = RelativeUTCUnixTimeDeltaRule
+
+    def test_shortcut(self):
+        """ Test rule shortcut.
+        """
+        from wheezy.validation.rules import RelativeUTCUnixTimeDeltaRule
+        from wheezy.validation.rules import relative_utcunixtime
+        from wheezy.validation.rules import relative_utctimestamp
+        assert relative_utcunixtime == RelativeUTCUnixTimeDeltaRule
+        assert relative_utcunixtime == relative_utctimestamp