docs: def grant_role(self, principal, role, obj=None):
          """Grant `role` to `user` (either globally, if `obj` is None, or on the
          specific `obj`)."""
          assert principal
          principal = unwrap(principal)
          session = object_session(obj) if obj is not None else db.session
          manager = self._current_user_manager(session=session)
          args = {
              "role": role,
              "object": obj,
              "anonymous": False,
              "user": None,
              "group": None,
          }
  
          if principal is AnonymousRole or (
              hasattr(principal, "is_anonymous") and principal.is_anonymous
          ):
              args["anonymous"] = True
          elif isinstance(principal, User):
              args["user"] = principal
          else:
              args["group"] = principal
  
          query = session.query(RoleAssignment)
          if query.filter_by(**args).limit(1).count():
              # role already granted, nothing to do
              return
  
          # same as above but in current, not yet flushed objects in session. We
          # cannot call flush() in grant_role() since this method may be called a
          # great number of times in the same transaction, and sqlalchemy limits
          # to 100 flushes before triggering a warning
          for ra in (
              o
              for models in (session.new, session.dirty)
              for o in models
              if isinstance(o, RoleAssignment)
          ):
              if all(getattr(ra, attr) == val for attr, val in args.items()):
                  return
  
          ra = RoleAssignment(**args)
          session.add(ra)
          audit = SecurityAudit(manager=manager, op=SecurityAudit.GRANT, **args)
          if obj is not None:
              audit.object_id = obj.id
              audit.object_type = obj.entity_type
              object_name = ""
              for attr_name in ("name", "path", "__path_before_delete"):
                  if hasattr(obj, attr_name):
                      object_name = getattr(obj, attr_name)
              audit.object_name = object_name
  
          session.add(audit)
          self._needs_flush()
  
          if hasattr(principal, "__roles_cache__"):
              del principal.__roles_cache__