mirror of
https://github.com/Tencent/MMKV.git
synced 2026-10-16 20:21:12 +08:00
Win32 Python support done, remove duplicate files #1289
This commit is contained in:
+1
-4
@@ -1,6 +1,3 @@
|
||||
[submodule "POSIX/Python/pybind11"]
|
||||
[submodule "Python/pybind11"]
|
||||
path = Python/pybind11
|
||||
url = https://github.com/pybind/pybind11.git
|
||||
[submodule "Win32/Python/pybind11"]
|
||||
path = Win32/Python/pybind11
|
||||
url = https://github.com/pybind/pybind11.git
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#
|
||||
# Tencent is pleased to support the open source community by making
|
||||
# MMKV available.
|
||||
#
|
||||
# Copyright (C) 2020 THL A29 Limited, a Tencent company.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Licensed under the BSD 3-Clause License (the "License"); you may not use
|
||||
# this file except in compliance with the License. You may obtain a copy of
|
||||
# the License at
|
||||
#
|
||||
# https://opensource.org/licenses/BSD-3-Clause
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# Sets the minimum version of CMake required to build the native library.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10.0)
|
||||
|
||||
project(mmkv_python)
|
||||
|
||||
IF(APPLE)
|
||||
add_compile_definitions(FORCE_POSIX)
|
||||
ENDIF()
|
||||
|
||||
add_subdirectory(../../Core Core)
|
||||
add_subdirectory(pybind11)
|
||||
|
||||
|
||||
pybind11_add_module( # Sets the name of the library.
|
||||
mmkv
|
||||
|
||||
# Sets the library as a python module library.
|
||||
MODULE
|
||||
|
||||
# Provides a relative path to your source file(s).
|
||||
libmmkv_python.cpp
|
||||
)
|
||||
|
||||
target_include_directories(mmkv PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set_target_properties(mmkv PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_EXTENSIONS OFF
|
||||
)
|
||||
|
||||
target_link_libraries( # Specifies the target library.
|
||||
mmkv PRIVATE
|
||||
core
|
||||
)
|
||||
@@ -1,253 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import mmkv
|
||||
import time
|
||||
import tempfile
|
||||
|
||||
|
||||
def functional_test(mmap_id, decode_only):
|
||||
# pass MMKVMode.MultiProcess to get a multi-process instance
|
||||
# kv = mmkv.MMKV('test_python', mmkv.MMKVMode.MultiProcess)
|
||||
kv = mmkv.MMKV(mmap_id)
|
||||
|
||||
if not decode_only:
|
||||
kv.set(True, 'bool')
|
||||
print('bool = ', kv.getBool('bool'))
|
||||
|
||||
if not decode_only:
|
||||
kv.set(-1 * (2 ** 31), 'int32')
|
||||
print('int32 = ', kv.getInt('int32'))
|
||||
|
||||
if not decode_only:
|
||||
kv.set((2 ** 32) - 1, 'uint32')
|
||||
print('uint32 = ', kv.getUInt('uint32'))
|
||||
|
||||
if not decode_only:
|
||||
kv.set(2 ** 63, 'int64')
|
||||
print('int64 = ', kv.getLongInt('int64'))
|
||||
|
||||
if not decode_only:
|
||||
kv.set((2 ** 64) - 1, 'uint64')
|
||||
print('uint64 = ', kv.getLongUInt('uint64'))
|
||||
|
||||
if not decode_only:
|
||||
kv.set(3.1415926, 'float')
|
||||
print('float = ', kv.getFloat('float'))
|
||||
|
||||
if not decode_only:
|
||||
kv.set('Hello world, MMKV for Python!', 'string')
|
||||
print('string = ', kv.getString('string'))
|
||||
|
||||
if not decode_only:
|
||||
ls = range(0, 10)
|
||||
kv.set(bytes(ls), 'bytes')
|
||||
b = kv.getBytes('bytes')
|
||||
print('raw bytes = ', b)
|
||||
if sys.version_info >= (3, 0):
|
||||
print('decoded bytes = ', list(b))
|
||||
|
||||
if not decode_only:
|
||||
print('keys before remove:', sorted(kv.keys()))
|
||||
kv.remove('bool')
|
||||
print('"bool" exist after remove: ', ('bool' in kv))
|
||||
kv.remove(['int32', 'float'])
|
||||
print('keys after remove:', sorted(kv.keys()))
|
||||
else:
|
||||
print('keys:', sorted(kv.keys()))
|
||||
|
||||
|
||||
def test_backup():
|
||||
temp_dir = tempfile.gettempdir()
|
||||
root_dir = temp_dir + '/mmkv_backup'
|
||||
mmap_id = "test_python"
|
||||
ret = mmkv.MMKV.backupOneToDirectory(mmap_id, root_dir)
|
||||
print("backup one return: ", ret)
|
||||
|
||||
kv = mmkv.MMKV("test/Encrypt", mmkv.MMKVMode.SingleProcess, "cryptKey")
|
||||
kv.remove("test_restore_key")
|
||||
|
||||
count = mmkv.MMKV.backupAllToDirectory(root_dir)
|
||||
print("backup all count: ", count)
|
||||
|
||||
|
||||
# just for testing
|
||||
def utf8len(s):
|
||||
return len(s.encode('utf-8'))
|
||||
|
||||
|
||||
def test_expected_capacity():
|
||||
key = "key0"
|
||||
value = "🏊🏻®4️⃣🐅_"
|
||||
dataLen = 10000
|
||||
for i in range(dataLen):
|
||||
value += "0"
|
||||
|
||||
print("value size =", utf8len(value))
|
||||
expectedSize = utf8len(key) + utf8len(value)
|
||||
# if we know exactly the sizes of key and value, set expectedCapacity for performance improvement
|
||||
kv = mmkv.MMKV("mmkv_capacity0", mmkv.MMKVMode.SingleProcess, "", "", expectedSize)
|
||||
# 0 times expand
|
||||
kv.set(value, key)
|
||||
print("data size from MMKV =", len(kv.getString(key)))
|
||||
|
||||
countTick = 10
|
||||
expectedSize *= countTick
|
||||
kv = mmkv.MMKV("mmkv_capacity1", mmkv.MMKVMode.SingleProcess, "", "", expectedSize)
|
||||
for i in range(countTick):
|
||||
key1 = "key" + str(i)
|
||||
# 0 times expand
|
||||
kv.set(value, key1)
|
||||
|
||||
|
||||
def test_restore():
|
||||
temp_dir = tempfile.gettempdir()
|
||||
root_dir = temp_dir + '/mmkv_backup'
|
||||
mmap_id = "test/Encrypt"
|
||||
aes_key = "cryptKey"
|
||||
a_kv = mmkv.MMKV(mmap_id, mmkv.MMKVMode.SingleProcess, aes_key)
|
||||
a_kv.set("string value before restore", "test_restore_key")
|
||||
print("before restore [", a_kv.mmapID(), "] allKeys: ", a_kv.keys())
|
||||
|
||||
ret = mmkv.MMKV.restoreOneFromDirectory(mmap_id, root_dir)
|
||||
print("restore one return: ", ret)
|
||||
if ret:
|
||||
print("after restore [", a_kv.mmapID(), "] allKeys: ", a_kv.keys())
|
||||
|
||||
count = mmkv.MMKV.restoreAllFromDirectory(root_dir)
|
||||
print("restore all count: ", count)
|
||||
if count > 0:
|
||||
backup_mmkv = mmkv.MMKV(mmap_id, mmkv.MMKVMode.SingleProcess, aes_key)
|
||||
print("check on restore [", backup_mmkv.mmapID(), "] allKeys: ", backup_mmkv.keys())
|
||||
|
||||
backup_mmkv = mmkv.MMKV("test_python")
|
||||
print("check on restore [", backup_mmkv.mmapID(), "] allKeys: ", backup_mmkv.keys())
|
||||
|
||||
|
||||
def test_auto_expire():
|
||||
kv = mmkv.MMKV("test_auto_expire")
|
||||
kv.clearAll(True)
|
||||
kv.disableAutoKeyExpire()
|
||||
|
||||
kv.set(True, "auto_expire_key_1")
|
||||
kv.enableAutoKeyExpire(1)
|
||||
kv.set("never_expire_value_1", "never_expire_key_1", 0)
|
||||
|
||||
time.sleep(2)
|
||||
print("contains auto_expire_key_1:", "auto_expire_key_1" in kv)
|
||||
print("contains never_expire_key_1:", "never_expire_key_1" in kv)
|
||||
|
||||
kv.remove("never_expire_key_1")
|
||||
kv.enableAutoKeyExpire(0)
|
||||
kv.set("never_expire_value_1", "never_expire_key_1")
|
||||
kv.set(True, "auto_expire_key_1", 1)
|
||||
time.sleep(2)
|
||||
print("contains never_expire_key_1:", "never_expire_key_1" in kv)
|
||||
print("contains auto_expire_key_1:", "auto_expire_key_1" in kv)
|
||||
print("count filter expire key:", kv.count(True))
|
||||
print("all non expire keys:", kv.keys(True))
|
||||
|
||||
|
||||
def test_compare_before_set():
|
||||
kv = mmkv.MMKV("testCompareBeforeSet")
|
||||
kv.enableCompareBeforeSet()
|
||||
kv.set("extraValue", "extraKey")
|
||||
|
||||
key = "bool"
|
||||
kv.set(True, key)
|
||||
print("testCompareBeforeSet: bool value = ", kv.getBool(key))
|
||||
actualSize1 = kv.actualSize()
|
||||
print("testCompareBeforeSet: actualSize = ", actualSize1)
|
||||
print("testCompareBeforeSet: bool value = ", kv.getBool(key))
|
||||
kv.set(True, key)
|
||||
actualSize2 = kv.actualSize()
|
||||
print("testCompareBeforeSet: actualSize2 = ", actualSize2)
|
||||
if actualSize1 != actualSize2:
|
||||
raise("size not match")
|
||||
|
||||
kv.set(False, key)
|
||||
print("testCompareBeforeSet: bool value = ", kv.getBool(key))
|
||||
if kv.getBool(key):
|
||||
print("value not update")
|
||||
|
||||
s1 = "🏊🏻®hhh4️⃣🐅_yyy"
|
||||
s2 = "0aA🏊🏻®hhh4️⃣🐅_zzz"
|
||||
key = "string"
|
||||
kv.set(s1, key)
|
||||
resultString = kv.getString(key)
|
||||
print("testCompareBeforeSet: string = ", resultString)
|
||||
actualSize1 = kv.actualSize()
|
||||
print("testCompareBeforeSet: actualSize = ", actualSize1)
|
||||
resultString = kv.getString(key)
|
||||
print("testCompareBeforeSet: string = ", resultString)
|
||||
kv.set(s1, key)
|
||||
actualSize2 = kv.actualSize()
|
||||
if actualSize1 != actualSize2:
|
||||
print("size not match")
|
||||
|
||||
kv.set(s2, key)
|
||||
resultString = kv.getString(key)
|
||||
print("testCompareBeforeSet: string = ", resultString)
|
||||
if resultString != s2:
|
||||
print("value not update")
|
||||
|
||||
kv.disableCompareBeforeSet()
|
||||
|
||||
|
||||
def test_remove_storage():
|
||||
kv = mmkv.MMKV("test_remove", mmkv.MMKVMode.MultiProcess)
|
||||
kv.set(True, "bool")
|
||||
|
||||
mmkv.MMKV.removeStorage("test_remove")
|
||||
kv = mmkv.MMKV("test_remove", mmkv.MMKVMode.MultiProcess)
|
||||
if kv.count() != 0:
|
||||
print("storage not successfully remove")
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
rootDir = temp_dir + "/dev/mmkv_sg"
|
||||
kv = mmkv.MMKV("test_remove/sg", rootDir = rootDir)
|
||||
kv.set(True, "bool")
|
||||
|
||||
mmkv.MMKV.removeStorage("test_remove/sg")
|
||||
kv = mmkv.MMKV("test_remove/sg", rootDir = rootDir)
|
||||
if kv.count() != 0:
|
||||
print("storage not successfully remove")
|
||||
|
||||
|
||||
def logger(log_level, file, line, function, message):
|
||||
level = {mmkv.MMKVLogLevel.NoLog: 'N',
|
||||
mmkv.MMKVLogLevel.Debug: 'D',
|
||||
mmkv.MMKVLogLevel.Info: 'I',
|
||||
mmkv.MMKVLogLevel.Warning: 'W',
|
||||
mmkv.MMKVLogLevel.Error: 'E'}
|
||||
print('r-[{0}] <{1}:{2}:{3}> {4}'.format(level[log_level], file, line, function, message))
|
||||
|
||||
|
||||
def error_handler(mmap_id, error_type):
|
||||
print('[{}] has error {}'.format(mmap_id, error_type))
|
||||
return mmkv.MMKVErrorType.OnErrorRecover
|
||||
|
||||
|
||||
def content_change_handler(mmap_id):
|
||||
print("[%s]'s content has been changed by other process" % mmap_id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
temp_dir = tempfile.gettempdir()
|
||||
temp_dir = temp_dir + '/mmkv'
|
||||
mmkv.MMKV.initializeMMKV(temp_dir)
|
||||
|
||||
test_expected_capacity()
|
||||
|
||||
functional_test('test_python', False)
|
||||
|
||||
test_backup()
|
||||
|
||||
test_restore()
|
||||
|
||||
test_auto_expire()
|
||||
test_compare_before_set()
|
||||
test_remove_storage()
|
||||
|
||||
mmkv.MMKV.onExit()
|
||||
@@ -1,387 +0,0 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making
|
||||
* MMKV available.
|
||||
*
|
||||
* Copyright (C) 2020 THL A29 Limited, a Tencent company.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License (the "License"); you may not use
|
||||
* this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* https://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "MMKV.h"
|
||||
#include <pybind11/functional.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <basetsd.h>
|
||||
|
||||
using namespace mmkv;
|
||||
using namespace std;
|
||||
namespace py = pybind11;
|
||||
|
||||
typedef SSIZE_T ssize_t;
|
||||
|
||||
static MMBuffer pyBytes2MMBuffer(const py::bytes &bytes) {
|
||||
char *buffer = nullptr;
|
||||
ssize_t length = 0;
|
||||
if (PYBIND11_BYTES_AS_STRING_AND_SIZE(bytes.ptr(), &buffer, &length) == 0) {
|
||||
return {buffer, static_cast<size_t>(length), MMBufferNoCopy};
|
||||
}
|
||||
return MMBuffer(0);
|
||||
}
|
||||
|
||||
static function<void(MMKVLogLevel level, const char *file, int line, const char *function, const string &message)>
|
||||
g_logHandler = nullptr;
|
||||
static void MyLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const string &message) {
|
||||
if (g_logHandler) {
|
||||
g_logHandler(level, file, line, function, message);
|
||||
}
|
||||
}
|
||||
|
||||
static function<MMKVRecoverStrategic(const string &mmapID, MMKVErrorType errorType)> g_errorHandler = nullptr;
|
||||
static MMKVRecoverStrategic MyErrorHandler(const string &mmapID, MMKVErrorType errorType) {
|
||||
if (g_errorHandler) {
|
||||
return g_errorHandler(mmapID, errorType);
|
||||
}
|
||||
return OnErrorDiscard;
|
||||
}
|
||||
|
||||
static function<void(const string &mmapID)> g_contentHandler = nullptr;
|
||||
static void MyContentChangeHandler(const std::string &mmapID) {
|
||||
if (g_contentHandler) {
|
||||
g_contentHandler(mmapID);
|
||||
}
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(mmkv, m) {
|
||||
m.doc() = "An efficient, small key-value storage framework developed by WeChat Team.";
|
||||
|
||||
py::enum_<MMKVMode>(m, "MMKVMode")
|
||||
.value("SingleProcess", MMKVMode::MMKV_SINGLE_PROCESS)
|
||||
.value("MultiProcess", MMKVMode::MMKV_MULTI_PROCESS)
|
||||
.export_values();
|
||||
|
||||
py::enum_<MMKVLogLevel>(m, "MMKVLogLevel")
|
||||
.value("NoLog", MMKVLogLevel::MMKVLogNone)
|
||||
.value("Debug", MMKVLogLevel::MMKVLogDebug)
|
||||
.value("Info", MMKVLogLevel::MMKVLogInfo)
|
||||
.value("Warning", MMKVLogLevel::MMKVLogWarning)
|
||||
.value("Error", MMKVLogLevel::MMKVLogError)
|
||||
.export_values();
|
||||
|
||||
py::enum_<SyncFlag>(m, "SyncFlag")
|
||||
.value("Sync", SyncFlag::MMKV_SYNC)
|
||||
.value("ASync", SyncFlag::MMKV_ASYNC)
|
||||
.export_values();
|
||||
|
||||
py::enum_<MMKVRecoverStrategic>(m, "MMKVRecoverStrategic")
|
||||
.value("OnErrorDiscard", MMKVRecoverStrategic::OnErrorDiscard)
|
||||
.value("OnErrorRecover", MMKVRecoverStrategic::OnErrorRecover)
|
||||
.export_values();
|
||||
|
||||
py::enum_<MMKVErrorType>(m, "MMKVErrorType")
|
||||
.value("CRCCheckFail", MMKVErrorType::MMKVCRCCheckFail)
|
||||
.value("FileLength", MMKVErrorType::MMKVFileLength)
|
||||
.export_values();
|
||||
|
||||
py::class_<MMKV, unique_ptr<MMKV, py::nodelete>> clsMMKV(m, "MMKV");
|
||||
|
||||
// TODO: not working
|
||||
// clsMMKV.def(py::init(&MMKV::mmkvWithID),
|
||||
// py::arg("mmapID"),
|
||||
// py::arg("mode") = MMKV_SINGLE_PROCESS,
|
||||
// py::arg("cryptKey") = (string*) nullptr,
|
||||
// py::arg("rootDir") = (string*) nullptr);
|
||||
|
||||
clsMMKV.def(py::init([](const string &mmapID, MMKVMode mode, const string &cryptKey, const wstring &rootDir,
|
||||
const size_t expectedCapacity) {
|
||||
string *cryptKeyPtr = (cryptKey.length() > 0) ? (string *) &cryptKey : nullptr;
|
||||
wstring *rootDirPtr = (rootDir.length() > 0) ? (wstring *) &rootDir : nullptr;
|
||||
return MMKV::mmkvWithID(mmapID, mode, cryptKeyPtr, rootDirPtr, expectedCapacity);
|
||||
}),
|
||||
"Parameters:\n"
|
||||
" mmapID: all instances of the same mmapID share the same data and file storage\n"
|
||||
" mode: pass MMKVMode.MultiProcess for a multi-process MMKV\n"
|
||||
" cryptKey: pass a non-empty string for an encrypted MMKV, 16 bytes at most\n"
|
||||
" rootDir: custom root directory",
|
||||
" expectedCapacity: the file size you expected when opening or creating file", py::arg("mmapID"),
|
||||
py::arg("mode") = MMKV_SINGLE_PROCESS, py::arg("cryptKey") = string(), py::arg("rootDir") = string(),
|
||||
py::arg("expectedCapacity") = 0);
|
||||
|
||||
clsMMKV.def("__eq__", [](MMKV &kv, const MMKV &other) { return kv.mmapID() == other.mmapID(); });
|
||||
|
||||
clsMMKV.def_static(
|
||||
"initializeMMKV",
|
||||
[](const wstring &rootDir, MMKVLogLevel logLevel, decltype(g_logHandler) logHandler) {
|
||||
if (logHandler) {
|
||||
g_logHandler = std::move(logHandler);
|
||||
MMKV::initializeMMKV(rootDir, logLevel, MyLogHandler);
|
||||
} else {
|
||||
MMKV::initializeMMKV(rootDir, logLevel, nullptr);
|
||||
}
|
||||
},
|
||||
"must call this before getting any MMKV instance", py::arg("rootDir"), py::arg("logLevel") = MMKVLogNone,
|
||||
py::arg("log_handler") = nullptr);
|
||||
|
||||
clsMMKV.def_static(
|
||||
"defaultMMKV",
|
||||
[](MMKVMode mode, const string &cryptKey) {
|
||||
string *cryptKeyPtr = (cryptKey.length() > 0) ? (string *) &cryptKey : nullptr;
|
||||
return MMKV::defaultMMKV(mode, cryptKeyPtr);
|
||||
},
|
||||
"a generic purpose instance", py::arg("mode") = MMKV_SINGLE_PROCESS, py::arg("cryptKey") = string());
|
||||
|
||||
clsMMKV.def("mmapID", &MMKV::mmapID);
|
||||
clsMMKV.def_readonly("isInterProcess", &MMKV::m_isInterProcess);
|
||||
|
||||
clsMMKV.def("cryptKey", &MMKV::cryptKey);
|
||||
clsMMKV.def("reKey", &MMKV::reKey,
|
||||
"transform plain text into encrypted text, or vice versa with an empty cryptKey\n"
|
||||
"Parameters:\n"
|
||||
" newCryptKey: 16 bytes at most",
|
||||
py::arg("newCryptKey"));
|
||||
clsMMKV.def("checkReSetCryptKey", &MMKV::checkReSetCryptKey,
|
||||
"just reset cryptKey (will not encrypt or decrypt anything),\n"
|
||||
"usually you should call this method after other process reKey() a multi-process mmkv",
|
||||
py::arg("newCryptKey"));
|
||||
|
||||
// TODO: Doesn't work, why?
|
||||
// clsMMKV.def("set", py::overload_cast<bool, const string&>(&MMKV::set), py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(bool, const string &))(&MMKV::set), "encode a boolean value", py::arg("value"),
|
||||
py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(bool, const string &, uint32_t))(&MMKV::set),
|
||||
"encode a boolean value with expiration", py::arg("value"), py::arg("key"), py::arg("expireDuration"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(int32_t, const string &))(&MMKV::set), "encode an int32 value", py::arg("value"),
|
||||
py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(int32_t, const string &, uint32_t))(&MMKV::set),
|
||||
"encode an int32 value with expiration", py::arg("value"), py::arg("key"), py::arg("expireDuration"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(uint32_t, const string &))(&MMKV::set), "encode an unsigned int32 value",
|
||||
py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(uint32_t, const string &, uint32_t))(&MMKV::set),
|
||||
"encode an unsigned int32 value with expiration", py::arg("value"), py::arg("key"),
|
||||
py::arg("expireDuration"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(int64_t, const string &))(&MMKV::set), "encode an int64 value", py::arg("value"),
|
||||
py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(int64_t, const string &, uint32_t))(&MMKV::set),
|
||||
"encode an int64 value with expiration", py::arg("value"), py::arg("key"), py::arg("expireDuration"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(uint64_t, const string &))(&MMKV::set), "encode an unsigned int64 value",
|
||||
py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(uint64_t, const string &, uint32_t))(&MMKV::set),
|
||||
"encode an unsigned int64 value with expiration", py::arg("value"), py::arg("key"),
|
||||
py::arg("expireDuration"));
|
||||
//clsMMKV.def("set", (bool (MMKV::*)(float, const string &))(&MMKV::set), py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(double, const string &))(&MMKV::set), "encode a float/double value",
|
||||
py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(double, const string &, uint32_t))(&MMKV::set),
|
||||
"encode a float/double value with expiration", py::arg("value"), py::arg("key"),
|
||||
py::arg("expireDuration"));
|
||||
//clsMMKV.def("set", (bool (MMKV::*)(const char*, const string&))(&MMKV::set), py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(const string &, const string &))(&MMKV::set),
|
||||
"encode an UTF-8 String/bytes value", py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def("set", (bool (MMKV::*)(const string &, const string &, uint32_t))(&MMKV::set),
|
||||
"encode an UTF-8 String/bytes value with expiration", py::arg("value"), py::arg("key"),
|
||||
py::arg("expireDuration"));
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
clsMMKV.def(
|
||||
"set", [](MMKV &kv, const py::bytes &value, const string &key) { return kv.set(pyBytes2MMBuffer(value), key); },
|
||||
"encode a bytes value", py::arg("value"), py::arg("key"));
|
||||
clsMMKV.def(
|
||||
"set",
|
||||
[](MMKV &kv, const py::bytes &value, const string &key, uint32_t expireDuration) {
|
||||
return kv.set(pyBytes2MMBuffer(value), key, expireDuration);
|
||||
},
|
||||
"encode a bytes value with expiration", py::arg("value"), py::arg("key"), py::arg("expireDuration"));
|
||||
#endif
|
||||
|
||||
clsMMKV.def("getBool", &MMKV::getBool, "decode a boolean value", py::arg("key"), py::arg("defaultValue") = false,
|
||||
py::arg("hasValue") = nullptr);
|
||||
clsMMKV.def("getInt", &MMKV::getInt32, "decode an int32 value", py::arg("key"), py::arg("defaultValue") = 0,
|
||||
py::arg("hasValue") = nullptr);
|
||||
clsMMKV.def("getUInt", &MMKV::getUInt32, "decode an unsigned int32 value", py::arg("key"),
|
||||
py::arg("defaultValue") = 0, py::arg("hasValue") = nullptr);
|
||||
clsMMKV.def("getLongInt", &MMKV::getInt64, "decode an int64 value", py::arg("key"), py::arg("defaultValue") = 0,
|
||||
py::arg("hasValue") = nullptr);
|
||||
clsMMKV.def("getLongUInt", &MMKV::getUInt64, "decode an unsigned int64 value", py::arg("key"),
|
||||
py::arg("defaultValue") = 0, py::arg("hasValue") = nullptr);
|
||||
//clsMMKV.def("getFloat", &MMKV::getFloat, py::arg("key"), py::arg("defaultValue") = 0);
|
||||
clsMMKV.def("getFloat", &MMKV::getDouble, "decode a float/double value", py::arg("key"),
|
||||
py::arg("defaultValue") = 0, py::arg("hasValue") = nullptr);
|
||||
clsMMKV.def(
|
||||
"getString",
|
||||
[](MMKV &kv, const string &key, const string &defaultValue) {
|
||||
string result;
|
||||
if (kv.getString(key, result)) {
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
},
|
||||
"decode an UTF-8 String/bytes value", py::arg("key"), py::arg("defaultValue") = string());
|
||||
|
||||
clsMMKV.def(
|
||||
"getBytes",
|
||||
[](MMKV &kv, const string &key, const py::bytes &defaultValue) {
|
||||
MMBuffer result = kv.getBytes(key);
|
||||
if (result.length() > 0) {
|
||||
return py::bytes((const char *) result.getPtr(), result.length());
|
||||
}
|
||||
return defaultValue;
|
||||
},
|
||||
"decode a bytes value", py::arg("key"), py::arg("defaultValue") = py::bytes());
|
||||
|
||||
clsMMKV.def("__contains__", &MMKV::containsKey, py::arg("key"));
|
||||
clsMMKV.def("keys", &MMKV::allKeys, py::arg("filterExpire") = false);
|
||||
|
||||
clsMMKV.def("count", &MMKV::count, py::arg("filterExpire") = false);
|
||||
clsMMKV.def("totalSize", &MMKV::totalSize);
|
||||
clsMMKV.def("actualSize", &MMKV::actualSize);
|
||||
|
||||
clsMMKV.def("remove", &MMKV::removeValueForKey, py::arg("key"));
|
||||
clsMMKV.def("remove", &MMKV::removeValuesForKeys, py::arg("keys"));
|
||||
clsMMKV.def("clearAll", &MMKV::clearAll, py::arg("keepSpace") = false, "remove all key-values");
|
||||
clsMMKV.def("trim", &MMKV::trim, "call this method after lots of removing if you care about disk usage");
|
||||
clsMMKV.def("clearMemoryCache", &MMKV::clearMemoryCache, "call this method if you are facing memory-warning");
|
||||
|
||||
clsMMKV.def("sync", &MMKV::sync, py::arg("flag") = MMKV_SYNC,
|
||||
"this call is not necessary unless you worry about unexpected shutdown of the machine (running out of "
|
||||
"battery, etc)");
|
||||
|
||||
clsMMKV.def("enableAutoKeyExpire", &MMKV::enableAutoKeyExpire, py::arg("expireDurationInSecond"),
|
||||
"turn on auto key expiration, passing 0 means never expire");
|
||||
clsMMKV.def("disableAutoKeyExpire", &MMKV::disableAutoKeyExpire, "turn off auto key expiration");
|
||||
|
||||
clsMMKV.def("enableCompareBeforeSet", &MMKV::enableCompareBeforeSet, "turn on compare before set/update");
|
||||
clsMMKV.def("disableCompareBeforeSet", &MMKV::disableCompareBeforeSet, "turn off compare before set/update");
|
||||
|
||||
clsMMKV.def("lock", &MMKV::lock, "get exclusive access, won't return until the lock is obtained");
|
||||
clsMMKV.def("unlock", &MMKV::unlock);
|
||||
clsMMKV.def("try_lock", &MMKV::try_lock, "try to get exclusive access");
|
||||
|
||||
// log callback handler
|
||||
clsMMKV.def_static(
|
||||
"registerLogHandler",
|
||||
[](decltype(g_logHandler) callback) {
|
||||
g_logHandler = std::move(callback);
|
||||
MMKV::registerLogHandler(MyLogHandler);
|
||||
},
|
||||
"call this method to redirect MMKV's log,\n"
|
||||
"must call MMKV.unRegisterLogHandler() or MMKV.onExit() before exit\n"
|
||||
"Parameters:\n"
|
||||
" log_handler: (logLevel: mmkv.MMKVLogLevel, file: str, line: int, function: str, message: str) -> None",
|
||||
py::arg("log_handler"));
|
||||
clsMMKV.def_static(
|
||||
"unRegisterLogHandler",
|
||||
[] {
|
||||
g_logHandler = nullptr;
|
||||
MMKV::unRegisterLogHandler();
|
||||
},
|
||||
"If you have registered a log handler, you must call this method or MMKV.onExit() before exit. "
|
||||
"Otherwise your app/script won't exit properly.");
|
||||
|
||||
// error callback handler
|
||||
clsMMKV.def_static(
|
||||
"registerErrorHandler",
|
||||
[](decltype(g_errorHandler) callback) {
|
||||
g_errorHandler = std::move(callback);
|
||||
MMKV::registerErrorHandler(MyErrorHandler);
|
||||
},
|
||||
"call this method to handle MMKV failure,\n"
|
||||
"must call MMKV.unRegisterErrorHandler() or MMKV.onExit() before exit\n"
|
||||
"Parameters:\n"
|
||||
" error_handler: (mmapID: str, errorType: mmkv.MMKVErrorType) -> mmkv.MMKVRecoverStrategic",
|
||||
py::arg("error_handler"));
|
||||
clsMMKV.def_static(
|
||||
"unRegisterErrorHandler",
|
||||
[] {
|
||||
g_errorHandler = nullptr;
|
||||
MMKV::unRegisterErrorHandler();
|
||||
},
|
||||
"If you have registered an error handler, you must call this method or MMKV.onExit() before exit. "
|
||||
"Otherwise your app/script won't exit properly.");
|
||||
|
||||
// content change callback handler
|
||||
clsMMKV.def("checkContentChanged", &MMKV::checkContentChanged, "check if content been changed by other process");
|
||||
clsMMKV.def_static(
|
||||
"registerContentChangeHandler",
|
||||
[](decltype(g_contentHandler) callback) {
|
||||
g_contentHandler = std::move(callback);
|
||||
MMKV::registerContentChangeHandler(MyContentChangeHandler);
|
||||
},
|
||||
"register a content change handler,\n"
|
||||
"get notified when an MMKV instance has been changed by other process (not guarantee real-time notification),\n"
|
||||
"must call MMKV.unRegisterContentChangeHandler() or MMKV.onExit() before exit\n"
|
||||
"Parameters:\n"
|
||||
" content_change_handler: (mmapID: str) -> None",
|
||||
py::arg("content_change_handler"));
|
||||
clsMMKV.def_static(
|
||||
"unRegisterContentChangeHandler",
|
||||
[] {
|
||||
g_contentHandler = nullptr;
|
||||
MMKV::unRegisterContentChangeHandler();
|
||||
},
|
||||
"If you have registered a content change handler, you must call this method or MMKV.onExit() before exit. "
|
||||
"Otherwise your app/script won't exit properly.");
|
||||
|
||||
clsMMKV.def_static(
|
||||
"onExit",
|
||||
[] {
|
||||
MMKV::onExit();
|
||||
g_logHandler = nullptr;
|
||||
g_errorHandler = nullptr;
|
||||
g_contentHandler = nullptr;
|
||||
},
|
||||
"call this method before exit, especially if you have registered any callback handlers");
|
||||
|
||||
clsMMKV.def_static(
|
||||
"backupOneToDirectory",
|
||||
[](const string &mmapID, const wstring &dstDir, const wstring &srcDir) {
|
||||
wstring *srcDirPtr = (srcDir.length() > 0) ? (wstring *) &srcDir : nullptr;
|
||||
return MMKV::backupOneToDirectory(mmapID, dstDir, srcDirPtr);
|
||||
},
|
||||
"backup one MMKV instance from srcDir (default to the root dir of MMKV) to dstDir", py::arg("mmapID"),
|
||||
py::arg("dstDir"), py::arg("srcDir") = string());
|
||||
|
||||
clsMMKV.def_static(
|
||||
"restoreOneFromDirectory",
|
||||
[](const string &mmapID, const wstring &srcDir, const wstring &dstDir) {
|
||||
wstring *dstDirPtr = (dstDir.length() > 0) ? (wstring *) &dstDir : nullptr;
|
||||
return MMKV::restoreOneFromDirectory(mmapID, srcDir, dstDirPtr);
|
||||
},
|
||||
"restore one MMKV instance from srcDir to dstDir (default to the root dir of MMKV)", py::arg("mmapID"),
|
||||
py::arg("srcDir"), py::arg("dstDir") = string());
|
||||
|
||||
clsMMKV.def_static(
|
||||
"backupAllToDirectory",
|
||||
[](const wstring &dstDir, const string &srcDir) {
|
||||
wstring *srcDirPtr = (srcDir.length() > 0) ? (wstring *) &srcDir : nullptr;
|
||||
return MMKV::backupAllToDirectory(dstDir, srcDirPtr);
|
||||
},
|
||||
"backup all MMKV instance from srcDir (default to the root dir of MMKV) to dstDir", py::arg("dstDir"),
|
||||
py::arg("srcDir") = string());
|
||||
|
||||
clsMMKV.def_static(
|
||||
"restoreAllFromDirectory",
|
||||
[](const wstring &srcDir, const wstring &dstDir) {
|
||||
wstring *dstDirPtr = (dstDir.length() > 0) ? (wstring *) &dstDir : nullptr;
|
||||
return MMKV::restoreAllFromDirectory(srcDir, dstDirPtr);
|
||||
},
|
||||
"restore all MMKV instance from srcDir to dstDir (default to the root dir of MMKV)", py::arg("srcDir"),
|
||||
py::arg("dstDir") = string());
|
||||
|
||||
clsMMKV.def_static(
|
||||
"removeStorage",
|
||||
[](const string &mmapID, const wstring &rootDir) {
|
||||
wstring *rootDirPtr = (rootDir.length() > 0) ? (wstring *) &rootDir : nullptr;
|
||||
return MMKV::removeStorage(mmapID, rootDirPtr);
|
||||
},
|
||||
"remove the storage of the MMKV, including the data file & meta file (.crc)", py::arg("mmapID"),
|
||||
py::arg("rootDir") = string());
|
||||
}
|
||||
Submodule Win32/Python/pybind11 deleted from 750257797c
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
from setuptools import setup, Extension
|
||||
from setuptools.command.build_ext import build_ext
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
|
||||
class CMakeExtension(Extension):
|
||||
def __init__(self, name, sourcedir=''):
|
||||
Extension.__init__(self, name, sources=[])
|
||||
self.sourcedir = os.path.abspath(sourcedir)
|
||||
|
||||
|
||||
class CMakeBuild(build_ext):
|
||||
def run(self):
|
||||
try:
|
||||
out = subprocess.check_output(['cmake', '--version'])
|
||||
except OSError:
|
||||
raise RuntimeError("CMake must be installed to build the following extensions: " +
|
||||
", ".join(e.name for e in self.extensions))
|
||||
|
||||
if platform.system() == "Windows":
|
||||
cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1))
|
||||
if cmake_version < '3.1.0':
|
||||
raise RuntimeError("CMake >= 3.1.0 is required on Windows")
|
||||
|
||||
for ext in self.extensions:
|
||||
self.build_extension(ext)
|
||||
|
||||
def build_extension(self, ext):
|
||||
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
|
||||
# required for auto-detection of auxiliary "native" libs
|
||||
if not extdir.endswith(os.path.sep):
|
||||
extdir += os.path.sep
|
||||
|
||||
cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
|
||||
'-DPYTHON_EXECUTABLE=' + sys.executable]
|
||||
|
||||
cfg = 'Debug' if self.debug else 'Release'
|
||||
build_args = ['--config', cfg]
|
||||
|
||||
if platform.system() == "Windows":
|
||||
cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
|
||||
if sys.maxsize > 2 ** 32:
|
||||
cmake_args += ['-A', 'x64']
|
||||
build_args += ['--', '/m']
|
||||
else:
|
||||
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
|
||||
build_args += ['--', '-j8']
|
||||
|
||||
env = os.environ.copy()
|
||||
env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), self.distribution.get_version())
|
||||
if not os.path.exists(self.build_temp):
|
||||
os.makedirs(self.build_temp)
|
||||
subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
|
||||
subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp)
|
||||
|
||||
|
||||
setup(
|
||||
name='mmkv',
|
||||
version='1.2.13',
|
||||
author='Tencent',
|
||||
description='An efficient, small key-value storage framework developed by WeChat Team.',
|
||||
long_description='The MMKV, for Python. MMKV is an efficient, generic, easy-to-use key-value storage framework '
|
||||
'developed by the WeChat Team. It can be a replacement for SQLite.',
|
||||
ext_modules=[CMakeExtension('mmkv')],
|
||||
cmdclass=dict(build_ext=CMakeBuild),
|
||||
zip_safe=False,
|
||||
)
|
||||
@@ -1,209 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import mmkv
|
||||
import tempfile
|
||||
|
||||
KeyNotExist = 'KeyNotExist'
|
||||
|
||||
|
||||
def test_bool(kv):
|
||||
ret = kv.set(True, 'bool')
|
||||
assert ret
|
||||
|
||||
value = kv.getBool('bool')
|
||||
assert value
|
||||
|
||||
value = kv.getBool(KeyNotExist)
|
||||
assert not value
|
||||
|
||||
value = kv.getBool(KeyNotExist, True)
|
||||
assert value
|
||||
|
||||
print('test bool: passed')
|
||||
|
||||
|
||||
def test_int(kv):
|
||||
MinInt = -1 * (2 ** 31)
|
||||
ret = kv.set(MinInt, 'Int')
|
||||
assert ret
|
||||
|
||||
value = kv.getInt('Int')
|
||||
assert value == MinInt
|
||||
|
||||
MaxInt = (2 ** 31) - 1
|
||||
ret = kv.set(MaxInt, 'Int')
|
||||
assert ret
|
||||
|
||||
value = kv.getInt('Int')
|
||||
assert value == MaxInt
|
||||
|
||||
value = kv.getInt(KeyNotExist)
|
||||
assert value == 0
|
||||
|
||||
value = kv.getInt(KeyNotExist, -1)
|
||||
assert value == -1
|
||||
|
||||
print('test int: passed')
|
||||
|
||||
|
||||
def test_uint(kv):
|
||||
MaxUInt = (2 ** 32) - 1
|
||||
ret = kv.set(MaxUInt, 'UInt')
|
||||
assert ret
|
||||
|
||||
value = kv.getUInt('UInt')
|
||||
assert value == MaxUInt
|
||||
|
||||
value = kv.getUInt(KeyNotExist)
|
||||
assert value == 0
|
||||
|
||||
value = kv.getUInt(KeyNotExist, 1)
|
||||
assert value == 1
|
||||
|
||||
print('test unsigned int: passed')
|
||||
|
||||
|
||||
def test_long_int(kv):
|
||||
MinLongInt = -1 * (2 ** 63)
|
||||
ret = kv.set(MinLongInt, 'LongInt')
|
||||
assert ret
|
||||
|
||||
value = kv.getLongInt('LongInt')
|
||||
assert value == MinLongInt
|
||||
|
||||
MaxLongInt = (2 ** 63) - 1
|
||||
ret = kv.set(MaxLongInt, 'LongInt')
|
||||
assert ret
|
||||
|
||||
value = kv.getLongInt('LongInt')
|
||||
assert value == MaxLongInt
|
||||
|
||||
value = kv.getLongInt(KeyNotExist)
|
||||
assert value == 0
|
||||
|
||||
value = kv.getLongInt(KeyNotExist, -1)
|
||||
assert value == -1
|
||||
|
||||
print('test long int: passed')
|
||||
|
||||
|
||||
def test_long_uint(kv):
|
||||
MaxLongUInt = (2 ** 64) - 1
|
||||
ret = kv.set(MaxLongUInt, 'LongUInt')
|
||||
assert ret
|
||||
|
||||
value = kv.getLongUInt('LongUInt')
|
||||
assert value == MaxLongUInt
|
||||
|
||||
value = kv.getLongUInt(KeyNotExist)
|
||||
assert value == 0
|
||||
|
||||
value = kv.getLongUInt(KeyNotExist, 1)
|
||||
assert value == 1
|
||||
|
||||
print('test long unsigned int: passed')
|
||||
|
||||
|
||||
def is_float_equal(a, b, accuracy=1e-09):
|
||||
return abs(a - b) <= max(accuracy * max(abs(a), abs(b)), 0.0)
|
||||
|
||||
|
||||
def test_float(kv):
|
||||
MinFloat = sys.float_info.min
|
||||
ret = kv.set(MinFloat, 'Float')
|
||||
assert ret
|
||||
|
||||
value = kv.getFloat('Float')
|
||||
assert is_float_equal(value, MinFloat)
|
||||
|
||||
MaxFloat = sys.float_info.max
|
||||
ret = kv.set(MaxFloat, 'Float')
|
||||
assert ret
|
||||
|
||||
value = kv.getFloat('Float')
|
||||
assert is_float_equal(value, MaxFloat)
|
||||
|
||||
value = kv.getFloat(KeyNotExist)
|
||||
assert is_float_equal(value, 0.0)
|
||||
|
||||
value = kv.getFloat(KeyNotExist, -1.0)
|
||||
assert is_float_equal(value, -1.0)
|
||||
|
||||
print('test float: passed')
|
||||
|
||||
|
||||
def test_string(kv):
|
||||
OlympicString = 'Hello 2021 Olympic Games 奥运会'
|
||||
ret = kv.set(OlympicString, 'String')
|
||||
assert ret
|
||||
|
||||
value = kv.getString('String')
|
||||
if sys.version_info >= (3, 0):
|
||||
assert value == OlympicString
|
||||
else:
|
||||
assert value == OlympicString.decode('utf-8')
|
||||
|
||||
value = kv.getString(KeyNotExist)
|
||||
assert (not value) or (len(value) == 0)
|
||||
|
||||
value = kv.getString(KeyNotExist, 'a')
|
||||
assert value == 'a'
|
||||
|
||||
print('test string: passed')
|
||||
|
||||
|
||||
def test_bytes(kv):
|
||||
OlympicYears = [0, 4, 8, 12, 16, 21]
|
||||
OlympicYearsBytes = bytes(OlympicYears)
|
||||
ret = kv.set(OlympicYearsBytes, 'Bytes')
|
||||
assert ret
|
||||
|
||||
value = kv.getBytes('Bytes')
|
||||
assert value == OlympicYearsBytes
|
||||
if sys.version_info >= (3, 0):
|
||||
value = list(value)
|
||||
assert value == OlympicYears
|
||||
|
||||
value = kv.getBytes(KeyNotExist)
|
||||
assert (not value) or (len(value) == 0)
|
||||
|
||||
default_value = None
|
||||
if sys.version_info >= (3, 0):
|
||||
default_value = bytes('哦豁', 'utf8')
|
||||
else:
|
||||
default_value = bytes('a')
|
||||
value = kv.getBytes(KeyNotExist, default_value)
|
||||
assert value == default_value
|
||||
|
||||
print('test bytes: passed')
|
||||
|
||||
|
||||
def test_equal(kv, mmap_id):
|
||||
assert kv.mmapID() == mmap_id
|
||||
|
||||
another = mmkv.MMKV(mmap_id)
|
||||
assert kv == another
|
||||
|
||||
kv_keys = sorted(kv.keys())
|
||||
another_keys = sorted(another.keys())
|
||||
assert kv_keys == another_keys
|
||||
|
||||
print('test equal: passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
temp_dir = tempfile.gettempdir()
|
||||
temp_dir = temp_dir + '/mmkv'
|
||||
mmkv.MMKV.initializeMMKV(temp_dir)
|
||||
kv = mmkv.MMKV('unit_test_python')
|
||||
test_bool(kv)
|
||||
test_int(kv)
|
||||
test_uint(kv)
|
||||
test_long_int(kv)
|
||||
test_long_uint(kv)
|
||||
test_float(kv)
|
||||
test_string(kv)
|
||||
test_bytes(kv)
|
||||
test_equal(kv, 'unit_test_python')
|
||||
Reference in New Issue
Block a user