This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 58819c9885 [python] Add table_type parameter support for list_tables
API (#7291)
58819c9885 is described below
commit 58819c988524cd535b99c389e80dfca62a124166
Author: Jiajia Li <[email protected]>
AuthorDate: Wed Feb 25 10:22:17 2026 +0800
[python] Add table_type parameter support for list_tables API (#7291)
---
paimon-python/pypaimon/api/rest_api.py | 9 +-
.../pypaimon/catalog/rest/rest_catalog.py | 6 +-
paimon-python/pypaimon/tests/rest/api_test.py | 130 +++++++++++++++++++++
paimon-python/pypaimon/tests/rest/rest_server.py | 12 ++
4 files changed, 154 insertions(+), 3 deletions(-)
diff --git a/paimon-python/pypaimon/api/rest_api.py
b/paimon-python/pypaimon/api/rest_api.py
index 6b871ca58e..dc2c02104b 100755
--- a/paimon-python/pypaimon/api/rest_api.py
+++ b/paimon-python/pypaimon/api/rest_api.py
@@ -46,6 +46,7 @@ class RESTApi:
PAGE_TOKEN = "pageToken"
DATABASE_NAME_PATTERN = "databaseNamePattern"
TABLE_NAME_PATTERN = "tableNamePattern"
+ TABLE_TYPE = "tableType"
TOKEN_EXPIRATION_SAFE_TIME_MILLIS = 3_600_000
def __init__(self, options: Union[Options, Dict[str, str]],
config_required: bool = True):
@@ -228,6 +229,7 @@ class RESTApi:
max_results: Optional[int] = None,
page_token: Optional[str] = None,
table_name_pattern: Optional[str] = None,
+ table_type: Optional[str] = None,
) -> PagedList[str]:
if not database_name or not database_name.strip():
raise ValueError("Database name cannot be empty")
@@ -235,7 +237,12 @@ class RESTApi:
response = self.client.get_with_params(
self.resource_paths.tables(database_name),
self.__build_paged_query_params(
- max_results, page_token, {self.TABLE_NAME_PATTERN:
table_name_pattern}
+ max_results,
+ page_token,
+ {
+ self.TABLE_NAME_PATTERN: table_name_pattern,
+ self.TABLE_TYPE: table_type,
+ },
),
ListTablesResponse,
self.rest_auth_function,
diff --git a/paimon-python/pypaimon/catalog/rest/rest_catalog.py
b/paimon-python/pypaimon/catalog/rest/rest_catalog.py
index c7c0e91052..9cf138c254 100644
--- a/paimon-python/pypaimon/catalog/rest/rest_catalog.py
+++ b/paimon-python/pypaimon/catalog/rest/rest_catalog.py
@@ -172,14 +172,16 @@ class RESTCatalog(Catalog):
database_name: str,
max_results: Optional[int] = None,
page_token: Optional[str] = None,
- table_name_pattern: Optional[str] = None
+ table_name_pattern: Optional[str] = None,
+ table_type: Optional[str] = None
) -> PagedList[str]:
try:
return self.rest_api.list_tables_paged(
database_name,
max_results,
page_token,
- table_name_pattern
+ table_name_pattern,
+ table_type
)
except NoSuchResourceException as e:
raise DatabaseNotExistException(database_name) from e
diff --git a/paimon-python/pypaimon/tests/rest/api_test.py
b/paimon-python/pypaimon/tests/rest/api_test.py
index e70d99fe42..b43c16a81e 100644
--- a/paimon-python/pypaimon/tests/rest/api_test.py
+++ b/paimon-python/pypaimon/tests/rest/api_test.py
@@ -331,3 +331,133 @@ class ApiTest(unittest.TestCase):
with self.assertRaises(ValueError) as context:
rest_api.commit_snapshot(Mock(), "uuid", Mock(), None)
self.assertIn("Statistics cannot be None", str(context.exception))
+
+ def test_list_tables_paged_with_table_type_param(self):
+ config = ConfigResponse(defaults={"prefix": "mock-test"})
+ token = str(uuid.uuid4())
+ server = RESTCatalogServer(
+ data_path="/tmp/test_warehouse",
+ auth_provider=BearTokenAuthProvider(token),
+ config=config,
+ warehouse="test_warehouse"
+ )
+ try:
+ server.start()
+
+ server.database_store.update({
+ "default": server.mock_database("default", {"env": "test"})
+ })
+
+ data_fields = [
+ DataField(0, "id", AtomicType("INT"), "id"),
+ ]
+ table_schema = TableSchema(
+ TableSchema.CURRENT_VERSION,
+ len(data_fields),
+ data_fields,
+ len(data_fields),
+ [],
+ [],
+ {"type": "table"},
+ "",
+ )
+ format_table_schema = TableSchema(
+ TableSchema.CURRENT_VERSION,
+ len(data_fields),
+ data_fields,
+ len(data_fields),
+ [],
+ [],
+ {"type": "format-table"},
+ "",
+ )
+ iceberg_table_schema = TableSchema(
+ TableSchema.CURRENT_VERSION,
+ len(data_fields),
+ data_fields,
+ len(data_fields),
+ [],
+ [],
+ {"type": "iceberg-table"},
+ "",
+ )
+ server.table_metadata_store.update({
+ "default.normal_table_1": TableMetadata(
+ uuid=str(uuid.uuid4()),
+ is_external=True,
+ schema=table_schema
+ ),
+ "default.format_table_1": TableMetadata(
+ uuid=str(uuid.uuid4()),
+ is_external=True,
+ schema=format_table_schema
+ ),
+ "default.iceberg_table_1": TableMetadata(
+ uuid=str(uuid.uuid4()),
+ is_external=True,
+ schema=iceberg_table_schema
+ ),
+ "default.normal_table_2": TableMetadata(
+ uuid=str(uuid.uuid4()),
+ is_external=True,
+ schema=table_schema
+ ),
+ "default.format_table_2": TableMetadata(
+ uuid=str(uuid.uuid4()),
+ is_external=True,
+ schema=format_table_schema
+ ),
+ "default.iceberg_table_2": TableMetadata(
+ uuid=str(uuid.uuid4()),
+ is_external=True,
+ schema=iceberg_table_schema
+ ),
+ })
+
+ options = {
+ 'uri': f"http://localhost:{server.port}",
+ 'warehouse': 'test_warehouse',
+ 'dlf.region': 'cn-hangzhou',
+ "token.provider": "bear",
+ 'token': token
+ }
+ rest_api = RESTApi(options)
+
+ all_result = rest_api.list_tables_paged("default")
+ table_result = rest_api.list_tables_paged("default",
table_type="table")
+ format_table_result = rest_api.list_tables_paged("default",
table_type="format-table")
+ iceberg_table_result = rest_api.list_tables_paged("default",
table_type="iceberg-table")
+
+ self.assertEqual(
+ [
+ "format_table_1",
+ "format_table_2",
+ "iceberg_table_1",
+ "iceberg_table_2",
+ "normal_table_1",
+ "normal_table_2",
+ ],
+ all_result.elements,
+ )
+ self.assertEqual(["normal_table_1", "normal_table_2"],
table_result.elements)
+ self.assertEqual(["format_table_1", "format_table_2"],
format_table_result.elements)
+ self.assertEqual(["iceberg_table_1", "iceberg_table_2"],
iceberg_table_result.elements)
+
+ filtered_with_pattern = rest_api.list_tables_paged(
+ "default", table_type="table", table_name_pattern="%_2"
+ )
+ self.assertEqual(["normal_table_2"],
filtered_with_pattern.elements)
+
+ first_page = rest_api.list_tables_paged(
+ "default", max_results=1, table_type="table"
+ )
+ self.assertEqual(["normal_table_1"], first_page.elements)
+ self.assertIsNotNone(first_page.next_page_token)
+
+ second_page = rest_api.list_tables_paged(
+ "default", max_results=1,
page_token=first_page.next_page_token, table_type="table"
+ )
+ self.assertEqual(["normal_table_2"], second_page.elements)
+ self.assertEqual("normal_table_2", second_page.next_page_token)
+ finally:
+ server.shutdown()
diff --git a/paimon-python/pypaimon/tests/rest/rest_server.py
b/paimon-python/pypaimon/tests/rest/rest_server.py
index cb7b321083..fbfa5a487a 100755
--- a/paimon-python/pypaimon/tests/rest/rest_server.py
+++ b/paimon-python/pypaimon/tests/rest/rest_server.py
@@ -79,6 +79,7 @@ AUTHORIZATION_HEADER_KEY = "Authorization"
# REST API parameter constants
DATABASE_NAME_PATTERN = "databaseNamePattern"
TABLE_NAME_PATTERN = "tableNamePattern"
+TABLE_TYPE = "tableType"
VIEW_NAME_PATTERN = "viewNamePattern"
FUNCTION_NAME_PATTERN = "functionNamePattern"
PARTITION_NAME_PATTERN = "partitionNamePattern"
@@ -871,11 +872,22 @@ class RESTCatalogServer:
def _list_tables(self, database_name: str, parameters: Dict[str, str]) ->
List[str]:
"""List tables in database"""
table_name_pattern = parameters.get(TABLE_NAME_PATTERN)
+ table_type = parameters.get(TABLE_TYPE)
tables = []
for full_name, metadata in self.table_metadata_store.items():
identifier = Identifier.from_string(full_name)
+ metadata_table_type = (
+ metadata.schema.options.get(TYPE, "table")
+ if metadata and metadata.schema and metadata.schema.options
+ else "table"
+ )
+ table_type_matches = (
+ not table_type
+ or metadata_table_type == table_type
+ )
if (identifier.get_database_name() == database_name and
+ table_type_matches and
(not table_name_pattern or
self._match_name_pattern(identifier.get_table_name(),
table_name_pattern))):
tables.append(identifier.get_table_name())