Welcome, guest | Sign In | My Account | Store | Cart

A typesafe wrapper for namedtuple.

Python, 35 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def typecheck(func):
   if not hasattr(func,'__annotations__'): return method
   import inspect
   argspec = inspect.getfullargspec(func)
   def check( t, T ):
      if type(T) == type: return isinstance(t,T)   #types
      else: return T(t)                            #predicates   
   def wrapper(*args):
      if len(argspec.args) != len(args):
         raise TypeError( "%s() takes exactly %s positional argument (%s given)"
                           %(func.__name__,len(argspec.args),len(args)) )
      for argname,t in zip(argspec.args, args):
         if argname in func.__annotations__:
            T = func.__annotations__[argname]
            if not check( t, T  ):
               raise TypeError(  "%s( %s:%s ) but received %s=%s"
                                 % (func.__name__, argname,T, argname,repr(t)) )
      r = func(*args)
      if 'return' in func.__annotations__:
         T = func.__annotations__['return']
         if not check( r, T ):
            raise TypeError( "%s() -> %s but returned %s"%(func.__name__,T,repr(r)) )
      return r
   return wrapper


def Record(sig,*types):
   cls_name, _, cls_vars = sig.partition(' ')
   import collections
   cls = collections.namedtuple(cls_name,cls_vars)
   cls_vars = cls_vars.split(' ')
   assert len(types)==len(cls_vars)
   cls.__new__.__annotations__ = dict(zip(cls_vars,types))
   cls.__new__ = typecheck(cls.__new__)
   return cls

I had been torn between haskell and python until I thought of this recipe. Now if I just had a static type-checker for Python...

#example usage: an sql record

import re
re_email = re.compile('^[_.0-9a-z-]+@([0-9a-z][0-9a-z-]+.)+[a-z]{2,4}$')

def is_email(s): 
   return isinstance(s,str) and re_email.match(s) 

User = Record('User email pwdhash',is_email,str)


u = User('me@mysite.com','ajfs80')
u.email
u.pwdhash