comparison tincan.py @ 15:560c8fb55e4a draft

Fix bugs in #python directive, make code-behind class loading simpler.
author David Barts <n5jrn@me.com>
date Thu, 16 May 2019 18:42:22 -0700
parents 9d0497dc19f8
children 448fc3d534f8
comparison
equal deleted inserted replaced
14:9d0497dc19f8 15:560c8fb55e4a
382 # If this is a #hidden page, we ignore it for now, since hidden pages 382 # If this is a #hidden page, we ignore it for now, since hidden pages
383 # don't get routes made for them. 383 # don't get routes made for them.
384 if oheader.hidden and not oheader.errors: 384 if oheader.hidden and not oheader.errors:
385 return 385 return
386 # Get the code-behind #python 386 # Get the code-behind #python
387 if self._header.python is not None: 387 if self._header.python is None:
388 self._python_specified = False
389 else:
388 if not self._header.python.endswith(_PEXTEN): 390 if not self._header.python.endswith(_PEXTEN):
389 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) 391 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN))
390 self._python = self._header.python 392 self._python = self._header.python
393 self._python_specified = True
391 # Obtain a class object by importing and introspecting a module. 394 # Obtain a class object by importing and introspecting a module.
392 self._getclass() 395 self._getclass()
393 # Build body object (#template) 396 # Build body object (#template)
394 if self._header.template is not None: 397 if self._header.template is not None:
395 if not self._header.template.endswith(_TEXTEN): 398 if not self._header.template.endswith(_TEXTEN):
438 return 0 441 return 0
439 except OSError as e: 442 except OSError as e:
440 raise TinCanError(str(e)) from e 443 raise TinCanError(str(e)) from e
441 444
442 def _getclass(self): 445 def _getclass(self):
443 pypath = os.path.normpath(os.path.join(self._fsroot, *self._splitpath(self._python))) 446 try:
447 pypath = os.path.normpath(os.path.join(self._fsroot, *self._splitpath(self._python)))
448 except IndexError as e:
449 raise TinCanError("{0}: invalid #python".format(self._urlpath)) from e
444 klass = ErrorPage if self._header.errors else Page 450 klass = ErrorPage if self._header.errors else Page
445 # Give 'em a default code-behind if they don't furnish one 451 # Give 'em a default code-behind if they don't furnish one
446 pytime = self._gettime(pypath) 452 pytime = self._gettime(pypath)
447 if not pytime: 453 if not pytime:
454 if self._python_specified:
455 raise TinCanError("{0}: #python file not found".format(self._urlpath))
448 self._class = klass 456 self._class = klass
449 return 457 return
450 # Else load the code-behind from a .py file 458 # Else load the code-behind from a .py file
451 pycpath = pypath + 'c' 459 pycpath = pypath + 'c'
452 pyctime = self._gettime(pycpath) 460 pyctime = self._gettime(pycpath)
461 spec = importlib.util.spec_from_file_location(_mangle(self._name), pycpath) 469 spec = importlib.util.spec_from_file_location(_mangle(self._name), pycpath)
462 mod = importlib.util.module_from_spec(spec) 470 mod = importlib.util.module_from_spec(spec)
463 spec.loader.exec_module(mod) 471 spec.loader.exec_module(mod)
464 except Exception as e: 472 except Exception as e:
465 raise TinCanError("{0}: error importing".format(pycpath)) from e 473 raise TinCanError("{0}: error importing".format(pycpath)) from e
474 # Locate a suitable class
466 self._class = None 475 self._class = None
467 for i in dir(mod): 476 for i in dir(mod):
468 v = getattr(mod, i) 477 v = getattr(mod, i)
469 if isclass(v) and issubclass(v, klass): 478 if isclass(v) and issubclass(v, klass) and v is not klass:
470 if self._class is None: 479 if self._class is not None:
471 self._class = v 480 raise TinCanError("{0}: contains multiple {1} classes".format(pypath, klass.__name__))
472 else: 481 self._class = v
473 if self._class is klass:
474 self._class = v
475 elif v is not klass:
476 raise TinCanError("{0}: contains multiple {1} classes".format(
477 pypath, klass.__name__))
478 if self._class is None: 482 if self._class is None:
479 raise TinCanError("{0}: contains no {1} classes".format(pypath, klass.__name__)) 483 raise TinCanError("{0}: contains no {1} classes".format(pypath, klass.__name__))
480 484
481 def _redirect(self): 485 def _redirect(self):
482 try: 486 try: