Package translate :: Package misc :: Package typecheck |
[hide private]
[frames] | no frames]

# Source Code for Package translate.misc.typecheck

       1  __all__ = ['accepts', 'returns', 'yields', 'TypeCheckError', 'Length', 'Empty'
       2            ,'TypeSignatureError', 'And', 'Any', 'Class', 'Exact', 'HasAttr'
       3            ,'IsAllOf', 'IsCallable', 'IsIterable', 'IsNoneOf', 'IsOneOf'
       4            ,'IsOnlyOneOf', 'Not', 'Or', 'Self', 'Xor', 'YieldSeq'
       5            ,'register_type', 'is_registered_type', 'unregister_type'
       6            ,'Function']
       7
       8  import inspect
       9  import types
      10
      11  from types import GeneratorType, FunctionType, MethodType, ClassType, TypeType
      12
      13  # Controls whether typechecking is on (True) or off (False)
      14  enable_checking = True
      15
      16  # Pretty little wrapper function around __typecheck__
      17 -def check_type(type, func, val):
      18      type.__typecheck__(func, val)
      19
      20  ### Internal exception classes (these MUST NOT get out to the user)
      21  ### typecheck_{args,return,yield} should catch these and convert them to
      22  ### appropriate Type{Check,Signature}Error instances
      23
      24  # We can't inherit from object because raise doesn't like new-style classes
      25  # We can't use super() because we can't inherit from object
      26 -class _TC_Exception(Exception):
      27 -    def error_message(self):
      28          raise NotImplementedError("Incomplete _TC_Exception subclass (%s)" % str(self.__class__))
      29
      30 -    def format_bad_object(self, bad_object):
      31          return ("for %s, " % str(bad_object), self)
      32
      33 -class _TC_LengthError(_TC_Exception):
      34 -    def __init__(self, wrong, right=None):
      35          _TC_Exception.__init__(self)
      36
      37          self.wrong = wrong
      38          self.right = right
      39
      40 -    def error_message(self):
      41          m = None
      42          if self.right is not None:
      43              m = ", expected %d" % self.right
      44          return "length was %d%s" % (self.wrong, m or "")
      45
      46 -class _TC_TypeError(_TC_Exception):
      47 -    def __init__(self, wrong, right):
      48          _TC_Exception.__init__(self)
      49
      50          self.wrong = calculate_type(wrong)
      51          self.right = right
      52
      53 -    def error_message(self):
      54          return "expected %s, got %s" % (self.right, self.wrong)
      55
      56 -class _TC_NestedError(_TC_Exception):
      57 -    def __init__(self, inner_exception):
      58          self.inner = inner_exception
      59
      60 -    def error_message(self):
      61          try:
      62              return ", " + self.inner.error_message()
      63          except:
      64              print "'%s'" % self.inner.message
      65              raw_input()
      66              raise
      67
      68 -class _TC_IndexError(_TC_NestedError):
      69 -    def __init__(self, index, inner_exception):
      70          _TC_NestedError.__init__(self, inner_exception)
      71
      72          self.index = index
      73
      74 -    def error_message(self):
      75          return ("at index %d" % self.index) + _TC_NestedError.error_message(self)
      76
      77  # _TC_DictError exists as a wrapper around dict-related exceptions.
      78  # It provides a single place to sort the bad dictionary's keys in the error
      79  # message.
      80 -class _TC_DictError(_TC_NestedError):
      81 -    def format_bad_object(self, bad_object):
      82          message = "for {%s}, " % ', '.join(["%s: %s" % (repr(k), repr(bad_object[k])) for k in sorted(bad_object.keys())])
      83
      84          if not isinstance(self.inner, _TC_LengthError):
      85              return (message, self)
      86          return (message, self.inner)
      87
      88 -    def error_message(self):
      89          raise NotImplementedError("Incomplete _TC_DictError subclass: " + str(self.__class__))
      90
      91 -class _TC_KeyError(_TC_DictError):
      92 -    def __init__(self, key, inner_exception):
      93          _TC_NestedError.__init__(self, inner_exception)
      94
      95          self.key = key
      96
      97 -    def error_message(self):
      98          return ("for key %s" % repr(self.key)) + _TC_NestedError.error_message(self)
      99
     100 -class _TC_KeyValError(_TC_KeyError):
     101 -    def __init__(self, key, val, inner_exception):
     102          _TC_KeyError.__init__(self, key, inner_exception)
     103
     104          self.val = val
     105
     106 -    def error_message(self):
     107          return ("at key %s, value %s" % (repr(self.key), repr(self.val))) + _TC_NestedError.error_message(self)
     108
     109 -class _TC_GeneratorError(_TC_NestedError):
     110 -    def __init__(self, yield_no, inner_exception):
     111          _TC_NestedError.__init__(self, inner_exception)
     112
     113          self.yield_no = yield_no
     114
     115 -    def error_message(self):
     116          raise RuntimeError("_TC_GeneratorError.message should never be called")
     117
     118 -    def format_bad_object(self, bad_object):
     119          bad_obj, start_message = self.inner.format_bad_object(bad_object)
     120          message = "At yield #%d: %s" % (self.yield_no, bad_obj)
     121          return (message, start_message)
     122
     123  ### These next three exceptions exist to give HasAttr better error messages
     124 -class _TC_AttrException(_TC_Exception):
     125 -    def __init__(self, attr):
     126          _TC_Exception.__init__(self, attr)
     127
     128          self.attr = attr
     129
     130 -class _TC_AttrError(_TC_AttrException, _TC_NestedError):
     131 -    def __init__(self, attr, inner_exception):
     132          _TC_AttrException.__init__(self, attr)
     133          _TC_NestedError.__init__(self, inner_exception)
     134
     135 -    def error_message(self):
     136          return ("as for attribute %s" % self.attr) + _TC_NestedError.error_message(self)
     137
     138 -class _TC_MissingAttrError(_TC_AttrException):
     139 -    def error_message(self):
     140          return "missing attribute %s" % self.attr
     141
     142  # This is like _TC_LengthError for YieldSeq
     143 -class _TC_YieldCountError(_TC_Exception):
     144 -    def __init__(self, expected):
     145          _TC_Exception.__init__(self, expected)
     146
     147          self.expected = expected
     148
     149 -    def format_bad_object(self, bad_object):
     150          return ("", self)
     151
     152 -    def error_message(self):
     153          plural = "s"
     154          if self.expected == 1:
     155              plural = ""
     156
     157          return "only expected the generator to yield %d time%s" % (self.expected, plural)
     158
     159  # This exists to provide more detailed error messages about why a given
     160  # Xor() assertion failed
     161 -class _TC_XorError(_TC_NestedError):
     162 -    def __init__(self, matched_conds, inner_exception):
     163          assert matched_conds in (0, 2)
     164          assert isinstance(inner_exception, _TC_TypeError)
     165
     166          _TC_Exception.__init__(self, matched_conds, inner_exception)
     167          _TC_NestedError.__init__(self, inner_exception)
     168          self.matched_conds = matched_conds
     169
     170 -    def error_message(self):
     171          if self.matched_conds == 0:
     172              m = "neither assertion"
     173          else:
     174              m = "both assertions"
     175
     176          return _TC_NestedError.error_message(self) + " (matched %s)" % m
     177
     178 -class _TC_FunctionError(_TC_Exception):
     179 -    def __init__(self, checking_func, obj):
     180          self.checking_func = checking_func
     181          self.rejected_obj = obj
     182
     183 -    def error_message(self):
     184          return " was rejected by %s" % self.checking_func
     185
     186 -    def format_bad_object(self, bad_object):
     187          return (str(bad_object), self)
     188
     189 -class _TC_ExactError(_TC_Exception):
     190 -    def __init__(self, wrong, right):
     191          self.wrong = wrong
     192          self.right = right
     193
     194 -    def error_message(self):
     195          return "expected %s, got %s" % (self.right, self.wrong)
     196
     197  ### The following exist to provide detailed TypeSignatureErrors
     198 -class _TS_Exception(Exception):
     199 -    def error_message(self):
     200          raise NotImplementedError("Incomplete _TS_Exception subclass (%s)" % str(self.__class__))
     201
     202  # This is used when there was an error related to an auto-unpacked tuple
     203  # in the function's signature
     204 -class _TS_TupleError(_TS_Exception):
     205 -    def __init__(self, parameters, types):
     206          parameters = _rec_tuple(parameters)
     207          types = _rec_tuple(types)
     208          _TS_Exception.__init__(self, parameters, types)
     209
     210          self.parameters = parameters
     211          self.types = types
     212
     213 -    def error_message(self):
     214          return "the signature type %s does not match %s" % (str(self.types), str(self.parameters))
     215
     216 -class _TS_ExtraKeywordError(_TS_Exception):
     217 -    def __init__(self, keyword):
     218          _TS_Exception.__init__(self, keyword)
     219
     220          self.keyword = keyword
     221
     222 -    def error_message(self):
     223          return "the keyword '%s' in the signature is not in the function" % self.keyword
     224
     225 -class _TS_ExtraPositionalError(_TS_Exception):
     226 -    def __init__(self, type):
     227          _TS_Exception.__init__(self, type)
     228
     229          self.type = type
     230
     231 -    def error_message(self):
     232          return "an extra positional type has been supplied"
     233
     234 -class _TS_MissingTypeError(_TS_Exception):
     235 -    def __init__(self, parameter):
     236          _TS_Exception.__init__(self, parameter)
     237
     238          self.parameter = parameter
     239
     240 -    def error_message(self):
     241          return "parameter '%s' lacks a type" % self.parameter
     242
     243  # If the user has given a keyword parameter a type both positionally and
     244  # with a keyword argument, this will be raised
     245 -class _TS_TwiceTypedError(_TS_Exception):
     246 -    def __init__(self, parameter, kw_type, pos_type):
     247          _TS_Exception.__init__(self, parameter, kw_type, pos_type)
     248
     249          self.parameter = parameter
     250          self.kw_type = kw_type
     251          self.pos_type = pos_type
     252
     253 -    def error_message(self):
     254          return "parameter '%s' is provided two types (%s and %s)" % (self.parameter, str(self.kw_type), str(self.pos_type))
     255
     256  ### The following functions are the way new type handlers are registered
     257  ### The Type function will iterate over all registered type handlers;
     258  ### the first handler to return a non-None value is considered the winner
     259  #########################################################################
     260
     261  _hooks = ("__typesig__", "__startchecking__", "__stopchecking__", "__switchchecking__")
     262
     263  _registered_types = set()
     264  _registered_hooks = dict([(_h, set()) for _h in _hooks])
     265
     266 -def _manage_registration(add_remove, reg_type):
     267      if not isinstance(reg_type, (types.ClassType, types.TypeType)):
     268          raise ValueError("registered types must be classes or types")
     269
     270      valid = False
     271      for hook in _hooks:
     272          if hasattr(reg_type, hook):
     273              getattr(_registered_hooks[hook], add_remove)(reg_type)
     274              valid = True
     275
     276      if valid:
     277          getattr(_registered_types, add_remove)(reg_type)
     278      else:
     279          raise ValueError("registered types must have at least one of the following methods: " + ", ".join(_hooks))
     280
     281 -def register_type(reg_type):
     282      _manage_registration('add', reg_type)
     283
     284 -def unregister_type(reg_type):
     285      _manage_registration('remove', reg_type)
     286
     287 -def is_registered_type(reg_type):
     288      return reg_type in _registered_types
     289
     290  ### Factory function; this is what should be used to dispatch
     291  ### type-checker class requests
     292
     293 -def Type(obj):
     294      # Note that registered types cannot count on being run in a certain order;
     295      # their __typesig__ methods must be sufficiently flexible to account for
     296      # this
     297      for reg_type in _registered_hooks['__typesig__']:
     298          v = reg_type.__typesig__(obj)
     299          if v is not None:
     300              return v
     301
     302      raise AssertionError("Object is of type '%s'; not a type" % str(type(obj)))
     303
     304 -def __checking(start_stop, *args):
     305      attr = '__%schecking__' % start_stop
     306
     307      for reg_type in _registered_hooks[attr]:
     308          getattr(reg_type, attr)(*args)
     309
     310 -def start_checking(function):
     311      __checking('start', function)
     312
     313 -def stop_checking(function):
     314      __checking('stop', function)
     315
     316 -def switch_checking(from_func, to_func):
     317      for reg_type in _registered_types:
     318          if hasattr(reg_type, '__switchchecking__'):
     319              getattr(reg_type, '__switchchecking__')(from_func, to_func)
     320          else:
     321              if hasattr(reg_type, '__stopchecking__'):
     322                  getattr(reg_type, '__stopchecking__')(from_func)
     323              if hasattr(reg_type, '__startchecking__'):
     324                  getattr(reg_type, '__startchecking__')(to_func)
     325
     326  ### Deduce the type of a data structure
     327  ###
     328  ### XXX: Find a way to allow registered utility classes
     329  ### to hook into this
     330 -def calculate_type(obj):
     331      if isinstance(obj, types.InstanceType):
     332          return obj.__class__
     333      elif isinstance(obj, dict):
     334          if len(obj) == 0:
     335              return {}
     336
     337          key_types = set()
     338          val_types = set()
     339
     340          for (k,v) in obj.items():
     341              key_types.add( calculate_type(k) )
     342              val_types.add( calculate_type(v) )
     343
     344          if len(key_types) == 1:
     345              key_types = key_types.pop()
     346          else:
     347              key_types = Or(*key_types)
     348
     349          if len(val_types) == 1:
     350              val_types = val_types.pop()
     351          else:
     352              val_types = Or(*val_types)
     353
     354          return {key_types: val_types}
     355      elif isinstance(obj, tuple):
     356          return tuple([calculate_type(t) for t in obj])
     357      elif isinstance(obj, list):
     358          length = len(obj)
     359          if length == 0:
     360              return []
     361          obj = [calculate_type(o) for o in obj]
     362
     363          partitions = [1]
     364          partitions.extend([i for i in range(2, int(length/2)+1) if length%i==0])
     365          partitions.append(length)
     366
     367          def evaluate(items_per):
     368              parts = length / items_per
     369
     370              for i in range(0, parts):
     371                  for j in range(0, items_per):
     372                      if obj[items_per * i + j] != obj[j]:
     373                          raise StopIteration
     374              return obj[0:items_per]
     375
     376          for items_per in partitions:
     377              try:
     378                  return evaluate(items_per)
     379              except StopIteration:
     380                  continue
     381      else:
     382          return type(obj)
     383
     384  ### The following classes are the work-horses of the typechecker
     385
     386  # The base class for all the other utility classes
     387 -class CheckType(object):
     388 -    def __repr__(self):
     389          return type(self).name + '(' + ', '.join(sorted(repr(t) for t in self._types)) + ')'
     390
     391      __str__ = __repr__
     392
     393 -    def __eq__(self, other):
     394          return not self != other
     395
     396 -    def __ne__(self, other):
     397          return not self == other
     398
     399 -    def __hash__(self):
     400          raise NotImplementedError("Incomplete CheckType subclass: %s" % self.__class__)
     401
     402 -    def __typecheck__(self, func, obj):
     403          raise NotImplementedError("Incomplete CheckType subclass: %s" % self.__class__)
     404
     405      @classmethod
     406 -    def __typesig__(cls, obj):
     407          if isinstance(obj, CheckType):
     408              return obj
     409
     410 -class Single(CheckType):
     411      name = "Single"
     412
     413 -    def __init__(self, type):
     414          if not isinstance(type, (types.ClassType, types.TypeType)):
     415              raise TypeError("Cannot type-check a %s" % type(type))
     416          else:
     417              self.type = type
     418
     419          self._types = [self.type]
     420
     421 -    def __typecheck__(self, func, to_check):
     422          if not isinstance(to_check, self.type):
     423              raise _TC_TypeError(to_check, self.type)
     424
     425 -    def __eq__(self, other):
     426          if other.__class__ is not self.__class__:
     427              return False
     428          return self.type == other.type
     429
     430 -    def __hash__(self):
     431          return hash(str(hash(self.__class__)) + str(hash(self.type)))
     432
     433      # XXX Is this really a good idea?
     434      # Removing this only breaks 3 tests; that seems suspiciously low
     435 -    def __repr__(self):
     436          return repr(self.type)
     437
     438      @classmethod
     439 -    def __typesig__(cls, obj):
     440          if isinstance(obj, (types.ClassType, types.TypeType)):
     441              return Single(obj)
     442
     443  ### Provide a way to enforce the empty-ness of iterators
     444 -class Empty(Single):
     445      name = "Empty"
     446
     447 -    def __init__(self, type):
     448          if not hasattr(type, '__len__'):
     449              raise TypeError("Can only assert emptyness for types with __len__ methods")
     450
     451          Single.__init__(self, type)
     452
     453 -    def __typecheck__(self, func, to_check):
     454          Single.__typecheck__(self, func, to_check)
     455
     456          if len(to_check) > 0:
     457              err = _TC_LengthError(len(to_check), 0)
     458              if isinstance(to_check, dict):
     459                  raise _TC_DictError(err)
     460              raise err
     461
     462 -class Dict(CheckType):
     463      name = "Dict"
     464
     465 -    def __init__(self, key, val):
     466          self.__check_key = Type(key)
     467          self.__check_val = Type(val)
     468
     469          self.type = {key: val}
     470          self._types = [key, val]
     471
     472 -    def __typecheck__(self, func, to_check):
     473          if not isinstance(to_check, types.DictType):
     474              raise _TC_TypeError(to_check, self.type)
     475
     476          for (k, v) in to_check.items():
     477              # Check the key
     478              try:
     479                  check_type(self.__check_key, func, k)
     480              except _TC_Exception, inner:
     481                  raise _TC_KeyError(k, inner)
     482
     483              # Check the value
     484              try:
     485                  check_type(self.__check_val, func, v)
     486              except _TC_Exception, inner:
     487                  raise _TC_KeyValError(k, v, inner)
     488
     489 -    def __eq__(self, other):
     490          if other.__class__ is not self.__class__:
     491              return False
     492          return self.type == other.type
     493
     494 -    def __hash__(self):
     495          cls = self.__class__
     496          key = self.__check_key
     497          val = self.__check_val
     498
     499          def strhash(obj):
     500              return str(hash(obj))
     501
     502          return hash(''.join(map(strhash, [cls, key, val])))
     503
     504      @classmethod
     505 -    def __typesig__(cls, obj):
     506          if isinstance(obj, dict):
     507              if len(obj) == 0:
     508                  return Empty(dict)
     509              return Dict(obj.keys()[0], obj.values()[0])
     510
     511  ### Provide typechecking for the built-in list() type
     512 -class List(CheckType):
     513      name = "List"
     514
     515 -    def __init__(self, *type):
     516          self._types = [Type(t) for t in type]
     517          self.type = [t.type for t in self._types]
     518
     519 -    def __typecheck__(self, func, to_check):
     520          if not isinstance(to_check, list):
     521              raise _TC_TypeError(to_check, self.type)
     522          if len(to_check) % len(self._types):
     523              raise _TC_LengthError(len(to_check))
     524
     525          # lists can be patterned, meaning that [int, float]
     526          # requires that the to-be-checked list contain an alternating
     527          # sequence of integers and floats. The pattern must be completed
     528          # (e.g, [5, 5.0, 6, 6.0] but not [5, 5.0, 6]) for the list to
     529          # typecheck successfully.
     530          #
     531          # A list with a single type, [int], is a sub-case of patterned
     532          # lists
     533          #
     534          # XXX: Investigate speed increases by special-casing single-typed
     535          # lists
     536          pat_len = len(self._types)
     537          type_tuples = [(i, val, self._types[i % pat_len]) for (i, val)
     538                      in enumerate(to_check)]
     539          for (i, val, type) in type_tuples:
     540              try:
     541                  check_type(type, func, val)
     542              except _TC_Exception, e:
     543                  raise _TC_IndexError(i, e)
     544
     545 -    def __eq__(self, other):
     546          if other.__class__ is not self.__class__:
     547              return False
     548
     549          if len(self._types) != len(other._types):
     550              return False
     551
     552          for (s, o) in zip(self._types, other._types):
     553              if s != o:
     554                  return False
     555          return True
     556
     557 -    def __hash__(self):
     558          def strhash(obj):
     559              return str(hash(obj))
     560
     561          return hash(''.join(map(strhash, [self.__class__] + self._types)))
     562
     563      @classmethod
     564 -    def __typesig__(cls, obj):
     565          if isinstance(obj, list):
     566              if len(obj) == 0:
     567                  return Empty(list)
     568              return List(*obj)
     569
     570  ### Provide typechecking for the built-in tuple() class
     571 -class Tuple(List):
     572      name = "Tuple"
     573
     574 -    def __init__(self, *type):
     575          List.__init__(self, *type)
     576
     577          self.type = tuple(self.type)
     578
     579 -    def __typecheck__(self, func, to_check):
     580          # Note that tuples of varying length (e.g., (int, int) and (int, int, int))
     581          # are separate types, not merely differences in length like lists
     582          if not isinstance(to_check, types.TupleType) or len(to_check) != len(self._types):
     583              raise _TC_TypeError(to_check, self.type)
     584
     585          for (i, (val, type)) in enumerate(zip(to_check, self._types)):
     586              try:
     587                  check_type(type, func, val)
     588              except _TC_Exception, inner:
     589                  raise _TC_IndexError(i, inner)
     590
     591      @classmethod
     592 -    def __typesig__(cls, obj):
     593          if isinstance(obj, tuple):
     594              return Tuple(*obj)
     595
     596 -class TypeVariables(CheckType):
     597      # This is a stack of {typevariable -> type} mappings
     598      # It is intentional that it is class-wide; it maintains
     599      # the mappings of the outer functions if we descend into
     600      # nested typechecked functions
     601      __mapping_stack = []
     602
     603      # This is the {typevariable -> type} mapping for the function
     604      # currently being checked
     605      __active_mapping = None
     606
     607      # This dict maps generators to their mappings
     608      __gen_mappings = {}
     609
     610 -    def __init__(self, name):
     611          self.type = name
     612
     613 -    def __str__(self):
     614          return "TypeVariable(%s)" % self.type
     615
     616      __repr__ = __str__
     617
     618 -    def __hash__(self):
     619          return hash(''.join([str(o) for o in self.__class__
     620                                             , hash(type(self.type))
     621                                             , hash(self.type)]))
     622
     623 -    def __eq__(self, other):
     624          if self.__class__ is not other.__class__:
     625              return False
     626          return type(self.type) is type(other.type) and self.type == other.type
     627
     628 -    def __typecheck__(self, func, to_check):
     629          name = self.type
     630          if isinstance(func, GeneratorType):
     631              active = self.__class__.__gen_mappings[func]
     632          else:
     633              active = self.__class__.__active_mapping
     634
     635          # We have to do this because u'a' == 'a'
     636          lookup = (name, type(name))
     637          if lookup in active:
     638              check_type(active[lookup], func, to_check)
     639          else:
     640              # This is the first time we've encountered this
     641              # typevariable for this function call.
     642              #
     643              # In this case, we automatically approve the object
     644              active[lookup] = Type(calculate_type(to_check))
     645
     646      @classmethod
     647 -    def __typesig__(cls, obj):
     648          if isinstance(obj, basestring):
     649              return cls(obj)
     650
     651      @classmethod
     652 -    def __startchecking__(cls, func):
     653          if isinstance(func, GeneratorType):
     654              cls.__gen_mappings.setdefault(func, {})
     655          elif isinstance(func, FunctionType):
     656              cls.__mapping_stack.append(cls.__active_mapping)
     657              cls.__active_mapping = {}
     658          else:
     659              raise TypeError(func)
     660
     661      @classmethod
     662 -    def __switchchecking__(cls, from_func, to_func):
     663          if isinstance(from_func, FunctionType):
     664              if isinstance(to_func, GeneratorType):
     665                  cls.__gen_mappings[to_func] = cls.__active_mapping
     666                  cls.__stopchecking__(from_func)
     667              elif isinstance(to_func, FunctionType):
     668                  cls.__stopchecking__(from_func)
     669                  cls.__startchecking__(to_func)
     670              else:
     671                  raise TypeError(to_func)
     672          else:
     673              raise TypeError(from_func)
     674
     675      @classmethod
     676 -    def __stopchecking__(cls, func):
     677          if isinstance(func, GeneratorType):
     678              del cls.__gen_mappings[func]
     679          elif isinstance(func, FunctionType):
     680              cls.__active_mapping = cls.__mapping_stack.pop()
     681          else:
     682              raise TypeError(func)
     683
     684 -class Function(CheckType):
     685 -    def __init__(self, func):
     686          self._func = func
     687          self.type = self
     688
     689      @classmethod
     690 -    def __typesig__(cls, obj):
     691          if isinstance(obj, (FunctionType, MethodType)):
     692              return cls(obj)
     693
     694          # Snag callable class instances (that aren't types or classes)
     695          if type(obj) not in (types.ClassType, type) and callable(obj):
     696              return cls(obj)
     697
     698 -    def __typecheck__(self, func, to_check):
     699          if False == self._func(to_check):
     700              raise _TC_FunctionError(self._func, to_check)
     701
     702 -    def __str__(self):
     703          return "Function(%s)" % self._func
     704
     705 -    def __repr__(self):
     706          return str(self)
     707
     708 -    def __eq__(self, other):
     709          if self.__class__ is not other.__class__:
     710              return False
     711          return self._func is other._func
     712
     713 -    def __hash__(self):
     714          return hash(str(self.__class__) + str(hash(self._func)))
     715
     716  # Register some of the above types so that Type() knows about them
     717  for c in (CheckType, List, Tuple, Dict, Single, TypeVariables, Function):
     718      register_type(c)
     719
     720  ### The following are utility classes intended to make writing complex
     721  ### signatures easier.
     722  ######################################################################
     723
     724  ### Instances of Any() automatically approve of the object they're supposed
     725  ### to be checking (ie, they don't actually check it; use this with caution)
     726 -class Any(CheckType):
     727      name = "Any"
     728
     729 -    def __init__(self):
     730          self.type = object
     731
     732 -    def __typecheck__(self, func, to_check):
     733          pass
     734
     735 -    def __str__(self):
     736          return "Any()"
     737
     738      __repr__ = __str__
     739
     740      # All instances of this class are equal
     741 -    def __eq__(self, other):
     742          return other.__class__ is self.__class__
     743
     744 -    def __hash__(self):
     745          return hash(self.__class__)
     746
     747  ### Base class for Or() and And()
     748 -class _Boolean(CheckType):
     749 -    def __init__(self, first_type, second_type, *types):
     750          self._types = set()
     751
     752          for t in (first_type, second_type)+types:
     753              if type(t) is type(self):
     754                  self._types.update(t._types)
     755              else:
     756                  self._types.add(Type(t))
     757
     758          if len(self._types) < 2:
     759              raise TypeError("there must be at least 2 distinct parameters to __init__()")
     760
     761          self.type = self
     762
     763 -    def __eq__(self, other):
     764          if other.__class__ is not self.__class__:
     765              return False
     766
     767          return self._types == other._types
     768
     769 -    def __hash__(self):
     770          return hash(str(hash(self.__class__)) + str(hash(frozenset(self._types))))
     771
     772 -class Or(_Boolean):
     773      name = "Or"
     774
     775 -    def __typecheck__(self, func, to_check):
     776          for type in self._types:
     777              try:
     778                  check_type(type, func, to_check)
     779                  return
     780              except _TC_Exception:
     781                  pass
     782
     783          raise _TC_TypeError(to_check, self)
     784
     785 -class And(_Boolean):
     786      name = "And"
     787
     788 -    def __typecheck__(self, func, to_check):
     789          for type in self._types:
     790              try:
     791                  check_type(type, func, to_check)
     792              except _TC_Exception, e:
     793                  raise _TC_TypeError(to_check, self)
     794
     795 -class Not(Or):
     796      name = "Not"
     797
     798      # We override _Boolean's __init__ so that we can accept a single
     799      # condition
     800 -    def __init__(self, first_type, *types):
     801          self._types = set([Type(t) for t in (first_type,)+types])
     802
     803          self.type = self
     804
     805 -    def __typecheck__(self, func, to_check):
     806          # Or does our work for us, but we invert its result
     807          try:
     808              Or.__typecheck__(self, func, to_check)
     809          except _TC_Exception:
     810              return
     811          raise _TC_TypeError(to_check, self)
     812
     813 -class Xor(_Boolean):
     814      name = "Xor"
     815
     816 -    def __typecheck__(self, func, to_check):
     817          already_met_1_cond = False
     818
     819          for typ in self._types:
     820              try:
     821                  check_type(typ, func, to_check)
     822              except _TC_Exception:
     823                  pass
     824              else:
     825                  if already_met_1_cond:
     826                      raise _TC_XorError(2, _TC_TypeError(to_check, self))
     827                  already_met_1_cond = True
     828
     829          if not already_met_1_cond:
     830              raise _TC_XorError(0, _TC_TypeError(to_check, self))
     831
     832 -class IsCallable(CheckType):
     833 -    def __init__(self):
     834          self.type = self
     835
     836 -    def __str__(self):
     837          return "IsCallable()"
     838
     839      __repr__ = __str__
     840
     841      # They're all the same
     842      # XXX Change IsCallable to a singleton class
     843 -    def __hash__(self):
     844          return id(self.__class__)
     845
     846 -    def __eq__(self, other):
     847          return self.__class__ is other.__class__
     848
     849 -    def __typecheck__(self, func, to_check):
     850          if not callable(to_check):
     851              raise _TC_TypeError(to_check, 'a callable')
     852
     853 -class HasAttr(CheckType):
     854 -    def __init__(self, set_1, set_2=None):
     855          attr_sets = {list: [], dict: {}}
     856
     857          for (arg_1, arg_2) in ((set_1, set_2), (set_2, set_1)):
     858              for t in (list, dict):
     859                  if isinstance(arg_1, t):
     860                      attr_sets[t] = arg_1
     861                      if isinstance(arg_2, t):
     862                          raise TypeError("can only have one list and/or one dict")
     863
     864          self._attr_types = dict.fromkeys(attr_sets[list], Any())
     865
     866          for (attr, typ) in attr_sets[dict].items():
     867              self._attr_types[attr] = Type(typ)
     868
     869 -    def __typecheck__(self, func, to_check):
     870          for (attr, typ) in self._attr_types.items():
     871              if not hasattr(to_check, attr):
     872                  raise _TC_MissingAttrError(attr)
     873
     874              try:
     875                  check_type(typ, func, getattr(to_check, attr))
     876              except _TC_Exception, e:
     877                  raise _TC_AttrError(attr, e)
     878
     879 -    def __eq__(self, other):
     880          if self.__class__ is not other.__class__:
     881              return False
     882          return self._attr_types == other._attr_types
     883
     884 -    def __hash__(self):
     885          return hash(str(hash(self.__class__)) + str(hash(str(self._attr_types))))
     886
     887 -    def __str__(self):
     888          any_type = []
     889          spec_type = {}
     890
     891          any = Any()
     892
     893          for (attr, typ) in self._attr_types.items():
     894              if typ == any:
     895                  any_type.append(attr)
     896              else:
     897                  spec_type[attr] = typ
     898
     899          msg = [t for t in (any_type, spec_type) if len(t)]
     900
     901          return "HasAttr(" + ', '.join(map(str, msg)) + ")"
     902
     903      __repr__ = __str__
     904
     905 -class IsIterable(CheckType):
     906 -    def __init__(self):
     907          self.type = self
     908
     909 -    def __eq__(self, other):
     910          return self.__class__ is other.__class__
     911
     912      # They're all the same
     913      # XXX Change IsIterable to a singleton class
     914 -    def __hash__(self):
     915          return id(self.__class__)
     916
     917 -    def __str__(self):
     918          return "IsIterable()"
     919
     920      __repr__ = __str__
     921
     922 -    def __typecheck__(self, func, to_check):
     923          if not (hasattr(to_check, '__iter__') and callable(to_check.__iter__)):
     924              raise _TC_TypeError(to_check, "an iterable")
     925
     926 -class YieldSeq(CheckType):
     927      _index_map = {}
     928
     929 -    def __init__(self, type_1, type_2, *types):
     930          self.type = self
     931
     932          self._type = [type_1, type_2] + list(types)
     933          self._types = [Type(t) for t in self._type]
     934
     935 -    def __hash__(self):
     936          return id(self)
     937
     938 -    def __str__(self):
     939          return "YieldSeq(" + ", ".join(map(str, self._type)) + ")"
     940
     941      __repr__ = __str__
     942
     943 -    def __eq__(self, other):
     944          if self.__class__ is not other.__class__:
     945              return False
     946          return self._types == other._types
     947
     948 -    def __hash__(self):
     949          return hash(str(self.__class__) + str([hash(t) for t in self._types]))
     950
     951      # We have to use __{start,stop}checking__ so that the indexes get
     952      # reset every time we run through the typechecking sequence
     953      @classmethod
     954 -    def __startchecking__(cls, gen):
     955          if isinstance(gen, GeneratorType):
     956              cls._index_map[gen] = {}
     957
     958      @classmethod
     959 -    def __stopchecking__(cls, gen):
     960          if gen in cls._index_map:
     961              del cls._index_map[gen]
     962
     963 -    def __typecheck__(self, gen, to_check):
     964          index_map = self.__class__._index_map
     965
     966          # There might be multiple YieldSeq's per signature
     967          if self not in index_map[gen]:
     968              index_map[gen][self] = -1
     969          index = index_map[gen]
     970
     971          if index[self] >= len(self._types)-1:
     972              raise _TC_YieldCountError(len(self._types))
     973
     974          index[self] += 1
     975          check_type(self._types[index[self]], gen, to_check)
     976
     977  register_type(YieldSeq)
     978
     979 -class Exact(CheckType):
     980 -    def __init__(self, obj):
     981          self.type = self
     982          self._obj = obj
     983
     984 -    def __hash__(self):
     985          try:
     986              obj_hash = str(hash(self._obj))
     987          except TypeError:
     988              obj_hash = str(type(self._obj)) + str(self._obj)
     989
     990          return hash(str(self.__class__) + obj_hash)
     991
     992 -    def __eq__(self, other):
     993          if self.__class__ is not other.__class__:
     994              return False
     995          return self._obj == other._obj
     996
     997 -    def __typecheck__(self, func, to_check):
     998          if self._obj != to_check:
     999              raise _TC_ExactError(to_check, self._obj)
    1000
    1001 -class Length(CheckType):
    1002 -    def __init__(self, length):
    1003          self.type = self
    1004          self._length = int(length)
    1005
    1006 -    def __hash__(self):
    1007          return hash(str(self.__class__) + str(self._length))
    1008
    1009 -    def __eq__(self, other):
    1010          if self.__class__ is not other.__class__:
    1011              return False
    1012          return self._length == other._length
    1013
    1014 -    def __typecheck__(self, func, to_check):
    1015          try:
    1016              length = len(to_check)
    1017          except TypeError:
    1018              raise _TC_TypeError(to_check, "something with a __len__ method")
    1019
    1020          if length != self._length:
    1021              raise _TC_LengthError(length, self._length)
    1022
    1023  import sys
    1024 -class Class(CheckType):
    1025 -    def __init__(self, class_name):
    1026          self.type = self
    1027          self.class_name = class_name
    1028          self.class_obj = None
    1029          self._frame = sys._getframe(1)
    1030
    1031 -    def __hash__(self):
    1032          return hash(str(self.__class__) + self.class_name)
    1033
    1034 -    def __str__(self):
    1035          return "Class('%s')" % self.class_name
    1036
    1037      __repr__ = __str__
    1038
    1039 -    def __eq__(self, other):
    1040          if self.__class__ is not other.__class__:
    1041              return False
    1042          return self.class_name == other.class_name
    1043
    1044 -    def __typecheck__(self, func, to_check):
    1045          if self.class_obj is None:
    1046              class_name = self.class_name
    1047              frame = self._frame
    1048
    1049              for f_dict in (frame.f_locals, frame.f_globals):
    1050                  if class_name in frame.f_locals:
    1051                      if self is not frame.f_locals[class_name]:
    1052                          self.class_obj = frame.f_locals[class_name]
    1053                          self._frame = None
    1054                          break
    1055              else:
    1056                  raise NameError("name '%s' is not defined" % class_name)
    1057
    1058          if not isinstance(to_check, self.class_obj):
    1059              raise _TC_TypeError(to_check, self.class_obj)
    1060
    1061 -class Typeclass(CheckType):
    1062      bad_members = dict.fromkeys(['__class__', '__new__', '__init__'], True)
    1063
    1064 -    def __init__(self, *types):
    1065          if len(types) == 0:
    1066              raise TypeError("Must supply at least one type to __init__()")
    1067
    1068          self.type = self
    1069
    1070          self._cache = set()
    1071          self._interface = set()
    1072          self._instances = set()
    1073          for t in types:
    1074              self.add_instance(t)
    1075
    1076          self._calculate_interface()
    1077
    1078 -    def recalculate_interface(self):
    1079          self._cache = self._instances.copy()
    1080          self._calculate_interface()
    1081
    1082 -    def instances(self):
    1083          return list(self._instances)
    1084
    1085 -    def interface(self):
    1086          return list(self._interface)
    1087
    1088 -    def has_instance(self, instance):
    1089          return instance in self._instances
    1090
    1091 -    def add_instance(self, instance):
    1092          if isinstance(instance, self.__class__):
    1093              for inst in instance.instances():
    1094                  self._instances.add(inst)
    1095                  self._cache.add(inst)
    1096          elif isinstance(instance, (ClassType, TypeType)):
    1097              self._instances.add(instance)
    1098              self._cache.add(instance)
    1099          else:
    1100              raise TypeError("All instances must be classes or types")
    1101
    1102 -    def intersect(self, other):
    1103          if isinstance(other, self.__class__):
    1104              new_instances = other.instances()
    1105          else:
    1106              new_instances = other
    1107
    1108          self._instances.update(new_instances)
    1109          self._cache.update(new_instances)
    1110          self._calculate_interface()
    1111
    1112 -    def _calculate_interface(self):
    1113          bad_members = self.bad_members
    1114
    1115          for instance in self._instances:
    1116              inst_attrs = []
    1117
    1118              for attr, obj in instance.__dict__.items():
    1119                  if callable(obj) and attr not in bad_members:
    1120                      inst_attrs.append(attr)
    1121
    1122              if len(self._interface) == 0:
    1123                  self._interface = set(inst_attrs)
    1124              else:
    1125                  self._interface.intersection_update(inst_attrs)
    1126
    1127 -    def __typecheck__(self, func, to_check):
    1128          if to_check.__class__ in self._cache:
    1129              return
    1130
    1131          for method in self._interface:
    1132              if not hasattr(to_check, method):
    1133                  raise _TC_MissingAttrError(method)
    1134
    1135              attr = getattr(to_check, method)
    1136              if not callable(attr):
    1137                  raise _TC_AttrError(method, _TC_TypeError(attr, IsCallable()))
    1138
    1139          self._cache.add(to_check.__class__)
    1140
    1141 -    def __eq__(self, other):
    1142          if self.__class__ is not other.__class__:
    1143              return False
    1144          return self._instances == other._instances
    1145
    1146 -    def __hash__(self):
    1147          return hash(str(self.__class__) + str(hash(frozenset(self._instances))))
    1148
    1149 -    def __repr__(self):
    1150          return object.__repr__(self)
    1151
    1152 -    def __str__(self):
    1153          return 'Typeclass(' + ', '.join(map(str, self._instances)) + ')'
    1154
    1155  # The current implementation of Self relies on the TypeVariables machinery
    1156  _Self = TypeVariables("this is the class of the invocant")
    1157 -def Self():
    1158      return _Self
    1159
    1160  ### Aliases
    1161  ###########
    1162
    1163  IsOneOf = Or
    1164  IsAllOf = And
    1165  IsNoneOf = Not
    1166  IsOnlyOneOf = Xor
    1167
    1168  ### This is the public side of the module
    1169  #########################################
    1170
    1171  # This is for backwards compatibility with v0.1.6 and earlier
    1172 -class TypeCheckException(Exception):
    1173      pass
    1174
    1175 -class TypeCheckError(TypeCheckException):
    1176 -    def __init__(self, prefix, bad_object, exception):
    1177          TypeCheckException.__init__(self, prefix, bad_object, exception)
    1178
    1179          self.prefix = prefix
    1180          self.internal = exception
    1181          self.bad_object = bad_object
    1182
    1183          (bad_obj_str, start_message) = exception.format_bad_object(bad_object)
    1184          self.__message = prefix + bad_obj_str + start_message.error_message()
    1185
    1186 -    def __str__(self):
    1187          return self.__message
    1188
    1189 -class TypeSignatureError(Exception):
    1190 -    def __init__(self, internal_exc):
    1191          Exception.__init__(self, internal_exc)
    1192
    1193          self.internal = internal_exc
    1194          self.__message = internal_exc.error_message()
    1195
    1196 -    def __str__(self):
    1197          return self.__message
    1198
    1199  ### Begin helper classes/functions for typecheck_args
    1200  #####################################################
    1201 -def _rec_tuple(obj):
    1202      if isinstance(obj, list):
    1203          return tuple(_rec_tuple(o) for o in obj)
    1204      return obj
    1205
    1206 -def _rec_tuple_str(obj):
    1207      if not isinstance(obj, (list, tuple)):
    1208          return obj
    1209
    1210      if len(obj) == 1:
    1211          return '(%s,)' % obj
    1212
    1213      return '(' + ', '.join(_rec_tuple_str(o) for o in obj) + ')'
    1214
    1215 -def _gen_arg_to_param(func, (posargs, varargs, varkw, defaults)):
    1216      sig_args = list()
    1217      dic_args = list()
    1218
    1219      for obj in posargs:
    1220          if isinstance(obj, list):
    1221              rts = _rec_tuple_str(obj)
    1222
    1223              sig_args.append(rts)
    1224              dic_args.append((_rec_tuple(obj), rts))
    1225          else:
    1226              sig_args.append(str(obj))
    1227              dic_args.append(('"%s"' % obj, obj))
    1228
    1229      func_code = ''
    1230      if varargs:
    1231          dic_args.append(('"%s"' % varargs, varargs))
    1232          sig_args.append('*' + varargs)
    1233          func_code = '\n\t%s = list(%s)' % (varargs, varargs)
    1234      if varkw:
    1235          dic_args.append(('"%s"' % varkw, varkw))
    1236          sig_args.append('**' + varkw)
    1237
    1238      func_name = func.func_name + '_'
    1239      while func_name in dic_args:
    1240          func_name += '_'
    1241
    1242      func_def = 'def %s(' % func.func_name
    1243      func_return = func_code \
    1244                  + '\n\treturn {' \
    1245                  + ', '.join('%s: %s' % kv for kv in dic_args) \
    1246                  + '}'
    1247
    1248      locals = {}
    1249      exec func_def + ','.join(sig_args) + '):' + func_return in locals
    1250      func = locals[func.func_name]
    1251      func.func_defaults = defaults
    1252      return func
    1253
    1254 -def _validate_tuple(ref, obj):
    1255      if not isinstance(ref, (list, tuple)):
    1256          return
    1257      if not isinstance(obj, (list, tuple)):
    1258          raise _TS_TupleError(ref, obj)
    1259
    1260      if len(ref) != len(obj):
    1261          raise _TS_TupleError(ref, obj)
    1262
    1263      try:
    1264          for r, o in zip(ref, obj):
    1265              _validate_tuple(r, o)
    1266      except _TS_TupleError:
    1267          raise _TS_TupleError(ref, obj)
    1268
    1269 -def _param_to_type((params, varg_name, kwarg_name), vargs, kwargs):
    1270      vargs = list(vargs)
    1271      kwargs = dict(kwargs)
    1272
    1273      # Make parameter names to values
    1274      param_value = dict()
    1275
    1276      # There are excess positional arguments, but no *args parameter
    1277      if len(params) < len(vargs) and varg_name is None:
    1278          raise _TS_ExtraPositionalError(vargs[len(params)])
    1279      # There are not enough position args and no kwargs to draw from
    1280      if len(params) > len(vargs) and len(kwargs) == 0:
    1281          raise _TS_MissingTypeError(params[len(vargs)])
    1282
    1283      # No reason to do this if there aren't any vargs
    1284      if len(vargs):
    1285          for p, a in zip(params, vargs):
    1286              # Make sure all auto-unpacked tuples match up
    1287              _validate_tuple(p, a)
    1288              param_value[_rec_tuple(p)] = a
    1289
    1290      # No reason to do all this work if there aren't any kwargs
    1291      if len(kwargs) > 0:
    1292          # All params that still need values
    1293          params = set([k for k in params if k not in param_value])
    1294          if kwarg_name and kwarg_name not in param_value:
    1295              params.add(kwarg_name)
    1296          if varg_name and varg_name not in param_value:
    1297              params.add(varg_name)
    1298
    1299          # Lift this out of the loop
    1300          no_double_star = kwarg_name is None
    1301
    1302          # All parameter slots have been filled, but there are still keyword
    1303          # args remaining with no **kwargs parameter present
    1304          if len(params) == 0 and no_double_star:
    1305              raise _TS_ExtraKeywordError(kwargs.keys()[0])
    1306
    1307          # Match up remaining keyword args with open parameter slots
    1308          for p, a in kwargs.items():
    1309              if p in param_value:
    1310                  raise _TS_TwiceTypedError(p, a, param_value[p])
    1311              if p not in params and no_double_star:
    1312                  raise _TS_ExtraKeywordError(p)
    1313
    1314              # Make sure all auto-unpacked tuples match up
    1315              _validate_tuple(p, a)
    1316
    1317              # Bookkeeping
    1318              params.remove(p)
    1319              param_value[p] = a
    1320
    1321          # Any elements left in params indicate that the parameter is missing
    1322          # a value
    1323          if len(params):
    1324              raise _TS_MissingTypeError(params.pop())
    1325
    1326      return param_value
    1327
    1328 -def _make_fake_function(func):
    1329      def fake_function(*vargs, **kwargs):
    1330          # We call start_checking here, but __check_result
    1331          # has to call stop_checking on its own. The reason
    1332          # for this is so that typecheck_yield can call
    1333          # stop_checking on the function and then start_checking
    1334          # on the generator
    1335          start_checking(func)
    1336
    1337          # If either one of these operations fails, we need to call
    1338          # stop_checking()
    1339          try:
    1340              fake_function.__check_args(vargs, kwargs)
    1341              result = func(*vargs, **kwargs)
    1342          except:
    1343              stop_checking(func)
    1344              raise
    1345
    1346          return fake_function.__check_result(func, result)
    1347
    1348      # These are the default implementations of __check_args
    1349      # and __check_results
    1350      def _pass_args(vargs, kwargs):
    1351          pass
    1352      def _pass_result(func, result):
    1353          stop_checking(func)
    1354          return result
    1355
    1356      fake_function.__check_args = _pass_args
    1357      fake_function.__check_result = _pass_result
    1358      fake_function.__wrapped_func = func
    1359
    1360      # Mock-up the fake function to look as much like the
    1361      # real function as possible
    1362      fake_function.__module__ = func.__module__
    1363      fake_function.__name__ = func.__name__
    1364      fake_function.__doc__ = func.__doc__
    1365
    1366      return fake_function
    1367
    1368  ###################################################
    1369  ### End helper classes/functions for typecheck_args
    1370
    1371 -def typecheck_args(*v_sig, **kw_sig):
    1372      # typecheck_args is run to obtain the real decorator
    1373      def decorator(func):
    1374          if hasattr(func, '__wrapped_func'):
    1375              if hasattr(func, 'type_args'):
    1376                  raise RuntimeError('Cannot use the same typecheck_* function more than once on the same function')
    1377              wrapped_func = func.__wrapped_func
    1378          else:
    1379              wrapped_func = func
    1380
    1381          param_list, varg_name, kwarg_name, defaults = inspect.getargspec(wrapped_func)
    1382          args_to_params = _gen_arg_to_param(wrapped_func, (param_list, varg_name, kwarg_name, defaults))
    1383
    1384          try:
    1385              param_types = _param_to_type((param_list, varg_name, kwarg_name), v_sig, kw_sig)
    1386          except _TS_Exception, e:
    1387              raise TypeSignatureError(e)
    1388
    1389          ### We need to fix-up the types of the *vargs and **kwargs parameters
    1390          #####################################################################
    1391          if varg_name:
    1392              if not isinstance(param_types[varg_name], list):
    1393                  param_types[varg_name] = [param_types[varg_name]]
    1394
    1395          if kwarg_name:
    1396              if not isinstance(param_types[kwarg_name], dict):
    1397                  param_types[kwarg_name] = {str: param_types[kwarg_name]}
    1398
    1399          #####################################################################
    1400          ### /Fix-up
    1401
    1402          # Convert the signatures to types now, rather than rebuild them in every function call
    1403          check_param_types = dict()
    1404          for k, v in param_types.items():
    1405              check_param_types[k] = Type(v)
    1406
    1407          def __check_args(__vargs, __kwargs):
    1408              # Type-checking can be turned on and off by toggling the
    1409              # value of the global enable_checking variable
    1410              if enable_checking:
    1411                  arg_dict = args_to_params(*__vargs, **__kwargs)
    1412
    1413                  # Type-check the keyword arguments
    1414                  try:
    1415                      for name, val in arg_dict.items():
    1416                          check_type(check_param_types[name], wrapped_func, val)
    1417                  except _TC_Exception, e:
    1418                      str_name = _rec_tuple_str(name)
    1419                      raise TypeCheckError("Argument %s: " % str_name, val, e)
    1420
    1421          if hasattr(func, '__check_result'):
    1422              # This is one of our wrapper functions, probably created by
    1423              # typecheck_yield or typecheck_return
    1424              fake_function = func
    1425          else:
    1426              # We need to build a wrapper
    1427              fake_function = _make_fake_function(func)
    1428
    1429          # Specify how argument checking should be done
    1430          fake_function.__check_args = __check_args
    1431
    1432          ### Add the publically-accessible signature information
    1433          fake_function.type_args = param_types
    1434
    1435          return fake_function
    1436      return decorator
    1437
    1438  # Refactor this out of typecheck_{return,yield}
    1439 -def _decorator(signature, conflict_field, twice_field, check_result_func):
    1440      def decorator(func):
    1441          if hasattr(func, '__check_result'):
    1442              # This is one of our wrapper functions, probably created by
    1443              # typecheck_args
    1444              if hasattr(func, conflict_field):
    1445                  raise RuntimeError("Cannot use typecheck_return and typecheck_yield on the same function")
    1446              elif hasattr(func, twice_field):
    1447                  raise RuntimeError('Cannot use the same typecheck_* function more than once on the same function')
    1448
    1449              fake_function = func
    1450          else:
    1451              fake_function = _make_fake_function(func)
    1452
    1453          setattr(fake_function, twice_field, signature)
    1454          fake_function.__check_result = check_result_func
    1455          return fake_function
    1456      return decorator
    1457
    1458 -def typecheck_return(*signature):
    1459      if len(signature) == 1:
    1460          signature = signature[0]
    1461      sig_types = Type(signature)
    1462
    1463      def __check_return(func, return_vals):
    1464          if enable_checking:
    1465              try:
    1466                  check_type(sig_types, func, return_vals)
    1467              except _TC_Exception, e:
    1468                  stop_checking(func)
    1469                  raise TypeCheckError("Return value: ", return_vals, e)
    1470
    1471          stop_checking(func)
    1472          return return_vals
    1473      return _decorator(signature, 'type_yield', 'type_return', __check_return)
    1474
    1475 -class Fake_generator(object):
    1476 -    def __init__(self, real_gen, signature):
    1477          # The generator should have the same yield signature
    1478          # as the function that produced it; however, we don't
    1479          # copy the args signature because the generator
    1480          # doesn't take arguments
    1481          self.type_yield = signature
    1482
    1483          self.__yield_no = 0
    1484          self.__real_gen = real_gen
    1485          self.__sig_types = Type(signature)
    1486          self.__needs_stopping = True
    1487
    1488 -    def next(self):
    1489          gen = self.__real_gen
    1490
    1491          self.__yield_no += 1
    1492
    1493          try:
    1494              return_vals = gen.next()
    1495          except StopIteration:
    1496              if self.__needs_stopping:
    1497                  stop_checking(gen)
    1498                  self.__needs_stopping = False
    1499              raise
    1500
    1501          if enable_checking:
    1502              try:
    1503                  check_type(self.__sig_types, gen, return_vals)
    1504              except _TC_Exception, e:
    1505                  # Insert this error into the chain so we can know
    1506                  # which yield the error occurred at
    1507                  middle_exc = _TC_GeneratorError(self.__yield_no, e)
    1508                  raise TypeCheckError("", return_vals, middle_exc)
    1509
    1510          # Everything checks out. Return the results
    1511          return return_vals
    1512
    1513 -    def __del__(self):
    1514          if self.__needs_stopping:
    1515              stop_checking(self.__real_gen)
    1516
    1517 -def typecheck_yield(*signature):
    1518      if len(signature) == 1:
    1519          signature = signature[0]
    1520
    1521      def __check_yield(func, gen):
    1522          # If the return value isn't a generator, we blow up
    1523          if not isinstance(gen, types.GeneratorType):
    1524              stop_checking(func)
    1525              raise TypeError("typecheck_yield only works for generators")
    1526
    1527          # Inform all listening classes that they might want to preserve any information
    1528          # from the function to the generator (*hint* TypeVariables *hint*)
    1529          #
    1530          # stop_checking() will not be invoked on the generator until it raises
    1531          # StopIteration or its refcount drops to 0
    1532          switch_checking(func, gen)
    1533
    1534          # Otherwise, we build ourselves a fake generator
    1535          return Fake_generator(gen, signature)
    1536      return _decorator(signature, 'type_return', 'type_yield', __check_yield)
    1537
    1538  _null_decorator = lambda *args, **kwargs: lambda f: f
    1539  typecheck = _null_decorator
    1540  accepts = _null_decorator
    1541  returns = _null_decorator
    1542  yields = _null_decorator
    1543
    1544  # Aliases
    1545 -def enable_typechecking():
    1546      global typecheck
    1547      global accepts
    1548      global returns
    1549      global yields
    1550
    1551      typecheck = typecheck_args
    1552      accepts = typecheck_args
    1553      returns = typecheck_return
    1554      yields = typecheck_yield
    1555
    1556  import os
    1557  if "PYTHONTYPECHECK" in os.environ:
    1558      enable_typechecking()
    1559