Repository: aiven/aiven-client

Issue:
Error quote_from_bytes()
# What happened?
Ran the following command and got the error
`avn --auth-token $AIVEN_USER_TOKEN service list`
```
Traceback (most recent call last):
  File "/usr/bin/avn", line 8, in <module>
    sys.exit(main())
  File "/usr/lib/python3.8/site-packages/aiven/client/__main__.py", line 5, in main
    AivenCLI().main()
  File "/usr/lib/python3.8/site-packages/aiven/client/argx.py", line 309, in main
    sys.exit(self.run(args))
  File "/usr/lib/python3.8/site-packages/aiven/client/argx.py", line 281, in run
    return self.run_actual(args)
  File "/usr/lib/python3.8/site-packages/aiven/client/argx.py", line 303, in run_actual
    return func()  # pylint: disable=not-callable
  File "/usr/lib/python3.8/site-packages/aiven/client/cli.py", line 966, in service__list
    services = self.client.get_services(project=self.get_project())
  File "/usr/lib/python3.8/site-packages/aiven/client/client.py", line 1546, in get_services
    self.build_path("project", project, "service"),
  File "/usr/lib/python3.8/site-packages/aiven/client/client.py", line 168, in build_path
    return "/" + "/".join(quote(part, safe="") for part in parts)
  File "/usr/lib/python3.8/site-packages/aiven/client/client.py", line 168, in <genexpr>
    return "/" + "/".join(quote(part, safe="") for part in parts)
  File "/usr/lib/python3.8/urllib/parse.py", line 851, in quote
    return quote_from_bytes(string, safe)
  File "/usr/lib/python3.8/urllib/parse.py", line 876, in quote_from_bytes
    raise TypeError("quote_from_bytes() expected bytes")
TypeError: quote_from_bytes() expected bytes
```

# What did you expect to happen?
A list of all services

# What else do we need to know?
This command fails on the following system: Alpine Linux 3.12.0
```
> python3 --version
Python 3.8.10
> avn --version
aiven-client 2.14.4
```

This command run successfully on Ubuntu 20.04, with same version of python3 and aiven-client



Discussion:

Patch:
diff --git a/aiven/client/cli.py b/aiven/client/cli.py
index 68bc4eb..2c0cb84 100644
--- a/aiven/client/cli.py
+++ b/aiven/client/cli.py
@@ -298,7 +298,12 @@ class AivenCLI(argx.CommandLineTool):
         """Return project given as cmdline argument or the default project from config file"""
         if getattr(self.args, "project", None) and self.args.project:
             return self.args.project
-        return self.config.get("default_project")
+        default_project = self.config.get("default_project")
+        if not default_project:
+            raise argx.UserError(
+                "Specify project: use --project in the command line or the default_project item in the config file."
+            )
+        return default_project
 
     @no_auth
     @arg("pattern", nargs="*", help="command search pattern")


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 42edfea..9fa2229 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -7,6 +7,7 @@
 from aiven.client import AivenClient
 from aiven.client.cli import AivenCLI, ClientFactory, EOL_ADVANCE_WARNING_TIME
 from collections import namedtuple
+from contextlib import contextmanager
 from datetime import datetime, timedelta, timezone
 from unittest import mock
 
@@ -295,3 +296,25 @@ def test_update_service_connection_pool():
     aiven_client.update_service_connection_pool.assert_called_with(
         project="testproject", service="pg-foo-bar", pool_name="foo", dbname=None, pool_size=42, pool_mode=None
     )
+
+
+@contextmanager
+def mock_config(return_value):
+    with mock.patch("aiven.client.argx.Config", side_effect=lambda _: return_value):
+        yield
+
+
+def test_get_project(caplog):
+    # https://github.com/aiven/aiven-client/issues/246
+    aiven_client = mock.Mock(spec_set=AivenClient)
+    aiven_client.get_services.side_effect = lambda project: []
+    args = ["service", "list"]
+    with mock_config({}):
+        assert build_aiven_cli(aiven_client).run(args=args) == 1
+    assert "specify project" in caplog.text.lower()
+    caplog.clear()
+    assert build_aiven_cli(aiven_client).run(args=args + ["--project", "project_0"]) is None
+    assert not caplog.text
+    with mock_config({"default_project": "project_1"}):
+        assert build_aiven_cli(aiven_client).run(args=args) is None
+    assert not caplog.text