summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst5
-rw-r--r--pyasn1/__init__.py2
-rw-r--r--pyasn1/type/base.py10
-rw-r--r--pyasn1/type/forwardref.py45
-rw-r--r--pyasn1/type/namedtype.py24
-rw-r--r--pyasn1/type/univ.py11
-rw-r--r--tests/type/test_univ.py42
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)