Skip to content

feat(storage): add Preconditions to AsyncAppendableObjectWriter - #18641

Open
yuxin00j wants to merge 5 commits into
googleapis:mainfrom
yuxin00j:storage-if-generation-match
Open

yuxin00j wants to merge 5 commits into
googleapis:mainfrom
yuxin00j:storage-if-generation-match

Conversation

@yuxin00j

@yuxin00j yuxin00j commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

AsyncAppendableObjectWriter overloads generation: None / 0 creates a new object via WriteObjectSpec(if_generation_match=generation), while N > 0 reopens an existing object via AppendObjectSpec(generation=N). There was no way to pass if_generation_match=N (N > 0) on WriteObjectSpec (overwrite only if the current generation matches N) or other write preconditions (if_generation_not_match, if_metageneration_match, if_metageneration_not_match).

This adds a Preconditions dataclass (if_generation_match, if_generation_not_match, if_metageneration_match, if_metageneration_not_match) accepted via preconditions: Optional[Preconditions] = None on AsyncAppendableObjectWriter.__init__, AsyncAppendableObjectWriter.from_blob, and _AsyncWriteObjectStream.__init__:

  • Generation preconditions (if_generation_match, if_generation_not_match) require generation=None. Combining them with generation raises ValueError, including from_blob when blob.generation is set. To overwrite a fetched Blob, pass generation=None to from_blob, for example with Preconditions(if_generation_match=blob.generation). generation=0 without preconditions still means if_generation_match=0.
  • Creating or overwriting (generation is None or 0) sets all 4 fields on WriteObjectSpec. Appending to an existing object (generation > 0) sets the metageneration preconditions on AppendObjectSpec. Metageneration preconditions cannot be combined with write_handle (raises ValueError), because GCS only checks them when no write handle is sent.
  • After the writer creates a generation, reconnects send AppendObjectSpec without preconditions, because they describe the object that was replaced.

Motivation: fsspec/gcsfs#1101 (compare-and-swap writes in gcsfs).

Test plan

cd packages/google-cloud-storage
PYTHONPATH=$PWD python -m pytest tests/unit/asyncio/test_async_write_object_stream.py tests/unit/asyncio/test_async_appendable_object_writer.py   # 86 passed
ruff check --select I --target-version=py310 --line-length=88 <changed files>
ruff format --check --target-version=py310 --line-length=88 <changed files>
flake8 <changed files>

This change was authored with an AI assistant; the description above was reviewed by the author.

`AsyncAppendableObjectWriter` and `_AsyncWriteObjectStream` overloaded a
single `generation` argument: `None`/`0` created a new object via
`WriteObjectSpec(if_generation_match=generation)`, while `N > 0` reopened
an existing object via `AppendObjectSpec(generation=N)`. There was no way
to request `WriteObjectSpec(appendable=True, if_generation_match=N)`,
i.e. overwrite an object only if its current generation is still `N`,
although the gRPC API and the other GCS client libraries support it.

This adds an explicit `if_generation_match` parameter. It is set on
`WriteObjectSpec` when creating a new object (`generation` is `None` or
`0`; `generation=0` keeps meaning `if_generation_match=0` when the new
parameter is not given) and is ignored when `generation > 0` selects an
existing object to append to, so the redirect-retry path that reopens
with the redirected generation is unaffected.

Test Plan:

```
cd packages/google-cloud-storage
PYTHONPATH=$PWD python -m pytest tests/unit/asyncio/test_async_write_object_stream.py tests/unit/asyncio/test_async_appendable_object_writer.py  # 73 passed
ruff check --select I --target-version=py310 --line-length=88 <changed files>
ruff format --check --target-version=py310 --line-length=88 <changed files>
flake8 <changed files>
```

Authored with an AI assistant.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for the if_generation_match precondition parameter to the asynchronous appendable object writer and write object stream classes. It updates AsyncAppendableObjectWriter and _AsyncWriteObjectStream to accept, document, and propagate this parameter to the underlying BidiWriteObjectRequest. Unit tests have also been added to verify the correct handling of this parameter. There are no review comments to address, and I have no feedback to provide.

@yuxin00j
yuxin00j requested a review from chandra-siri October 13, 2026 05:41
@yuxin00j
yuxin00j marked this pull request as ready for review October 13, 2026 05:41
@yuxin00j
yuxin00j requested a review from a team as a code owner October 13, 2026 05:41
@chandra-siri chandra-siri self-assigned this Oct 22, 2026
@chandra-siri

Copy link
Copy Markdown
Contributor

@chandra-siri

Copy link
Copy Markdown
Contributor

@yuxin00j , please see my comment http://shortn/_bOdi6IPxm9

@yuxin00j
yuxin00j requested a review from a team as a code owner October 29, 2026 07:44
@yuxin00j yuxin00j changed the title feat(storage): add if_generation_match to AsyncAppendableObjectWriter feat(storage): add Preconditions to AsyncAppendableObjectWriter Oct 29, 2026
@yuxin00j

Copy link
Copy Markdown
Contributor Author

/gcbrun

@yuxin00j

Copy link
Copy Markdown
Contributor Author

/gcbrun

1 similar comment
@yuxin00j

Copy link
Copy Markdown
Contributor Author

/gcbrun

@yuxin00j

Copy link
Copy Markdown
Contributor Author

/gcbrun

@yuxin00j
yuxin00j requested a review from zhixiangli October 30, 2026 06:39
…le with metageneration preconditions

Passing generation=None to from_blob overwrites a fetched Blob (for example with if_generation_match=blob.generation) while keeping its metadata. Metageneration preconditions are rejected together with write_handle because GCS only checks them when the handle is empty. Also consolidates the precondition unit tests.
@yuxin00j

Copy link
Copy Markdown
Contributor Author

/gcbrun

This branch has not been deployed

No deployments
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.

3 participants