Skip to content

Check the source and the destination before a copy - #63

Merged
apdavison merged 3 commits into
HumanBrainProject:masterfrom
ehennestad:copy-to-checks-source-and-target
Nov 3, 2026
Merged

apdavison merged 3 commits into
HumanBrainProject:masterfrom
ehennestad:copy-to-checks-source-and-target

Conversation

@ehennestad

@ehennestad ehennestad commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

The data-proxy copy endpoint answers 201 "Your copy is processing" to every request and completes the copy in the background. A copy that fails is reported only by email. I checked two cases on a production bucket: a copy of an object that no longer exists gets 201, creates nothing, and is reported by email, and a copy onto an existing object replaces it without warning. So copy_to() returned True for a copy that never happens, and replaced an object at the destination silently.

copy_to(dst_name=None, *, dst_bucket=None, overwrite=False, send_success_email=False) now checks before the request:

  • that the object is still in the bucket, raising DoesNotExist if not;
  • that no object at the destination has the destination name, raising FileExistsError if one does, unless overwrite=True. The destination is dst_bucket when given, and the object's own bucket otherwise.

Neither check sends the copy request, so a rejected copy produces no email. send_success_email is passed to the endpoint and defaults to False. The data-proxy emails about a failed copy either way. This follows the review of DataproxyFile.rename() in #55, so both methods behave the same.

Before this change, copy_to replaced an existing destination object without warning. In 0.7.0 every call raised ClientHttpError (#57), even though the copy was made, so released code is unlikely to depend on that behaviour. Code that caught the error and relied on the overwrite would now get FileExistsError and need overwrite=True.

The tests cover a copy within the bucket and to another bucket, a missing source with the destination free, taken, and with overwrite=True, an existing destination in the same bucket and in another bucket, and overwrite=True. The checks that reject a copy also assert that no request was sent. The tests from #58 that run a real BucketApiClient against 200, 201 and 403 replies are kept, with Bucket.get_file stubbed for the new checks. The "Copy File" section of doc.md describes the new arguments and exceptions.

Fixes #56

🤖 Generated with Claude Code

ehennestad and others added 2 commits October 9, 2026 20:52
The data-proxy answers 201 to every copy and reports a failure only by
email, so copy_to() returned True for a copy of an object that no
longer exists, and replaced an object at the destination without
warning.

copy_to() now raises DoesNotExist when the object is no longer in the
bucket, and FileExistsError when the destination object exists, in
the same bucket or in dst_bucket, unless overwrite=True. Both checks
run before the request. send_success_email is passed to the endpoint
and defaults to False; the data-proxy emails about a failed copy
either way. This follows DataproxyFile.rename().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The source check runs whether or not overwrite is set, but only the
case without overwrite was tested, so moving the check under
"if not overwrite" passed every test. The missing-source test now
also runs with the destination taken, and with overwrite=True.

The stand-in for Bucket.get_file returned MagicMock(name=name), which
names the mock rather than setting its name attribute; it now returns
a SimpleNamespace with that name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ehennestad
ehennestad force-pushed the copy-to-checks-source-and-target branch from 0167f6a to 612d578 Compare October 9, 2026 18:52
@ehennestad
ehennestad marked this pull request as ready for review October 9, 2026 18:56
ehennestad added a commit to ehennestad/EBRAINS-MATLAB that referenced this pull request Nov 3, 2026
…he request (#90)

The Data Proxy copies an object within the object store through `PUT
/v1/buckets/{bucket}/{object}/copy?name=<target>`. `BucketsClient` gets
a `copyObject` method for it and the namespace an
`ebrains.bucket.copyObject` function, next to `renameObject`. A folder
is copied with everything in it when both names end with `/`.

The copy endpoint takes the name as one percent-encoded path segment. A
folder name ends with `/`, and sent as path segments the request path
holds `folder//copy`; the proxy collapses the double slash before it
reads the name, copies a single object that does not exist, and reports
the failure only by mail, as described in
HumanBrainProject/ebrains-storage#60. The other endpoints keep the path
segments, since the signed temporary URLs they return need real
separators.

Rename and copy answer `201 "Your copy is processing"` and complete in
the background, and a failed operation is reported only by mail. Both
functions therefore check that the source exists
(`EBRAINS:Bucket:ObjectNotFound`) and that the target is free
(`EBRAINS:Bucket:TargetExists`) before the request, with
`Overwrite=true` to replace the target. Both pass
`send_success_email=false` unless `SendSuccessEmail=true`. Before this
change the rename required a 200, so it threw on every rename the proxy
accepted, and each rename mailed the account. The options and defaults
follow `DataproxyFile.rename()` and `copy_to()` in ebrains-storage
(HumanBrainProject/ebrains-storage#55,
HumanBrainProject/ebrains-storage#63).

The live tests gain a folder copy, a single-object copy and a rename,
each polling the listing until the proxy has completed the operation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@apdavison
apdavison merged commit c4e7f74 into HumanBrainProject:master Nov 3, 2026
8 checks passed
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.

DataproxyFile.copy_to sends an email for every copy

2 participants