def create_role(db, role_dict, *, commit=True, reset_to_defaults=True):
    """Adds a new role to database or modifies an existing one

    Raises ScopeNotFound if one of the scopes defined for the role does not exist.
    Raises KeyError when the 'name' key is missing.
    Raises RoleValueError when attempting to override the `admin` role.
    Raises InvalidRoleNameError if role name is invalid.

    Returns the role object.
    """
    default_roles = get_default_roles()

    if 'name' not in role_dict.keys():
        raise KeyError('Role definition must have a name')
    else:
        name = role_dict['name']
        _validate_role_name(name)
        role = orm.Role.find(db, name)

    description = role_dict.get('description')
    scopes = role_dict.get('scopes')

    if name == "admin":
        for _role in get_default_roles():
            if _role["name"] == "admin":
                admin_spec = _role
                break
        for key in ["description", "scopes"]:
            if key in role_dict and role_dict[key] != admin_spec[key]:
                raise RoleValueError(
                    f"Cannot override admin role admin.{key} = {role_dict[key]}"
                )

    # check if the provided scopes exist
    if scopes:
        # avoid circular import
        from .scopes import _check_scopes_exist

        _check_scopes_exist(scopes, who_for=f"role {role_dict['name']}")
    else:
        app_log.warning('Role %s will have no scopes', name)

    if role is None:
        managed_by_auth = role_dict.get('managed_by_auth', False)
        role = orm.Role(
            name=name,
            description=description,
            scopes=scopes,
            managed_by_auth=managed_by_auth,
        )
        db.add(role)
        if role_dict not in default_roles:
            app_log.info('Role %s added to database', name)
    else:
        for attr in ["description", "scopes"]:
            default_value = getattr(orm.Role, attr).default
            if default_value:
                default_value = default_value.arg

            new_value = role_dict.get(attr, default_value)
            old_value = getattr(role, attr)
            if new_value != old_value and (
                reset_to_defaults or new_value != default_value
            ):
                setattr(role, attr, new_value)
                app_log.info(
                    f'Role attribute {role.name}.{attr} has been changed',
                )
                app_log.debug(
                    f'Role attribute {role.name}.{attr} changed from %r to %r',
                    old_value,
                    new_value,
                )
    if commit:
        db.commit()
    return role
Adds a new role to database or modifies an existing one

Raises ScopeNotFound if one of the scopes defined for the role does not exist.
Raises KeyError when the 'name' key is missing.
Raises RoleValueError when attempting to override the `admin` role.
Raises InvalidRoleNameError if role name is invalid.

Returns the role object.
jupyterhub/roles.py