Skip to content

Fix ValueError in calculate_work() when licensed_through is empty (PP-3977)#3227

Open
dbernstein wants to merge 1 commit intomainfrom
PP-3977-fix-ValueError-in-apply-task
Open

Fix ValueError in calculate_work() when licensed_through is empty (PP-3977)#3227
dbernstein wants to merge 1 commit intomainfrom
PP-3977-fix-ValueError-in-apply-task

Conversation

@dbernstein
Copy link
Copy Markdown
Contributor

@dbernstein dbernstein commented Apr 10, 2026

Fix ValueError in calculate_work() when licensed_through is transiently empty

Description

Tighten the condition in LicensePool.calculate_work() that reads a consensus Work from Identifier.licensed_through. Change the bare else: to elif len(existing_works) == 1: so the destructuring assignment [self.work] = existing_works is only attempted when the set contains exactly one element.

Motivation and Context

LicensePool.calculate_work() builds a set of existing works from all pools sharing the same identifier:

existing_works = {x.work for x in self.identifier.licensed_through}

It then branches on the size of that set:

if len(existing_works) > 1:
    # clear all works — inconsistent state
else:
    # "consensus" case — unpack the single work
    [self.work] = existing_works   # raises if the set is empty

The else branch is reached whenever len(existing_works) <= 1, including the empty case. When licensed_through is transiently empty — e.g. because the SQLAlchemy relationship collection has not yet been populated during a flush — the bare destructuring raises:

ValueError: not enough values to unpack (expected 1, got 0)

This was observed in production as a Celery task failure on apply.bibliographic_apply. With the fix, when the set is empty self.work is left unchanged and the method falls through to the existing "create a new Work" logic below.

How Has This Been Tested?

Added TestWorkConsolidation::test_calculate_work_does_not_raise_when_licensed_through_is_empty, a regression test that temporarily mocks Identifier.licensed_through to return [] and asserts that calculate_work() returns a valid new Work without raising. The full TestWorkConsolidation suite (27 tests) was run and all pass.

Checklist

  • I have updated the documentation accordingly.
  • All new and existing tests passed.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.30%. Comparing base (7b790a0) to head (5cd942c).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3227   +/-   ##
=======================================
  Coverage   93.30%   93.30%           
=======================================
  Files         497      497           
  Lines       46144    46145    +1     
  Branches     6318     6319    +1     
=======================================
+ Hits        43055    43057    +2     
+ Misses       2004     2003    -1     
  Partials     1085     1085           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dbernstein dbernstein marked this pull request as draft April 10, 2026 18:04
@dbernstein dbernstein marked this pull request as ready for review April 10, 2026 18:04
@dbernstein dbernstein requested a review from a team April 10, 2026 18:04
@dbernstein
Copy link
Copy Markdown
Contributor Author

I'm not sure why we are seeing this now (likely it is something with the parallel imports in overdrive, but it does appear that the code did not previously assume that there would be a work associated with the license pool when it was created. This change just makes that assumption explicit.

Copy link
Copy Markdown
Member

@jonathangreen jonathangreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned about this fix @dbernstein. I think we at least need to look more deeply into it. Before the recent Overdrive changes we never hit this error, so something changed in the Overdrive importer to cause us to hit this.

If it is in fact it is just transient because self.identifier.licensed_through is empty only because the relationship cache is stale then I don't think this behaviour is what we want. In that case, any sibling LicensePools already in the database keep pointing at the old Work while self is moved to a new one, which breaks the assumption that all LicensePools with a given Identifier must share a work and can leave duplicate works for the same identifier.

@dbernstein dbernstein force-pushed the PP-3977-fix-ValueError-in-apply-task branch from d7cab14 to 5cd942c Compare April 10, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants