summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2019-11-03 18:40:03 +0100
committerIlya Etingof <etingof@gmail.com>2019-11-03 22:37:26 +0100
commitcfc3f96f0f087fa0e44fee85f9a528a92a69ff81 (patch)
treec0ea5be491d00e8f42734d1f8c7909909cfb63b3
parent40d5a7f27b8f56e103cdc83d3a294f02c5eb1496 (diff)
downloadpyasn1-git-add-except-clause-support.tar.gz
Allow combining constraints operating on setsadd-except-clause-support
Added ability of combining `SingleValueConstraint` and `PermittedAlphabetConstraint` objects into one for proper modeling `FROM ... EXCEPT ...` ASN.1 clause.
-rw-r--r--CHANGES.rst4
-rw-r--r--pyasn1/type/constraint.py110
-rw-r--r--tests/type/test_constraint.py36
3 files changed, 117 insertions, 33 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index ef005ff..9b4c3af 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,7 +2,9 @@
Revision 0.4.8, released XX-09-2019
-----------------------------------
-No changes yet.
+- Added ability of combining `SingleValueConstraint` and
+ `PermittedAlphabetConstraint` objects into one for proper modeling
+ `FROM ... EXCEPT ...` ASN.1 clause.
Revision 0.4.7, released 01-09-2019
-----------------------------------
diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py
index b66627d..8f152e9 100644
--- a/pyasn1/type/constraint.py
+++ b/pyasn1/type/constraint.py
@@ -103,6 +103,11 @@ class SingleValueConstraint(AbstractConstraint):
The SingleValueConstraint satisfies any value that
is present in the set of permitted values.
+ Objects of this type are iterable (emitting constraint values) and
+ can act as operands for some arithmetic operations e.g. addition
+ and subtraction. The latter can be used for combining multiple
+ SingleValueConstraint objects into one.
+
The SingleValueConstraint object can be applied to
any ASN.1 type.
@@ -137,6 +142,23 @@ class SingleValueConstraint(AbstractConstraint):
if value not in self._set:
raise error.ValueConstraintError(value)
+ # Constrains can be merged or reduced
+
+ def __contains__(self, item):
+ return item in self._set
+
+ def __iter__(self):
+ return iter(self._set)
+
+ def __sub__(self, constraint):
+ return self.__class__(*(self._set.difference(constraint)))
+
+ def __add__(self, constraint):
+ return self.__class__(*(self._set.union(constraint)))
+
+ def __sub__(self, constraint):
+ return self.__class__(*(self._set.difference(constraint)))
+
class ContainedSubtypeConstraint(AbstractConstraint):
"""Create a ContainedSubtypeConstraint object.
@@ -305,6 +327,10 @@ class PermittedAlphabetConstraint(SingleValueConstraint):
string for as long as all its characters are present in
the set of permitted characters.
+ Objects of this type are iterable (emitting constraint values) and
+ can act as operands for some arithmetic operations e.g. addition
+ and subtraction.
+
The PermittedAlphabetConstraint object can only be applied
to the :ref:`character ASN.1 types <type.char>` such as
:class:`~pyasn1.type.char.IA5String`.
@@ -314,8 +340,8 @@ class PermittedAlphabetConstraint(SingleValueConstraint):
*alphabet: :class:`str`
Full set of characters permitted by this constraint object.
- Examples
- --------
+ Example
+ -------
.. code-block:: python
class BooleanValue(IA5String):
@@ -332,6 +358,42 @@ class PermittedAlphabetConstraint(SingleValueConstraint):
# this will raise ValueConstraintError
garbage = BooleanValue('TAF')
+
+ ASN.1 `FROM ... EXCEPT ...` clause can be modelled by combining multiple
+ PermittedAlphabetConstraint objects into one:
+
+ Example
+ -------
+ .. code-block:: python
+
+ class Lipogramme(IA5String):
+ '''
+ ASN.1 specification:
+
+ Lipogramme ::=
+ IA5String (FROM (ALL EXCEPT ("e"|"E")))
+ '''
+ subtypeSpec = (
+ PermittedAlphabetConstraint(*string.printable) -
+ PermittedAlphabetConstraint('e', 'E')
+ )
+
+ # this will succeed
+ lipogramme = Lipogramme('A work of fiction?')
+
+ # this will raise ValueConstraintError
+ lipogramme = Lipogramme('Eel')
+
+ Note
+ ----
+ Although `ConstraintsExclusion` object could seemingly be used for this
+ purpose, practically, for it to work, it needs to represent its operand
+ constraints as sets and intersect one with the other. That would require
+ the insight into the constraint values (and their types) that are otherwise
+ hidden inside the constraint object.
+
+ Therefore it's more practical to model `EXCEPT` clause at
+ `PermittedAlphabetConstraint` level instead.
"""
def _setValues(self, values):
self._values = values
@@ -526,49 +588,41 @@ class ConstraintsExclusion(AbstractConstraint):
Parameters
----------
- constraint:
- Constraint or logic operator object.
+ *constraints:
+ Constraint or logic operator objects.
Examples
--------
.. code-block:: python
- class Lipogramme(IA5STRING):
- '''
- ASN.1 specification:
-
- Lipogramme ::=
- IA5String (FROM (ALL EXCEPT ("e"|"E")))
- '''
+ class LuckyNumber(Integer):
subtypeSpec = ConstraintsExclusion(
- PermittedAlphabetConstraint('e', 'E')
+ SingleValueConstraint(13)
)
# this will succeed
- lipogramme = Lipogramme('A work of fiction?')
+ luckyNumber = LuckyNumber(12)
# this will raise ValueConstraintError
- lipogramme = Lipogramme('Eel')
+ luckyNumber = LuckyNumber(13)
- Warning
- -------
- The above example involving PermittedAlphabetConstraint might
- not work due to the way how PermittedAlphabetConstraint works.
- The other constraints might work with ConstraintsExclusion
- though.
+ Note
+ ----
+ The `FROM ... EXCEPT ...` ASN.1 clause should be modeled by combining
+ constraint objects into one. See `PermittedAlphabetConstraint` for more
+ information.
"""
def _testValue(self, value, idx):
- try:
- self._values[0](value, idx)
- except error.ValueConstraintError:
- return
- else:
+ for constraint in self._values:
+ try:
+ constraint(value, idx)
+
+ except error.ValueConstraintError:
+ continue
+
raise error.ValueConstraintError(value)
def _setValues(self, values):
- if len(values) != 1:
- raise error.PyAsn1Error('Single constraint expected')
-
AbstractConstraint._setValues(self, values)
diff --git a/tests/type/test_constraint.py b/tests/type/test_constraint.py
index 0f49c78..7ef6293 100644
--- a/tests/type/test_constraint.py
+++ b/tests/type/test_constraint.py
@@ -21,8 +21,10 @@ from pyasn1.type import error
class SingleValueConstraintTestCase(BaseTestCase):
def setUp(self):
BaseTestCase.setUp(self)
- self.c1 = constraint.SingleValueConstraint(1, 2)
- self.c2 = constraint.SingleValueConstraint(3, 4)
+ self.v1 = 1, 2
+ self.v2 = 3, 4
+ self.c1 = constraint.SingleValueConstraint(*self.v1)
+ self.c2 = constraint.SingleValueConstraint(*self.v2)
def testCmp(self):
assert self.c1 == self.c1, 'comparison fails'
@@ -45,6 +47,27 @@ class SingleValueConstraintTestCase(BaseTestCase):
else:
assert 0, 'constraint check fails'
+ def testContains(self):
+ for v in self.v1:
+ assert v in self.c1
+ assert v not in self.c2
+
+ for v in self.v2:
+ assert v in self.c2
+ assert v not in self.c1
+
+ def testIter(self):
+ assert set(self.v1) == set(self.c1)
+ assert set(self.v2) == set(self.c2)
+
+ def testSub(self):
+ subconst = self.c1 - constraint.SingleValueConstraint(self.v1[0])
+ assert list(subconst) == [self.v1[1]]
+
+ def testAdd(self):
+ superconst = self.c1 + self.c2
+ assert set(superconst) == set(self.v1 + self.v2)
+
class ContainedSubtypeConstraintTestCase(BaseTestCase):
def setUp(self):
@@ -110,20 +133,25 @@ class ValueSizeConstraintTestCase(BaseTestCase):
class PermittedAlphabetConstraintTestCase(SingleValueConstraintTestCase):
def setUp(self):
- self.c1 = constraint.PermittedAlphabetConstraint('A', 'B', 'C')
- self.c2 = constraint.PermittedAlphabetConstraint('DEF')
+ self.v1 = 'A', 'B'
+ self.v2 = 'C', 'D'
+ self.c1 = constraint.PermittedAlphabetConstraint(*self.v1)
+ self.c2 = constraint.PermittedAlphabetConstraint(*self.v2)
def testGoodVal(self):
try:
self.c1('A')
+
except error.ValueConstraintError:
assert 0, 'constraint check fails'
def testBadVal(self):
try:
self.c1('E')
+
except error.ValueConstraintError:
pass
+
else:
assert 0, 'constraint check fails'