Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add login command #3600

Merged
merged 20 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ These are the section headers that we use:
### Added

- Added `login` function in `argilla.client.login` to login into an Argilla server and store the credentials locally ([#3582](https://github.com/argilla-io/argilla/pull/3582)).
- Added `login` command to login into an Argilla server ([#3600](https://github.com/argilla-io/argilla/pull/3600)).

### Changed

Expand Down
7 changes: 4 additions & 3 deletions src/argilla/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
# limitations under the License.


from argilla.tasks import database_app, server_app, training_app, users_app
from argilla.tasks import database_app, login_app, server_app, training_app, users_app
from argilla.tasks.async_typer import AsyncTyper

app = AsyncTyper(rich_help_panel=True, help="Argilla CLI", no_args_is_help=True)

app.add_typer(users_app, name="users")
app.add_typer(database_app, name="database")
app.add_typer(training_app, name="train")
app.add_typer(login_app, name="login")
app.add_typer(server_app, name="server")
app.add_typer(training_app, name="train")
app.add_typer(users_app, name="users")

if __name__ == "__main__":
app()
4 changes: 4 additions & 0 deletions src/argilla/client/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def remove(cls) -> None:

ARGILLA_CREDENTIALS_FILE.unlink()

@classmethod
def exists(cls) -> bool:
return ARGILLA_CREDENTIALS_FILE.exists()


def login(
api_url: str, api_key: str, workspace: Optional[str] = None, extra_headers: Optional[Dict[str, str]] = None
Expand Down
1 change: 1 addition & 0 deletions src/argilla/tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from .database import app as database_app
from .login import app as login_app
from .server import app as server_app
from .training import app as training_app
from .users import app as users_app
26 changes: 26 additions & 0 deletions src/argilla/tasks/callback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2021-present, the Recognai S.L. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import typer

from argilla.client.api import init
from argilla.client.login import ArgillaCredentials


def init_callback() -> None:
if not ArgillaCredentials.exists():
typer.echo("You are not logged in. Please run `argilla login` to login to an Argilla server.")
raise typer.Exit(code=1)

init()
18 changes: 18 additions & 0 deletions src/argilla/tasks/login/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2021-present, the Recognai S.L. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .__main__ import app

if __name__ == "__main__":
app()
55 changes: 55 additions & 0 deletions src/argilla/tasks/login/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2021-present, the Recognai S.L. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Optional

import typer

app = typer.Typer(invoke_without_command=True)


@app.callback(help="Login to an Argilla server")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@app.callback(help="Login to an Argilla server")
@app.callback(help="Login into an Argilla Server")

def login(
api_url: str = typer.Option(..., help="The URL of the Argilla server to login"),
api_key: str = typer.Option(..., help="The API key to use to login to the Argilla server", prompt="API Key"),
workspace: Optional[str] = typer.Option(
None, help="The default workspace over which the operations will be performed"
),
extra_headers: Optional[str] = typer.Option(
None, help="A JSON string with extra headers to be sent in the requests to the Argilla server"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
api_url: str = typer.Option(..., help="The URL of the Argilla server to login"),
api_key: str = typer.Option(..., help="The API key to use to login to the Argilla server", prompt="API Key"),
workspace: Optional[str] = typer.Option(
None, help="The default workspace over which the operations will be performed"
),
extra_headers: Optional[str] = typer.Option(
None, help="A JSON string with extra headers to be sent in the requests to the Argilla server"
api_url: str = typer.Option(..., help="The URL of the Argilla Server to login into"),
api_key: str = typer.Option(..., help="The API key to use to login into the Argilla Server", prompt="API Key"),
workspace: Optional[str] = typer.Option(
None, help="The default workspace over which the operations will be performed"
),
extra_headers: Optional[str] = typer.Option(
None, help="A JSON string with extra headers to be sent in the requests to the Argilla Server"

),
):
import json

from argilla.client.login import login as login_func

try:
if extra_headers:
headers = json.loads(extra_headers)
else:
headers = {}
login_func(api_url=api_url, api_key=api_key, workspace=workspace, extra_headers=headers)
typer.echo(f"Logged in successfully to '{api_url}' Argilla server!")
except json.JSONDecodeError as e:
typer.echo("The provided extra headers are not a valid JSON string.")
raise typer.Exit(code=1) from e
except ValueError as e:
typer.echo(
f"Could not login to the '{api_url}' Argilla server. Please check the provided credentials and try again."
)
raise typer.Exit(code=1) from e


if __name__ == "__main__":
app()
13 changes: 13 additions & 0 deletions tests/unit/tasks/login/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2021-present, the Recognai S.L. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
64 changes: 64 additions & 0 deletions tests/unit/tasks/login/test_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2021-present, the Recognai S.L. team.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from click.testing import CliRunner
from pytest_mock import MockerFixture
from typer import Typer


def test_login(cli_runner: "CliRunner", cli: "Typer", mocker: "MockerFixture"):
login_mock = mocker.patch("argilla.client.login.login")

result = cli_runner.invoke(
cli,
'login --api-url http://localhost:6900 --api-key api.key --workspace my_workspace --extra-headers "{\\"X-Unit-Test\\": \\"true\\"}"',
)

assert result.exit_code == 0
login_mock.assert_called_once_with(
api_url="http://localhost:6900",
api_key="api.key",
workspace="my_workspace",
extra_headers={"X-Unit-Test": "true"},
)


def test_login_fails(cli_runner: "CliRunner", cli: "Typer", mocker: "MockerFixture"):
login_mock = mocker.patch("argilla.client.login.login")
login_mock.side_effect = ValueError

result = cli_runner.invoke(
cli,
'login --api-url http://localhost:6900 --api-key api.key --workspace my_workspace --extra-headers "{\\"X-Unit-Test\\": \\"true\\"}"',
)

assert result.exit_code == 1
login_mock.assert_called_once_with(
api_url="http://localhost:6900",
api_key="api.key",
workspace="my_workspace",
extra_headers={"X-Unit-Test": "true"},
)


def test_login_with_invalid_extra_headers(cli_runner: "CliRunner", cli: "Typer"):
result = cli_runner.invoke(
cli,
'login --api-url http://localhost:6900 --api-key api.key --workspace my_workspace --extra-headers "{\\"X-Unit-Test\\": \\"true\\""',
)

assert result.exit_code == 1
Loading