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 fd605b8a11 [python] Support get_table method of python RestCatalog 
(#5962)
fd605b8a11 is described below

commit fd605b8a118759ecc37cd9addc477d3b0054e1a5
Author: HeavenZH <[email protected]>
AuthorDate: Mon Jul 28 13:45:17 2025 +0800

    [python] Support get_table method of python RestCatalog (#5962)
---
 paimon-python/pypaimon/api/__init__.py             |  2 +-
 paimon-python/pypaimon/api/api_response.py         | 76 +++++++-------------
 paimon-python/pypaimon/api/api_resquest.py         |  2 +-
 paimon-python/pypaimon/api/core_options.py         | 44 ++++++++++++
 .../pypaimon/api/{typedef.py => identifier.py}     | 30 +++-----
 paimon-python/pypaimon/api/schema.py               | 52 ++++++++++++++
 paimon-python/pypaimon/api/table_schema.py         | 83 ++++++++++++++++++++++
 paimon-python/pypaimon/api/typedef.py              | 46 +-----------
 paimon-python/pypaimon/catalog/catalog_loader.py   | 24 +++++++
 paimon-python/pypaimon/catalog/catalog_utils.py    | 55 ++++++++++++++
 paimon-python/pypaimon/catalog/table_metadata.py   | 40 +++++++++++
 paimon-python/pypaimon/common/__init__.py          | 17 +++++
 paimon-python/pypaimon/common/file_io.py           | 25 +++++++
 paimon-python/pypaimon/pvfs/__init__.py            |  3 +-
 paimon-python/pypaimon/rest/rest_catalog.py        | 47 +++++++++++-
 paimon-python/pypaimon/rest/rest_token_file_io.py  | 34 +++++++++
 paimon-python/pypaimon/schema/__init__.py          | 17 +++++
 paimon-python/pypaimon/schema/schema_manager.py    | 29 ++++++++
 paimon-python/pypaimon/table/__init__.py           | 17 +++++
 .../pypaimon/table/catalog_environment.py          | 36 ++++++++++
 paimon-python/pypaimon/table/file_store_table.py   | 42 +++++++++++
 .../pypaimon/table/file_store_table_factory.py     | 35 +++++++++
 paimon-python/pypaimon/table/table.py              | 23 ++++++
 paimon-python/pypaimon/tests/api_test.py           | 11 ++-
 paimon-python/pypaimon/tests/pvfs_test.py          |  3 +-
 paimon-python/pypaimon/tests/rest_server.py        |  9 +--
 26 files changed, 676 insertions(+), 126 deletions(-)

diff --git a/paimon-python/pypaimon/api/__init__.py 
b/paimon-python/pypaimon/api/__init__.py
index f235c2815e..3c8d1fb2ef 100644
--- a/paimon-python/pypaimon/api/__init__.py
+++ b/paimon-python/pypaimon/api/__init__.py
@@ -31,9 +31,9 @@ from pypaimon.api.api_response import (
 )
 from pypaimon.api.api_resquest import CreateDatabaseRequest, 
AlterDatabaseRequest, RenameTableRequest, \
     CreateTableRequest
-from pypaimon.api.typedef import Identifier
 from pypaimon.api.config import RESTCatalogOptions
 from pypaimon.api.client import HttpClient
+from pypaimon.api.identifier import Identifier
 from pypaimon.api.typedef import T
 
 
diff --git a/paimon-python/pypaimon/api/api_response.py 
b/paimon-python/pypaimon/api/api_response.py
index 7e0b6a564a..b8d559d611 100644
--- a/paimon-python/pypaimon/api/api_response.py
+++ b/paimon-python/pypaimon/api/api_response.py
@@ -21,8 +21,8 @@ from typing import Dict, Optional, Generic, List
 from dataclasses import dataclass
 
 from .rest_json import json_field
+from .schema import Schema
 from .typedef import T
-from .data_types import DataField
 
 
 @dataclass
@@ -84,6 +84,14 @@ class AuditRESTResponse(RESTResponse):
     def get_updated_by(self) -> Optional[str]:
         return self.updated_by
 
+    def put_audit_options_to(self, options: dict[str, str]) -> None:
+        """Puts audit-related options into the provided dictionary."""
+        options[self.FIELD_OWNER] = self.get_owner()
+        options[self.FIELD_CREATED_BY] = str(self.get_created_by())
+        options[self.FIELD_CREATED_AT] = str(self.get_created_at())
+        options[self.FIELD_UPDATED_BY] = str(self.get_updated_by())
+        options[self.FIELD_UPDATED_AT] = str(self.get_updated_at())
+
 
 class PagedResponse(RESTResponse, Generic[T]):
     FIELD_NEXT_PAGE_TOKEN = "nextPageToken"
@@ -126,54 +134,6 @@ class ListTablesResponse(PagedResponse[str]):
         return self.next_page_token
 
 
-@dataclass
-class Schema:
-    FIELD_FIELDS = "fields"
-    FIELD_PARTITION_KEYS = "partitionKeys"
-    FIELD_PRIMARY_KEYS = "primaryKeys"
-    FIELD_OPTIONS = "options"
-    FIELD_COMMENT = "comment"
-
-    fields: List[DataField] = json_field(FIELD_FIELDS, default_factory=list)
-    partition_keys: List[str] = json_field(
-        FIELD_PARTITION_KEYS, default_factory=list)
-    primary_keys: List[str] = json_field(
-        FIELD_PRIMARY_KEYS, default_factory=list)
-    options: Dict[str, str] = json_field(FIELD_OPTIONS, default_factory=dict)
-    comment: Optional[str] = json_field(FIELD_COMMENT, default=None)
-
-
-@dataclass
-class TableSchema:
-    """Table schema with ID"""
-
-    id: int
-    fields: List[DataField]
-    highest_field_id: int
-    partition_keys: List[str]
-    primary_keys: List[str]
-    options: Dict[str, str]
-    comment: Optional[str]
-
-    def to_schema(self) -> Schema:
-        return Schema(
-            fields=self.fields,
-            partition_keys=self.partition_keys,
-            primary_keys=self.primary_keys,
-            options=self.options,
-            comment=self.comment,
-        )
-
-
-@dataclass
-class TableMetadata:
-    """Table metadata"""
-
-    schema: TableSchema
-    is_external: bool
-    uuid: str
-
-
 @dataclass
 class RESTToken:
     """REST authentication token"""
@@ -223,6 +183,24 @@ class GetTableResponse(AuditRESTResponse):
         self.schema_id = schema_id
         self.schema = schema
 
+    def get_id(self) -> str:
+        return self.id
+
+    def get_name(self) -> str:
+        return self.name
+
+    def get_path(self) -> str:
+        return self.path
+
+    def get_is_external(self) -> bool:
+        return self.is_external
+
+    def get_schema_id(self) -> int:
+        return self.schema_id
+
+    def get_schema(self) -> Schema:
+        return self.schema
+
 
 @dataclass
 class GetDatabaseResponse(AuditRESTResponse):
diff --git a/paimon-python/pypaimon/api/api_resquest.py 
b/paimon-python/pypaimon/api/api_resquest.py
index 7bc4f6b2a1..3ec7c12edc 100644
--- a/paimon-python/pypaimon/api/api_resquest.py
+++ b/paimon-python/pypaimon/api/api_resquest.py
@@ -21,7 +21,7 @@ from dataclasses import dataclass
 from typing import Dict, List
 
 from .api_response import Schema
-from .typedef import Identifier
+from .identifier import Identifier
 from .rest_json import json_field
 
 
diff --git a/paimon-python/pypaimon/api/core_options.py 
b/paimon-python/pypaimon/api/core_options.py
new file mode 100644
index 0000000000..2745d1c281
--- /dev/null
+++ b/paimon-python/pypaimon/api/core_options.py
@@ -0,0 +1,44 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+from enum import Enum
+
+
+class CoreOptions(str, Enum):
+    """Core options for paimon."""
+
+    def __str__(self):
+        return self.value
+
+    # Basic options
+    AUTO_CREATE = "auto-create"
+    PATH = "path"
+    TYPE = "type"
+    BRANCH = "branch"
+    BUCKET = "bucket"
+    BUCKET_KEY = "bucket-key"
+    WAREHOUSE = "warehouse"
+    # File format options
+    FILE_FORMAT = "file.format"
+    FILE_FORMAT_ORC = "orc"
+    FILE_FORMAT_AVRO = "avro"
+    FILE_FORMAT_PARQUET = "parquet"
+    FILE_COMPRESSION = "file.compression"
+    FILE_COMPRESSION_PER_LEVEL = "file.compression.per.level"
+    FILE_FORMAT_PER_LEVEL = "file.format.per.level"
+    FILE_BLOCK_SIZE = "file.block-size"
diff --git a/paimon-python/pypaimon/api/typedef.py 
b/paimon-python/pypaimon/api/identifier.py
similarity index 74%
copy from paimon-python/pypaimon/api/typedef.py
copy to paimon-python/pypaimon/api/identifier.py
index 0501d5e1e1..0d280bbf21 100644
--- a/paimon-python/pypaimon/api/typedef.py
+++ b/paimon-python/pypaimon/api/identifier.py
@@ -1,3 +1,4 @@
+################################################################################
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -6,26 +7,25 @@
 #  "License"); you may not use this file except in compliance
 #  with the License.  You may obtain a copy of the License at
 #
-#    http://www.apache.org/licenses/LICENSE-2.0
+#      http://www.apache.org/licenses/LICENSE-2.0
 #
-#  Unless required by applicable law or agreed to in writing,
-#  software distributed under the License is distributed on an
-#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-#  KIND, either express or implied.  See the License for the
-#  specific language governing permissions and limitations
-#  under the License.
-
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
 from dataclasses import dataclass
-from typing import Optional, TypeVar, Dict
+from typing import Optional
 
 from pypaimon.api.rest_json import json_field
 
-T = TypeVar("T")
+SYSTEM_TABLE_SPLITTER = '$'
+SYSTEM_BRANCH_PREFIX = 'branch-'
 
 
 @dataclass
 class Identifier:
-    """Table/View/Function identifier"""
 
     database_name: str = json_field("database", default=None)
     object_name: str = json_field("object", default=None)
@@ -64,11 +64,3 @@ class Identifier:
 
     def is_system_table(self) -> bool:
         return self.object_name.startswith('$')
-
-
-@dataclass
-class RESTAuthParameter:
-    method: str
-    path: str
-    data: str
-    parameters: Dict[str, str]
diff --git a/paimon-python/pypaimon/api/schema.py 
b/paimon-python/pypaimon/api/schema.py
new file mode 100644
index 0000000000..4bc06806c8
--- /dev/null
+++ b/paimon-python/pypaimon/api/schema.py
@@ -0,0 +1,52 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+from dataclasses import dataclass
+from typing import Optional, List, Dict
+
+import pyarrow as pa
+from pypaimon.api.data_types import DataField
+from pypaimon.api.rest_json import json_field
+
+
+@dataclass
+class Schema:
+    FIELD_FIELDS = "fields"
+    FIELD_PARTITION_KEYS = "partitionKeys"
+    FIELD_PRIMARY_KEYS = "primaryKeys"
+    FIELD_OPTIONS = "options"
+    FIELD_COMMENT = "comment"
+
+    pa_schema: Optional[pa.Schema] = None
+    fields: List[DataField] = json_field(FIELD_FIELDS, default_factory=list)
+    partition_keys: List[str] = json_field(
+        FIELD_PARTITION_KEYS, default_factory=list)
+    primary_keys: List[str] = json_field(
+        FIELD_PRIMARY_KEYS, default_factory=list)
+    options: Dict[str, str] = json_field(FIELD_OPTIONS, default_factory=dict)
+    comment: Optional[str] = json_field(FIELD_COMMENT, default=None)
+
+    @staticmethod
+    def from_dict(data: dict):
+        fields = [DataField.from_dict(field) for field in data["fields"]]
+        return Schema(
+            fields=fields,
+            partition_keys=data["partitionKeys"],
+            primary_keys=data["primaryKeys"],
+            options=data["options"],
+            comment=data.get("comment"),
+        )
diff --git a/paimon-python/pypaimon/api/table_schema.py 
b/paimon-python/pypaimon/api/table_schema.py
new file mode 100644
index 0000000000..ac0c2b7a11
--- /dev/null
+++ b/paimon-python/pypaimon/api/table_schema.py
@@ -0,0 +1,83 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import time
+from typing import List, Dict, Optional
+
+from pypaimon.api.schema import Schema
+from pypaimon.api.data_types import DataField
+
+
+class TableSchema:
+
+    def __init__(self, id: int, fields: List[DataField], highest_field_id: int,
+                 partition_keys: List[str], primary_keys: List[str], options: 
Dict[str, str],
+                 comment: Optional[str] = None, time_millis: Optional[int] = 
None):
+        self.id = id
+        self.fields = fields
+        self.highest_field_id = highest_field_id
+        self.partition_keys = partition_keys or []
+        self.primary_keys = primary_keys or []
+        self.options = options or {}
+        self.comment = comment
+        self.time_millis = time_millis if time_millis is not None else 
int(time.time() * 1000)
+
+    def to_schema(self) -> Schema:
+        # pa_schema = schema_util.convert_data_fields_to_pa_schema(self.fields)
+        return Schema(
+            fields=self.fields,
+            partition_keys=self.partition_keys,
+            primary_keys=self.primary_keys,
+            options=self.options,
+            comment=self.comment
+        )
+
+    @staticmethod
+    def create(schema_id: int, schema: Schema) -> "TableSchema":
+        fields: List[DataField] = schema.fields
+
+        partition_keys: List[str] = schema.partition_keys
+
+        primary_keys: List[str] = schema.primary_keys
+
+        options: Dict[str, str] = schema.options
+
+        highest_field_id: int = None
+
+        return TableSchema(
+            schema_id,
+            fields,
+            highest_field_id,
+            partition_keys,
+            primary_keys,
+            options,
+            schema.comment,
+            int(time.time())
+        )
+
+    def copy(self, new_options: Optional[Dict[str, str]] = None) -> 
"TableSchema":
+        return TableSchema(
+            id=self.id,
+            fields=self.fields,
+            highest_field_id=self.highest_field_id,
+            partition_keys=self.partition_keys,
+            primary_keys=self.primary_keys,
+            options=new_options,
+            comment=self.comment,
+            time_millis=self.time_millis
+        )
diff --git a/paimon-python/pypaimon/api/typedef.py 
b/paimon-python/pypaimon/api/typedef.py
index 0501d5e1e1..f70ef9adf4 100644
--- a/paimon-python/pypaimon/api/typedef.py
+++ b/paimon-python/pypaimon/api/typedef.py
@@ -16,56 +16,12 @@
 #  under the License.
 
 from dataclasses import dataclass
-from typing import Optional, TypeVar, Dict
+from typing import TypeVar, Dict
 
-from pypaimon.api.rest_json import json_field
 
 T = TypeVar("T")
 
 
-@dataclass
-class Identifier:
-    """Table/View/Function identifier"""
-
-    database_name: str = json_field("database", default=None)
-    object_name: str = json_field("object", default=None)
-    branch_name: Optional[str] = json_field("branch", default=None)
-
-    @classmethod
-    def create(cls, database_name: str, object_name: str) -> "Identifier":
-        return cls(database_name, object_name)
-
-    @classmethod
-    def from_string(cls, full_name: str) -> "Identifier":
-        parts = full_name.split(".")
-        if len(parts) == 2:
-            return cls(parts[0], parts[1])
-        elif len(parts) == 3:
-            return cls(parts[0], parts[1], parts[2])
-        else:
-            raise ValueError(f"Invalid identifier format: {full_name}")
-
-    def get_full_name(self) -> str:
-        if self.branch_name:
-            return 
f"{self.database_name}.{self.object_name}.{self.branch_name}"
-        return f"{self.database_name}.{self.object_name}"
-
-    def get_database_name(self) -> str:
-        return self.database_name
-
-    def get_table_name(self) -> str:
-        return self.object_name
-
-    def get_object_name(self) -> str:
-        return self.object_name
-
-    def get_branch_name(self) -> Optional[str]:
-        return self.branch_name
-
-    def is_system_table(self) -> bool:
-        return self.object_name.startswith('$')
-
-
 @dataclass
 class RESTAuthParameter:
     method: str
diff --git a/paimon-python/pypaimon/catalog/catalog_loader.py 
b/paimon-python/pypaimon/catalog/catalog_loader.py
new file mode 100644
index 0000000000..09b62492ff
--- /dev/null
+++ b/paimon-python/pypaimon/catalog/catalog_loader.py
@@ -0,0 +1,24 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from abc import ABC, abstractmethod
+
+
+class CatalogLoader(ABC):
+    @abstractmethod
+    def load(self):
+        pass
diff --git a/paimon-python/pypaimon/catalog/catalog_utils.py 
b/paimon-python/pypaimon/catalog/catalog_utils.py
new file mode 100644
index 0000000000..d51de0754d
--- /dev/null
+++ b/paimon-python/pypaimon/catalog/catalog_utils.py
@@ -0,0 +1,55 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from pathlib import Path
+from typing import Callable, Any
+
+from pypaimon.api.core_options import CoreOptions
+from pypaimon.api.identifier import Identifier
+
+from pypaimon.catalog.catalog import Catalog
+from pypaimon.catalog.table_metadata import TableMetadata
+from pypaimon.table.catalog_environment import CatalogEnvironment
+from pypaimon.table.file_store_table import FileStoreTable
+from pypaimon.table.file_store_table_factory import FileStoreTableFactory
+
+
+class CatalogUtils:
+    @staticmethod
+    def load_table(
+            identifier: Identifier,
+            internal_file_io: Callable[[Path], Any],
+            external_file_io: Callable[[Path], Any],
+            metadata_loader: Callable[[Identifier], TableMetadata],
+    ) -> FileStoreTable:
+        metadata = metadata_loader(identifier)
+        schema = metadata.schema
+        data_file_io = external_file_io if metadata.is_external else 
internal_file_io
+        catalog_env = CatalogEnvironment(
+            identifier=identifier,
+            uuid=metadata.uuid,
+            catalog_loader=None,
+            supports_version_management=False
+        )
+
+        path = Path(schema.options.get(CoreOptions.PATH))
+        table = FileStoreTableFactory.create(data_file_io(path), path, schema, 
catalog_env)
+        return table
+
+    @staticmethod
+    def is_system_database(database_name: str) -> bool:
+        return Catalog.SYSTEM_DATABASE_NAME.equals(database_name)
diff --git a/paimon-python/pypaimon/catalog/table_metadata.py 
b/paimon-python/pypaimon/catalog/table_metadata.py
new file mode 100644
index 0000000000..db79ac522a
--- /dev/null
+++ b/paimon-python/pypaimon/catalog/table_metadata.py
@@ -0,0 +1,40 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from typing import Optional
+
+from pypaimon.api.table_schema import TableSchema
+
+
+class TableMetadata:
+
+    def __init__(self, schema: TableSchema, is_external: bool, uuid: 
Optional[str] = None):
+        self._schema = schema
+        self._is_external = is_external
+        self._uuid = uuid
+
+    @property
+    def schema(self) -> TableSchema:
+        return self._schema
+
+    @property
+    def is_external(self) -> bool:
+        return self._is_external
+
+    @property
+    def uuid(self) -> Optional[str]:
+        return self._uuid
diff --git a/paimon-python/pypaimon/common/__init__.py 
b/paimon-python/pypaimon/common/__init__.py
new file mode 100644
index 0000000000..53ed4d36c2
--- /dev/null
+++ b/paimon-python/pypaimon/common/__init__.py
@@ -0,0 +1,17 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
diff --git a/paimon-python/pypaimon/common/file_io.py 
b/paimon-python/pypaimon/common/file_io.py
new file mode 100644
index 0000000000..438b73ce1d
--- /dev/null
+++ b/paimon-python/pypaimon/common/file_io.py
@@ -0,0 +1,25 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+from abc import ABC, abstractmethod
+from pathlib import Path
+
+
+class FileIO(ABC):
+    @abstractmethod
+    def exists(self, path: Path) -> bool:
+        raise NotImplementedError("Method 'exists' must be implemented by 
subclasses.")
diff --git a/paimon-python/pypaimon/pvfs/__init__.py 
b/paimon-python/pypaimon/pvfs/__init__.py
index 36ba2f2909..25dd7df7ea 100644
--- a/paimon-python/pypaimon/pvfs/__init__.py
+++ b/paimon-python/pypaimon/pvfs/__init__.py
@@ -31,9 +31,8 @@ import fsspec
 from fsspec import AbstractFileSystem
 from fsspec.implementations.local import LocalFileSystem
 
-from pypaimon.api import RESTApi, GetTableTokenResponse, Schema, 
GetTableResponse
+from pypaimon.api import RESTApi, GetTableTokenResponse, Schema, 
GetTableResponse, Identifier
 from pypaimon.api.client import NoSuchResourceException, AlreadyExistsException
-from pypaimon.api.typedef import Identifier
 from pypaimon.api.config import RESTCatalogOptions, OssOptions, PVFSOptions
 
 PROTOCOL_NAME = "pvfs"
diff --git a/paimon-python/pypaimon/rest/rest_catalog.py 
b/paimon-python/pypaimon/rest/rest_catalog.py
index 8fca50501b..2f76aa5a1d 100644
--- a/paimon-python/pypaimon/rest/rest_catalog.py
+++ b/paimon-python/pypaimon/rest/rest_catalog.py
@@ -15,16 +15,26 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 """
+from pathlib import Path
 from typing import List, Dict, Optional
 
 from pypaimon.api import RESTApi, RESTCatalogOptions
-from pypaimon.api.api_response import PagedList
+from pypaimon.api.api_response import PagedList, GetTableResponse
+from pypaimon.api.core_options import CoreOptions
+from pypaimon.api.identifier import Identifier
 from pypaimon.api.options import Options
 
+from pypaimon.api.schema import Schema
+from pypaimon.api.table_schema import TableSchema
+
 from pypaimon.catalog.catalog import Catalog
 from pypaimon.catalog.catalog_context import CatalogContext
+from pypaimon.catalog.catalog_utils import CatalogUtils
 from pypaimon.catalog.database import Database
 from pypaimon.catalog.property_change import PropertyChange
+from pypaimon.catalog.table_metadata import TableMetadata
+from pypaimon.rest.rest_token_file_io import RESTTokenFileIO
+from pypaimon.table.file_store_table import FileStoreTable
 
 
 class RESTCatalog(Catalog):
@@ -48,6 +58,7 @@ class RESTCatalog(Catalog):
         response = self.api.get_database(name)
         options = response.options
         options[Catalog.DB_LOCATION_PROP] = response.location
+        response.put_audit_options_to(options)
         if response is not None:
             return Database(name, options)
 
@@ -74,3 +85,37 @@ class RESTCatalog(Catalog):
             page_token,
             table_name_pattern
         )
+
+    def get_table(self, identifier: Identifier) -> FileStoreTable:
+        return CatalogUtils.load_table(
+            identifier,
+            lambda path: self.file_io_for_data(path, identifier),
+            self.file_io_from_options,
+            self.load_table_metadata,
+        )
+
+    def load_table_metadata(self, identifier: Identifier) -> TableMetadata:
+        response = self.api.get_table(identifier)
+        return self.to_table_metadata(identifier.get_database_name(), response)
+
+    def to_table_metadata(self, db: str, response: GetTableResponse) -> 
TableMetadata:
+        schema = TableSchema.create(response.get_schema_id(), 
Schema.from_dict(response.get_schema()))
+        options: Dict[str, str] = dict(schema.options)
+        options[CoreOptions.PATH] = response.get_path()
+        response.put_audit_options_to(options)
+
+        identifier = Identifier.create(db, response.get_name())
+        if identifier.get_branch_name() is not None:
+            options[CoreOptions.BRANCH] = identifier.get_branch_name()
+
+        return TableMetadata(
+            schema=schema.copy(options),
+            is_external=response.get_is_external(),
+            uuid=response.get_id()
+        )
+
+    def file_io_from_options(self, path: Path):
+        return None
+
+    def file_io_for_data(self, path: Path, identifier: Identifier):
+        return RESTTokenFileIO(identifier, path, None, None) if 
self.data_token_enabled else None
diff --git a/paimon-python/pypaimon/rest/rest_token_file_io.py 
b/paimon-python/pypaimon/rest/rest_token_file_io.py
new file mode 100644
index 0000000000..5b179b95f0
--- /dev/null
+++ b/paimon-python/pypaimon/rest/rest_token_file_io.py
@@ -0,0 +1,34 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from pathlib import Path
+from typing import Optional
+
+from pypaimon.api.identifier import Identifier
+from pypaimon.common.file_io import FileIO
+
+
+class RESTTokenFileIO(FileIO):
+
+    def __init__(self, identifier: Identifier, path: Path, warehouse: 
Optional[str] = None,
+                 catalog_options: Optional[dict] = None):
+        super().__init__(warehouse, catalog_options)
+        self.identifier = identifier
+        self.path = path
+
+    def exists(self, path: Path) -> bool:
+        pass
diff --git a/paimon-python/pypaimon/schema/__init__.py 
b/paimon-python/pypaimon/schema/__init__.py
new file mode 100644
index 0000000000..53ed4d36c2
--- /dev/null
+++ b/paimon-python/pypaimon/schema/__init__.py
@@ -0,0 +1,17 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
diff --git a/paimon-python/pypaimon/schema/schema_manager.py 
b/paimon-python/pypaimon/schema/schema_manager.py
new file mode 100644
index 0000000000..c87240062b
--- /dev/null
+++ b/paimon-python/pypaimon/schema/schema_manager.py
@@ -0,0 +1,29 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+from pathlib import Path
+
+from pypaimon.common.file_io import FileIO
+
+
+class SchemaManager:
+
+    def __init__(self, file_io: FileIO, table_path: Path):
+        self.schema_prefix = "schema-"
+        self.file_io = file_io
+        self.table_path = table_path
+        self.schema_path = table_path / "schema"
diff --git a/paimon-python/pypaimon/table/__init__.py 
b/paimon-python/pypaimon/table/__init__.py
new file mode 100644
index 0000000000..53ed4d36c2
--- /dev/null
+++ b/paimon-python/pypaimon/table/__init__.py
@@ -0,0 +1,17 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
diff --git a/paimon-python/pypaimon/table/catalog_environment.py 
b/paimon-python/pypaimon/table/catalog_environment.py
new file mode 100644
index 0000000000..3150e2d78e
--- /dev/null
+++ b/paimon-python/pypaimon/table/catalog_environment.py
@@ -0,0 +1,36 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from typing import Optional
+
+from pypaimon.api.identifier import Identifier
+from pypaimon.catalog.catalog_loader import CatalogLoader
+
+
+class CatalogEnvironment:
+
+    def __init__(
+            self,
+            identifier: Optional[Identifier] = None,
+            uuid: Optional[str] = None,
+            catalog_loader: Optional[CatalogLoader] = None,
+            supports_version_management: bool = False
+    ):
+        self.identifier = identifier
+        self.uuid = uuid
+        self.catalog_loader = catalog_loader
+        self.supports_version_management = supports_version_management
diff --git a/paimon-python/pypaimon/table/file_store_table.py 
b/paimon-python/pypaimon/table/file_store_table.py
new file mode 100644
index 0000000000..de5f8a870f
--- /dev/null
+++ b/paimon-python/pypaimon/table/file_store_table.py
@@ -0,0 +1,42 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+from pathlib import Path
+
+from pypaimon.api.identifier import Identifier
+from pypaimon.api.table_schema import TableSchema
+from pypaimon.common.file_io import FileIO
+from pypaimon.schema.schema_manager import SchemaManager
+from pypaimon.table.table import Table
+
+
+class FileStoreTable(Table):
+    def __init__(self, file_io: FileIO, identifier: Identifier, table_path: 
Path,
+                 table_schema: TableSchema):
+        self.file_io = file_io
+        self.identifier = identifier
+        self.table_path = table_path
+
+        self.fields = table_schema.fields
+        self.primary_keys = table_schema.primary_keys
+        self.partition_keys = table_schema.partition_keys
+
+        self.options = table_schema.options
+        self.table_schema = table_schema
+        self.schema_manager = SchemaManager(file_io, table_path)
+        self.is_primary_key_table = bool(self.primary_keys)
diff --git a/paimon-python/pypaimon/table/file_store_table_factory.py 
b/paimon-python/pypaimon/table/file_store_table_factory.py
new file mode 100644
index 0000000000..5569f60d8e
--- /dev/null
+++ b/paimon-python/pypaimon/table/file_store_table_factory.py
@@ -0,0 +1,35 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+from pathlib import Path
+
+from pypaimon.api.table_schema import TableSchema
+from pypaimon.common.file_io import FileIO
+from pypaimon.table.catalog_environment import CatalogEnvironment
+from pypaimon.table.file_store_table import FileStoreTable
+
+
+class FileStoreTableFactory:
+    @staticmethod
+    def create(
+            file_io: FileIO,
+            table_path: Path,
+            table_schema: TableSchema,
+            catalog_environment: CatalogEnvironment
+    ) -> FileStoreTable:
+        """Create FileStoreTable with dynamic options and catalog 
environment"""
+        return FileStoreTable(file_io, catalog_environment.identifier, 
table_path, table_schema)
diff --git a/paimon-python/pypaimon/table/table.py 
b/paimon-python/pypaimon/table/table.py
new file mode 100644
index 0000000000..6f15b049ae
--- /dev/null
+++ b/paimon-python/pypaimon/table/table.py
@@ -0,0 +1,23 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+
+from abc import ABC
+
+
+class Table(ABC):
+    """A table provides basic abstraction for table read and write."""
diff --git a/paimon-python/pypaimon/tests/api_test.py 
b/paimon-python/pypaimon/tests/api_test.py
index 63bd994ab7..d32c5c201c 100644
--- a/paimon-python/pypaimon/tests/api_test.py
+++ b/paimon-python/pypaimon/tests/api_test.py
@@ -21,15 +21,18 @@ import unittest
 
 import pypaimon.api as api
 from .rest_server import RESTCatalogServer
-from ..api.api_response import (ConfigResponse, TableMetadata, TableSchema, 
DataField)
+from ..api.api_response import (ConfigResponse)
 from ..api import RESTApi
 from ..api.auth import BearTokenAuthProvider
+from ..api.identifier import Identifier
 from ..api.options import Options
 from ..api.rest_json import JSON
+from ..api.table_schema import TableSchema
 from ..api.token_loader import DLFTokenLoaderFactory, DLFToken
-from ..api.typedef import Identifier
-from ..api.data_types import AtomicInteger, DataTypeParser, AtomicType, 
ArrayType, MapType, RowType
+
+from ..api.data_types import AtomicInteger, DataTypeParser, AtomicType, 
ArrayType, MapType, RowType, DataField
 from ..catalog.catalog_context import CatalogContext
+from ..catalog.table_metadata import TableMetadata
 from ..rest.rest_catalog import RESTCatalog
 
 
@@ -226,6 +229,8 @@ class ApiTestCase(unittest.TestCase):
             rest_catalog = 
RESTCatalog(CatalogContext.create_from_options(Options(options)))
             self.assertSetEqual(set(rest_catalog.list_databases()), 
{*test_databases})
             self.assertEqual(rest_catalog.get_database('default').name, 
test_databases.get('default').name)
+            table = 
rest_catalog.get_table(Identifier.from_string('default.user'))
+            self.assertEqual(table.identifier.get_full_name(), 'default.user')
         finally:
             # Shutdown server
             server.shutdown()
diff --git a/paimon-python/pypaimon/tests/pvfs_test.py 
b/paimon-python/pypaimon/tests/pvfs_test.py
index c3248dcea7..3b084d0019 100644
--- a/paimon-python/pypaimon/tests/pvfs_test.py
+++ b/paimon-python/pypaimon/tests/pvfs_test.py
@@ -23,9 +23,10 @@ import pandas
 from pathlib import Path
 
 from pypaimon.api import ConfigResponse
-from pypaimon.api.api_response import TableSchema, TableMetadata
 from pypaimon.api.auth import BearTokenAuthProvider
 from pypaimon.api.data_types import DataField, AtomicType
+from pypaimon.api.table_schema import TableSchema
+from pypaimon.catalog.table_metadata import TableMetadata
 from pypaimon.pvfs import PaimonVirtualFileSystem
 from pypaimon.tests.api_test import RESTCatalogServer
 
diff --git a/paimon-python/pypaimon/tests/rest_server.py 
b/paimon-python/pypaimon/tests/rest_server.py
index 5439402c6a..0bcf5d1dd3 100644
--- a/paimon-python/pypaimon/tests/rest_server.py
+++ b/paimon-python/pypaimon/tests/rest_server.py
@@ -28,12 +28,13 @@ from http.server import HTTPServer, BaseHTTPRequestHandler
 from urllib.parse import urlparse
 
 import pypaimon.api as api
-from ..api import RenameTableRequest, CreateTableRequest, CreateDatabaseRequest
+from ..api import RenameTableRequest, CreateTableRequest, 
CreateDatabaseRequest, Identifier
 from ..api.api_response import (ConfigResponse, ListDatabasesResponse, 
GetDatabaseResponse,
-                                TableMetadata, Schema, GetTableResponse, 
ListTablesResponse,
-                                TableSchema, RESTResponse, PagedList)
+                                Schema, GetTableResponse, ListTablesResponse,
+                                RESTResponse, PagedList)
 from ..api.rest_json import JSON
-from ..api.typedef import Identifier
+from ..api.table_schema import TableSchema
+from ..catalog.table_metadata import TableMetadata
 
 
 @dataclass


Reply via email to