routes.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import inspect
  2. import os.path
  3. from functools import wraps
  4. from typing import List
  5. import justpy as jp
  6. from starlette import requests, routing
  7. from starlette.responses import FileResponse
  8. from starlette.routing import BaseRoute, Mount, Route
  9. from starlette.staticfiles import StaticFiles
  10. from . import globals
  11. from .helpers import is_coroutine
  12. from .page import Page
  13. from .task_logger import create_task
  14. def add_route(self, route: BaseRoute) -> None:
  15. """
  16. :param route: starlette route including a path and a function to be called
  17. :return:
  18. """
  19. globals.app.routes.insert(0, route)
  20. def add_static_files(self, path: str, directory: str) -> None:
  21. """
  22. :param path: string that starts with a '/'
  23. :param directory: folder with static files to serve under the given path
  24. """
  25. add_route(None, Mount(path, app=StaticFiles(directory=directory)))
  26. def get(self, path: str):
  27. """
  28. Use as a decorator for a function like @ui.get('/another/route/{id}').
  29. :param path: string that starts with a '/'
  30. :return:
  31. """
  32. *_, converters = routing.compile_path(path)
  33. def decorator(func):
  34. @wraps(func)
  35. async def decorated(request: requests.Request):
  36. args = {name: converter.convert(request.path_params.get(name)) for name, converter in converters.items()}
  37. parameters = inspect.signature(func).parameters
  38. for key in parameters:
  39. if parameters[key].annotation.__name__ == 'bool':
  40. args[key] = bool(args[key])
  41. if parameters[key].annotation.__name__ == 'int':
  42. args[key] = int(args[key])
  43. elif parameters[key].annotation.__name__ == 'float':
  44. args[key] = float(args[key])
  45. elif parameters[key].annotation.__name__ == 'complex':
  46. args[key] = complex(args[key])
  47. if 'request' in parameters and 'request' not in args:
  48. args['request'] = request
  49. return await func(**args) if is_coroutine(func) else func(**args)
  50. self.add_route(routing.Route(path, decorated))
  51. return decorated
  52. return decorator
  53. def add_dependencies(py_filepath: str, dependencies: List[str] = []) -> None:
  54. vue_filepath = os.path.splitext(os.path.realpath(py_filepath))[0] + '.js'
  55. for dependency in dependencies:
  56. is_remote = dependency.startswith('http://') or dependency.startswith('https://')
  57. src = dependency if is_remote else f'lib/{dependency}'
  58. if src not in jp.component_file_list:
  59. jp.component_file_list += [src]
  60. if not is_remote:
  61. filepath = f'{os.path.dirname(vue_filepath)}/{src}'
  62. route = Route(f'/{src}', lambda _, filepath=filepath: FileResponse(filepath))
  63. jp.app.routes.insert(0, route)
  64. if vue_filepath not in jp.component_file_list:
  65. filename = os.path.basename(vue_filepath)
  66. jp.app.routes.insert(0, Route(f'/{filename}', lambda _: FileResponse(vue_filepath)))
  67. jp.component_file_list += [filename]