1""" 2Helper module that will enable lazy imports of Cocoa wrapper items. 3 4This should improve startup times and memory usage, at the cost 5of not being able to use 'from Cocoa import *' 6""" 7__all__ = ('ObjCLazyModule',) 8 9import sys 10import re 11import struct 12 13from objc import lookUpClass, getClassList, nosuchclass_error, loadBundle 14import objc 15ModuleType = type(sys) 16 17def _loadBundle(frameworkName, frameworkIdentifier, frameworkPath): 18 if frameworkIdentifier is None: 19 bundle = loadBundle( 20 frameworkName, 21 {}, 22 bundle_path=frameworkPath, 23 scan_classes=False) 24 25 else: 26 try: 27 bundle = loadBundle( 28 frameworkName, 29 {}, 30 bundle_identifier=frameworkIdentifier, 31 scan_classes=False) 32 33 except ImportError: 34 bundle = loadBundle( 35 frameworkName, 36 {}, 37 bundle_path=frameworkPath, 38 scan_classes=False) 39 40 return bundle 41 42class GetAttrMap (object): 43 __slots__ = ('_container',) 44 def __init__(self, container): 45 self._container = container 46 47 def __getitem__(self, key): 48 try: 49 return getattr(self._container, key) 50 except AttributeError: 51 raise KeyError(key) 52 53class ObjCLazyModule (ModuleType): 54 55 # Define slots for all attributes, that way they don't end up it __dict__. 56 __slots__ = ( 57 '_ObjCLazyModule__bundle', '_ObjCLazyModule__enummap', '_ObjCLazyModule__funcmap', 58 '_ObjCLazyModule__parents', '_ObjCLazyModule__varmap', '_ObjCLazyModule__inlinelist', 59 '_ObjCLazyModule__aliases', 60 ) 61 62 def __init__(self, name, frameworkIdentifier, frameworkPath, metadict, inline_list=None, initialdict={}, parents=()): 63 super(ObjCLazyModule, self).__init__(name) 64 65 if frameworkIdentifier is not None or frameworkPath is not None: 66 self.__bundle = self.__dict__['__bundle__'] = _loadBundle(name, frameworkIdentifier, frameworkPath) 67 68 pfx = name + '.' 69 for nm in sys.modules: 70 if nm.startswith(pfx): 71 rest = nm[len(pfx):] 72 if '.' in rest: continue 73 if sys.modules[nm] is not None: 74 self.__dict__[rest] = sys.modules[nm] 75 76 self.__dict__.update(initialdict) 77 self.__dict__.update(metadict.get('misc', {})) 78 self.__parents = parents 79 self.__varmap = metadict.get('constants') 80 self.__varmap_dct = metadict.get('constants_dict', {}) 81 self.__enummap = metadict.get('enums') 82 self.__funcmap = metadict.get('functions') 83 self.__aliases = metadict.get('aliases') 84 self.__inlinelist = inline_list 85 86 self.__expressions = metadict.get('expressions') 87 self.__expressions_mapping = GetAttrMap(self) 88 89 self.__load_cftypes(metadict.get('cftypes')) 90 91 if metadict.get('protocols') is not None: 92 self.__dict__['protocols'] = ModuleType('%s.protocols'%(name,)) 93 self.__dict__['protocols'].__dict__.update( 94 metadict['protocols']) 95 96 for p in objc.protocolsForProcess(): 97 setattr(self.__dict__['protocols'], p.__name__, p) 98 99 100 def __dir__(self): 101 return self.__all__ 102 103 def __getattr__(self, name): 104 if name == "__all__": 105 # Load everything immediately 106 value = self.__calc_all() 107 self.__dict__[name] = value 108 return value 109 110 # First try parent module, as we had done 111 # 'from parents import *' 112 for p in self.__parents: 113 try: 114 value = getattr(p, name) 115 except AttributeError: 116 pass 117 118 else: 119 self.__dict__[name] = value 120 return value 121 122 # Check if the name is a constant from 123 # the metadata files 124 try: 125 value = self.__get_constant(name) 126 except AttributeError: 127 pass 128 else: 129 self.__dict__[name] = value 130 return value 131 132 # Then check if the name is class 133 try: 134 value = lookUpClass(name) 135 except nosuchclass_error: 136 pass 137 138 else: 139 self.__dict__[name] = value 140 return value 141 142 # Finally give up and raise AttributeError 143 raise AttributeError(name) 144 145 def __calc_all(self): 146 all = set() 147 148 # Ensure that all dynamic entries get loaded 149 if self.__varmap_dct: 150 for nm in self.__varmap_dct: 151 try: 152 getattr(self, nm) 153 except AttributeError: 154 pass 155 156 if self.__varmap: 157 for nm in re.findall(r"\$([A-Z0-9a-z_]*)(?:@[^$]*)?(?=\$)", self.__varmap): 158 try: 159 getattr(self, nm) 160 except AttributeError: 161 pass 162 163 if self.__enummap: 164 for nm in re.findall(r"\$([A-Z0-9a-z_]*)@[^$]*(?=\$)", self.__enummap): 165 try: 166 getattr(self, nm) 167 except AttributeError: 168 pass 169 170 if self.__funcmap: 171 for nm in self.__funcmap: 172 try: 173 getattr(self, nm) 174 except AttributeError: 175 pass 176 177 if self.__expressions: 178 for nm in self.__expressions: 179 try: 180 getattr(self, nm) 181 except AttributeError: 182 pass 183 184 if self.__aliases: 185 for nm in self.__aliases: 186 try: 187 getattr(self, nm) 188 except AttributeError: 189 pass 190 191 # Add all names that are already in our __dict__ 192 all.update(self.__dict__) 193 194 # Merge __all__of parents ('from parent import *') 195 for p in self.__parents: 196 all.update(getattr(p, '__all__', ())) 197 198 # Add all class names 199 all.update(cls.__name__ for cls in getClassList()) 200 201 202 return [ v for v in all if not v.startswith('_') ] 203 204 return list(all) 205 206 def __get_constant(self, name): 207 # FIXME: Loading variables and functions requires too much 208 # code at the moment, the objc API can be adjusted for 209 # this later on. 210 if self.__varmap_dct: 211 if name in self.__varmap_dct: 212 tp = self.__varmap_dct[name] 213 return objc._loadConstant(name, tp, False) 214 215 if self.__varmap: 216 m = re.search(r"\$%s(@[^$]*)?\$"%(name,), self.__varmap) 217 if m is not None: 218 tp = m.group(1) 219 if tp is None: 220 tp = '@' 221 else: 222 tp = tp[1:] 223 224 d = {} 225 if tp.startswith('='): 226 tp = tp[1:] 227 magic = True 228 else: 229 magic = False 230 231 #try: 232 return objc._loadConstant(name, tp, magic) 233 #except Exception as exc: 234 # print "LOAD %r %r %r -> raise %s"%(name, tp, magic, exc) 235 # raise 236 237 if self.__enummap: 238 m = re.search(r"\$%s@([^$]*)\$"%(name,), self.__enummap) 239 if m is not None: 240 val = m.group(1) 241 242 if val.startswith("'"): 243 if isinstance(val, bytes): 244 # Python 2.x 245 val, = struct.unpack('>l', val[1:-1]) 246 else: 247 # Python 3.x 248 val, = struct.unpack('>l', val[1:-1].encode('latin1')) 249 250 elif '.' in val: 251 val = float(val) 252 else: 253 val = int(val) 254 255 return val 256 257 if self.__funcmap: 258 if name in self.__funcmap: 259 info = self.__funcmap[name] 260 261 func_list = [ (name,) + info ] 262 263 d = {} 264 objc.loadBundleFunctions(self.__bundle, d, func_list) 265 if name in d: 266 return d[name] 267 268 if self.__inlinelist is not None: 269 try: 270 objc.loadFunctionList( 271 self.__inlinelist, d, func_list, skip_undefined=False) 272 except objc.error: 273 pass 274 275 else: 276 if name in d: 277 return d[name] 278 279 if self.__expressions: 280 if name in self.__expressions: 281 info = self.__expressions[name] 282 try: 283 return eval(info, {}, self.__expressions_mapping) 284 except NameError: 285 pass 286 287 if self.__aliases: 288 if name in self.__aliases: 289 alias = self.__aliases[name] 290 if alias == 'ULONG_MAX': 291 return (sys.maxsize * 2) + 1 292 elif alias == 'LONG_MAX': 293 return sys.maxsize 294 elif alias == 'LONG_MIN': 295 return -sys.maxsize-1 296 297 return getattr(self, alias) 298 299 raise AttributeError(name) 300 301 def __load_cftypes(self, cftypes): 302 if not cftypes: return 303 304 for name, type, gettypeid_func, tollfree in cftypes: 305 if tollfree: 306 for nm in tollfree.split(','): 307 try: 308 objc.lookUpClass(nm) 309 except objc.error: 310 pass 311 else: 312 tollfree = nm 313 break 314 try: 315 v = objc.registerCFSignature(name, type, None, tollfree) 316 if v is not None: 317 self.__dict__[name] = v 318 continue 319 except objc.nosuchclass_error: 320 pass 321 322 try: 323 func = getattr(self, gettypeid_func) 324 except AttributeError: 325 # GetTypeID function not found, this is either 326 # a CFType that isn't present on the current 327 # platform, or a CFType without a public GetTypeID 328 # function. Proxy using the generic CFType 329 if tollfree is None: 330 v = objc.registerCFSignature(name, type, None, 'NSCFType') 331 if v is not None: 332 self.__dict__[name] = v 333 continue 334 335 v = objc.registerCFSignature(name, type, func()) 336 if v is not None: 337 self.__dict__[name] = v 338