Skip to content

Commit b6aff67

Browse files
committed
view api
1 parent a9ad3a3 commit b6aff67

3 files changed

Lines changed: 204 additions & 4 deletions

File tree

pyiceberg/view/__init__.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
# under the License.
1717
from __future__ import annotations
1818

19-
from typing import (
20-
Any,
21-
)
19+
from typing import Any
20+
from uuid import UUID
2221

22+
from pyiceberg.schema import Schema
2323
from pyiceberg.typedef import Identifier
24-
from pyiceberg.view.metadata import ViewMetadata
24+
from pyiceberg.view.metadata import SQLViewRepresentation, ViewHistoryEntry, ViewMetadata, ViewVersion
2525

2626

2727
class View:
@@ -42,6 +42,48 @@ def name(self) -> Identifier:
4242
"""Return the identifier of this view."""
4343
return self._identifier
4444

45+
def schema(self) -> Schema:
46+
"""Return the schema for this view."""
47+
return next(schema for schema in self.metadata.schemas if schema.schema_id == self.current_version().schema_id)
48+
49+
def schemas(self) -> dict[int, Schema]:
50+
"""Return the schemas for this view."""
51+
return {schema.schema_id: schema for schema in self.metadata.schemas}
52+
53+
def current_version(self) -> ViewVersion:
54+
"""Get the version of this view."""
55+
return next(version for version in self.metadata.versions if version.version_id == self.metadata.current_version_id)
56+
57+
@property
58+
def versions(self) -> list[ViewVersion]:
59+
"""Get the versions of this view."""
60+
return self.metadata.versions
61+
62+
def version(self, version_id: int) -> ViewVersion:
63+
"""Get the version in this view by ID."""
64+
return next(version for version in self.metadata.versions if version.version_id == version_id)
65+
66+
def history(self) -> list[ViewHistoryEntry]:
67+
"""Get the version of this history view."""
68+
return self.metadata.version_log
69+
70+
@property
71+
def properties(self) -> dict[str, str]:
72+
"""Return a map of string properties for this view."""
73+
return self.metadata.properties
74+
75+
def location(self) -> str:
76+
"""Return the view's base location."""
77+
return self.metadata.location
78+
79+
def uuid(self) -> UUID:
80+
"""Return the view's UUID."""
81+
return UUID(self.metadata.view_uuid)
82+
83+
def sql_for(self, dialect: str) -> SQLViewRepresentation:
84+
"""Return the view representation for the sql dialect."""
85+
return next(repr.root for repr in self.current_version().representations if repr.root.dialect == dialect)
86+
4587
def __eq__(self, other: Any) -> bool:
4688
"""Return the equality of two instances of the View class."""
4789
return self.name() == other.name() and self.metadata == other.metadata if isinstance(other, View) else False

tests/conftest.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,43 @@ def example_view_metadata_v1() -> dict[str, Any]:
11741174
}
11751175

11761176

1177+
@pytest.fixture
1178+
def example_view_metadata_v1_multiple_versions() -> dict[str, Any]:
1179+
return {
1180+
"view-uuid": "a20125c8-7284-442c-9aea-15fee620737c",
1181+
"format-version": 1,
1182+
"location": "s3://bucket/test/location/test_view",
1183+
"current-version-id": 2,
1184+
"versions": [
1185+
{
1186+
"version-id": 1,
1187+
"timestamp-ms": 1602638573874,
1188+
"schema-id": 1,
1189+
"summary": {},
1190+
"representations": [{"type": "sql", "sql": "SELECT 1", "dialect": "spark"}],
1191+
"default-namespace": ["default"],
1192+
},
1193+
{
1194+
"version-id": 2,
1195+
"timestamp-ms": 1602638573875,
1196+
"schema-id": 2,
1197+
"summary": {},
1198+
"representations": [{"type": "sql", "sql": "SELECT 2", "dialect": "spark"}],
1199+
"default-namespace": ["default"],
1200+
},
1201+
],
1202+
"schemas": [
1203+
{"type": "struct", "schema-id": 1, "fields": [{"id": 1, "name": "a", "required": True, "type": "long"}]},
1204+
{"type": "struct", "schema-id": 2, "fields": [{"id": 2, "name": "b", "required": True, "type": "string"}]},
1205+
],
1206+
"version-log": [
1207+
{"timestamp-ms": 1602638573874, "version-id": 1},
1208+
{"timestamp-ms": 1602638573875, "version-id": 2},
1209+
],
1210+
"properties": {},
1211+
}
1212+
1213+
11771214
@pytest.fixture
11781215
def example_table_metadata_v3() -> dict[str, Any]:
11791216
return EXAMPLE_TABLE_METADATA_V3

tests/test_view.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
from typing import Any
18+
from uuid import UUID
19+
20+
import pytest
21+
22+
from pyiceberg.schema import Schema
23+
from pyiceberg.view import View
24+
from pyiceberg.view.metadata import SQLViewRepresentation, ViewHistoryEntry, ViewMetadata, ViewVersion
25+
26+
27+
@pytest.fixture
28+
def view(example_view_metadata_v1: dict[str, Any]) -> View:
29+
metadata = ViewMetadata.model_validate(example_view_metadata_v1)
30+
return View(("default", "test_view"), metadata)
31+
32+
33+
def test_view_schema(view: View) -> None:
34+
schema = view.schema()
35+
assert isinstance(schema, Schema)
36+
assert schema.schema_id == 1
37+
assert len(schema.fields) == 3
38+
assert schema.find_field("x") is not None
39+
assert schema.find_field("y") is not None
40+
assert schema.find_field("z") is not None
41+
42+
43+
def test_view_schemas(view: View) -> None:
44+
schemas = view.schemas()
45+
assert isinstance(schemas, dict)
46+
assert len(schemas) == 1
47+
assert 1 in schemas
48+
assert isinstance(schemas[1], Schema)
49+
50+
51+
def test_view_current_version(view: View) -> None:
52+
version = view.current_version()
53+
assert isinstance(version, ViewVersion)
54+
assert version.version_id == 1
55+
assert version.schema_id == 1
56+
57+
58+
def test_view_versions(view: View) -> None:
59+
versions = view.versions
60+
assert len(versions) == 1
61+
assert isinstance(versions[0], ViewVersion)
62+
assert versions[0].version_id == 1
63+
64+
65+
def test_view_version_by_id(view: View) -> None:
66+
version = view.version(1)
67+
assert isinstance(version, ViewVersion)
68+
assert version.version_id == 1
69+
assert version == view.current_version()
70+
71+
72+
def test_view_history(view: View) -> None:
73+
history = view.history()
74+
assert len(history) == 1
75+
assert isinstance(history[0], ViewHistoryEntry)
76+
assert history[0].version_id == 1
77+
assert history[0].timestamp_ms == 1602638573874
78+
79+
80+
def test_view_properties(view: View) -> None:
81+
assert view.properties == {"comment": "this is a test view"}
82+
83+
84+
def test_view_location(view: View) -> None:
85+
assert view.location() == "s3://bucket/test/location/test_view"
86+
87+
88+
def test_view_uuid(view: View) -> None:
89+
assert view.uuid() == UUID("a20125c8-7284-442c-9aea-15fee620737c")
90+
91+
92+
def test_view_sql_for_dialect(view: View) -> None:
93+
repr = view.sql_for("spark")
94+
assert isinstance(repr, SQLViewRepresentation)
95+
assert repr.dialect == "spark"
96+
assert repr.sql == "SELECT * FROM prod.db.table"
97+
98+
99+
def test_view_schemas_multiple(example_view_metadata_v1_multiple_versions: dict[str, Any]) -> None:
100+
view = View(("default", "test_view"), ViewMetadata.model_validate(example_view_metadata_v1_multiple_versions))
101+
schemas = view.schemas()
102+
assert len(schemas) == 2
103+
assert 1 in schemas
104+
assert 2 in schemas
105+
assert view.schema().schema_id == 2
106+
107+
108+
def test_view_versions_multiple(example_view_metadata_v1_multiple_versions: dict[str, Any]) -> None:
109+
view = View(("default", "test_view"), ViewMetadata.model_validate(example_view_metadata_v1_multiple_versions))
110+
assert len(view.versions) == 2
111+
assert view.current_version().version_id == 2
112+
113+
114+
def test_view_version_unknown_id(view: View) -> None:
115+
with pytest.raises(StopIteration):
116+
view.version(999)
117+
118+
119+
def test_view_sql_for_unknown_dialect(view: View) -> None:
120+
with pytest.raises(StopIteration):
121+
view.sql_for("trino")

0 commit comments

Comments
 (0)