Add support for environment context - #3441
Conversation
838ef11 to
07587a3
Compare
abnobdoss
left a comment
There was a problem hiding this comment.
This is great! This looks very helpful for debugging.
# Rationale for this change Allow users to create a view with fewer parameters. We can set an environment context (`engine-name` and `engine-version`) in `summary` field by default once #3441 is merged. ## Are these changes tested? Yes ## Are there any user-facing changes? No <!-- In the case of user-facing changes, please add the changelog label. -->
07587a3 to
afe8bec
Compare
kevinjqliu
left a comment
There was a problem hiding this comment.
Thanks for the PR! This will be very useful to users. I got a couple of nits
| timestamp_ms: int = Field(alias="timestamp-ms", default_factory=lambda: int(time.time() * 1000)) | ||
| """Timestamp when the version was created (ms from epoch)""" | ||
| summary: dict[str, str] = Field(default_factory=dict) | ||
| summary: dict[str, str] = Field(default_factory=lambda: EnvironmentContext.get()) |
There was a problem hiding this comment.
this will add EnvironmentContext to all ViewVersion which might not be the desired behavior.
Similar to Table Metadata, can we only add EnvironmentContext in the write path?
| "total-files-size": str(file_size), | ||
| "total-position-deletes": "0", | ||
| "total-records": "3", | ||
| "engine-name": "pyiceberg", |
There was a problem hiding this comment.
Could we find a way to avoid repeating env context everywhere? Perhaps a small test helper like this would keep the exact assertions while localizing this new default behavior:
def with_environment_context(summary: dict[str, str]) -> dict[str, str]:
return {**summary, **EnvironmentContext.get()}
assert summaries[0] == with_environment_context({
"added-data-files": "3",
"added-records": "5",
...
})
| def test_default_value() -> None: | ||
| actual = EnvironmentContext.get() | ||
| assert len(actual) == 2 | ||
| assert actual["engine-name"] == "pyiceberg" | ||
| assert re.match(r"^\d+\.\d+\.\d+", actual["engine-version"]) |
There was a problem hiding this comment.
| def test_default_value() -> None: | |
| actual = EnvironmentContext.get() | |
| assert len(actual) == 2 | |
| assert actual["engine-name"] == "pyiceberg" | |
| assert re.match(r"^\d+\.\d+\.\d+", actual["engine-version"]) | |
| def test_default_value() -> None: | |
| assert EnvironmentContext.get() == { | |
| "engine-name": "pyiceberg", | |
| "engine-version": __version__, | |
| } | |
| def test_get_returns_copy() -> None: | |
| actual = EnvironmentContext.get() | |
| actual["test-key"] = "test-value" | |
| assert "test-key" not in EnvironmentContext.get() |
EnvironmentContext might have been mutated, this is a better way to test that the returned value is a copy
There was a problem hiding this comment.
+1 on this. The regex is really brittle and won't work for RC builds.
There was a problem hiding this comment.
Ugh, I was missing the RC build.
I intentionally avoided using the same logic between environment_context.py and this test. I generally don't believe it's a good idea to write such tests.
| def test_put_and_remove() -> None: | ||
| EnvironmentContext.put("test-key", "test-value") | ||
| assert EnvironmentContext.get()["test-key"] == "test-value" | ||
|
|
||
| EnvironmentContext.remove("test-key") | ||
| assert "test-key" not in EnvironmentContext.get() |
There was a problem hiding this comment.
| def test_put_and_remove() -> None: | |
| EnvironmentContext.put("test-key", "test-value") | |
| assert EnvironmentContext.get()["test-key"] == "test-value" | |
| EnvironmentContext.remove("test-key") | |
| assert "test-key" not in EnvironmentContext.get() | |
| def test_put_and_remove() -> None: | |
| try: | |
| EnvironmentContext.put("test-key", "test-value") | |
| assert EnvironmentContext.get()["test-key"] == "test-value" | |
| assert EnvironmentContext.remove("test-key") == "test-value" | |
| assert "test-key" not in EnvironmentContext.get() | |
| finally: | |
| EnvironmentContext.remove("test-key") |
use try/finally to avoid mutating EnvironmentContext
| for key, value in EnvironmentContext.get().items(): | ||
| summary.__setitem__(key, value) |
There was a problem hiding this comment.
| for key, value in EnvironmentContext.get().items(): | |
| summary.__setitem__(key, value) | |
| for key, value in EnvironmentContext.get().items(): | |
| summary[key] = value |
nit
| class EnvironmentContext: | ||
| _PROPERTIES: dict[str, str] = { | ||
| "engine-name": "pyiceberg", | ||
| "engine-version": version("pyiceberg"), |
There was a problem hiding this comment.
| "engine-version": version("pyiceberg"), | |
| "engine-version": __version__, |
nit
|
This looks great! My biggest note is to remove the version regexes, since they won't work on RCs. |
8f55791 to
61e5f42
Compare
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that's incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
61e5f42 to
507431b
Compare
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that's incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
507431b to
abaf725
Compare
Return fresh engine metadata, copy only engine fields into new snapshot summaries, and cover writes and property precedence without changing existing summary assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
f932d18 to
93ba3f1
Compare
| if context := EnvironmentContext.get(): | ||
| # Defensively select only engine fields so future context additions cannot overwrite snapshot metadata. | ||
| summary["engine-name"] = context["engine-name"] | ||
| summary["engine-version"] = context["engine-version"] |
There was a problem hiding this comment.
i think this is the best because we dont want to accidentally allow EnvironmentContext override other summary
| @pytest.fixture(autouse=True, scope="session") | ||
| def _disable_environment_context() -> Generator[None, None, None]: |
There was a problem hiding this comment.
this allows us to not make a lot of changes to existing tests
Rationale for this change
Adds an
EnvironmentContextutility class that provides the PyIceberg engine name and version for new snapshot summaries. Existing snapshots are unchanged.Are these changes tested?
Yes. Tests cover metadata values, dictionary independence, snapshot writes, and property precedence.
Are there any user-facing changes?
Yes. New snapshot summaries include
engine-nameandengine-version, overriding same-named entries insnapshot_properties.