edina / nbexchange

External exchange for nbgrader
Other
6 stars 2 forks source link

Allow the get_current_user method to be async #173

Open ykazakov opened 1 month ago

ykazakov commented 1 month ago

According to README to use nbexchange, one has to implement a handler that provides the relevant user information

class MyUserHandler(BaseUserHandler):

    def get_current_user(self, request):
        return {
          "name": "username",
          "full_name": "Joe Bloggs",
          "course_id": "cool_course_id",
          "course_title": "cool course",
          "course_role": "Student",
          "org_id": 1,
          "cust_id": 2,
    }

In some situations, when requesting the user information, one might need to make an asynchronous request, e.g., to an external Authentifier, which might take some time. To avoid blocking the thread during this request, it would be nice to allow the function get_current_user to be asynchronous.

Technically, requesting the user in an asynchronous call in nbexchange.handlers.BaseHandler can be implemented using the function prepare() of torando's web.RequestHandler. See the documentation of the function current_user:

@property
    def current_user(self) -> Any:
        """The authenticated user for this request.

        This is set in one of two ways:

        * A subclass may override `get_current_user()`, which will be called
          automatically the first time ``self.current_user`` is accessed.
          `get_current_user()` will only be called once per request,
          and is cached for future access::

              def get_current_user(self):
                  user_cookie = self.get_secure_cookie("user")
                  if user_cookie:
                      return json.loads(user_cookie)
                  return None

        * It may be set as a normal variable, typically from an overridden
          `prepare()`::

              @gen.coroutine
              def prepare(self):
                  user_id_cookie = self.get_secure_cookie("user_id")
                  if user_id_cookie:
                      self.current_user = yield load_user(user_id_cookie)

        Note that `prepare()` may be a coroutine while `get_current_user()`
        may not, so the latter form is necessary if loading the user requires
        asynchronous operations.

        The user object may be any type of the application's choosing.
        """