Skip to content

item_copyrights returns a string instead of int — silently breaks truthiness checks for zero #180

@Valyrian-Code

Description

@Valyrian-Code

Describe the bug

Fossology.item_copyrights is documented and type-annotated as returning int, but it actually returns the raw JSON value from the API, which is a string. The Fossology server returns {"total_copyrights": "0"} (note the quotes), and the library passes that through without coercion.

The function signature at fossology/items.py:55-60:

def item_copyrights(
    self,
    upload: Upload,
    item_id: int,
    status: CopyrightStatus,
) -> int:

The implementation at fossology/items.py:79:

if response.status_code == 200:
    return response.json()["total_copyrights"]

Why this matters more than a type-annotation nit

The most natural usage pattern is a truthiness check:

if foss.item_copyrights(upload, item_id, CopyrightStatus.ACTIVE):
    do_something_with_copyrights()

Under the documented int contract, this is False when the count is 0. Under the actual string return, "0" is truthy in Python, so the branch is taken even when there are zero copyrights. The bug silently produces the wrong logic without raising any error — much worse than a clean TypeError.

Similar quiet wrongness for any arithmetic or comparisons (> 0, += 1, etc.).

Reproduction (against Fossology 4.4.0 / API 1.6.2)

import requests, fossology, secrets, time
from fossology.enums import TokenScope, AccessLevel

URL = "http://localhost/repo"
token = fossology.fossology_token(URL, "fossy", "fossy", secrets.token_urlsafe(8), TokenScope.WRITE)
foss = fossology.Fossology(URL, token)

upload = foss.upload_file(foss.rootFolder, file="tests/files/base-files_11.tar.xz",
                          description="repro", access_level=AccessLevel.PUBLIC, wait_time=10)
# wait for scanner agents to complete (omitted for brevity)

files, _ = foss.search(license="BSD")
result = foss.item_copyrights(upload, files[0].uploadTreeId, CopyrightStatus.ACTIVE)
print(type(result), repr(result))   # <class 'str'> '0'
print(bool(result))                  # True  -- BUG: count is 0 but truthy

Raw HTTP response confirming the string shape:

GET /api/v1/uploads/{id}/item/{itemId}/totalcopyrights?status=active
200 OK
{"total_copyrights":"0"}

Affected test on a clean clone

FAILED tests/test_items.py::test_item_copyrights — AssertionError: assert '0' == 0

The existing test already encodes the expected int contract; it just doesn't pass today because the library doesn't honour it.

Proposed fix

One-line coercion at fossology/items.py:79:

-            return response.json()["total_copyrights"]
+            return int(response.json()["total_copyrights"])

This matches both the type annotation and the docstring. Locally verified — tests/test_items.py::test_item_copyrights passes after the change, no other test regresses.

Open question for maintainers:

  • Is the string return from the Fossology server considered a server bug (filed upstream?) or stable behaviour the wrapper is expected to absorb? Either way, coercion on the client side makes the documented contract hold for users.

Happy to open a PR if you'd like.

Environment

  • fossology-python version: 3.5.0 (main)
  • Fossology server: 4.4.0 / API 1.6.2 (fossology/fossology:latest Docker image)
  • Python: 3.13

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions