This is an automated email from the ASF dual-hosted git repository.

ColinLeeo pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new a2fdf7116 Python error code alignment (#870)
a2fdf7116 is described below

commit a2fdf71161790af959a4311f49a88ff0c46abc4a
Author: Colin Lee <[email protected]>
AuthorDate: Mon Jul 27 15:40:11 2026 +0800

    Python error code alignment (#870)
---
 cpp/examples/c_examples/demo_read.c             |   2 +-
 cpp/src/cwrapper/errno_define_c.h               |  54 +--
 cpp/src/file/read_file.cc                       |   4 +-
 cpp/src/reader/tsfile_executor.cc               |   2 +-
 cpp/src/reader/tsfile_reader.cc                 |  32 +-
 cpp/src/utils/errno_define.h                    |  16 -
 cpp/test/cwrapper/c_release_test.cc             |  19 +-
 cpp/test/cwrapper/query_by_row_cwrapper_test.cc |  27 ++
 cpp/tools/format/output_format.cc               |   2 -
 python/tests/test_exceptions.py                 | 142 ++++++
 python/tests/test_write_and_read.py             |   8 +-
 python/tsfile/exceptions.py                     | 145 ++++++-
 python/tsfile/tsfile_cpp.pxd                    |   5 +
 python/tsfile/tsfile_py_cpp.pxd                 |   4 +-
 python/tsfile/tsfile_py_cpp.pyx                 | 554 ++++++++++++++++--------
 python/tsfile/tsfile_reader.pyx                 |  18 +-
 python/tsfile/tsfile_writer.pyx                 |   6 +-
 python/tsfile/utils.py                          |   8 +-
 18 files changed, 787 insertions(+), 261 deletions(-)

diff --git a/cpp/examples/c_examples/demo_read.c 
b/cpp/examples/c_examples/demo_read.c
index 5ac6111e7..efa6b63d0 100644
--- a/cpp/examples/c_examples/demo_read.c
+++ b/cpp/examples/c_examples/demo_read.c
@@ -37,7 +37,7 @@ ERRNO read_tsfile() {
     HANDLE_ERROR(code);
 
     if (ret == NULL) {
-        HANDLE_ERROR(RET_INVALID_QUERY);
+        HANDLE_ERROR(RET_INVALID_ARG);
     }
 
     // Get query result metadata: column name and datatype
diff --git a/cpp/src/cwrapper/errno_define_c.h 
b/cpp/src/cwrapper/errno_define_c.h
index 3ceb06416..3ad1c1301 100644
--- a/cpp/src/cwrapper/errno_define_c.h
+++ b/cpp/src/cwrapper/errno_define_c.h
@@ -17,8 +17,8 @@
  * under the License.
  */
 
-#ifndef CWRAPPER_ERRNO_DEFINRET_H
-#define CWRAPPER_ERRNO_DEFINRET_H
+#ifndef CWRAPPER_ERRNO_DEFINE_H
+#define CWRAPPER_ERRNO_DEFINE_H
 
 #define RET_OK 0
 #define RET_OOM 1
@@ -28,49 +28,33 @@
 #define RET_OUT_OF_RANGE 5
 #define RET_PARTIAL_READ 6
 #define RET_INVALID_SCHEMA 8
-#define RET_NET_EPOLL_ERR 9
-#define RET_NET_EPOLL_WAIT_ERR 10
-#define RET_NET_RECV_ERR 11
-#define RET_NET_ACCEPT_ERR 12
-#define RET_NET_FCNTL_ERR 13
-#define RET_NET_LISTEN_ERR 14
-#define RET_NET_SEND_ERR 15
-#define RET_PIPRET_ERR 16
-#define RET_THREAD_CREATRET_ERR 17
-#define RET_MUTEX_ERR 18
-#define RET_COND_ERR 19
 #define RET_OVERFLOW 20
-#define RET_NO_MORRET_DATA 21
+#define RET_NO_MORE_DATA 21
 #define RET_OUT_OF_ORDER 22
-#define RET_TSBLOCK_TYPRET_NOT_SUPPORTED 23
 #define RET_TSBLOCK_DATA_INCONSISTENCY 24
-#define RET_DDL_UNKNOWN_TYPE 25
-#define RET_TYPRET_NOT_SUPPORTED 26
-#define RET_TYPRET_NOT_MATCH 27
-#define RET_FILRET_OPEN_ERR 28
-#define RET_FILRET_CLOSRET_ERR 29
-#define RET_FILRET_WRITRET_ERR 30
-#define RET_FILRET_READ_ERR 31
-#define RET_FILRET_SYNC_ERR 32
-#define RET_TSFILRET_WRITER_META_ERR 33
-#define RET_FILRET_STAT_ERR 34
-#define RET_TSFILRET_CORRUPTED 35
+#define RET_TYPE_NOT_SUPPORTED 26
+#define RET_TYPE_NOT_MATCH 27
+#define RET_FILE_OPEN_ERR 28
+#define RET_FILE_CLOSE_ERR 29
+#define RET_FILE_WRITE_ERR 30
+#define RET_FILE_READ_ERR 31
+#define RET_FILE_SYNC_ERR 32
+#define RET_TSFILE_WRITER_META_ERR 33
+#define RET_FILE_STAT_ERR 34
+#define RET_TSFILE_CORRUPTED 35
 #define RET_BUF_NOT_ENOUGH 36
 #define RET_INVALID_PATH 37
 #define RET_NOT_MATCH 38
-#define RET_JSON_INVALID 39
 #define RET_NOT_SUPPORT 40
-#define RET_PARSER_ERR 41
-#define RET_ANALYZRET_ERR 42
 #define RET_INVALID_DATA_POINT 43
-#define RET_DEVICRET_NOT_EXIST 44
+#define RET_DEVICE_NOT_EXIST 44
 #define RET_MEASUREMENT_NOT_EXIST 45
-#define RET_INVALID_QUERY 46
-#define RET_SDK_QUERY_OPTIMIZRET_ERR 47
 #define RET_COMPRESS_ERR 48
-#define RET_TABLRET_NOT_EXIST 49
+#define RET_TABLE_NOT_EXIST 49
 #define RET_COLUMN_NOT_EXIST 50
 #define RET_UNSUPPORTED_ORDER 51
-#define RET_INVALID_NODRET_TYPE 52
+#define RET_INVALID_NODE_TYPE 52
+#define RET_ENCODE_ERR 53
+#define RET_DECODE_ERR 54
 
-#endif /* CWRAPPER_ERRNO_DEFINRET_H */
\ No newline at end of file
+#endif /* CWRAPPER_ERRNO_DEFINE_H */
diff --git a/cpp/src/file/read_file.cc b/cpp/src/file/read_file.cc
index c6bfd547a..7d41d7095 100644
--- a/cpp/src/file/read_file.cc
+++ b/cpp/src/file/read_file.cc
@@ -40,7 +40,7 @@ using namespace common;
 namespace storage {
 
 void ReadFile::close() {
-    if (fd_ > 0) {
+    if (fd_ >= 0) {
         ::close(fd_);
         fd_ = -1;
     }
@@ -65,7 +65,7 @@ int ReadFile::open(const std::string& file_path) {
     } else if (RET_FAIL(check_file_magic())) {
     }
     if (IS_FAIL(ret)) {
-        ::close(fd_);
+        close();
     }
     return ret;
 }
diff --git a/cpp/src/reader/tsfile_executor.cc 
b/cpp/src/reader/tsfile_executor.cc
index f2d34167d..15aaa161b 100644
--- a/cpp/src/reader/tsfile_executor.cc
+++ b/cpp/src/reader/tsfile_executor.cc
@@ -67,7 +67,7 @@ int TsFileExecutor::execute(QueryExpression* query_expr, 
ResultSet*& ret_qds) {
     if (query_exprs_->has_filter_) {
         regular_expr = query_exprs_->optimize(origin_expr, paths);
         if (regular_expr == nullptr) {
-            return E_SDK_QUERY_OPTIMIZE_ERR;
+            return E_INVALID_ARG;
         }
         query_exprs_->set_expression(regular_expr);
     }
diff --git a/cpp/src/reader/tsfile_reader.cc b/cpp/src/reader/tsfile_reader.cc
index 540674f33..33d0d8967 100644
--- a/cpp/src/reader/tsfile_reader.cc
+++ b/cpp/src/reader/tsfile_reader.cc
@@ -18,6 +18,8 @@
  */
 #include "tsfile_reader.h"
 
+#include <stdexcept>
+
 #include "common/schema.h"
 #include "filter/time_operator.h"
 #include "tsfile_executor.h"
@@ -33,6 +35,19 @@ struct DeviceMetaEntry {
     int64_t end_offset;
 };
 
+int parse_paths(const std::vector<std::string>& path_list,
+                std::vector<Path>& parsed_paths) {
+    try {
+        parsed_paths.reserve(path_list.size());
+        for (const auto& path : path_list) {
+            parsed_paths.emplace_back(path, true);
+        }
+    } catch (const std::runtime_error&) {
+        return E_INVALID_PATH;
+    }
+    return E_OK;
+}
+
 int get_all_device_entries(std::vector<DeviceMetaEntry>& entries,
                            std::shared_ptr<MetaIndexNode> index_node,
                            ReadFile* read_file, PageArena& pa) {
@@ -152,14 +167,15 @@ int TsFileReader::query(QueryExpression* qe, ResultSet*& 
ret_qds) {
 
 int TsFileReader::query(std::vector<std::string>& path_list, int64_t 
start_time,
                         int64_t end_time, ResultSet*& result_set) {
-    int ret = E_OK;
+    std::vector<Path> path_list_vec;
+    int ret = parse_paths(path_list, path_list_vec);
+    if (ret != E_OK) {
+        return ret;
+    }
+
     Filter* time_filter = new TimeBetween(start_time, end_time, false);
     Expression* exp =
         new storage::Expression(storage::GLOBALTIME_EXPR, time_filter);
-    std::vector<Path> path_list_vec;
-    for (const auto& path : path_list) {
-        path_list_vec.emplace_back(Path(path, true));
-    }
     QueryExpression* query_expression =
         QueryExpression::create(path_list_vec, exp);
     ret = tsfile_executor_->execute(query_expression, result_set);
@@ -200,10 +216,10 @@ int TsFileReader::query(const std::string& table_name,
 
 int TsFileReader::queryByRow(std::vector<std::string>& path_list, int offset,
                              int limit, ResultSet*& result_set) {
-    int ret = E_OK;
     std::vector<Path> path_list_vec;
-    for (const auto& path : path_list) {
-        path_list_vec.emplace_back(Path(path, true));
+    int ret = parse_paths(path_list, path_list_vec);
+    if (ret != E_OK) {
+        return ret;
     }
     QueryExpression* query_expression =
         QueryExpression::create(path_list_vec, nullptr);
diff --git a/cpp/src/utils/errno_define.h b/cpp/src/utils/errno_define.h
index f52cead99..ca2d1397a 100644
--- a/cpp/src/utils/errno_define.h
+++ b/cpp/src/utils/errno_define.h
@@ -29,17 +29,6 @@ const int E_INVALID_ARG = 4;
 const int E_OUT_OF_RANGE = 5;
 const int E_PARTIAL_READ = 6;
 const int E_INVALID_SCHEMA = 8;
-const int E_NET_EPOLL_ERR = 9;
-const int E_NET_EPOLL_WAIT_ERR = 10;
-const int E_NET_RECV_ERR = 11;
-const int E_NET_ACCEPT_ERR = 12;
-const int E_NET_FCNTL_ERR = 13;
-const int E_NET_LISTEN_ERR = 14;
-const int E_NET_SEND_ERR = 15;
-const int E_PIPE_ERR = 16;
-const int E_THREAD_CREATE_ERR = 17;
-const int E_MUTEX_ERR = 18;
-const int E_COND_ERR = 19;
 const int E_OVERFLOW = 20;
 const int E_NO_MORE_DATA = 21;
 const int E_OUT_OF_ORDER = 22;
@@ -57,15 +46,10 @@ const int E_TSFILE_CORRUPTED = 35;
 const int E_BUF_NOT_ENOUGH = 36;
 const int E_INVALID_PATH = 37;
 const int E_NOT_MATCH = 38;
-const int E_JSON_INVALID = 39;
 const int E_NOT_SUPPORT = 40;
-const int E_PARSER_ERR = 41;
-const int E_ANALYZE_ERR = 42;
 const int E_INVALID_DATA_POINT = 43;
 const int E_DEVICE_NOT_EXIST = 44;
 const int E_MEASUREMENT_NOT_EXIST = 45;
-const int E_INVALID_QUERY = 46;
-const int E_SDK_QUERY_OPTIMIZE_ERR = 47;
 const int E_COMPRESS_ERR = 48;
 const int E_TABLE_NOT_EXIST = 49;
 const int E_COLUMN_NOT_EXIST = 50;
diff --git a/cpp/test/cwrapper/c_release_test.cc 
b/cpp/test/cwrapper/c_release_test.cc
index bb21483f7..ae3f5cb3d 100644
--- a/cpp/test/cwrapper/c_release_test.cc
+++ b/cpp/test/cwrapper/c_release_test.cc
@@ -25,6 +25,7 @@
 #endif
 #include <utils/db_utils.h>
 
+#include <cstdio>
 #include <cstring>
 #include <string>
 
@@ -54,13 +55,27 @@ TEST_F(CReleaseTest, TestCreateFile) {
     // Folder: rejected either as an open error (POSIX) or as already-existing
     // (Windows / filesystems where the directory already exists).
     file = write_file_new("test/", &error_no);
-    ASSERT_TRUE(error_no == RET_FILRET_OPEN_ERR ||
-                error_no == RET_ALREADY_EXIST);
+    ASSERT_TRUE(error_no == RET_FILE_OPEN_ERR || error_no == 
RET_ALREADY_EXIST);
 
     remove("create_file1.tsfile");
     free_write_file(&file);
 }
 
+TEST_F(CReleaseTest, RejectCorruptedFileWithoutDoubleClosingDescriptor) {
+    const char* file_name = "corrupted_empty_file.tsfile";
+    remove(file_name);
+    FILE* empty_file = fopen(file_name, "wb");
+    ASSERT_NE(nullptr, empty_file);
+    ASSERT_EQ(0, fclose(empty_file));
+
+    ERRNO error_no = RET_OK;
+    TsFileReader reader = tsfile_reader_new(file_name, &error_no);
+    EXPECT_EQ(nullptr, reader);
+    EXPECT_EQ(RET_TSFILE_CORRUPTED, error_no);
+
+    remove(file_name);
+}
+
 TEST_F(CReleaseTest, TsFileWriterNew) {
     ERRNO error_code = RET_OK;
 
diff --git a/cpp/test/cwrapper/query_by_row_cwrapper_test.cc 
b/cpp/test/cwrapper/query_by_row_cwrapper_test.cc
index 4983c57ea..6f36c491c 100644
--- a/cpp/test/cwrapper/query_by_row_cwrapper_test.cc
+++ b/cpp/test/cwrapper/query_by_row_cwrapper_test.cc
@@ -193,6 +193,33 @@ TEST_F(CWrapperQueryByRowTest, TreeByRowOffsetLimit) {
     storage::libtsfile_destroy();
 }
 
+TEST_F(CWrapperQueryByRowTest, InvalidTreePathReturnsErrorCode) {
+    storage::libtsfile_init();
+
+    const char* file_name = "cwrapper_invalid_tree_path_test.tsfile";
+    remove(file_name);
+    write_tree_tsfile(file_name, {"root.d1"}, {"s1"}, 1);
+
+    ERRNO code = RET_OK;
+    TsFileReader reader = tsfile_reader_new(file_name, &code);
+    ASSERT_EQ(code, RET_OK);
+    ASSERT_NE(reader, nullptr);
+
+    char device_id[] = "root.d1";
+    char invalid_measurement[] = "a*%";
+    char* device_ids[] = {device_id};
+    char* measurement_ids[] = {invalid_measurement};
+    ResultSet result = tsfile_reader_query_tree_by_row(
+        reader, device_ids, 1, measurement_ids, 1, 0, -1, &code);
+
+    EXPECT_EQ(code, RET_INVALID_PATH);
+    EXPECT_EQ(result, nullptr);
+    EXPECT_EQ(tsfile_reader_close(reader), RET_OK);
+    remove(file_name);
+
+    storage::libtsfile_destroy();
+}
+
 TEST_F(CWrapperQueryByRowTest, TableByRowOffsetLimit) {
     storage::libtsfile_init();
 
diff --git a/cpp/tools/format/output_format.cc 
b/cpp/tools/format/output_format.cc
index e9ae8cb0b..2962cda25 100644
--- a/cpp/tools/format/output_format.cc
+++ b/cpp/tools/format/output_format.cc
@@ -58,8 +58,6 @@ const char* error_code_message(int code) {
             return "table does not exist";
         case common::E_COLUMN_NOT_EXIST:
             return "column does not exist";
-        case common::E_INVALID_QUERY:
-            return "invalid query";
         case common::E_TYPE_NOT_SUPPORTED:
             return "data type not supported";
         case common::E_TYPE_NOT_MATCH:
diff --git a/python/tests/test_exceptions.py b/python/tests/test_exceptions.py
new file mode 100644
index 000000000..dbc12e792
--- /dev/null
+++ b/python/tests/test_exceptions.py
@@ -0,0 +1,142 @@
+# 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 pytest
+
+from tsfile import Field, RowRecord, Tablet, TimeseriesSchema, TSDataType
+from tsfile import TsFileReader, TsFileWriter
+from tsfile.exceptions import (
+    AlreadyExistsError,
+    ErrorCode,
+    FileOpenError,
+    InvalidArgumentError,
+    InvalidPathError,
+    LibraryError,
+    TsFileCorruptedError,
+    get_exception,
+)
+
+
+def test_get_exception_preserves_known_and_unknown_codes():
+    corrupted = get_exception(ErrorCode.TSFILE_CORRUPTED)
+    assert isinstance(corrupted, TsFileCorruptedError)
+    assert corrupted.code == ErrorCode.TSFILE_CORRUPTED
+    assert corrupted.message == "TsFile is corrupted"
+
+    unknown = get_exception(999, "opening reader")
+    assert type(unknown) is LibraryError
+    assert unknown.code == 999
+    assert unknown.message == "opening reader: Unknown library error"
+
+    retired_codes = {
+        *range(9, 20),
+        23,
+        25,
+        39,
+        41,
+        42,
+        46,
+        47,
+    }
+    assert retired_codes.isdisjoint(error.value for error in ErrorCode)
+    for code in retired_codes:
+        retired = get_exception(code)
+        assert type(retired) is LibraryError
+        assert retired.code == code
+        assert retired.message == "Unknown library error"
+
+    invalid_path = get_exception(37)
+    assert isinstance(invalid_path, InvalidPathError)
+    assert invalid_path.code == ErrorCode.INVALID_PATH
+
+
+def test_invalid_tree_path_propagates_native_error(tmp_path):
+    path = tmp_path / "invalid-path.tsfile"
+    with TsFileWriter(str(path)) as writer:
+        writer.register_timeseries(
+            "root.device", TimeseriesSchema("value", TSDataType.INT64)
+        )
+
+    with TsFileReader(str(path)) as reader:
+        with pytest.raises(InvalidPathError) as exc_info:
+            reader.query_timeseries("root.device", ["a*%"], 0, 2)
+
+    assert exc_info.value.code == ErrorCode.INVALID_PATH
+
+
+def test_writer_constructor_propagates_native_error(tmp_path, capsys):
+    path = tmp_path / "already-exists.tsfile"
+    path.touch()
+
+    with pytest.raises(AlreadyExistsError) as exc_info:
+        TsFileWriter(str(path))
+
+    assert exc_info.value.code == ErrorCode.ALREADY_EXIST
+    assert exc_info.value.message == "Resource already exists"
+    assert capsys.readouterr().out == ""
+
+
+def test_reader_constructor_propagates_native_errors(tmp_path):
+    missing = tmp_path / "missing.tsfile"
+    with pytest.raises(FileOpenError) as exc_info:
+        TsFileReader(str(missing))
+    assert exc_info.value.code == ErrorCode.FILE_OPEN_ERROR
+
+    corrupted = tmp_path / "corrupted.tsfile"
+    corrupted.touch()
+    with pytest.raises(TsFileCorruptedError) as exc_info:
+        TsFileReader(str(corrupted))
+    assert exc_info.value.code == ErrorCode.TSFILE_CORRUPTED
+
+
+def test_duplicate_tablet_columns_do_not_silently_drop_rows(tmp_path):
+    path = tmp_path / "duplicate-columns.tsfile"
+    with TsFileWriter(str(path)) as writer:
+        writer.register_timeseries(
+            "root.device", TimeseriesSchema("value", TSDataType.INT64)
+        )
+        tablet = Tablet(
+            ["value", "VALUE"],
+            [TSDataType.INT64, TSDataType.INT64],
+            max_row_num=1,
+        )
+        tablet.set_table_name("root.device")
+        tablet.add_timestamp(0, 1)
+        tablet.add_value_by_index(0, 0, 10)
+        tablet.add_value_by_index(1, 0, 20)
+
+        with pytest.raises(InvalidArgumentError) as exc_info:
+            writer.write_tablet(tablet)
+
+        assert exc_info.value.code == ErrorCode.INVALID_ARGUMENT
+        assert "unique" in exc_info.value.message
+
+
+def test_row_record_still_writes_after_error_handling_changes(tmp_path):
+    path = tmp_path / "row-record.tsfile"
+    with TsFileWriter(str(path)) as writer:
+        writer.register_timeseries(
+            "root.device", TimeseriesSchema("value", TSDataType.INT64)
+        )
+        writer.write_row_record(
+            RowRecord("root.device", 1, [Field("value", 10, TSDataType.INT64)])
+        )
+
+    with TsFileReader(str(path)) as reader:
+        with reader.query_timeseries("root.device", ["value"], 0, 2) as result:
+            assert result.next()
+            assert result.get_value_by_index(2) == 10
diff --git a/python/tests/test_write_and_read.py 
b/python/tests/test_write_and_read.py
index a1b3b609e..fda145efd 100644
--- a/python/tests/test_write_and_read.py
+++ b/python/tests/test_write_and_read.py
@@ -639,10 +639,14 @@ def test_tsfile_to_df():
             max_row_num=8000,
         )
         assert df3.shape == (4097, 3)
-        with pytest.raises(TableNotExistError):
+        with pytest.raises(TableNotExistError) as table_error:
             to_dataframe("table_write_to_df.tsfile", "test_tb")
-        with pytest.raises(ColumnNotExistError):
+        assert table_error.value.code == 49
+        assert "test_tb" in table_error.value.message
+        with pytest.raises(ColumnNotExistError) as column_error:
             to_dataframe("table_write_to_df.tsfile", "test_table", ["device1"])
+        assert column_error.value.code == 50
+        assert "device1" in column_error.value.message
     finally:
         os.remove("table_write_to_df.tsfile")
 
diff --git a/python/tsfile/exceptions.py b/python/tsfile/exceptions.py
index a2e930bad..c0a963862 100644
--- a/python/tsfile/exceptions.py
+++ b/python/tsfile/exceptions.py
@@ -17,6 +17,48 @@
 #
 
 
+from enum import IntEnum
+
+
+class ErrorCode(IntEnum):
+    OK = 0
+    OOM = 1
+    NOT_EXIST = 2
+    ALREADY_EXIST = 3
+    INVALID_ARGUMENT = 4
+    OUT_OF_RANGE = 5
+    PARTIAL_READ = 6
+    INVALID_SCHEMA = 8
+    OVERFLOW = 20
+    NO_MORE_DATA = 21
+    OUT_OF_ORDER = 22
+    DATA_INCONSISTENCY = 24
+    TYPE_NOT_SUPPORTED = 26
+    TYPE_MISMATCH = 27
+    FILE_OPEN_ERROR = 28
+    FILE_CLOSE_ERROR = 29
+    FILE_WRITE_ERROR = 30
+    FILE_READ_ERROR = 31
+    FILE_SYNC_ERROR = 32
+    WRITER_METADATA_ERROR = 33
+    FILE_STAT_ERROR = 34
+    TSFILE_CORRUPTED = 35
+    BUFFER_NOT_ENOUGH = 36
+    INVALID_PATH = 37
+    NOT_MATCH = 38
+    NOT_SUPPORTED = 40
+    INVALID_DATA_POINT = 43
+    DEVICE_NOT_EXIST = 44
+    MEASUREMENT_NOT_EXIST = 45
+    COMPRESSION_ERROR = 48
+    TABLE_NOT_EXIST = 49
+    COLUMN_NOT_EXIST = 50
+    UNSUPPORTED_ORDER = 51
+    INVALID_NODE_TYPE = 52
+    ENCODE_ERROR = 53
+    DECODE_ERROR = 54
+
+
 class LibraryError(Exception):
     _default_message = "Unknown error occurred"
     _default_code = -1
@@ -60,6 +102,31 @@ class PartialReadError(LibraryError):
     _default_code = 6
 
 
+class InvalidSchemaError(LibraryError):
+    _default_message = "Invalid schema"
+    _default_code = 8
+
+
+class TsFileOverflowError(LibraryError):
+    _default_message = "Buffer or value overflow"
+    _default_code = 20
+
+
+class NoMoreDataError(LibraryError):
+    _default_message = "No more data"
+    _default_code = 21
+
+
+class OutOfOrderError(LibraryError):
+    _default_message = "Data is out of order"
+    _default_code = 22
+
+
+class DataInconsistencyError(LibraryError):
+    _default_message = "Data is inconsistent"
+    _default_code = 24
+
+
 class FileOpenError(LibraryError):
     _default_message = "Failed to open file"
     _default_code = 28
@@ -90,11 +157,26 @@ class MetadataError(LibraryError):
     _default_code = 33
 
 
+class FileStatError(LibraryError):
+    _default_message = "Failed to inspect file metadata"
+    _default_code = 34
+
+
+class TsFileCorruptedError(LibraryError):
+    _default_message = "TsFile is corrupted"
+    _default_code = 35
+
+
 class BufferNotEnoughError(LibraryError):
     _default_message = "Insufficient buffer space"
     _default_code = 36
 
 
+class InvalidPathError(LibraryError):
+    _default_message = "Invalid path"
+    _default_code = 37
+
+
 class NotSupportedError(LibraryError):
     _default_message = "Not support yet"
     _default_code = 40
@@ -110,11 +192,6 @@ class MeasurementNotExistError(LibraryError):
     _default_code = 45
 
 
-class InvalidQueryError(LibraryError):
-    _default_message = "Malformed query syntax"
-    _default_code = 46
-
-
 class CompressionError(LibraryError):
     _default_message = "Data compression/decompression failed"
     _default_code = 48
@@ -140,6 +217,26 @@ class ColumnNotExistError(LibraryError):
     _default_code = 50
 
 
+class UnsupportedOrderError(LibraryError):
+    _default_message = "Unsupported ordering"
+    _default_code = 51
+
+
+class InvalidNodeTypeError(LibraryError):
+    _default_message = "Invalid node type"
+    _default_code = 52
+
+
+class EncodeError(LibraryError):
+    _default_message = "Failed to encode data"
+    _default_code = 53
+
+
+class DecodeError(LibraryError):
+    _default_message = "Failed to decode data"
+    _default_code = 54
+
+
 ERROR_MAPPING = {
     1: OOMError,
     2: NotExistsError,
@@ -147,6 +244,11 @@ ERROR_MAPPING = {
     4: InvalidArgumentError,
     5: OutOfRangeError,
     6: PartialReadError,
+    8: InvalidSchemaError,
+    20: TsFileOverflowError,
+    21: NoMoreDataError,
+    22: OutOfOrderError,
+    24: DataInconsistencyError,
     26: TypeNotSupportedError,
     27: TypeMismatchError,
     28: FileOpenError,
@@ -155,24 +257,41 @@ ERROR_MAPPING = {
     31: FileReadError,
     32: FileSyncError,
     33: MetadataError,
+    34: FileStatError,
+    35: TsFileCorruptedError,
     36: BufferNotEnoughError,
+    37: InvalidPathError,
     40: NotSupportedError,
     44: DeviceNotExistError,
     45: MeasurementNotExistError,
-    46: InvalidQueryError,
     48: CompressionError,
     49: TableNotExistError,
     50: ColumnNotExistError,
+    51: UnsupportedOrderError,
+    52: InvalidNodeTypeError,
+    53: EncodeError,
+    54: DecodeError,
+}
+
+
+ERROR_MESSAGES = {
+    error.value: error.name.replace("_", " ").lower() for error in ErrorCode
 }
+ERROR_MESSAGES.update(
+    {
+        code: exception_type._default_message
+        for code, exception_type in ERROR_MAPPING.items()
+    }
+)
 
 
 def get_exception(code: int, context: str = None):
-    if code == 0:
+    code = int(code)
+    if code == ErrorCode.OK:
         return None
 
-    exc_type = ERROR_MAPPING.get(code)
-    if not exc_type:
-        return LibraryError(
-            code=code, context=f"Unmapped error code: {code}, message: 
{context}"
-        )
-    return exc_type(code=code, context=context)
+    exc_type = ERROR_MAPPING.get(code, LibraryError)
+    message = ERROR_MESSAGES.get(code, "Unknown library error")
+    if context:
+        message = f"{context}: {message}"
+    return exc_type(code=code, context=message)
diff --git a/python/tsfile/tsfile_cpp.pxd b/python/tsfile/tsfile_cpp.pxd
index 4e90dd483..cc14f4034 100644
--- a/python/tsfile/tsfile_cpp.pxd
+++ b/python/tsfile/tsfile_cpp.pxd
@@ -21,6 +21,11 @@ from libc.stdint cimport uint32_t, int32_t, int64_t, 
uint64_t, uint8_t
 
 ctypedef int32_t ErrorCode
 
+cdef extern from "cwrapper/errno_define_c.h":
+    enum:
+        RET_OK
+        RET_NO_MORE_DATA
+
 # import symbols from tsfile_cwrapper.h
 cdef extern from "cwrapper/tsfile_cwrapper.h":
     # common
diff --git a/python/tsfile/tsfile_py_cpp.pxd b/python/tsfile/tsfile_py_cpp.pxd
index 7286e78fb..0e2f91cbc 100644
--- a/python/tsfile/tsfile_py_cpp.pxd
+++ b/python/tsfile/tsfile_py_cpp.pxd
@@ -41,8 +41,8 @@ cdef public api void 
free_c_timeseries_schema(TimeseriesSchema* c_schema)
 cdef public api void free_c_device_schema(DeviceSchema* c_schema)
 cdef public api void free_c_tablet(Tablet tablet)
 cdef public api void free_c_row_record(TsRecord record)
-cdef public api TsFileWriter tsfile_writer_new_c(object pathname, uint64_t 
memory_threshold) except +
-cdef public api TsFileReader tsfile_reader_new_c(object pathname) except +
+cdef public api TsFileWriter tsfile_writer_new_c(object pathname, uint64_t 
memory_threshold) except NULL
+cdef public api TsFileReader tsfile_reader_new_c(object pathname) except NULL
 cdef public api ErrorCode tsfile_writer_register_device_py_cpp(TsFileWriter 
writer, DeviceSchema *schema)
 cdef public api ErrorCode 
tsfile_writer_register_timeseries_py_cpp(TsFileWriter writer, object 
device_name,
                                                         TimeseriesSchema 
*schema)
diff --git a/python/tsfile/tsfile_py_cpp.pyx b/python/tsfile/tsfile_py_cpp.pyx
index 038336a42..936c74af1 100644
--- a/python/tsfile/tsfile_py_cpp.pyx
+++ b/python/tsfile/tsfile_py_cpp.pyx
@@ -27,11 +27,10 @@ from libc.stdlib cimport free
 from libc.stdlib cimport malloc
 from libc.string cimport strdup
 from libc.string cimport memset
-from cpython.exc cimport PyErr_SetObject
 from cpython.unicode cimport PyUnicode_AsUTF8String, PyUnicode_AsUTF8, 
PyUnicode_AsUTF8AndSize
 from cpython.bytes cimport PyBytes_AsString, PyBytes_AsStringAndSize
 
-from tsfile.exceptions import ERROR_MAPPING, TypeMismatchError
+from tsfile.exceptions import get_exception, InvalidArgumentError, 
TypeMismatchError
 from tsfile.schema import ResultSetMetaData as ResultSetMetaDataPy
 from tsfile.schema import TSDataType as TSDataTypePy, TSEncoding as 
TSEncodingPy
 from tsfile.schema import Compressor as CompressorPy, ColumnCategory as 
CategoryPy
@@ -49,17 +48,11 @@ from tsfile.schema import TimeseriesMetadata as 
TimeseriesMetadataPy
 
 # check exception and set py exception object
 cdef inline void check_error(int errcode, const char * context=NULL) except*:
-    cdef:
-        object exc_type
-        object exc_instance
-
-    if errcode == 0:
+    if errcode == RET_OK:
         return
 
-    exc_type = ERROR_MAPPING.get(errcode)
-    print(exc_type)
-    exc_instance = exc_type(errcode, "")
-    PyErr_SetObject(exc_type, exc_instance)
+    py_context = context.decode('utf-8') if context != NULL else None
+    raise get_exception(errcode, py_context)
 
 # convert from c to python
 cdef object from_c_result_set_meta_data(ResultSetMetaData schema):
@@ -226,18 +219,35 @@ cdef TableSchema * to_c_table_schema(object py_schema):
     return c_schema
 
 cdef Tablet to_c_tablet(object tablet):
-    cdef Tablet ctablet
+    cdef Tablet ctablet = NULL
     cdef int max_row_num
+    cdef int column_num
+    cdef int i, row, col
     cdef TSDataType data_type
     cdef int64_t timestamp
     cdef bytes device_id_bytes
     cdef const char * device_id_c
-    cdef char** columns_names
-    cdef TSDataType * column_types
-    cdef bytes row_bytes
-    cdef char *raw_str
+    cdef char** columns_names = NULL
+    cdef TSDataType * columns_types = NULL
+    cdef char *raw_str = NULL
     cdef const char * str_ptr
     cdef Py_ssize_t raw_len
+    cdef object column_name_list = tablet.get_column_name_list()
+    cdef object data_type_list = tablet.get_data_type_list()
+    cdef object value_list = tablet.get_value_list()
+
+    column_num = len(column_name_list)
+    max_row_num = tablet.get_max_row_num()
+    if column_num != len(data_type_list) or column_num != len(value_list):
+        raise InvalidArgumentError(
+            context="Tablet column names, data types, and values must have 
equal lengths"
+        )
+    if len({(<str> name).lower() for name in column_name_list}) != column_num:
+        raise InvalidArgumentError(
+            context="Tablet column names must be unique (case-insensitive)"
+        )
+    if max_row_num <= 0:
+        raise InvalidArgumentError(context="Tablet max_row_num must be 
positive")
 
     if tablet.get_target_name() is not None:
         device_id_bytes = PyUnicode_AsUTF8String(tablet.get_target_name())
@@ -245,90 +255,153 @@ cdef Tablet to_c_tablet(object tablet):
     else:
         device_id_c = NULL
 
-    column_num = len(tablet.get_column_name_list())
     columns_names = <char**> malloc(sizeof(char *) * column_num)
     columns_types = <TSDataType *> malloc(sizeof(TSDataType) * column_num)
-    for i in range(column_num):
-        columns_names[i] = 
strdup(tablet.get_column_name_list()[i].encode('utf-8'))
-        columns_types[i] = to_c_data_type(tablet.get_data_type_list()[i])
-
-    max_row_num = tablet.get_max_row_num()
-
-    ctablet = _tablet_new_with_target_name(device_id_c, columns_names, 
columns_types, column_num,
-                                           max_row_num)
-    free(columns_types)
-    for i in range(column_num):
-        free(columns_names[i])
-    free(columns_names)
-
-    for row in range(max_row_num):
-        timestamp_py = tablet.get_timestamp_list()[row]
-        if timestamp_py is None:
-            continue
-        timestamp = timestamp_py
-        tablet_add_timestamp(ctablet, row, timestamp)
-
-    for col in range(column_num):
-        data_type = to_c_data_type(tablet.get_data_type_list()[col])
-        value = tablet.get_value_list()[col]
-        # BOOLEAN
-        if data_type == TS_DATATYPE_BOOLEAN:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    tablet_add_value_by_index_bool(ctablet, row, col, 
value[row])
-        # INT32
-        elif data_type == TS_DATATYPE_INT32:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    tablet_add_value_by_index_int32_t(ctablet, row, col, 
value[row])
-
-        # INT64
-        elif data_type == TS_DATATYPE_INT64 or data_type == 
TS_DATATYPE_TIMESTAMP:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    tablet_add_value_by_index_int64_t(ctablet, row, col, 
value[row])
-        # FLOAT
-        elif data_type == TS_DATATYPE_FLOAT:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    tablet_add_value_by_index_float(ctablet, row, col, 
value[row])
+    if columns_names == NULL or columns_types == NULL:
+        free(columns_names)
+        free(columns_types)
+        raise MemoryError("Failed to allocate tablet schema arrays")
+    memset(columns_names, 0, sizeof(char *) * column_num)
+    try:
+        for i in range(column_num):
+            columns_names[i] = strdup((<str> 
column_name_list[i]).encode('utf-8'))
+            if columns_names[i] == NULL:
+                raise MemoryError("Failed to allocate tablet column name")
+            columns_types[i] = to_c_data_type(data_type_list[i])
+        ctablet = _tablet_new_with_target_name(
+            device_id_c, columns_names, columns_types, column_num, max_row_num
+        )
+    finally:
+        free(columns_types)
+        for i in range(column_num):
+            free(columns_names[i])
+        free(columns_names)
 
-        # DOUBLE
-        elif data_type == TS_DATATYPE_DOUBLE:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    tablet_add_value_by_index_double(ctablet, row, col, 
value[row])
+    if ctablet == NULL:
+        raise MemoryError("Failed to allocate native tablet")
 
-        elif data_type == TS_DATATYPE_DATE:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    tablet_add_value_by_index_int32_t(ctablet, row, col, 
parse_date_to_int(value[row]))
+    try:
+        for row in range(max_row_num):
+            timestamp_py = tablet.get_timestamp_list()[row]
+            if timestamp_py is None:
+                continue
+            timestamp = timestamp_py
+            check_error(
+                tablet_add_timestamp(ctablet, row, timestamp),
+                b"Failed to add tablet timestamp",
+            )
+
+        for col in range(column_num):
+            data_type = to_c_data_type(data_type_list[col])
+            value = value_list[col]
+            # BOOLEAN
+            if data_type == TS_DATATYPE_BOOLEAN:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        check_error(
+                            tablet_add_value_by_index_bool(
+                                ctablet, row, col, value[row]
+                            ),
+                            b"Failed to add BOOLEAN tablet value",
+                        )
+            # INT32
+            elif data_type == TS_DATATYPE_INT32:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        check_error(
+                            tablet_add_value_by_index_int32_t(
+                                ctablet, row, col, value[row]
+                            ),
+                            b"Failed to add INT32 tablet value",
+                        )
+            # INT64
+            elif data_type == TS_DATATYPE_INT64 or data_type == 
TS_DATATYPE_TIMESTAMP:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        check_error(
+                            tablet_add_value_by_index_int64_t(
+                                ctablet, row, col, value[row]
+                            ),
+                            b"Failed to add INT64 tablet value",
+                        )
+            # FLOAT
+            elif data_type == TS_DATATYPE_FLOAT:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        check_error(
+                            tablet_add_value_by_index_float(
+                                ctablet, row, col, value[row]
+                            ),
+                            b"Failed to add FLOAT tablet value",
+                        )
+            # DOUBLE
+            elif data_type == TS_DATATYPE_DOUBLE:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        check_error(
+                            tablet_add_value_by_index_double(
+                                ctablet, row, col, value[row]
+                            ),
+                            b"Failed to add DOUBLE tablet value",
+                        )
+            elif data_type == TS_DATATYPE_DATE:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        check_error(
+                            tablet_add_value_by_index_int32_t(
+                                ctablet, row, col, 
parse_date_to_int(value[row])
+                            ),
+                            b"Failed to add DATE tablet value",
+                        )
+            # STRING or TEXT
+            elif data_type == TS_DATATYPE_STRING or data_type == 
TS_DATATYPE_TEXT:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        py_value = value[row]
+                        str_ptr = PyUnicode_AsUTF8AndSize(py_value, &raw_len)
+                        check_error(
+                            tablet_add_value_by_index_string_with_len(
+                                ctablet, row, col, str_ptr, raw_len
+                            ),
+                            b"Failed to add STRING tablet value",
+                        )
+            elif data_type == TS_DATATYPE_BLOB:
+                for row in range(max_row_num):
+                    if value[row] is not None:
+                        if PyBytes_AsStringAndSize(
+                            value[row], &raw_str, &raw_len
+                        ) < 0:
+                            raise TypeError("BLOB tablet values must be bytes")
+                        check_error(
+                            tablet_add_value_by_index_string_with_len(
+                                ctablet, row, col, raw_str, raw_len
+                            ),
+                            b"Failed to add BLOB tablet value",
+                        )
+    except:
+        free_tablet(&ctablet)
+        raise
 
-        # STRING or TEXT
-        elif data_type == TS_DATATYPE_STRING or data_type == TS_DATATYPE_TEXT:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    py_value = value[row]
-                    str_ptr = PyUnicode_AsUTF8AndSize(py_value, &raw_len)
-                    tablet_add_value_by_index_string_with_len(ctablet, row, 
col, str_ptr, raw_len)
+    return ctablet
 
-        elif data_type == TS_DATATYPE_BLOB:
-            for row in range(max_row_num):
-                if value[row] is not None:
-                    PyBytes_AsStringAndSize(value[row], &raw_str, &raw_len)
-                    tablet_add_value_by_index_string_with_len(ctablet, row, 
col, raw_str, raw_len)
+cdef inline void check_dataframe_tablet_error(
+    Tablet tablet, int errcode, const char * context
+) except *:
+    if errcode == RET_OK:
+        return
+    free_tablet(&tablet)
+    check_error(errcode, context)
 
-    return ctablet
 
 cdef Tablet dataframe_to_c_tablet(object target_name, object dataframe, object 
table_schema):
-    cdef Tablet ctablet
+    cdef Tablet ctablet = NULL
     cdef int max_row_num
     cdef TSDataType data_type
     cdef int64_t timestamp
     cdef const char * device_id_c = NULL
-    cdef char** columns_names
-    cdef TSDataType * columns_types
-    cdef char *raw_str
+    cdef char** columns_names = NULL
+    cdef TSDataType * columns_types = NULL
+    cdef char *raw_str = NULL
     cdef const char * str_ptr
     cdef Py_ssize_t raw_len
     cdef int column_num
@@ -359,20 +432,68 @@ cdef Tablet dataframe_to_c_tablet(object target_name, 
object dataframe, object t
         data_type = table_schema.get_column(column).get_data_type()
         column_types_list.append(data_type)
 
+    # Validate object-backed columns before allocating the native Tablet so a
+    # validation exception cannot leak the native allocation.
+    for col in range(column_num):
+        col_name = data_columns[col]
+        data_type = column_types_list[col]
+        if data_type in (
+            TS_DATATYPE_DATE,
+            TS_DATATYPE_STRING,
+            TS_DATATYPE_TEXT,
+            TS_DATATYPE_BLOB,
+        ):
+            col_series = dataframe[col_name]
+            first_valid_idx = col_series.first_valid_index()
+            if first_valid_idx is not None:
+                value = col_series[first_valid_idx]
+                if data_type == TS_DATATYPE_DATE and not isinstance(value, 
date_type):
+                    raise TypeMismatchError(
+                        context=(
+                            f"Column '{col_name}': expected DATE 
(datetime.date), "
+                            f"got {type(value).__name__}: {value!r}"
+                        )
+                    )
+                if data_type in (TS_DATATYPE_STRING, TS_DATATYPE_TEXT) and not 
isinstance(value, str):
+                    raise TypeMismatchError(
+                        context=(
+                            f"Column '{col_name}': expected STRING/TEXT, "
+                            f"got {type(value).__name__}: {value!r}"
+                        )
+                    )
+                if data_type == TS_DATATYPE_BLOB and not isinstance(value, 
bytes):
+                    raise TypeMismatchError(
+                        context=(
+                            f"Column '{col_name}': expected BLOB (bytes), "
+                            f"got {type(value).__name__}: {value!r}"
+                        )
+                    )
+
     columns_names = <char**> malloc(sizeof(char *) * column_num)
     columns_types = <TSDataType *> malloc(sizeof(TSDataType) * column_num)
+    if columns_names == NULL or columns_types == NULL:
+        free(columns_names)
+        free(columns_types)
+        raise MemoryError("Failed to allocate DataFrame tablet schema arrays")
+    memset(columns_names, 0, sizeof(char *) * column_num)
+    try:
+        for i in range(column_num):
+            columns_names[i] = strdup(data_columns[i].lower().encode('utf-8'))
+            if columns_names[i] == NULL:
+                raise MemoryError("Failed to allocate DataFrame tablet column 
name")
+            columns_types[i] = to_c_data_type(column_types_list[i])
 
-    for i in range(column_num):
-        columns_names[i] = strdup(data_columns[i].lower().encode('utf-8'))
-        columns_types[i] = column_types_list[i]
-
-    ctablet = _tablet_new_with_target_name(device_id_c, columns_names, 
columns_types, column_num,
-                         max_row_num)
+        ctablet = _tablet_new_with_target_name(
+            device_id_c, columns_names, columns_types, column_num, max_row_num
+        )
+    finally:
+        free(columns_types)
+        for i in range(column_num):
+            free(columns_names[i])
+        free(columns_names)
 
-    free(columns_types)
-    for i in range(column_num):
-        free(columns_names[i])
-    free(columns_names)
+    if ctablet == NULL:
+        raise MemoryError("Failed to allocate native DataFrame tablet")
 
     if use_id_as_time:
         for row in range(max_row_num):
@@ -380,7 +501,11 @@ cdef Tablet dataframe_to_c_tablet(object target_name, 
object dataframe, object t
             if pd.isna(timestamp_py):
                 continue
             timestamp = <int64_t> timestamp_py
-            tablet_add_timestamp(ctablet, row, timestamp)
+            check_dataframe_tablet_error(
+                ctablet,
+                tablet_add_timestamp(ctablet, row, timestamp),
+                b"Failed to add DataFrame index timestamp",
+            )
     else:
         time_values = dataframe[time_column.get_column_name()].values
         for row in range(max_row_num):
@@ -388,74 +513,89 @@ cdef Tablet dataframe_to_c_tablet(object target_name, 
object dataframe, object t
             if pd.isna(timestamp_py):
                 continue
             timestamp = <int64_t> timestamp_py
-            tablet_add_timestamp(ctablet, row, timestamp)
+            check_dataframe_tablet_error(
+                ctablet,
+                tablet_add_timestamp(ctablet, row, timestamp),
+                b"Failed to add DataFrame timestamp",
+            )
 
     for col in range(column_num):
         col_name = data_columns[col]
         data_type = column_types_list[col]
         column_values = dataframe[col_name].values
 
-        # Per-column validation for object types (check first non-null value 
only)
-        if data_type in (TS_DATATYPE_DATE, TS_DATATYPE_STRING, 
TS_DATATYPE_TEXT, TS_DATATYPE_BLOB):
-            col_series = dataframe[col_name]
-            first_valid_idx = col_series.first_valid_index()
-            if first_valid_idx is not None:
-                value = col_series[first_valid_idx]
-                if data_type == TS_DATATYPE_DATE:
-                    if not isinstance(value, date_type):
-                        raise TypeMismatchError(context=
-                            f"Column '{col_name}': expected DATE 
(datetime.date), "
-                            f"got {type(value).__name__}: {value!r}"
-                        )
-                elif data_type in (TS_DATATYPE_STRING, TS_DATATYPE_TEXT):
-                    if not isinstance(value, str):
-                        raise TypeMismatchError(context=
-                            f"Column '{col_name}': expected STRING/TEXT, "
-                            f"got {type(value).__name__}: {value!r}"
-                        )
-                elif data_type == TS_DATATYPE_BLOB:
-                    if not isinstance(value, bytes):
-                        raise TypeMismatchError(context=
-                            f"Column '{col_name}': expected BLOB (bytes or 
bytearray), "
-                            f"got {type(value).__name__}: {value!r}"
-                        )
-
         # BOOLEAN
         if data_type == TS_DATATYPE_BOOLEAN:
             for row in range(max_row_num):
                 value = column_values[row]
                 if not pd.isna(value):
-                    tablet_add_value_by_index_bool(ctablet, row, col, <bint> 
value)
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_bool(
+                            ctablet, row, col, <bint> value
+                        ),
+                        b"Failed to add BOOLEAN DataFrame value",
+                    )
         # INT32
         elif data_type == TS_DATATYPE_INT32:
             for row in range(max_row_num):
                 value = column_values[row]
                 if not pd.isna(value):
-                    tablet_add_value_by_index_int32_t(ctablet, row, col, 
<int32_t> value)
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_int32_t(
+                            ctablet, row, col, <int32_t> value
+                        ),
+                        b"Failed to add INT32 DataFrame value",
+                    )
         # INT64
         elif data_type == TS_DATATYPE_INT64 or data_type == 
TS_DATATYPE_TIMESTAMP:
             for row in range(max_row_num):
                 value = column_values[row]
                 if not pd.isna(value):
-                    tablet_add_value_by_index_int64_t(ctablet, row, col, 
<int64_t> value)
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_int64_t(
+                            ctablet, row, col, <int64_t> value
+                        ),
+                        b"Failed to add INT64 DataFrame value",
+                    )
         # FLOAT
         elif data_type == TS_DATATYPE_FLOAT:
             for row in range(max_row_num):
                 value = column_values[row]
                 if not pd.isna(value):
-                    tablet_add_value_by_index_float(ctablet, row, col, <float> 
value)
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_float(
+                            ctablet, row, col, <float> value
+                        ),
+                        b"Failed to add FLOAT DataFrame value",
+                    )
         # DOUBLE
         elif data_type == TS_DATATYPE_DOUBLE:
             for row in range(max_row_num):
                 value = column_values[row]
                 if not pd.isna(value):
-                    tablet_add_value_by_index_double(ctablet, row, col, 
<double> value)
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_double(
+                            ctablet, row, col, <double> value
+                        ),
+                        b"Failed to add DOUBLE DataFrame value",
+                    )
         # DATE (validated per-column above)
         elif data_type == TS_DATATYPE_DATE:
             for row in range(max_row_num):
                 value = column_values[row]
                 if not pd.isna(value):
-                    tablet_add_value_by_index_int32_t(ctablet, row, col, 
parse_date_to_int(value))
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_int32_t(
+                            ctablet, row, col, parse_date_to_int(value)
+                        ),
+                        b"Failed to add DATE DataFrame value",
+                    )
         # STRING or TEXT (validated per-column above)
         elif data_type == TS_DATATYPE_STRING or data_type == TS_DATATYPE_TEXT:
             for row in range(max_row_num):
@@ -463,7 +603,13 @@ cdef Tablet dataframe_to_c_tablet(object target_name, 
object dataframe, object t
                 if not pd.isna(value):
                     py_value = str(value)
                     str_ptr = PyUnicode_AsUTF8AndSize(py_value, &raw_len)
-                    tablet_add_value_by_index_string_with_len(ctablet, row, 
col, str_ptr, raw_len)
+                    check_dataframe_tablet_error(
+                        ctablet,
+                        tablet_add_value_by_index_string_with_len(
+                            ctablet, row, col, str_ptr, raw_len
+                        ),
+                        b"Failed to add STRING DataFrame value",
+                    )
         # BLOB (validated per-column above)
         elif data_type == TS_DATATYPE_BLOB:
             for row in range(max_row_num):
@@ -471,11 +617,23 @@ cdef Tablet dataframe_to_c_tablet(object target_name, 
object dataframe, object t
                 if not pd.isna(value):
                     if isinstance(value, bytes):
                         PyBytes_AsStringAndSize(value, &raw_str, &raw_len)
-                        tablet_add_value_by_index_string_with_len(ctablet, 
row, col, raw_str, raw_len)
+                        check_dataframe_tablet_error(
+                            ctablet,
+                            tablet_add_value_by_index_string_with_len(
+                                ctablet, row, col, raw_str, raw_len
+                            ),
+                            b"Failed to add BLOB DataFrame value",
+                        )
                     else:
                         value_bytes = bytes(value)
                         PyBytes_AsStringAndSize(value_bytes, &raw_str, 
&raw_len)
-                        tablet_add_value_by_index_string_with_len(ctablet, 
row, col, raw_str, raw_len)
+                        check_dataframe_tablet_error(
+                            ctablet,
+                            tablet_add_value_by_index_string_with_len(
+                                ctablet, row, col, raw_str, raw_len
+                            ),
+                            b"Failed to add BLOB DataFrame value",
+                        )
 
     return ctablet
 
@@ -485,42 +643,100 @@ cdef TsRecord to_c_record(object row_record):
     cdef bytes device_id_bytes = 
PyUnicode_AsUTF8String(row_record.get_device_id())
     cdef const char * device_id = device_id_bytes
     cdef const char * str_ptr
-    cdef char * blob_ptr
+    cdef char * blob_ptr = NULL
     cdef Py_ssize_t str_len
-    cdef TsRecord record
+    cdef TsRecord record = NULL
     cdef int i
     cdef TSDataType data_type
     record = _ts_record_new(device_id, timestamp, field_num)
-    for i in range(field_num):
-        field = row_record.get_fields()[i]
-        data_type = to_c_data_type(field.get_data_type())
-        if data_type == TS_DATATYPE_BOOLEAN:
-            _insert_data_into_ts_record_by_name_bool(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                     field.get_bool_value())
-        elif data_type == TS_DATATYPE_INT32 or data_type == TS_DATATYPE_DATE:
-            _insert_data_into_ts_record_by_name_int32_t(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                        field.get_int_value())
-        elif data_type == TS_DATATYPE_INT64:
-            _insert_data_into_ts_record_by_name_int64_t(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                        field.get_long_value())
-        elif data_type == TS_DATATYPE_TIMESTAMP:
-            _insert_data_into_ts_record_by_name_int64_t(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                        
field.get_timestamp_value())
-        elif data_type == TS_DATATYPE_DOUBLE:
-            _insert_data_into_ts_record_by_name_double(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                       
field.get_double_value())
-        elif data_type == TS_DATATYPE_FLOAT:
-            _insert_data_into_ts_record_by_name_float(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                      field.get_float_value())
-        elif data_type == TS_DATATYPE_TEXT or data_type == TS_DATATYPE_STRING:
-            str_ptr = PyUnicode_AsUTF8AndSize(field.get_string_value(), 
&str_len)
-            _insert_data_into_ts_record_by_name_string_with_len(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                                str_ptr, 
str_len)
-        elif data_type == TS_DATATYPE_BLOB:
-            if PyBytes_AsStringAndSize(field.get_string_value(), &blob_ptr, 
&str_len) < 0:
-                raise ValueError("blob not legal")
-            _insert_data_into_ts_record_by_name_string_with_len(record, 
PyUnicode_AsUTF8(field.get_field_name()),
-                                                                <const char *> 
blob_ptr, <uint32_t> str_len)
+    if record == NULL:
+        raise MemoryError("Failed to allocate native row record")
+    try:
+        for i in range(field_num):
+            field = row_record.get_fields()[i]
+            data_type = to_c_data_type(field.get_data_type())
+            if data_type == TS_DATATYPE_BOOLEAN:
+                check_error(
+                    _insert_data_into_ts_record_by_name_bool(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        field.get_bool_value(),
+                    ),
+                    b"Failed to add BOOLEAN row-record field",
+                )
+            elif data_type == TS_DATATYPE_INT32 or data_type == 
TS_DATATYPE_DATE:
+                check_error(
+                    _insert_data_into_ts_record_by_name_int32_t(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        field.get_int_value(),
+                    ),
+                    b"Failed to add INT32 row-record field",
+                )
+            elif data_type == TS_DATATYPE_INT64:
+                check_error(
+                    _insert_data_into_ts_record_by_name_int64_t(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        field.get_long_value(),
+                    ),
+                    b"Failed to add INT64 row-record field",
+                )
+            elif data_type == TS_DATATYPE_TIMESTAMP:
+                check_error(
+                    _insert_data_into_ts_record_by_name_int64_t(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        field.get_timestamp_value(),
+                    ),
+                    b"Failed to add TIMESTAMP row-record field",
+                )
+            elif data_type == TS_DATATYPE_DOUBLE:
+                check_error(
+                    _insert_data_into_ts_record_by_name_double(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        field.get_double_value(),
+                    ),
+                    b"Failed to add DOUBLE row-record field",
+                )
+            elif data_type == TS_DATATYPE_FLOAT:
+                check_error(
+                    _insert_data_into_ts_record_by_name_float(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        field.get_float_value(),
+                    ),
+                    b"Failed to add FLOAT row-record field",
+                )
+            elif data_type == TS_DATATYPE_TEXT or data_type == 
TS_DATATYPE_STRING:
+                str_ptr = PyUnicode_AsUTF8AndSize(field.get_string_value(), 
&str_len)
+                check_error(
+                    _insert_data_into_ts_record_by_name_string_with_len(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        str_ptr,
+                        str_len,
+                    ),
+                    b"Failed to add STRING row-record field",
+                )
+            elif data_type == TS_DATATYPE_BLOB:
+                if PyBytes_AsStringAndSize(
+                    field.get_string_value(), &blob_ptr, &str_len
+                ) < 0:
+                    raise TypeError("BLOB row-record values must be bytes")
+                check_error(
+                    _insert_data_into_ts_record_by_name_string_with_len(
+                        record,
+                        PyUnicode_AsUTF8(field.get_field_name()),
+                        <const char *> blob_ptr,
+                        <uint32_t> str_len,
+                    ),
+                    b"Failed to add BLOB row-record field",
+                )
+    except:
+        _free_tsfile_ts_record(&record)
+        raise
     return record
 
 # Free c structs' space
@@ -549,18 +765,18 @@ cdef void free_c_row_record(TsRecord record):
     _free_tsfile_ts_record(&record)
 
 # Reader and writer new.
-cdef TsFileWriter tsfile_writer_new_c(object pathname, uint64_t 
memory_threshold) except +:
+cdef TsFileWriter tsfile_writer_new_c(object pathname, uint64_t 
memory_threshold) except NULL:
     cdef ErrorCode errno = 0
-    cdef TsFileWriter writer
+    cdef TsFileWriter writer = NULL
     cdef bytes encoded_path = PyUnicode_AsUTF8String(pathname)
     cdef const char * c_path = encoded_path
     writer = _tsfile_writer_new(c_path, memory_threshold, &errno)
     check_error(errno)
     return writer
 
-cdef TsFileReader tsfile_reader_new_c(object pathname) except +:
+cdef TsFileReader tsfile_reader_new_c(object pathname) except NULL:
     cdef ErrorCode errno = 0
-    cdef TsFileReader reader
+    cdef TsFileReader reader = NULL
     cdef bytes encoded_path = PyUnicode_AsUTF8String(pathname)
     cdef const char * c_path = encoded_path
     reader = tsfile_reader_new(c_path, &errno)
diff --git a/python/tsfile/tsfile_reader.pyx b/python/tsfile/tsfile_reader.pyx
index f94d42053..36374adde 100644
--- a/python/tsfile/tsfile_reader.pyx
+++ b/python/tsfile/tsfile_reader.pyx
@@ -58,6 +58,7 @@ cdef class ResultSetPy:
     cdef object is_tree
 
     def __init__(self, tsfile_reader : TsFileReaderPy, is_tree: bint = False):
+        self.result = NULL
         self.metadata = None
         self.valid = True
         self.tsfile_reader = weakref.ref(tsfile_reader)
@@ -164,9 +165,9 @@ cdef class ResultSetPy:
 
         code = tsfile_result_set_get_next_tsblock_as_arrow(self.result, 
&arrow_array, &arrow_schema)
 
-        if code == 21:  # E_NO_MORE_DATA
+        if code == RET_NO_MORE_DATA:
             return None
-        if code != 0:
+        if code != RET_OK:
             check_error(code)
 
         if arrow_schema.release == NULL or arrow_array.release == NULL:
@@ -296,7 +297,10 @@ cdef class ResultSetPy:
         self.close()
 
     def __dealloc__(self):
-        self.close()
+        try:
+            self.close()
+        except Exception:
+            pass
 
     def __enter__(self):
         return self
@@ -321,8 +325,9 @@ cdef class TsFileReaderPy:
         """
         Initialize a TsFile reader for the specified file path.
         """
-        self.init_reader(pathname)
+        self.reader = NULL
         self.activate_result_set_list = weakref.WeakSet()
+        self.init_reader(pathname)
 
     cdef init_reader(self, pathname):
         self.reader = tsfile_reader_new_c(pathname)
@@ -533,7 +538,10 @@ cdef class TsFileReaderPy:
         return self.activate_result_set_list
 
     def __dealloc__(self):
-        self.close()
+        try:
+            self.close()
+        except Exception:
+            pass
 
     def __enter__(self):
         return self
diff --git a/python/tsfile/tsfile_writer.pyx b/python/tsfile/tsfile_writer.pyx
index 30a29d3bb..9e84d83c0 100644
--- a/python/tsfile/tsfile_writer.pyx
+++ b/python/tsfile/tsfile_writer.pyx
@@ -33,6 +33,7 @@ cdef class TsFileWriterPy:
     cdef TsFileWriter writer
 
     def __init__(self, pathname: str, memory_threshold: int = 128 * 1024 * 
1024):
+        self.writer = NULL
         self.writer = tsfile_writer_new_c(pathname, memory_threshold)
 
     def register_timeseries(self, device_name : str, timeseries_schema : 
TimeseriesSchemaPy):
@@ -185,7 +186,10 @@ cdef class TsFileWriterPy:
         check_error(errno)
 
     def __dealloc__(self):
-        self.close()
+        try:
+            self.close()
+        except Exception:
+            pass
 
     def __enter__(self):
         return self
diff --git a/python/tsfile/utils.py b/python/tsfile/utils.py
index ea9263149..0ec54b3a7 100644
--- a/python/tsfile/utils.py
+++ b/python/tsfile/utils.py
@@ -142,7 +142,9 @@ def to_dataframe(
                 else:
                     _table_name = _table_name.lower()
                     if _table_name.lower() not in table_schema:
-                        raise TableNotExistError(_table_name)
+                        raise TableNotExistError(
+                            context=f"Table '{_table_name}' does not exist"
+                        )
                     table_schema = table_schema[_table_name]
 
                 column_names_in_file = []
@@ -155,7 +157,9 @@ def to_dataframe(
                 if _column_names is not None:
                     for column in _column_names:
                         if column not in column_names_in_file and column != 
time_column:
-                            raise ColumnNotExistError(column)
+                            raise ColumnNotExistError(
+                                context=f"Column '{column}' does not exist"
+                            )
                         if (
                             table_schema.get_column(column).get_category()
                             == ColumnCategory.FIELD

Reply via email to