fix(client): Python correctness sweep - logging, timeouts, file handle, None guards - #20
Merged
Merged
Conversation
… 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>
There was a problem hiding this comment.
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, constructortimeout=) 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
dictinInProgressSubmission.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 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 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>
There was a problem hiding this comment.
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))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Real-correctness / Python-hygiene fixes from a code sweep (the mutable
class-attribute defaults were intentionally left out of scope).
logging.basicConfig()at import (a librarymust not configure the root logger). Added a
NullHandlerand routed thestray root
logging.*calls through the module_logger.(
DEFAULT_TIMEOUT = 60,timeout=constructor arg) on everysessioncall,so a stalled DSpace can't hang the client forever.
create_bitstreamnow opens the upload file in awithblock; it was previously leaked to the GC.Bitstream(None)crash — guarded so the__init__membership checksdon't raise
TypeErroronNone.create_clarinlruallowancesnow takes ametadata_payloadargument; the leftover hardcoded{"metadataValue":"Test"}is gone (it refuses with no payload).
super(Cls, self)→super()acrossmodels.py.68 tests (5 new), no network. Happy-path behaviour is unchanged.