-
Notifications
You must be signed in to change notification settings - Fork 407
Add support for GPG keys #886
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
Conversation
src/github3/users.py
Outdated
if 'primary' in email: | ||
self.primary = email['primary'] | ||
if 'visibility' in email: | ||
self.visibility = email['visibility'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because the emails returned by the GPG key API only have the email
and the verified
attributes (see: https://developer.github.com/v3/users/gpg_keys/#response-2). An alternative implementation is to split the Email
model into ShortEmail
and Email
; I chose the above solution for simplicity, but I'm open to suggestions!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather consistency over simplicity. That means creating ShortEmail and Email classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing! Will fix.
src/github3/users.py
Outdated
self.emails = [Email(email, self) for email in key['emails']] | ||
try: | ||
self.expires_at = self._strptime(key['expires_at']) | ||
except ValueError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not valid in practice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What can happen here is that key['expires_at']
might be null
, for example https://developer.github.com/v3/users/gpg_keys/#response-2. I'm not sure what github3.py
usually does when a value might be null
, I originally had self.expires_at = None
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that _strptime()
handled that case automagically. If not, it should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, you are right! See:
github3.py/src/github3/models.py
Lines 87 to 100 in aba7b23
@classmethod | |
def _strptime(cls, time_str): | |
"""Convert an ISO 8601 formatted string to a datetime object. | |
We assume that the ISO 8601 formatted string is in UTC and we create | |
the datetime object so that it is timezone-aware. | |
:param str time_str: ISO 8601 formatted string | |
:returns: timezone-aware datetime object | |
:rtype: datetime or None | |
""" | |
if time_str: | |
return dateutil.parser.parse(time_str) | |
return None |
I was probably thinking of datetime
's strptime
. Will fix this too!
src/github3/users.py
Outdated
if 'primary' in email: | ||
self.primary = email['primary'] | ||
if 'visibility' in email: | ||
self.visibility = email['visibility'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather consistency over simplicity. That means creating ShortEmail and Email classes.
f46b683
to
9f08f95
Compare
@sigmavirus24 I did what you asked in the review and I filled in the missing documentation TODOs. I think this is ready for another round of review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple small changes to the tests. I really need to write better/updated documentation for the testing in this library
self.token_login() | ||
cassette_name = self.cassette_name('create_gpg_key') | ||
with self.recorder.use_cassette(cassette_name): | ||
gpg_key = self.gh.create_gpg_key(GPG_KEY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the past we've been a bit sloppy. Going forward I've started writing tests like so:
def test_create...(self):
self.token_login()
cassette_name = self.cassette_name('...')
with self.recorder.use_cassette(...):
gpg_key = self.gh.create_gpg_key(GPG_KEY)
assert isinstance(gpg_key, github3.users.GPGKey)
assert gpg_key.delete()
This way there's no out-of-band, manual clean-up required. Then for the
def test_gpg_key(self):
...
with ...:
created_gpg_key = self.gh.create_gpg_key(...)
assert isinstance(created_gpg_key, ...)
retrieved_gpg_key = self.gh.gpg_key(created_gpg_key.id)
assert isinstance(retrieved_gpg_key, ...)
assert created_gpg_key == retrieved_gpg_key
assert created_gpg_key.delete(0
And the same goes for the last method too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh, I like the idea of having each test responsible for its cleanup. Another benefit is that anybody can re-record the cassette if need arises. Done!
@@ -73,6 +88,17 @@ def test_following(self): | |||
for person in following: | |||
assert isinstance(person, github3.users.ShortUser) | |||
|
|||
def test_gpg_keys(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err... I can't decipher this comment! Do you want me to delete this test? It makes sense to me: it shows that an unauthenticated user can retrieve another user and ask for their GPG keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, sorry. I was clapping as in "this is great"
tests/integration/test_users.py
Outdated
self.token_login() | ||
cassette_name = self.cassette_name('delete') | ||
with self.recorder.use_cassette(cassette_name): | ||
gpg_key = self.gh.gpg_key(407238) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this test can look a lot like the create_gpg_key
test. It's good to have it anyway.
9f08f95
to
5bd94db
Compare
Implement the CRUD API for users' GPG keys described in https://developer.github.com/v3/users/gpg_keys/. Closes sigmavirus24#704
5bd94db
to
6506039
Compare
Implement the CRUD API for users' GPG keys described
in https://developer.github.com/v3/users/gpg_keys/.
Closes #704