diff options
-rw-r--r-- | CHANGES.rst | 5 | ||||
-rw-r--r-- | pyasn1/__init__.py | 2 | ||||
-rw-r--r-- | pyasn1/type/base.py | 10 | ||||
-rw-r--r-- | pyasn1/type/forwardref.py | 45 | ||||
-rw-r--r-- | pyasn1/type/namedtype.py | 24 | ||||
-rw-r--r-- | pyasn1/type/univ.py | 11 | ||||
-rw-r--r-- | tests/type/test_univ.py | 42 |
7 files changed, 132 insertions, 7 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 3e65b8f..41eaf1b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,8 @@ Revision 0.4.1, released XX-09-2017 ----------------------------------- -- ANY DEFINED BY clause support implemented +- Forward-referencing ASN.1 types implemented to handle + the case of recursive ASN.1 schemas. Revision 0.3.5, released 16-09-2017 ----------------------------------- @@ -17,6 +18,7 @@ Revision 0.3.5, released 16-09-2017 - Fixed crash at SequenceOf native decoder - Fixed Real.prettyPrint() to fail gracefully on overflow - Fixed a couple of crashes when debug mode is enabled +>>>>>>> devel-0.4.1 Revision 0.3.4, released 07-09-2017 ----------------------------------- @@ -48,6 +50,7 @@ Revision 0.3.3, released 27-08-2017 Revision 0.3.2, released 04-08-2017 ----------------------------------- +- The syntax for forward referencing ASN.1 types introduced - Fixed SequenceOf/SetOf types initialization syntax to remain backward compatible with pyasn1 0.2.* - Rectified thread safety issues by moving lazy, run-time computation diff --git a/pyasn1/__init__.py b/pyasn1/__init__.py index 26495d5..42f4704 100644 --- a/pyasn1/__init__.py +++ b/pyasn1/__init__.py @@ -1,7 +1,7 @@ import sys # http://www.python.org/dev/peps/pep-0396/ -__version__ = '0.3.5' +__version__ = '0.4.1' if sys.version_info[:2] < (2, 4): raise RuntimeError('PyASN1 requires Python 2.4 or later') diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index a76ae9c..e2bd3fc 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -5,7 +5,7 @@ # License: http://pyasn1.sf.net/license.html # import sys -from pyasn1.type import constraint, tagmap, tag +from pyasn1.type import constraint, tagmap, tag, forwardref from pyasn1.compat import calling from pyasn1 import error @@ -424,8 +424,14 @@ def setupComponent(): """ return noValue +class AbstractConstructedMeta(type): + def __init__(cls, name, bases, nmspc): + super(AbstractConstructedMeta, cls).__init__(name, bases, nmspc) -class AbstractConstructedAsn1Item(Asn1ItemBase): + forwardref.ForwardRef.newTypeNotification(name, cls()) + + +class AbstractConstructedAsn1Item(Asn1ItemBase, metaclass=AbstractConstructedMeta): #: If `True`, requires exact component type matching, #: otherwise subtype relation is only enforced diff --git a/pyasn1/type/forwardref.py b/pyasn1/type/forwardref.py new file mode 100644 index 0000000..c822419 --- /dev/null +++ b/pyasn1/type/forwardref.py @@ -0,0 +1,45 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com> +# License: http://pyasn1.sf.net/license.html +# +from pyasn1 import error + + +__all__ = ['ForwardRef'] + + +class ForwardRef(object): + # TODO: this is not thread-safe + waitingList = {} + + def __init__(self, symbol, *args, **kwargs): + self.__symbol = symbol + self.args = args + self.kwargs = kwargs + + def callLater(self, cbFun): + if self.__symbol not in self.waitingList: + self.__class__.waitingList[self.__symbol] = [] + self.waitingList[self.__symbol].append((self, cbFun)) + + # TODO: make two callbacks - one for updating types and the other for + # initialization once all updates are done + @classmethod + def newTypeNotification(cls, name, obj): + # TODO: name collision at different modules possible + if name in cls.waitingList: + for waitingObject, cbFun in cls.waitingList.pop(name): + + cbFun(obj.clone(*waitingObject.args, **waitingObject.kwargs)) + + def subtype(self, *args, **kwargs): + # TODO: make a copy; combine kw/args to mimic subtype/clone ops + self.args = args + self.kwargs = kwargs + return self + + def __getattr__(self, item): + raise error.PyAsn1Error('Unresolved forward reference %s (.%s attempted)' % (self.__symbol, item)) + diff --git a/pyasn1/type/namedtype.py b/pyasn1/type/namedtype.py index edcf3ce..41bd1fe 100644 --- a/pyasn1/type/namedtype.py +++ b/pyasn1/type/namedtype.py @@ -5,7 +5,8 @@ # License: http://pyasn1.sf.net/license.html # import sys -from pyasn1.type import tag, tagmap +import functools +from pyasn1.type import tag, tagmap, forwardref from pyasn1 import error __all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', 'NamedTypes'] @@ -120,8 +121,27 @@ class NamedTypes(object): def __init__(self, *namedTypes, **kwargs): self.__namedTypes = namedTypes self.__namedTypesLen = len(self.__namedTypes) - self.__minTagSet = self.__computeMinTagSet() self.__nameToPosMap = self.__computeNameToPosMap() + + def updateNameType(self, idx, name, obj): + # TODO: better way to do in-place update? + nt = list(self.__namedTypes) + nt[idx] = NamedType(name, obj) + self.__namedTypes = tuple(nt) + self.initialize() + + for idx, namedType in enumerate(namedTypes): + if isinstance(namedType.asn1Object, forwardref.ForwardRef): + cbFun = functools.partial(updateNameType, self, idx, namedType.name) + namedType.asn1Object.callLater(cbFun) + + self.initialize() + + def initialize(self): + if any(isinstance(x.asn1Object, forwardref.ForwardRef) for x in self.__namedTypes): + return + # TODO: verify initialization status + self.__minTagSet = self.__computeMinTagSet() self.__tagToPosMap = self.__computeTagToPosMap() self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {} self.__uniqueTagMap = self.__computeTagMaps(unique=True) diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 1e7c8ce..7d5e89f 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1727,6 +1727,17 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): base.AbstractConstructedAsn1Item.__init__(self, **kwargs) + def updateComponentType(self, obj): + self._componentType = obj + + import functools + from pyasn1.type import forwardref + + if isinstance(self.componentType, forwardref.ForwardRef): + cbFun = functools.partial(updateComponentType, self) + self.componentType.callLater(cbFun) + + # Python list protocol def clear(self): diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index 23f269e..09478d9 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -6,6 +6,9 @@ # import sys import math +from pyasn1.type import univ, tag, constraint, namedtype, namedval, forwardref, error +from pyasn1.compat.octets import str2octs, ints2octs, octs2ints +from pyasn1.error import PyAsn1Error try: import unittest2 as unittest @@ -1115,6 +1118,43 @@ class Sequence(BaseTestCase): s['name'] = 'abc' assert s['name'] == str2octs('abc') + def testSelfReferencingDef(self): + + class SelfRefSequence(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('name', univ.Integer()), + namedtype.OptionalNamedType('selfref', forwardref.ForwardRef('SelfRefSequence')), + ) + + s = SelfRefSequence() + + s[0] = 0 + s[1][0] = 1 + + assert s[0] == 0 + assert s[1][0] == 1 + assert not s[1][1].isValue + + # TODO: SequenceOf, Choice, en/decoding + + def testSelfReferencingDynamicDef(self): + + s = univ.Sequence( + componentType=namedtype.NamedTypes( + namedtype.NamedType('name', univ.Integer()), + namedtype.OptionalNamedType('selfref', forwardref.ForwardRef('sft')) + ) + ) + + s.componentType['selfref'].asn1Object.newTypeNotification('sft', s) + + s[0] = 0 + s[1][0] = 1 + + assert s[0] == 0 + assert s[1][0] == 1 + assert not s[1][1].isValue + class SequenceWithoutSchema(BaseTestCase): @@ -1390,4 +1430,4 @@ class Choice(BaseTestCase): suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite)
\ No newline at end of file + unittest.TextTestRunner(verbosity=2).run(suite) |