Merge ~hook25/ppa-dev-tools:support_env_credentials into ppa-dev-tools:main

Proposed by Massimiliano Girardi
Status: Merged
Merge reported by: Bryce Harrington
Merged at revision: fcde0dbd81222edc71971caefa268e54ed7a5d60
Proposed branch: ~hook25/ppa-dev-tools:support_env_credentials
Merge into: ppa-dev-tools:main
Diff against target: 117 lines (+65/-5)
2 files modified
ppa/lp.py (+37/-4)
tests/test_lp.py (+28/-1)
Reviewer Review Type Date Requested Status
Bryce Harrington Approve
Canonical Server Reporter Pending
Review via email: mp+443897@code.launchpad.net

This proposal supersedes a proposal from 2023-05-29.

Commit message

Add LP_CREDENTIALS login and documentation

Description of the change

This adds the possibility of running ppa-dev-tools automatically by making it retrieve the credentials for Launchpad from the LP_CREDENTIALS environment variable and documents another way to do so via the LP_CREDENTIALS_FILE environment variable

To post a comment you must log in.
Revision history for this message
Bryce Harrington (bryce) wrote : Posted in a previous version of this proposal

Good addition, a few nitpicks below but otherwise LGTM.

Other than a couple minor flake8 issues, `make check` passes.

Would you be willing to write a test case to go along with this? We have test_new_connection() that verifies that the login_with() call gets invoked, what we'd need is something analogous to that but that sets the environment variable and verifies that Credentials.from_string() gets invoked instead, and login_with() does NOT get invoked.

review: Needs Fixing
Revision history for this message
Massimiliano Girardi (hook25) wrote : Posted in a previous version of this proposal

I have a few replies to your comments and implemented your feedback where it was necessary (namely, new test, fixed naming of internal functions, removed useless proxy to (envvar or login))

Revision history for this message
Bryce Harrington (bryce) wrote :

Looks great, I've squashed the updates and pushed to the main branch:

To git+ssh://git.launchpad.net/ppa-dev-tools
   9dbaa6f..33b9284 main -> main

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/ppa/lp.py b/ppa/lp.py
2index 17b225a..9149810 100644
3--- a/ppa/lp.py
4+++ b/ppa/lp.py
5@@ -14,11 +14,12 @@
6 High level wrapper object for Launchpad's API
7 """
8
9+import os
10 from contextlib import suppress
11 from functools import lru_cache
12
13 from launchpadlib.launchpad import Launchpad
14-
15+from launchpadlib.credentials import Credentials
16
17 class Lp:
18 """
19@@ -52,8 +53,38 @@ class Lp:
20 else:
21 self._service_root = 'production'
22
23- def _get_instance(self):
24- """Authenticate to Launchpad."""
25+ def _get_instance_from_env(self) -> 'launchpadlib.launchpad.Launchpad | None':
26+ """
27+ Get an instance of _service using `$LP_CREDENTIAL` environment
28+ variable if it is defined, else return None
29+
30+ :rtype: launchpadlib.launchpad.Launchpad | None
31+ :returns: Logged in Launchpad instance if LP_CREDENTIALS is in env
32+ else None
33+ """
34+ lp_cred = os.getenv("LP_CREDENTIALS")
35+ if lp_cred:
36+ cred = Credentials.from_string(lp_cred)
37+ return self._service(
38+ cred, None, None,
39+ service_root=self._service_root,
40+ version='devel'
41+ )
42+ return None
43+
44+ def _get_instance_from_login(self) -> 'launchpadlib.launchpad.Launchpad':
45+ """
46+ Prompts the user to authorize the login of a new credential
47+ or use the cached one if it is available and valid
48+
49+ Note: This uses Launchpad.login_with if service is left to
50+ default, if you have a credential file you can provide
51+ the path to it via the LP_CREDENTIALS_FILE environment
52+ variable and this call will pass without prompting the
53+ user
54+ :rtype: launchpadlib.launchpad.Launchpad
55+ :returns: Logged in Launchpad instance
56+ """
57 return self._service.login_with(
58 application_name=self._app_name,
59 service_root=self._service_root,
60@@ -65,7 +96,9 @@ class Lp:
61 def _instance(self):
62 """Cache LP object."""
63 if not self._real_instance:
64- self._real_instance = self._get_instance()
65+ self._real_instance = (
66+ self._get_instance_from_env() or self._get_instance_from_login()
67+ )
68 return self._real_instance
69
70 @property
71diff --git a/tests/test_lp.py b/tests/test_lp.py
72index 4fe1410..a0c1546 100644
73--- a/tests/test_lp.py
74+++ b/tests/test_lp.py
75@@ -34,7 +34,7 @@ import os.path
76
77 import logging
78 import pytest
79-from mock import Mock
80+from mock import Mock, patch
81 from launchpadlib.launchpad import Launchpad
82
83 sys.path.insert(0, os.path.realpath(
84@@ -75,6 +75,33 @@ def test_new_connection():
85 version='devel',
86 )
87
88+@patch.dict(os.environ, {"LP_CREDENTIALS" : "..."})
89+@patch("ppa.lp.Credentials")
90+def test_new_connection_envvar(credentials_mock):
91+ """Verifies the Lp object will login via envvar if provided."""
92+ mock_launchpad = Mock(spec=Launchpad)
93+
94+ lp = Lp(
95+ application_name=APPNAME,
96+ service=mock_launchpad)
97+
98+ # Cause the lp._instance() internal property to be triggered
99+ logging.debug(lp.me)
100+ logging.debug(lp.people)
101+
102+ # Verify the triggering did not result in a login attempt
103+ mock_launchpad.login_with.assert_not_called()
104+ # Rather that the login was done via envvar
105+ # creating the credentials
106+ credentials_mock.from_string.assert_called_once()
107+ # and the service was created with the given credentials
108+ mock_launchpad.assert_called_once_with(
109+ credentials_mock.from_string.return_value,
110+ None,
111+ None,
112+ service_root="production",
113+ version="devel"
114+ )
115
116 def test_api_root(fakelp):
117 """Ensures we can get LP's API root."""

Subscribers

People subscribed via source and target branches