-
Notifications
You must be signed in to change notification settings - Fork 7
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
Fix 'NoneType' object has no attribute 'computed' error (PP-1961) #2184
Conversation
work: Work = pools[0].work | ||
# We know there is at least one LicensePool. Find the first one with | ||
# a work set on it. | ||
work: Work | None = first_or_default([lp.work for lp in pools if lp.work]) |
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 where the problem we were seeing in production stems from. pools[0].work
was None
, and that's where the trouble began. In that case there was another license pool that had a work set, so we try to find the first LP with a work set instead of just using the first one.
I also added some handling for the case where there is no work, since this case is still possible.
def permalink(self, identifier_type, identifier): | ||
def permalink( | ||
self, identifier_type: str, identifier: str | ||
) -> OPDSEntryResponse | ProblemDetail: |
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.
Added some type hinting here so mypy checks this function, which would have given us an indication this could have been a problem.
@@ -558,15 +557,13 @@ def single_entry_loans_feed( | |||
if not annotator: | |||
annotator = LibraryLoanAndHoldAnnotator(circulation, None, library) | |||
|
|||
log = logging.getLogger(cls.__name__) | |||
|
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.
The logging changes in this file are only tangentially related. I just cleaned up some of the logging here to use LoggerMixin
while I was at it, so its easier to see where these messages are coming from in the future.
@@ -608,7 +605,7 @@ def single_entry_loans_feed( | |||
@classmethod | |||
def single_entry( | |||
cls, | |||
work: Work | Edition | None, | |||
work: Work | Edition, |
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 looked at everyone calling this function to make sure they are not (or updated them) passing None
here.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #2184 +/- ##
==========================================
+ Coverage 91.01% 91.03% +0.01%
==========================================
Files 361 361
Lines 41205 41198 -7
Branches 8849 8846 -3
==========================================
Hits 37504 37504
+ Misses 2425 2420 -5
+ Partials 1276 1274 -2 ☔ View full report in Codecov by Sentry. 🚨 Try these New Features:
|
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.
Looks good! A couple small comments below.
else: | ||
content = etree.tostring(feed) | ||
return Response( | ||
response=content, | ||
status=200, | ||
content_type=OPDSFeed.ACQUISITION_FEED_TYPE, | ||
) | ||
raise PalaceValueError( | ||
"Unexpected return type from OPDSAcquisitionFeed.single_entry_loans_feed" | ||
) |
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.
Minor: We can remove the else
and pop the throw out one level, since it's guarded by other two cases.
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.
Agreed. I just completely removed the if statement and raise
since it should never get hit anyway.
|
||
# There's no reason to present a book that has no active license pool. | ||
if not identifier: | ||
logging.warning("%r HAS NO IDENTIFIER", work) | ||
return None | ||
raise PalaceValueError("Work has no identifier") |
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.
Do we not want the work info in this log message? Or are we getting that somehow?
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.
Good call. I updated the exception to add the repr
to the message:
PalaceValueError(f"Work has no associated identifier: {work!r}")
) | ||
return cls.error_message( | ||
identifier, | ||
403, | ||
"I know about this work but can offer no way of fulfilling it.", | ||
) | ||
except Exception as e: | ||
logging.error("Exception generating OPDS entry for %r", work, exc_info=e) | ||
return 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.
Drop this overly broad except
. If we are getting random exceptions, we should handle them as they come up. Looking back at our logs, I don't see this being triggered recently, so it should be fine to drop.
with services_fixture.wired(): | ||
feed = OPDSAcquisitionFeed.active_loans_for( | ||
None, patron, LibraryAnnotator(None, None, db.default_library()) | ||
) |
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 change is needed because without a wired service container we get an exception (and have been for awhile) that was being swallowed by the except clause I removed.
with ( | ||
admin_librarian_fixture.request_context_with_library_and_admin("/"), | ||
services_fixture.wired(), | ||
): |
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 change is needed because without a wired service container we get an exception (and have been for awhile) that was being swallowed by the except clause I removed.
Description
There are two parts to this fix:
None
handling, so that we log appropriate errors if we do get aNone
coming back.Motivation and Context
This fixes an error we were seeing in production , where we couldn't generate a single item feed for a work. I was actually able to find an item this was happening to and reproduce it, which let me get a fix in place.
Stack trace from production:
JIRA:
How Has This Been Tested?
Checklist