-
Notifications
You must be signed in to change notification settings - Fork 407
[RFC] [Meta] Design Reconsiderations #670
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
Comments
I'm going to interpret no response as tacit approval. =P |
After a couple weeks of users trying this out, it has revealed itself to be far too confusing. Users do not enjoy it and it makes them scratch their heads. This still needs to also be excised from our testsuite. Related-to gh-670
Now that we've excised the usage of Empty and its presence in github3.py, let's remove all references to it in our testsuite. Related-to #670
Now that we've removed Empty from our code and tests, remove it from our documentation. Related-to gh-670
As part of on going work to improve the overall design and simplicity, we're removing the NullObject and it's usage. At the present time, it causes a lot of confusion for users (not as much as Empty, but still up there) and there are simpler ways of designing the library than this. Related-to #670
Apologies for the lack of response.
Here's the only thing I want to add. Don't hide Github's API responses. They annoy me because if you search for a repo that does exist, but you're not logged in, they return a 404 rather than a 401/403. I think this is some security by obscurity and a script kidding will stop with a 404, but a 401/403 says try harder. /tangent /rant Anyways, I want to know that Github returned that 404, so don't obscure/obfuscate their response. My $.03 cents. |
So let's approach this with you being a user. What would you want back to not hide/obscure it? An exception? A different object that's a little like a nullobject? You can still rely on it being falsey, but it has extra data and doesn't pretend to have every method imaginable? I'm not sure what the right thing is here that won't obscure important information and won't confuse users. |
My stupid is gonna shine through, please allow some ignorant pseudo code Scenario:
I'd expect something like
And it pukes Don't hide that response. |
if I may add $0.02 to @esacteksab to make it $0.05: it'd be nice to offer the option of raising an error if the call does not succeed and we've passed some sort of |
@guyzmo so presently all exceptions contain the response (or if there is no response, the original exception). Here's my only counter-point to @esacteksab's thoughts: The only time we return I'm also firmly of the opinion that libraries like this should choose one design and should not be configurable to change fundamental behaviour. We can absolutely default to raising 404s as exceptions if people would find that useful, but I still fail to see the value in forcing users to litter their code with try:
gh.<something>(...)
except github3.exceptions.NotFound:
# handle it, or not |
Your library is by far the most comfortable one to use to control a git service (compared to gitlab's ones or bitbucket's ones), so whatever you decide, I'll be happy using it ☺ That said:
it depends on the use case, the real advantage with exceptions is to let them flow and catch them up in the stack:
there you don't need to clutter your code with neither:
you could even safely write:
and get a proper error instead of an |
True. All that said, it's rather easy to flip this switch right now. I'm hacking on splitting up the User object right now. I think I'll tack this onto the list. This will simplify our internals too. If doing url = self._build_url(...)
params = {...}
response = self._request(VERB, url, params=params)
json = self._json(response, SUCCESS_STATUS_CODE)
return Object(json, self) |
Generally speaking, I think the most pythonic strategy with errors is 'fail early, fail loudly' (explicit etc.). I'd say, though, it'd be nice to have in the exception in information about the github object that was queried and triggered the exception. In my example earlier, it'd be great to print an error with |
As in the object/object type and method where the exception came from? Yeah no. That will never happen. A few reasons why:
|
well, of course to ①, ② and ③! I was just thinking of something like a non-instanciated An alternative would be to have in the exception message the name of the thing we tried to get, but failed to. But I guess, we can also get that by looking at the |
Not much comment, only a side note: Sorry for lagging so hard—a lot of free time chewed up in the last couple weeks. |
Nah. I don't like that at all. I know I'll get bug reports in that case because people will have a repo object but no data. |
So far we have two Repository objects (ShortRepository, Repository). In looking at API responses, it seems GitHub has removed many Repository attributes altogether. I'll need to investigate this further. Related-to: gh-670
I'm quite late to the party, and quite underinformed overall, just a new user :), so take the following with a grain of salt, but: To me a few things are pretty clear: As a user, I'd definitely prefer an exception over both a null object and an error object like The null object pattern is fantastic, but I think it's the wrong thing here. This is pretty standard Python seemingly -- an error condition has happened, and as a user I want immediate notification that something has gone wrong. A data-less Repository object (or any other) just delays that inevitable fact, and None, or any other status response, just isn't rich enough. As a specific example, the current app I'm porting over wants to know what scopes the token that is being used was configured with, with the knowledge that the data we need requires the Having the option to make As for splitting objects up: that also seems to make sense to me, but I'm skeptical about how much that seems to be being exposed to end-users of the library. I'd have to look closer, but my intuition tells me that the fact that GitHub returns partial objects is something that can be much more hidden from end users -- e.g. you have some I think this probably shouldn't require individual implementations for each model object, it'd just require each model to be a bit more transparent about the data it needs to be whole, but I could be wrong. On the other hand, I've already been bitten by Would love to discuss any of this further, especially if I'm way off here. |
So, every exception includes a response attribute. That should allow you to introspect that once we start raising exceptions instead of returning None.
That's concerning. We've tried to make that completely transparent sans adding to every method that it does/does not make a network request. Can you demonstrate the code (in a separate issue) that surprised you? |
This creates 3 new objects, one for iterations (Short), one for direct GETs (Organization), and one for events (EventOrganization). Most attributes can now be directly assigned, aside from a few that are only returned in the API if they are set on the server. Default those to None. Many test cassettes needed to be updated to pick up attributes added to orgs since the cassette was recorded (yay for additive REST APIs...). Introduce a 'auto_login' method to handle either token auth or username/password auth. Use it in the places I needed to update the cassette. Fix a few POST calls that were sending malformed (according to GitHub API) json data. Specifically an empty permission is not accepted. Related-to: sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 new objects, one for iterations (Short), one for direct GETs (Organization), and one for events (EventOrganization). Most attributes can now be directly assigned, aside from a few that are only returned in the API if they are set on the server. Default those to None. Many test cassettes needed to be updated to pick up attributes added to orgs since the cassette was recorded (yay for additive REST APIs...). Introduce a 'auto_login' method to handle either token auth or username/password auth. Use it in the places I needed to update the cassette. Fix a few POST calls that were sending malformed (according to GitHub API) json data. Specifically an empty permission is not accepted. Related-to: sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 new objects, one for iterations (Short), one for direct GETs (Organization), and one for events (EventOrganization). Most attributes can now be directly assigned, aside from a few that are only returned in the API if they are set on the server. Default those to None. Many test cassettes needed to be updated to pick up attributes added to orgs since the cassette was recorded (yay for additive REST APIs...). Introduce a 'auto_login' method to handle either token auth or username/password auth. Use it in the places I needed to update the cassette. Fix a few POST calls that were sending malformed (according to GitHub API) json data. Specifically an empty permission is not accepted. Related-to: sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
Clean them up as done in sigmavirus24#680 for users.py Related-to: sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 new objects, one for iterations (Short), one for direct GETs (Organization), and one for events (EventOrganization). Most attributes can now be directly assigned, aside from a few that are only returned in the API if they are set on the server. Default those to None. Many test cassettes needed to be updated to pick up attributes added to orgs since the cassette was recorded (yay for additive REST APIs...). Introduce a 'auto_login' method to handle either token auth or username/password auth. Use it in the places I needed to update the cassette. Fix a few POST calls that were sending malformed (according to GitHub API) json data. Specifically an empty permission is not accepted. Related-to: sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
Related-to sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 classes; one for iterations (Short), one for direct GETs (PullRequest), and one for events (EventPullRequest). Most attributes are directly assigned, except where otherwise commented. A couple cassettes needed to be updated, and the sample json for pull_request needed to be updated as well. Updating that one required updating the tests for the now current data. Related-to sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 classes; one for iterations (Short), one for direct GETs (PullRequest), and one for events (EventPullRequest). Most attributes are directly assigned, except where otherwise commented. A couple cassettes needed to be updated, and the sample json for pull_request needed to be updated as well. Updating that one required updating the tests for the now current data. Related-to sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 classes; one for iterations (Short), one for direct GETs (Issue), and one for events (EventIssue). Most attributes are directly assigned, except where otherwise commented. Some cassettes needed to be updated for the relatively new 'assignees' attribute that now comes back. A sample json needed to be updated as well. Cassettes that needed updated that were associated with authenticated calls got the calls updated to use auto_login as well. The search tests needed to be updated as well, label name changed and syntax changed slightly. Related-to sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
This creates 3 classes; one for iterations (Short), one for direct GETs (Issue), and one for events (EventIssue). Most attributes are directly assigned, except where otherwise commented. Some cassettes needed to be updated for the relatively new 'assignees' attribute that now comes back. A sample json needed to be updated as well. Cassettes that needed updated that were associated with authenticated calls got the calls updated to use auto_login as well. Some cassettes will break between older requests and newer, so tag those tests accordingly. Allow the tests to work in older requests land for now. The search tests needed to be updated as well, label name changed and syntax changed slightly. Related-to sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
The Gist and GistFile objects have both short and regular representations. Further, we needed a separate representation for Gist forks that are part of the Gist representation. Refs gh-670
The Gist and GistFile objects have both short and regular representations. Further, we needed a separate representation for Gist forks that are part of the Gist representation. Refs gh-670
This started as an effort to break up Release classes for "Short" versions. However I discovered that there is no difference in data when iterating over a Release or an Asset vs a direct GET of them. I still cleaned up the classes to be more in line with other efforts. Remove custom header as Releases are out of prerelease. Update the example data sets with real release data from our own repository. Account for upload url differences from real data. Related: sigmavirus24#670 Signed-off-by: Jesse Keating <jkeating@j2solutions.net>
So @sigmavirus24 , where do we stand on this? I haven't gone digging to see if there is more to revamp, but is there an overall test or something we can use to see if our coverage is right? What else needs to be done to consider this ready, and to consider a release? |
Sorry @omgjlk there's a lot to parse in this thread. I added two more items that I think help summarize what I understand to be left. I'm kind of covering the last item in my WIP Pr. |
Thanks. We should probably add the open PR (I'll be fixing it this weekend) to make session non-optional. |
Also raise exceptions on any 400 or greater status code as discussed in gh-670 As it stands today, it's hard for people to determine whether None is returned because that's the API's response or because None is what we return in the event of a 404 or some other "error"-like status code. This change undoes that ambiguity and allows people to handle missing or permission-related problems using exceptions.
This adds logic so that Short* classes can return the fuller representation. For example: >>> import github3 >>> users = [(u, u.refresh()) for u in github3.all_users(10)] >>> for short_user, user in users: ... assert isinstance(short_user, github3.users.ShortUser) ... assert isinstance(user, github3.users.User) Will run without exceptions. Related to gh-670
…ion). See sigmavirus24#730 for some background information. This still fails 1 test because the cassette Repository_collaborators.json needs to be updated with the permissions as it somehow does not contain that information. The GitHub API does say that it should be there, but the recording is from 2014 so perhaps it wasn't returned back then. Note that I have currently put the named parameter `affiliation` at the front of the arguments in `Repository.collaborators`. I did this to be in line with the other similar iterators. This can be moved to the back, but I assume some backwards-incompatible changes are fine for 1.0.0? This uses separate classes for the specific User object uses as I understood the idea was from sigmavirus24#670.
…ion). See sigmavirus24#730 for some background information. Note that I have currently put the named parameter `affiliation` at the front of the arguments in `Repository.collaborators`. I did this to be in line with the other similar iterators. This can be moved to the back, but I assume some backwards-incompatible changes are fine for 1.0.0? This uses separate classes for the specific User object uses as I understood the idea was from sigmavirus24#670.
Uh oh!
There was an error while loading. Please reload this page.
So I'm beginning to have some doubts about the current direction of github3.py's design, specifically around:
It seems far too many people want to deal with something that's not a "NullObject" or want to treat it like None. The way it's currently implemented, it actually does behave like None. You can still do
And see what you expect
That said, people seem to prefer
None
toNullObject
and that's fine, but, I'm wondering how appropriateNone
is. We do return it carefully from methods. In the example above, the request fornon-existent-org
will return a 404. That is the only time we returnNullObject
(orNone
). With that in mind, I think using None there is valid.That brings us to using
None
for attribute values parsed from the API. Previously, we were usingNone
for attributes returned asnull
as well as attributes that didn't exist in the response. Why did we do this? Because I believed (and still do believe) that having an attribute is better than having anAttributeError
, especially when on one instance of an object you get a value and on another you might get anAttributeError
.This + a false idea of
DRY
led me to think that having something likeEmpty
would be the best option for users. I'm now convinced that I was wrong.Instead, I think we should approach this differently. For some resources there are two different (although intersecting) representations. For example, for users there is a short user listing and a long one. The short listing is returned when you list all users, e.g.,
gh.all_users()
. The long one is returned when you request a specific one, e.g.,gh.user('itsmemattchung')
.Thus, I'm proposing a new way of dealing with this:
Let's create separate classes (that don't inherit from each other) for resources that have these separate representations. For example (names are up for debate)
ShortUser
andUser
.The
Short<X>
classes should understand their relation toX
classes so that theShort<X>
classes can return an instance ofX
when a user callsrefresh()
. This is because currently, users can do the following to get full information on users:This will just mean that we introduce something that inherits from the base class for the short classes and overrides the refresh method and looks for a class attribute that defines the longer class.
Remove
Empty
and its usage.After we've done a few of these classes, determine the best way to reduce duplication. I suspect what we'll end up doing is something like this:
Is this more convoluted than using Empty? From an implementation stand-point, yes. From a user stand-point it offers the explicit that was missing. It also eliminates the confusion that users seem to be encountering.
Work Items
Empty
(Assignee: @sigmavirus24; PR Excise github3.empty.Empty #676)NullObject
(Assignee: @sigmavirus24; PR Excise NullObject from github3.py #678)self.attr = parsed_json['attr']
instead of indirect accessself.attr = parsed_json.get('attr')
. Consistency is a hobgoblin of a small mind #773Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
The text was updated successfully, but these errors were encountered: