Skip to content

fix(client): Python correctness sweep - logging, timeouts, file handle, None guards - #20

Merged
vidiecan merged 2 commits into
dtqfrom
fix/python-correctness-sweep
Aug 17, 2026
Merged

fix(client): Python correctness sweep - logging, timeouts, file handle, None guards#20
vidiecan merged 2 commits into
dtqfrom
fix/python-correctness-sweep

Conversation

@vidiecan

Copy link
Copy Markdown

Real-correctness / Python-hygiene fixes from a code sweep (the mutable
class-attribute defaults were intentionally left out of scope).

  • Root-logger hijack — removed logging.basicConfig() at import (a library
    must not configure the root logger). Added a NullHandler and routed the
    stray root logging.* calls through the module _logger.
  • No request timeouts — added a configurable per-request timeout
    (DEFAULT_TIMEOUT = 60, timeout= constructor arg) on every session call,
    so a stalled DSpace can't hang the client forever.
  • File-handle leakcreate_bitstream now opens the upload file in a
    with block; it was previously leaked to the GC.
  • Bitstream(None) crash — guarded so the __init__ membership checks
    don't raise TypeError on None.
  • Hardcoded debug payloadcreate_clarinlruallowances now takes a
    metadata_payload argument; the leftover hardcoded {"metadataValue":"Test"}
    is gone (it refuses with no payload).
  • super(Cls, self)super() across models.py.

68 tests (5 new), no network. Happy-path behaviour is unchanged.

… guards

Python-hygiene fixes (no behaviour change on the happy path):

- Remove logging.basicConfig() at import - a library must not configure the
  root logger. Attach a NullHandler and route everything through the module
  _logger (dropped the stray root logging.* calls).
- Add a configurable per-request timeout (DEFAULT_TIMEOUT=60, `timeout=`
  constructor arg) on every session call, so a stalled server cannot hang the
  client forever.
- create_bitstream: open the upload file in a `with` block so the handle is
  always closed instead of leaked to the GC.
- Bitstream(None) no longer raises TypeError on the __init__ membership checks.
- create_clarinlruallowances: parameterise metadata_payload; drop the leftover
  hardcoded {"metadataValue":"Test"} debug data (refuses with no payload).
- models: modernise super(Cls, self) -> super() throughout.

Mutable class-attribute defaults were intentionally left untouched.

68 tests (5 new), no network.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR applies a Python-correctness / hygiene sweep to the DSpace REST client library, focusing on safer logging behavior, bounded HTTP requests, safer file uploads, and guarding edge cases that previously caused runtime errors.

Changes:

  • Stops configuring the root logger at import time; routes logging through a module logger and adds a NullHandler.
  • Introduces a per-request timeout (DEFAULT_TIMEOUT, constructor timeout=) and applies it across HTTP requests.
  • Fixes correctness edge cases: closes upload file handles reliably, guards Bitstream(None), and removes the hardcoded debug payload by requiring a caller-supplied metadata payload.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
dspace_rest_client/client.py Removes root logger configuration, adds request timeouts everywhere, closes upload files via with, and updates CLARIN allowances API to require an explicit payload.
dspace_rest_client/models.py Replaces legacy super(Cls, self) patterns with super() and hardens Bitstream initialization against None.
tests/test_client_auth.py Adds tests asserting default/overridden timeout and verifying no root logger hijack (via NullHandler).
tests/test_client_write.py Adds tests ensuring CLARIN allowances refuse missing payload and POST the supplied payload correctly.
tests/test_models.py Adds regression test ensuring Bitstream(None) does not crash.
Suppressed comments (1)

dspace_rest_client/models.py:492

  • Avoid shadowing the built-in name dict in InProgressSubmission.as_dict(). Shadowing built-ins makes debugging harder and can break local tooling (e.g., type checkers or debuggers).
        parent_dict = super().as_dict()
        dict = {
            'lastModified': self.lastModified,

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dspace_rest_client/client.py Outdated
Comment on lines +29 to +33
_logger = logging.getLogger("dspace.client")
# A library must not configure the root logger - that is the consuming
# application's job. Attach a NullHandler so records are dropped unless the
# application opts in to logging.
_logger.addHandler(logging.NullHandler())
Comment thread dspace_rest_client/client.py Outdated
Comment on lines +860 to +862
h = self.session.headers
h.update({'Content-Encoding': 'gzip', 'User-Agent': self.USER_AGENT})
req = Request('POST', url, data=payload, headers=h, files=files)
Comment on lines 318 to 322
def as_dict(self):
dso_dict = super(Collection, self).as_dict()
dso_dict = super().as_dict()
"""
Return a dict representation of this Collection, based on super with collection-specific attributes added
@return: dict of Item for API use
- guard the NullHandler registration so reloads/re-imports don't accumulate
  duplicate handlers
- create_bitstream: copy the session headers before adding Content-Encoding so
  it doesn't leak onto every subsequent request / across threads
- move Collection.as_dict's docstring to the first statement (it was a no-op
  string literal after code); rename the `dict` builtin-shadow in
  InProgressSubmission.as_dict

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

tests/test_client_auth.py:40

  • This test/comment claims to verify that importing the client does not call logging.basicConfig(), but it only asserts that a NullHandler exists on the module logger. Either add an explicit assertion around basicConfig usage, or (at minimum) rename/trim the test so it matches what it actually checks.
    def test_library_does_not_hijack_root_logger(self):
        # importing the client must not call logging.basicConfig; the module
        # logger carries a NullHandler so records drop unless the app opts in.
        lg = logging.getLogger("dspace.client")
        self.assertTrue(
            any(isinstance(h, logging.NullHandler) for h in lg.handlers))

@vidiecan
vidiecan requested a review from jr-rk August 17, 2026 23:01
@vidiecan
vidiecan merged commit 68c4c42 into dtq Aug 17, 2026
3 checks passed
@vidiecan
vidiecan deleted the fix/python-correctness-sweep branch August 17, 2026 23:05
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