diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..7b35e57 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +## Additional Information +(The following information is very important in order to help us to help you. Omission of the following details may delay your support request or receive no attention at all.) + +- Version of python (python --version) + ``` + ``` + +- Version of K2HR3 OpenStack Notification Listener (k2hr3-osnl -v) + ``` + ``` + +- System information (uname -a) + ``` + ``` + +- Distro (cat /etc/issue) + ``` + ``` + +## Details about issue diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..40f1ac1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +## Relevant Issue (if applicable) +(If there are Issues related to this PullRequest, please list it.) + +## Details +(Please describe the details of PullRequest.) diff --git a/.github/scripts/install_dependencies.sh b/.github/scripts/install_dependencies.sh new file mode 100755 index 0000000..801ce5f --- /dev/null +++ b/.github/scripts/install_dependencies.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +echo $(basename $0) + +if test -f "/etc/os-release"; then + . /etc/os-release + OS_NAME=$ID + OS_VERSION=$VERSION_ID +elif test -f "/etc/centos-release"; then + echo "[OK] /etc/centos-release falling back to CentOS-7" + OS_NAME=centos + OS_VERSION=7 +else + echo "Unknown OS, neither /etc/os-release nor /etc/centos-release" + exit 1 +fi + +echo "[OK] HOSTNAME=${HOSTNAME} OS_NAME=${OS_NAME} OS_VERSION=${OS_VERSION}" + +case "${OS_NAME}-${OS_VERSION}" in + ubuntu*|debian*) + DEBIAN_FRONTEND="noninteractive" sudo apt-get update -y + DEBIAN_FRONTEND="noninteractive" sudo apt-get install -y curl pylint + curl -s https://packagecloud.io/install/repositories/antpickax/stable/script.deb.sh | sudo bash + ;; + centos-7) + sudo yum install -y epel-release-7 + sudo yum install -y --enablerepo=epel pylint python3 git curl which + curl -s https://packagecloud.io/install/repositories/antpickax/stable/script.rpm.sh | sudo bash + ;; + centos-8|fedora*) + sudo dnf install -y epel-release-8 + sudo dnf install -y python3-pylint git curl + curl -s https://packagecloud.io/install/repositories/antpickax/stable/script.rpm.sh | sudo bash + ;; +esac + +cd cluster +sh start_server.sh + +exit $? + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/.github/scripts/lint_with_pylint.sh b/.github/scripts/lint_with_pylint.sh new file mode 100755 index 0000000..f8b58a2 --- /dev/null +++ b/.github/scripts/lint_with_pylint.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +echo $(basename $0) + +if test -f "/etc/os-release"; then + . /etc/os-release + OS_NAME=$ID + OS_VERSION=$VERSION_ID +elif test -f "/etc/centos-release"; then + echo "[OK] /etc/centos-release falling back to CentOS-7" + OS_NAME=centos + OS_VERSION=7 +else + echo "Unknown OS, neither /etc/os-release nor /etc/centos-release" + exit 1 +fi + +echo "[OK] HOSTNAME=${HOSTNAME} OS_NAME=${OS_NAME} OS_VERSION=${OS_VERSION}" +cd src + +pylint k2hdkc --py3k -r n + +exit $? + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/.github/scripts/test_with_unittest.sh b/.github/scripts/test_with_unittest.sh new file mode 100755 index 0000000..6514bcc --- /dev/null +++ b/.github/scripts/test_with_unittest.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +echo $(basename $0) + +if test -f "/etc/os-release"; then + . /etc/os-release + OS_NAME=$ID + OS_VERSION=$VERSION_ID +elif test -f "/etc/centos-release"; then + echo "[OK] /etc/centos-release falling back to CentOS-7" + OS_NAME=centos + OS_VERSION=7 +else + echo "[NO] Unknown OS, neither /etc/os-release nor /etc/centos-release" + exit 1 +fi + +echo "[OK] HOSTNAME=${HOSTNAME} OS_NAME=${OS_NAME} OS_VERSION=${OS_VERSION}" +PYTHON="" +case "${OS_NAME}-${OS_VERSION}" in + ubuntu*|debian*) + PYTHON=$(which python) + ;; + centos*|fedora*) + PYTHON=$(which python3) + ;; +esac + +cd src + +TEST_FILES="test_k2hdkc.py test_k2hdkc_package.py" +for TEST_FILE in ${TEST_FILES} +do + ${PYTHON} -m unittest k2hdkc/tests/${TEST_FILE} + if test $? -ne 0; then + echo "[NO] ${PYTHON} -m unittest k2hdkc/tests/${TEST_FILE}" + exit 1 + fi +done + +exit 0 + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..d226dc2 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + release: + types: [published] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.x", "3.10", "3.9", "3.8", "3.7"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + ./.github/scripts/install_dependencies.sh + shell: sh + - name: Lint with pylint + run: | + ./.github/scripts/lint_with_pylint.sh + shell: sh + - name: Test with unittest + run: | + ./.github/scripts/test_with_unittest.sh + shell: sh + - name: Install dependencies for upload pypi package + if: startsWith(github.ref, 'refs/tags') + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + if: startsWith(github.ref, 'refs/tags') + run: python -m build + - name: Publish distribution to PyPI + if: ${{ matrix.python-version == '3.x' && startsWith(github.ref, 'refs/tags') }} + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.PYPI_API_TOKEN }} + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3461dea --- /dev/null +++ b/.gitignore @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + + +# emacs backup files +*~ + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..89ee6c6 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,13 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Hirotaka Wakabayashi + +Contributors +------------ + +* Takeshi Nakatani diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..3ac6634 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,8 @@ +======= +History +======= + +1.0.0 (2022-02-07) +------------------- + +* First release on PyPI. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7294404 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022 Yahoo Japan Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..73ad466 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# +include AUTHORS.rst +include HISTORY.rst +include LICENSE +include README.rst + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# + diff --git a/README.rst b/README.rst index 3de796c..9f39b56 100644 --- a/README.rst +++ b/README.rst @@ -1 +1,110 @@ -# k2hdkc_python +============== +k2hdkc_python +============== + +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://github.com/yahoojapan/k2hdkc_python/blob/master/LICENSE +.. image:: https://img.shields.io/pypi/pyversions/k2hdkc.svg + :target: https://pypi.python.org/pypi/k2hdkc +.. image:: https://img.shields.io/github/forks/yahoojapan/k2hdkc_python.svg + :target: https://github.com/yahoojapan/k2hdkc_python/network +.. image:: https://img.shields.io/github/stars/yahoojapan/k2hdkc_python.svg + :target: https://github.com/yahoojapan/k2hdkc_python/stargazers +.. image:: https://img.shields.io/github/issues/yahoojapan/k2hdkc_python.svg + :target: https://github.com/yahoojapan/k2hdkc_python/issues +.. image:: https://github.com/yahoojapan/k2hdkc_python/workflows/Python%20package/badge.svg + :target: https://github.com/yahoojapan/k2hdkc_python/actions +.. image:: https://readthedocs.org/projects/k2hdkc-python/badge/?version=latest + :target: https://k2hdkc-python.readthedocs.io/en/latest/?badge=latest +.. image:: https://img.shields.io/pypi/v/k2hdkc + :target: https://pypi.org/project/k2hdkc/ + + + +Overview +--------- + +k2hdkc_python is an official python driver for `k2hdkc`_. + +.. _`k2hdkc`: https://k2hdkc.antpick.ax/ + +.. image:: https://raw.githubusercontent.com/yahoojapan/k2hdkc_python/main/docs/images/top_k2hdkc_python.png + + +Install +-------- + +Let's install k2hdkc using pip:: + + pip install k2hdkc + + +Usage +------ + +Firstly you must install the k2hdkc shared library:: + + $ curl -o- https://raw.github.com/yahoojapan/k2hdkc_python/master/cluster/start_server.sh | bash + + +Then, Let's try to set a key and get it:: + + import k2hdkc + + k = k2hdkc.K2hdkc('slave.yaml') + k.set('hello', 'world') + v = k.get('hello') + print(v) // world + + +Development +------------ + +Clone this repository and go into the directory, then run the following command:: + + $ python3 -m pip install --upgrade build + $ python3 -m build + + +Documents +---------- + +Here are documents including other components. + +`Document top page`_ + +`About K2HDKC`_ + +`About AntPickax`_ + +.. _`Document top page`: https://k2hdkc-python.readthedocs.io/ +.. _`ドキュメントトップ`: https://k2hdkc-python.readthedocs.io/ +.. _`About K2HDKC`: https://k2hdkc.antpick.ax/ +.. _`K2HDKCについて`: https://k2hdkc.antpick.ax/ +.. _`About AntPickax`: https://antpick.ax +.. _`AntPickaxについて`: https://antpick.ax + + +Packages +-------- + +Here are packages including other components. + +`k2hdkc(python packages)`_ + +.. _`k2hdkc(python packages)`: https://pypi.org/project/k2hdkc/ + + +License +-------- + +MIT License. See the LICENSE file. + +AntPickax +--------- + +**k2hdkc_python** is a project by AntPickax_, which is an open source team in `Yahoo Japan Corporation`_. + +.. _AntPickax: https://antpick.ax/ +.. _`Yahoo Japan Corporation`: https://about.yahoo.co.jp/info/en/company/ + diff --git a/cluster/hosts b/cluster/hosts new file mode 100644 index 0000000..255b543 --- /dev/null +++ b/cluster/hosts @@ -0,0 +1,26 @@ +# +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# +127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 virtualbox.vagrant.example.com +::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 virtualbox.vagrant.example.com + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/cluster/k2hdkc.sh b/cluster/k2hdkc.sh new file mode 100755 index 0000000..0b0961b --- /dev/null +++ b/cluster/k2hdkc.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# + +# A K2hdkc Cluster management script. + +USER=$(whoami) +BASEDIR=$(dirname "$0") + +start_process() { + echo "chmpx -conf ${BASEDIR}/server.yaml -d dump > ${BASEDIR}/chmpx.log 2>&1" + nohup chmpx -conf ${BASEDIR}/server.yaml -d dump > ${BASEDIR}/chmpx.log 2>&1 & + echo "sleep 3" + sleep 3 + echo "k2hdkc -conf ${BASEDIR}/server.yaml -d dump > ${BASEDIR}/k2hdkc.log 2>&1" + nohup k2hdkc -conf ${BASEDIR}/server.yaml -d dump > ${BASEDIR}/k2hdkc.log 2>&1 & + echo "sleep 3" + sleep 3 + echo "chmpx -conf ${BASEDIR}/slave.yaml -d dump > ${BASEDIR}/slave.log 2>&1" + nohup chmpx -conf ${BASEDIR}/slave.yaml -d dump > ${BASEDIR}/slave.log 2>&1 & + echo "sleep 3" + sleep 3 +} + +stop_process() { + for PROC in chmpx k2hdkc; do + echo "pgrep -u ${USER} -x ${PROC}" + pgrep -u ${USER} -x ${PROC} + if test "${?}" = "0"; then + echo "pkill -x ${PROC}" + pkill -x ${PROC} + fi + done +} + +status_process() { + RET=0 + for PROC in chmpx k2hdkc; do + echo "pgrep -u ${USER} -x ${PROC}" + pgrep -u ${USER} -x ${PROC} + RET=$(echo ${?} + ${RET}|bc) + done + return ${RET} +} + +case $1 in +startifnotexist) + status_process + if test "${RET}" = "2" ; then + echo "start_process" + start_process + elif test "${RET}" = "1" ; then + echo "stop_process" + stop_process + echo "start_process" + start_process + fi + ;; +start) + start_process + ;; +stop) + stop_process + ;; +status) + status_process + ;; +*) + echo "$0 (start|stop|status|startifnotexist)" + exit 1 + ;; +esac +exit 0 + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# + diff --git a/cluster/server.yaml b/cluster/server.yaml new file mode 100644 index 0000000..38ade50 --- /dev/null +++ b/cluster/server.yaml @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +################################################################ +# CONFIGRATION FILE FOR STANDALONE TEST +# +# NOTE: +# - k2hdkc server node must not be use MQACK +# - k2hash for k2hdkc is memory mode, because multi server node +# run on one server and we do not use configration files for +# each process. +################################################################ + +# +# GLOBAL SECTION +# +GLOBAL: + { + FILEVERSION: 2, + DATE: "Wed, 07 Sep 2016 13:55:04 +0900", + GROUP: TESTDKC, + MODE: SERVER, + DELIVERMODE: hash, + MAXCHMPX: 8, + REPLICA: 1, + MAXMQSERVER: 2, + MAXMQCLIENT: 2, + MQPERATTACH: 1, + MAXQPERSERVERMQ: 2, + MAXQPERCLIENTMQ: 1, + MAXMQPERCLIENT: 1, + MAXHISTLOG: 10000, + PORT: 8020, + CTLPORT: 8021, + SELFCTLPORT: 8021, + RWTIMEOUT: 100000, + RETRYCNT: 1000, + CONTIMEOUT: 500000, + MQRWTIMEOUT: 1000, + MQRETRYCNT: 10000, + MQACK: no, + AUTOMERGE: on, + DOMERGE: on, + MERGETIMEOUT: 0, + SOCKTHREADCNT: 4, + MQTHREADCNT: 4, + MAXSOCKPOOL: 10, + SOCKPOOLTIMEOUT: 0, + SSL: no, + K2HFULLMAP: on, + K2HMASKBIT: 4, + K2HCMASKBIT: 4, + K2HMAXELE: 4 + } + +# +# SERVER NODES SECTION +# +SVRNODE: + [ + { + NAME: localhost, + PORT: 8020, + CTLPORT: 8021, + SSL: no + } + ] + +# +# SLAVE NODES SECTION +# +SLVNODE: + [ + { + NAME: "[.]*", + CTLPORT: 8031 + } + ] + +# +# K2HDKC SECTION +# +K2HDKC: + { + #RCVTIMEOUT: 1000, + #SVRNODEINI: , + #REPLCLUSTERINI: , + #DTORTHREADCNT: 1, + #DTORCTP: path.so, + K2HTYPE: mem, + #K2HTYPE: file, + #K2HFILE: /tmp/k2hdkc.k2h, + K2HFULLMAP: on, + K2HINIT: yes, + K2HMASKBIT: 8, + K2HCMASKBIT: 4, + K2HMAXELE: 16, + K2HPAGESIZE: 128, + #PASSPHRASES: , + #PASSFILE: , + #HISTORY: on, + #EXPIRE: 300, + #ATTRPLUGIN: , + #MINTHREAD: 1, + MAXTHREAD: 20, + #REDUCETIME: 30 + } + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/cluster/slave.yaml b/cluster/slave.yaml new file mode 100644 index 0000000..6914b53 --- /dev/null +++ b/cluster/slave.yaml @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# + +################################################################ +# CONFIGRATION FILE FOR STANDALONE TEST +# +# NOTE: +# - k2hdkc server node must not be use MQACK +################################################################ + +# +# GLOBAL SECTION +# +GLOBAL: + { + FILEVERSION: 2, + DATE: "Wed, 07 Sep 2016 13:55:04 +0900", + GROUP: TESTDKC, + MODE: SLAVE, + DELIVERMODE: hash, + MAXCHMPX: 8, + REPLICA: 1, + MAXMQSERVER: 2, + MAXMQCLIENT: 2, + MQPERATTACH: 1, + MAXQPERSERVERMQ: 2, + MAXQPERCLIENTMQ: 1, + MAXMQPERCLIENT: 1, + MAXHISTLOG: 10000, + #PORT: 8020, + CTLPORT: 8031, + SELFCTLPORT: 8031, + RWTIMEOUT: 100000, + RETRYCNT: 1000, + CONTIMEOUT: 500000, + MQRWTIMEOUT: 1000, + MQRETRYCNT: 10000, + MQACK: no, + AUTOMERGE: on, + DOMERGE: on, + MERGETIMEOUT: 0, + SOCKTHREADCNT: 4, + MQTHREADCNT: 4, + MAXSOCKPOOL: 10, + SOCKPOOLTIMEOUT: 0, + SSL: no, + K2HFULLMAP: on, + K2HMASKBIT: 4, + K2HCMASKBIT: 4, + K2HMAXELE: 4 + } + +# +# SERVER NODES SECTION +# +SVRNODE: + [ + { + NAME: localhost, + PORT: 8020, + CTLPORT: 8021, + SSL: no + } + ] + +# +# SLAVE NODES SECTION +# +SLVNODE: + [ + { + NAME: "[.]*", + CTLPORT: 8031 + } + ] + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/cluster/start_server.sh b/cluster/start_server.sh new file mode 100755 index 0000000..c9d6e85 --- /dev/null +++ b/cluster/start_server.sh @@ -0,0 +1,359 @@ +#!/bin/sh +# +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +# Sets PATH. setup_*.sh uses useradd command +PATH=${PATH}:/usr/sbin:/sbin + +# an unset parameter expansion will fail +set -u + +# umask 022 is enough +umask 022 + +# environments +SRCDIR=$(cd $(dirname "$0") && pwd) +DEBUG=1 +if test "${DEBUG}" -eq 1; then + TAG="$(basename $0) -s" +else + TAG=$(basename $0) +fi +USER=$(whoami) +LOGLEVEL=info + +# Starts chmpx and k2hdkc +# +start_process() { + echo "chmpx -conf ${SRCDIR}/server.yaml -d ${LOGLEVEL} > ${SRCDIR}/chmpx.log 2>&1" + nohup chmpx -conf ${SRCDIR}/server.yaml -d ${LOGLEVEL} > ${SRCDIR}/chmpx.log 2>&1 & + echo "sleep 3" + sleep 3 + echo "k2hdkc -conf ${SRCDIR}/server.yaml -d ${LOGLEVEL} > ${SRCDIR}/k2hdkc.log 2>&1" + nohup k2hdkc -conf ${SRCDIR}/server.yaml -d ${LOGLEVEL} > ${SRCDIR}/k2hdkc.log 2>&1 & + echo "sleep 3" + sleep 3 + echo "chmpx -conf ${SRCDIR}/slave.yaml -d ${LOGLEVEL} > ${SRCDIR}/slave.log 2>&1" + nohup chmpx -conf ${SRCDIR}/slave.yaml -d ${LOGLEVEL} > ${SRCDIR}/slave.log 2>&1 & + echo "sleep 3" + sleep 3 +} + +# Stops chmpx and k2hdkc +# +stop_process() { + for PROC in chmpx k2hdkc; do + echo "pgrep -u ${USER} -x ${PROC}" + pgrep -u ${USER} -x ${PROC} + if test "${?}" = "0"; then + echo "pkill -9 -x ${PROC}" + pkill -9 -x ${PROC} + fi + done +} + +# Shows status of chmpx and k2hdkc +# +status_process() { + RET=0 + for PROC in chmpx k2hdkc; do + echo "pgrep -u ${USER} -x ${PROC}" + pgrep -u ${USER} -x ${PROC} + RET=$(echo ${?} + ${RET}|bc) + done + return ${RET} +} + +# Starts chmpx and k2hdkc if no chmpx and k2hdkc processes are running +# +startifnotexist() { + status_process + if test "${RET}" = "2" ; then + echo "start_process" + start_process + elif test "${RET}" = "1" ; then + echo "stop_process" + stop_process + echo "start_process" + start_process + fi +} + +# Checks if k2hdkc is installed +# +# Params:: +# no params +# +# Returns:: +# 0 on installed +# 1 on not installed +# +which_k2hdkc() { + which k2hdkc + if test "${?}" = "0"; then + echo "[OK] k2hdkc already installed" + return 0 + fi + return 1 +} + +# Determines the current OS +# +# Params:: +# no params +# +# Returns:: +# 0 on success +# 1 on failure +# +setup_os_env() { + if test -f "/etc/os-release"; then + . /etc/os-release + OS_NAME=$ID + OS_VERSION=$VERSION_ID + else + echo "[OK] unknown OS, no /etc/os-release and /etc/centos-release falling back to CentOS-7" + OS_NAME=centos + OS_VERSION=7 + fi + + if test "${OS_NAME}" = "ubuntu"; then + echo "[OK] ubuntu configurations are currently equal to debian one" + OS_NAME=debian + fi + + HOSTNAME=$(hostname) + echo "[OK] HOSTNAME=${HOSTNAME} OS_NAME=${OS_NAME} OS_VERSION=${OS_VERSION}" +} + +# Builds k2hash from source code +# +# Params:: +# $1 os_name +# +# Returns:: +# 0 on success +# 1 on failure(exit) +# +make_k2hash() { + + _os_name=${1:?"os_name should be nonzero"} + + if test "${_os_name}" = "debian" -o "${_os_name}" = "ubuntu"; then + _configure_opt="--with-gcrypt" + sudo apt-get update -y + sudo apt-get install -y git curl autoconf autotools-dev gcc g++ make gdb libtool pkg-config libyaml-dev libgcrypt20-dev + elif test "${_os_name}" = "fedora"; then + _configure_opt="--with-nss" + sudo dnf install -y git curl autoconf automake gcc gcc-c++ gdb make libtool pkgconfig libyaml-devel nss-devel + elif test "${_os_name}" = "centos" -o "${_os_name}" = "rhel"; then + _configure_opt="--with-nss" + if test "${OS_VERSION}" = "7"; then + sudo yum install -y git curl autoconf automake gcc gcc-c++ gdb make libtool pkgconfig libyaml-devel nss-devel + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y git curl autoconf automake gcc gcc-c++ gdb make libtool pkgconfig + sudo dnf install -y --enablerepo=powertools nss-devel libyaml-devel + fi + else + echo "[NO] OS should be debian, ubuntu, fedora, centos or rhel" + exit 1 + fi + + echo "[OK] git clone https://github.com/yahoojapan/fullock" + git clone https://github.com/yahoojapan/fullock + echo "[OK] git clone https://github.com/yahoojapan/k2hash" + git clone https://github.com/yahoojapan/k2hash + + if ! test -d "fullock"; then + echo "no fullock" + exit 1 + fi + cd fullock + ./autogen.sh + ./configure --prefix=/usr + make + sudo make install + + if ! test -d "../k2hash"; then + echo "no k2hash" + exit 1 + fi + cd ../k2hash + ./autogen.sh + ./configure --prefix=/usr ${_configure_opt} + make + sudo make install + + return 0 +} + +# Builds k2hdkc from source code +# +# Params:: +# $1 os_name +# +# Returns:: +# 0 on success +# 1 on failure(exit) +# +make_k2hdkc() { + + _os_name=${1:?"os_name should be nonzero"} + + make_k2hash ${_os_name} + + if test "${_os_name}" = "debian" -o "${_os_name}" = "ubuntu"; then + _configure_opt="--with-gnutls" + sudo apt-get update -y + sudo apt-get install -y git curl autoconf autotools-dev gcc g++ make gdb libtool pkg-config libyaml-dev libgnutls28-dev + elif test "${_os_name}" = "fedora"; then + _configure_opt="--with-nss" + sudo dnf install -y git curl autoconf automake gcc gcc-c++ gdb make libtool pkgconfig libyaml-devel nss-devel + elif test "${_os_name}" = "centos" -o "${_os_name}" = "rhel"; then + _configure_opt="--with-nss" + if test "${OS_VERSION}" = "7"; then + sudo yum install -y git curl autoconf automake gcc gcc-c++ gdb make libtool pkgconfig libyaml-devel nss-devel + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y git curl autoconf automake gcc gcc-c++ gdb make libtool pkgconfig + sudo dnf install -y --enablerepo=powertools nss-devel libyaml-devel + fi + else + echo "[NO] OS should be debian, ubuntu, fedora, centos or rhel" + exit 1 + fi + + echo "[OK] git clone https://github.com/yahoojapan/k2hdkc" + git clone https://github.com/yahoojapan/k2hdkc + cd k2hdkc + + echo "[OK] git clone https://github.com/yahoojapan/chmpx" + git clone https://github.com/yahoojapan/chmpx + + cd chmpx + ./autogen.sh + ./configure --prefix=/usr ${_configure_opt} + make + sudo make install + + cd .. + ./autogen.sh + ./configure --prefix=/usr ${_configure_opt} + make + sudo make install + + which k2hdkc + if test "${?}" != "0"; then + echo "[NO] no k2hdkc installed" + exit 1 + fi + return 0 +} + +# +# main loop +# + +setup_os_env + +which_k2hdkc +if test "${?}" = "0"; then + startifnotexist + exit 0 +fi + +if test "${OS_NAME}" = "fedora"; then + which sudo + if test "${?}" = "1"; then + dnf install -y sudo + fi + which bc + if test "${?}" = "1"; then + sudo dnf install -y bc + fi + sudo dnf install -y curl + curl -s https://packagecloud.io/install/repositories/antpickax/current/script.rpm.sh | sudo bash + sudo dnf install k2hdkc-devel -y +elif test "${OS_NAME}" = "debian" -o "${OS_NAME}" = "ubuntu"; then + which sudo + if test "${?}" = "1"; then + apt-get update -y + apt-get install -y sudo + fi + which bc + if test "${?}" = "1"; then + sudo apt-get install -y bc + fi + sudo apt-get install -y curl + curl -s https://packagecloud.io/install/repositories/antpickax/stable/script.deb.sh | sudo bash + sudo apt-get install -y k2hdkc-dev +elif test "${OS_NAME}" = "centos" -o "${OS_NAME}" = "rhel"; then + which sudo + if test "${?}" = "1"; then + if test "${OS_VERSION}" = "7"; then + sudo yum install -y sudo + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y sudo + fi + fi + which bc + if test "${?}" = "1"; then + if test "${OS_VERSION}" = "7"; then + sudo yum install -y bc + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y bc + fi + fi + if test "${OS_VERSION}" = "7"; then + sudo yum install -y k2hdkc-devel + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y k2hdkc-devel + fi + if test "${?}" != "0"; then + if test "${OS_VERSION}" = "7"; then + sudo yum install -y curl + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y curl + fi + curl -s https://packagecloud.io/install/repositories/antpickax/stable/script.rpm.sh | sudo bash + if test "${OS_VERSION}" = "7"; then + sudo yum install -y k2hdkc-devel + elif test "${OS_VERSION}" = "8"; then + sudo dnf install -y k2hdkc-devel + fi + fi +else + echo "[NO] OS must be either fedora or centos or debian or ubuntu, not ${OS_NAME}" + exit 1 +fi + +which_k2hdkc +if test "${?}" = "0"; then + startifnotexist + exit 0 +else + echo "[NO] k2hdkc" + exit 1 +fi + +exit 0 + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..2fc4cc2 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/authors.rst b/docs/authors.rst new file mode 100644 index 0000000..e122f91 --- /dev/null +++ b/docs/authors.rst @@ -0,0 +1 @@ +.. include:: ../AUTHORS.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..17ef659 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../src/')) + + +# -- Project information ----------------------------------------------------- + +project = 'k2hdkc' +copyright = '2022, Yahoo Japan Corporation' +author = 'Hirotaka Wakabayashi, Takeshi Nakatani' + +# The full version, including alpha/beta/rc tags +release = '1.0.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +locale_dirs = ['locale/'] # path is example but recommended. +gettext_compact = False # optional. + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Extension configuration ------------------------------------------------- + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..2506499 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1 @@ +.. include:: ../HISTORY.rst diff --git a/docs/images/top_k2hdkc_python.png b/docs/images/top_k2hdkc_python.png new file mode 100644 index 0000000..f8d3385 Binary files /dev/null and b/docs/images/top_k2hdkc_python.png differ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..73e02c0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,19 @@ +Welcome to k2hdkc's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + readme + modules + authors + history + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/k2hdkc.rst b/docs/k2hdkc.rst new file mode 100644 index 0000000..46f49f1 --- /dev/null +++ b/docs/k2hdkc.rst @@ -0,0 +1,21 @@ +k2hdkc package +============== + +Submodules +---------- + +k2hdkc.k2hdkc module +-------------------- + +.. automodule:: k2hdkc.k2hdkc + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: k2hdkc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/locale/ja/LC_MESSAGES/authors.po b/docs/locale/ja/LC_MESSAGES/authors.po new file mode 100644 index 0000000..44ea7e0 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/authors.po @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hdkc \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-04 05:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Hirotaka Wakabayashi \n" +"Language-Team: AntPickax \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.7.0\n" + +#: ../../../AUTHORS.rst:3 +msgid "Credits" +msgstr "功績" + +#: ../../../AUTHORS.rst:6 +msgid "Development Lead" +msgstr "開発者" + +#: ../../../AUTHORS.rst:8 +msgid "Hirotaka Wakabayashi " +msgstr "若林 大崇" + +#: ../../../AUTHORS.rst:11 +msgid "Contributors" +msgstr "貢献者" + +#: ../../../AUTHORS.rst:13 +msgid "Takeshi Nakatani " +msgstr "中谷 武史" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/locale/ja/LC_MESSAGES/history.po b/docs/locale/ja/LC_MESSAGES/history.po new file mode 100644 index 0000000..d601ea3 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/history.po @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hdkc \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-04 05:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Hirotaka Wakabayashi \n" +"Language-Team: AntPickax \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.7.0\n" + +#: ../../../HISTORY.rst:3 +msgid "History" +msgstr "歴史" + +#: ../../../HISTORY.rst:6 +msgid "1.0.0 (2022-02-07)" +msgstr "" + +#: ../../../HISTORY.rst:8 +msgid "First release on PyPI." +msgstr "最初のリリース" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/locale/ja/LC_MESSAGES/index.po b/docs/locale/ja/LC_MESSAGES/index.po new file mode 100644 index 0000000..eccdc5a --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/index.po @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hdkc \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-04 05:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Hirotaka Wakabayashi \n" +"Language-Team: AntPickax \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.7.0\n" + +#: ../../index.rst:4 +msgid "Contents:" +msgstr "内容" + +#: ../../index.rst:2 +msgid "Welcome to k2hdkc's documentation!" +msgstr "k2hdkcのドキュメントへようこそ" + +#: ../../index.rst:15 +msgid "Indices and tables" +msgstr "一覧" + +#: ../../index.rst:17 +msgid ":ref:`genindex`" +msgstr "索引" + +#: ../../index.rst:18 +msgid ":ref:`modindex`" +msgstr "モジュール索引" + +#: ../../index.rst:19 +msgid ":ref:`search`" +msgstr "検索ページ" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/locale/ja/LC_MESSAGES/k2hdkc.po b/docs/locale/ja/LC_MESSAGES/k2hdkc.po new file mode 100644 index 0000000..d28c741 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/k2hdkc.po @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hdkc \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-04 05:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Hirotaka Wakabayashi \n" +"Language-Team: AntPickax \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.7.0\n" + +#: ../../k2hdkc.rst:2 k2hdkc:1 of +msgid "k2hdkc package" +msgstr "k2hdkcパッケージ" + +#: ../../k2hdkc.rst:5 +msgid "Submodules" +msgstr "サブモジュール" + +#: ../../k2hdkc.rst:8 +msgid "k2hdkc.k2hdkc module" +msgstr "" + +#: k2hdkc.k2hdkc:1 of +msgid "K2hdkc Python Driver" +msgstr "" + +#: k2hdkc.k2hdkc.K2hdkc:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: k2hdkc.k2hdkc.K2hdkc:1 of +msgid "" +"K2hdkc class provides methods to handle key/value pairs in k2hdkc hash " +"database." +msgstr "K2hdkcクラスはk2hdkcハッシュデータベースのkey/valueペアを操作するメソッドを提供します。" + +#: k2hdkc.k2hdkc.K2hdkc.add_subkey:1 of +msgid "Adds a new subkey to a current subkey." +msgstr "サブキーを追加します。" + +#: k2hdkc.k2hdkc.K2hdkc.cas_decrement:1 of +msgid "Decrements a variable in a cluster by using a CAS operation." +msgstr "CAS操作によって、値を一つ減じます。" + +#: k2hdkc.k2hdkc.K2hdkc.cas_get:1 of +msgid "Gets a variable from a cluster using a CAS operation." +msgstr "CAS操作によって、値を取得します。" + +#: k2hdkc.k2hdkc.K2hdkc.cas_increment:1 of +msgid "Increments a variable in a cluster by using a CAS operation." +msgstr "CAS操作によって、値を一つ足します。" + +#: k2hdkc.k2hdkc.K2hdkc.cas_init:1 of +msgid "Initializes a variable in a cluster by using a CAS operation." +msgstr "CAS操作によって、値を初期化します。" + +#: k2hdkc.k2hdkc.K2hdkc.cas_set:1 of +msgid "Sets a value in a cluster by using a CAS operation." +msgstr "CAS操作によって、値をセットします。" + +#: k2hdkc.k2hdkc.K2hdkc.clear_subkeys:1 of +msgid "" +"Clears subkeys of a key. Another subkeys that a subkey has will be " +"removed recursively." +msgstr "サブキーを削除します。" + +#: k2hdkc.k2hdkc.K2hdkc.close:1 of +msgid "Closes the handle" +msgstr "接続を閉じます。" + +#: k2hdkc.k2hdkc.K2hdkc.get:1 of +msgid "Gets the value" +msgstr "値を取得します。" + +#: k2hdkc.k2hdkc.K2hdkc.get_attributes:1 of +msgid "Retrievs attributes of a key." +msgstr "キーの属性を取得します。" + +#: k2hdkc.k2hdkc.K2hdkc.get_subkeys:1 of +msgid "Retrievs subkeys of a key." +msgstr "キーのサブキーを取得します。" + +#: k2hdkc.k2hdkc.K2hdkc.keyqueue_get:1 of +msgid "Gets a new key/value element from queue." +msgstr "キューからキーと値のセットを取得します。" + +#: k2hdkc.k2hdkc.K2hdkc.keyqueue_put:1 of +msgid "Adds a new key/value pair element to a queue." +msgstr "キューにキーと値のセットを追加します。" + +#: k2hdkc.K2hdkc.libc:1 k2hdkc.k2hdkc.K2hdkc.libc:1 of +msgid "returns libc handle" +msgstr "libcライブラリハンドルを返します。" + +#: k2hdkc.K2hdkc.libk2hdkc:1 k2hdkc.k2hdkc.K2hdkc.libk2hdkc:1 of +msgid "returns libk2hkc handle" +msgstr "" + +#: k2hdkc.k2hdkc.K2hdkc.queue_get:1 of +msgid "Gets a new element to a queue." +msgstr "キューの要素を取得します。" + +#: k2hdkc.k2hdkc.K2hdkc.queue_put:1 of +msgid "Adds a new element to a queue." +msgstr "キューに要素を追加します。" + +#: k2hdkc.k2hdkc.K2hdkc.remove:1 of +msgid "Removes a key from a cluster." +msgstr "キーを削除します。" + +#: k2hdkc.k2hdkc.K2hdkc.remove_subkeys:1 of +msgid "Removes a subkey from the current subkeys." +msgstr "サブキーを削除します。" + +#: k2hdkc.k2hdkc.K2hdkc.rename:1 of +msgid "Renames a key in a cluster." +msgstr "キーの名前を変えます。" + +#: k2hdkc.k2hdkc.K2hdkc.set:1 of +msgid "Sets a key/value pair" +msgstr "キーと値をセットします。" + +#: k2hdkc.k2hdkc.K2hdkc.set_subkeys:1 of +msgid "Replaces current subkeys with new one." +msgstr "サブキーをセットします。" + +#: ../../k2hdkc.rst:16 +msgid "Module contents" +msgstr "モジュール" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/locale/ja/LC_MESSAGES/modules.po b/docs/locale/ja/LC_MESSAGES/modules.po new file mode 100644 index 0000000..dc580f5 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/modules.po @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hdkc \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-04 05:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Hirotaka Wakabayashi \n" +"Language-Team: AntPickax \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.7.0\n" + +#: ../../modules.rst:2 +msgid "k2hdkc" +msgstr "" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/locale/ja/LC_MESSAGES/readme.po b/docs/locale/ja/LC_MESSAGES/readme.po new file mode 100644 index 0000000..3e7df5d --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/readme.po @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# Copyright (c) 2022 Yahoo Japan Corporation +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hdkc \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-04 16:47+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Hirotaka Wakabayashi \n" +"Language-Team: AntPickax \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.1\n" + +#: ../../../README.rst:3 +msgid "k2hdkc_python" +msgstr "" + +#: ../../../README.rst:25 +msgid "Overview" +msgstr "概要" + +#: ../../../README.rst:27 +msgid "k2hdkc_python is an official python driver for `k2hdkc`_." +msgstr "k2hdkc_pythonは、k2hdkc_ のPython版(公式)です。" + +#: ../../../README.rst:35 +msgid "Install" +msgstr "インストール" + +#: ../../../README.rst:37 +msgid "Let's install k2hdkc using pip::" +msgstr "pipコマンドを使って、k2hdkcのPython版をインストールしましょう。" + +#: ../../../README.rst:43 +msgid "Usage" +msgstr "使い方" + +#: ../../../README.rst:45 +msgid "Firstly you must install the k2hdkc shared library::" +msgstr "最初に、k2hdkcの共有ライブラリをインストールしなければなりません。" + +#: ../../../README.rst:50 +#, fuzzy +msgid "Then, Let's try to set a key and get it::" +msgstr "それでは、キーに値をセットして、ゲットしてみましょう。" + +#: ../../../README.rst:61 +msgid "Development" +msgstr "開発" + +#: ../../../README.rst:63 +msgid "" +"Clone this repository and go into the directory, then run the following " +"command::" +msgstr "このレポジトリをクローンして、以下のコマンドを打ち込んでください。" + +#: ../../../README.rst:70 +msgid "Documents" +msgstr "ドキュメント" + +#: ../../../README.rst:72 +msgid "Here are documents including other components." +msgstr "k2hdkcのPython版以外のコンポーネントのドキュメントはこちらです。" + +#: ../../../README.rst:74 +msgid "`Document top page`_" +msgstr "`ドキュメントトップ`_" + +#: ../../../README.rst:76 +msgid "`About K2HDKC`_" +msgstr "`K2HDKCについて`_" + +#: ../../../README.rst:78 +msgid "`About AntPickax`_" +msgstr "`AntPickaxについて`_" + +#: ../../../README.rst:89 +msgid "Packages" +msgstr "パッケージについて" + +#: ../../../README.rst:91 +msgid "Here are packages including other components." +msgstr "この他のコンポーネントのパッケージはこちらです。" + +#: ../../../README.rst:93 +msgid "`k2hdkc(python packages)`_" +msgstr "" + +#: ../../../README.rst:99 +msgid "License" +msgstr "ライセンス" + +#: ../../../README.rst:101 +msgid "MIT License. See the LICENSE file." +msgstr "MITライセンスです。詳しくは、LICENSEファイルを見てください。" + +#: ../../../README.rst:104 +msgid "AntPickax" +msgstr "" + +#: ../../../README.rst:106 +msgid "" +"**k2hdkc_python** is a project by AntPickax_, which is an open source " +"team in `Yahoo Japan Corporation`_." +msgstr "" +"k2hdkc_pythonは、 AntPickax_ によるプロジェクトの一つです。 AntPickax_ " +"は、ヤフー株式会社のオープンソース開発チームの一つです。" + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..9994a8b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 0000000..1188809 --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +k2hdkc +====== + +.. toctree:: + :maxdepth: 4 + + k2hdkc diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..72a3355 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..90b1ded --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +sphinx-autoapi +sphinx-rtd-theme + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..b152645 --- /dev/null +++ b/pylintrc @@ -0,0 +1,562 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns= + +# Add files or directories matching the regex patterns to the ignore-list. +# The regex matches against paths. NOTE: Because "\" represents the directory +# delimiter on Windows systems it can't be used as the escape character +# in these regular expressions. +ignore-paths= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + too-many-arguments, + too-many-instance-attributes, + too-many-public-methods, + consider-using-f-string, + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention' and 'info' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting and `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..283eec2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +# + +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..449c2a3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +[metadata] +# This includes the license file(s) in the wheel. +# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file +license_files = LICENSE + +name = k2hdkc +version = 1.0.0 +author = Hirotaka Wakabayashi +author_email = hiwakaba@yahoo-corp.jp +description = A module for k2hdkc from antpickax +long_description = file: README.rst +long_description_content_type = text/x-rst +url = https://github.com/yahoojapan/k2hdkc_python +project_urls = + Bug Tracker = https://github.com/yahoojapan/k2hdkc_python/issues +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: POSIX :: Linux + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.6 + +[options.packages.find] +where = src + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# + diff --git a/src/k2hdkc/__init__.py b/src/k2hdkc/__init__.py new file mode 100644 index 0000000..6da5731 --- /dev/null +++ b/src/k2hdkc/__init__.py @@ -0,0 +1,525 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +""" +k2hdkc package +""" +from __future__ import absolute_import + +__all__ = ['K2hdkc'] + +from typing import List, Set, Dict, Tuple, Optional, Union # noqa: pylint: disable=unused-import + +import ctypes +from ctypes.util import find_library +from ctypes import pointer, byref, cast, POINTER, Structure +from ctypes import c_bool, c_ubyte, c_size_t, c_short, c_int, c_ulonglong, c_char_p, c_ulong +from ctypes import c_uint8, c_uint16, c_uint32, c_uint64 +from enum import Enum +import logging +from logging.handlers import TimedRotatingFileHandler +from logging import StreamHandler +import sys + +LOG = logging.getLogger(__name__) + +# Library handles +_HANDLE: Dict[str, str] = {} + + +# https://docs.python.org/3/library/ctypes.html#incomplete-types +class FILE(Structure): # noqa: pylint:disable=too-few-public-methods + """C FILE structure + """ + + +# https://docs.python.org/3/library/ctypes.html#incomplete-types +class time_t(Structure): # noqa: pylint:disable=too-few-public-methods,invalid-name + """C time_t structure + """ + + +# KeyPack structure +# See: https://github.com/yahoojapan/k2hdkc/blob/master/lib/k2hdkc.h#L75 +# +# typedef struct k2h_key_pack{ +# unsigned char* pkey; +# size_t length; +# }K2HKEYPCK, *PK2HKEYPCK; +# +class KeyPack(Structure): # noqa: pylint:disable=too-few-public-methods + """C KeyPack structure + """ + _fields_ = [("pkey", POINTER(c_ubyte)), ("length", c_size_t)] + + +# AttrPack structure +# See: https://github.com/yahoojapan/k2hdkc/blob/master/lib/k2hdkc.h#L81 +# +# typedef struct k2h_attr_pack{ +# unsigned char* pkey; +# size_t keylength; +# unsigned char* pval; +# size_t vallength; +# }K2HATTRPCK, *PK2HATTRPCK; +# +class AttrPack(Structure): # noqa: pylint:disable=too-few-public-methods + """C Attr structure + """ + _fields_ = [("pkey", POINTER(c_ubyte)), ("keylength", c_size_t), + ("pval", POINTER(c_ubyte)), ("vallength", c_size_t)] + + +class TimeUnit(Enum): + """k2hdkc time units + """ + DAYS = 1 + HOURS = 2 + MILLISECONDS = 3 + MINUTES = 4 + SECONDS = 5 + + +class LayerLogLevel(Enum): + """k2hdkc layer log level + """ + # Silent disables logging. + SILENT = 1 + # logs on errors + COMMUCATION = 2 + # logs on (errors || warnings) + K2HDKC = 3 + # logs on (errors || warnings || info) + CHMPX = 4 + # logs on (errors || warnings || info || debug) + K2HASH = 5 + + +class DataType(Enum): + """DataType for CAS API + """ + U_BYTE = 1 + U_SHORT = 2 + U_INT32 = 4 + U_LONGLONG = 8 + + +# Initializes library handles and stores the result in the _HANDLE cache +def _init_library_handle(): + global _HANDLE # noqa: pylint: disable=global-statement + if _HANDLE: + return _HANDLE + + # Loads libc and libk2hdkc and ... + result = {} + result['c'] = _load_libc() + result['k2hash'] = _load_libk2hash() + result['chmpx'] = _load_libchmpx() + result['k2hdkc'] = _load_libk2hdkc() + _HANDLE = result + + # 0. sets the common loglevel as logging.WARNING + LOG.setLevel(logging.WARNING) + formatter = logging.Formatter( + '%(asctime)-15s %(levelname)s %(name)s:%(lineno)d %(message)s' + ) # hardcoding + stream_handler = StreamHandler(sys.stderr) + stream_handler.setFormatter(formatter) + LOG.addHandler(stream_handler) + + # 1. sets the each layered log level. + # 1.1. comlog + if _HANDLE['k2hdkc']: + _HANDLE['k2hdkc'].k2hdkc_disable_comlog() + # 1.1. k2hdkc + if _HANDLE['k2hdkc']: + _HANDLE['k2hdkc'].k2hdkc_set_debug_level_warning() + # 1.2. chmpx + if _HANDLE['chmpx']: + _HANDLE['chmpx'].chmpx_set_debug_level_warning() + # 1.3. k2hash + if _HANDLE['k2hash']: + _HANDLE['k2hash'].k2h_set_debug_level_warning() + + return result + + +def _load_libc(): + ret = ctypes.cdll.LoadLibrary(find_library("c")) + if ret is None: + raise FileNotFoundError + return ret + + +def _load_libk2hash(): + ret = ctypes.cdll.LoadLibrary(find_library("k2hash")) + if ret._name is None: + return None + return ret + + +def _load_libchmpx(): + ret = ctypes.cdll.LoadLibrary(find_library("chmpx")) + if ret._name is None: + return None + return ret + + +def _load_libk2hdkc(): # noqa: pylint: disable=too-many-statements + ret = ctypes.cdll.LoadLibrary(find_library("k2hdkc")) + if ret._name is None: + return None + + # k2hdkc api + # add_subkey API + # bool k2hdkc_pm_set_str_subkey_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* psubkey, const char* pskeyval, bool checkattr, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_set_str_subkey_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, c_char_p, c_bool, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_set_str_subkey_wa.restype = c_bool + + # cas_get API + # bool k2hdkc_pm_cas64_str_get_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, uint64_t* pval) + ret.k2hdkc_pm_cas8_str_get_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_uint8) + ] + ret.k2hdkc_pm_cas8_str_get_wa.restype = c_bool + ret.k2hdkc_pm_cas16_str_get_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_uint16) + ] + ret.k2hdkc_pm_cas16_str_get_wa.restype = c_bool + ret.k2hdkc_pm_cas32_str_get_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_uint32) + ] + ret.k2hdkc_pm_cas32_str_get_wa.restype = c_bool + ret.k2hdkc_pm_cas64_str_get_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_uint64) + ] + ret.k2hdkc_pm_cas64_str_get_wa.restype = c_bool + + # cas_decrement API + # bool k2hdkc_pm_cas_str_decrement_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_cas_str_decrement_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas_str_decrement_wa.restype = c_bool + + # cas_increment API + # bool k2hdkc_pm_cas_str_increment_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_cas_str_increment_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas_str_increment_wa.restype = c_bool + + # cas_init API + # bool k2hdkc_pm_cas8_str_init_wa(k2hdkc_chmpx_h handle, const char* pkey, uint8_t val, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_cas8_str_init_wa.argtypes = [ + c_uint64, c_char_p, c_uint8, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas8_str_init_wa.restype = c_bool + ret.k2hdkc_pm_cas16_str_init_wa.argtypes = [ + c_uint64, c_char_p, c_uint16, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas16_str_init_wa.restype = c_bool + ret.k2hdkc_pm_cas32_str_init_wa.argtypes = [ + c_uint64, c_char_p, c_uint32, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas32_str_init_wa.restype = c_bool + ret.k2hdkc_pm_cas64_str_init_wa.argtypes = [ + c_uint64, c_char_p, c_uint64, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas64_str_init_wa.restype = c_bool + + # cas_set API + # bool k2hdkc_pm_cas8_str_set_wa(k2hdkc_chmpx_h handle, const char* pkey, uint8_t oldval, uint8_t newval, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_cas8_str_set_wa.argtypes = [ + c_uint64, c_char_p, c_uint8, c_uint8, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas8_str_set_wa.restype = c_bool + ret.k2hdkc_pm_cas16_str_set_wa.argtypes = [ + c_uint64, c_char_p, c_uint16, c_uint16, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas16_str_set_wa.restype = c_bool + ret.k2hdkc_pm_cas32_str_set_wa.argtypes = [ + c_uint64, c_char_p, c_uint32, c_uint32, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas32_str_set_wa.restype = c_bool + ret.k2hdkc_pm_cas64_str_set_wa.argtypes = [ + c_uint64, c_char_p, c_uint64, c_uint64, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_cas64_str_set_wa.restype = c_bool + + # clear_subkeys API + # bool k2hdkc_pm_clear_str_subkeys(k2hdkc_chmpx_h handle, const char* pkey) + ret.k2hdkc_pm_clear_str_subkeys.argtypes = [c_uint64, c_char_p] + ret.k2hdkc_pm_clear_str_subkeys.restype = c_bool + + # get_attributes API + # PK2HDKCATTRPCK k2hdkc_pm_get_str_direct_attrs(k2hdkc_chmpx_h handle, const char* pkey, int* pattrspckcnt) + ret.k2hdkc_pm_get_str_direct_attrs.argtypes = [ + c_uint64, c_char_p, POINTER(c_int) + ] + ret.k2hdkc_pm_get_str_direct_attrs.restype = POINTER(AttrPack) + + # get_subkeys API + # int k2hdkc_pm_get_str_subkeys(k2hdkc_chmpx_h handle, const char* pkey, char*** ppskeyarray) + # char** k2hdkc_pm_get_str_direct_subkeys(k2hdkc_chmpx_h handle, const char* pkey) + # PK2HDKCKEYPCK k2hdkc_pm_get_direct_subkeys(k2hdkc_chmpx_h handle, const unsigned char* pkey, size_t keylength, int* pskeypckcnt) + ret.k2hdkc_pm_get_direct_subkeys.argtypes = [ + c_uint64, c_char_p, c_size_t, + POINTER(c_int) + ] + ret.k2hdkc_pm_get_direct_subkeys.restype = POINTER(KeyPack) + + # queue_get API + # bool k2hdkc_pm_q_str_push_wa(k2hdkc_chmpx_h handle, const char* pprefix, const char* pval, bool is_fifo, bool checkattr, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_q_str_push_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, c_bool, c_bool, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_q_str_push_wa.restype = c_bool + + # queue_put API + # bool k2hdkc_pm_q_str_pop_wp(k2hdkc_chmpx_h handle, const char* pprefix, bool is_fifo, const char* encpass, const char** ppval) + ret.k2hdkc_pm_q_str_pop_wp.argtypes = [ + c_uint64, c_char_p, c_bool, c_char_p, + POINTER(c_char_p) + ] + ret.k2hdkc_pm_q_str_pop_wp.restype = c_bool + + # keyqueue_get API + # bool k2hdkc_pm_keyq_str_pop_wp(k2hdkc_chmpx_h handle, const char* pprefix, bool is_fifo, const char* encpass, const char** ppkey, const char** ppval) + ret.k2hdkc_pm_keyq_str_pop_wp.argtypes = [ + c_uint64, c_char_p, c_bool, c_char_p, + POINTER(c_char_p), + POINTER(c_char_p) + ] + ret.k2hdkc_pm_keyq_str_pop_wp.restype = c_bool + + # keyqueue_put API + # bool k2hdkc_pm_keyq_str_push_wa(k2hdkc_chmpx_h handle, const char* pprefix, const char* pkey, const char* pval, bool is_fifo, bool checkattr, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_keyq_str_push_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, c_char_p, c_bool, c_bool, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_keyq_str_push_wa.restype = c_bool + + # remove API + # bool k2hdkc_pm_remove_str(k2hdkc_chmpx_h handle, const char* pkey) + ret.k2hdkc_pm_remove_str.argtypes = [c_uint64, c_char_p] + ret.k2hdkc_pm_remove_str.restype = c_bool + + # remove_subkeys API # NOTE subkeys accept multiple subkeys + # bool k2hdkc_pm_remove_str_subkey(k2hdkc_chmpx_h handle, const char* pkey, const char* psubkey, size_t subkeylength, bool is_nest) + ret.k2hdkc_pm_remove_str_subkey.argtypes = [ + c_uint64, c_char_p, c_char_p, c_char_p, c_size_t, c_bool + ] + ret.k2hdkc_pm_remove_str_subkey.restype = c_bool + + # rename API + # bool k2hdkc_pm_rename_with_parent_str_wa(k2hdkc_chmpx_h handle, const char* poldkey, const char* pnewkey, const char* pparentkey, bool checkattr, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_rename_with_parent_str_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, c_char_p, c_bool, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_rename_with_parent_str_wa.restype = c_bool + + # set_all API + # bool k2hdkc_pm_set_str_all_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* pval, const char** pskeyarray, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_set_str_all_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_char_p), c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_set_str_all_wa.restype = c_bool + # bool k2hdkc_pm_set_all_wa(k2hdkc_chmpx_h handle, const unsigned char* pkey, size_t keylength, const unsigned char* pval, size_t vallength, const PK2HDKCKEYPCK pskeypck, int skeypckcnt, const char* encpass, const time_t* expire) + ret.k2hdkc_pm_set_all_wa.argtypes = [ + c_uint64, c_char_p, c_size_t, c_char_p, c_size_t, + POINTER(KeyPack), c_int, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_set_all_wa.restype = c_bool + + # set_subkeys API + # bool k2hdkc_pm_set_str_subkeys(k2hdkc_chmpx_h handle, const char* pkey, const char** pskeyarray) + ret.k2hdkc_pm_set_str_subkeys.argtypes = [ + c_uint64, c_char_p, POINTER(c_char_p) + ] + ret.k2hdkc_pm_set_str_subkeys.restype = c_bool + # + # k2hdkc_pm_set_subkeys + # bool k2hdkc_pm_set_subkeys(k2hdkc_chmpx_h handle, const unsigned char* pkey, size_t keylength, const PK2HDKCKEYPCK pskeypck, int skeypckcnt) + ret.k2hdkc_pm_set_subkeys.argtypes = [ + c_uint64, + POINTER(c_ubyte), c_size_t, + POINTER(KeyPack), c_int + ] + ret.k2hdkc_pm_set_subkeys.restype = c_bool + + # get API + # bool k2hdkc_pm_get_str_value_wp(k2hdkc_chmpx_h handle, + # const char* pkey, const char* encpass, char** ppval); + ret.k2hdkc_pm_get_str_value_wp.argtypes = [ + c_uint64, c_char_p, c_char_p, + POINTER(c_char_p) + ] + ret.k2hdkc_pm_get_str_value_wp.restype = c_bool + + # set API + # k2hdkc_pm_set_str_value_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* pval, bool rmsubkeylist, const char* encpass, const time_t* expire); + ret.k2hdkc_pm_set_str_value_wa.argtypes = [ + c_uint64, c_char_p, c_char_p, c_bool, c_char_p, + POINTER(c_ulong) + ] + ret.k2hdkc_pm_set_str_value_wa.restype = c_bool + + # open API + # k2hdkc_chmpx_h k2hdkc_open_chmpx_full(const char* config, + # short ctlport, const char* cuk, bool is_auto_rejoin, bool no_giveup_rejoin, + # bool is_clean_bup) + ret.k2hdkc_open_chmpx_full.argtypes = [ + c_char_p, c_short, c_char_p, c_bool, c_bool, c_bool + ] + ret.k2hdkc_open_chmpx_full.restype = c_uint64 + ret.k2hdkc_open_chmpx_ex.argtypes = [ + c_char_p, c_short, c_bool, c_bool, c_bool + ] + ret.k2hdkc_open_chmpx_ex.restype = c_uint64 + ret.k2hdkc_open_chmpx.argtypes = [c_char_p] + ret.k2hdkc_open_chmpx.restype = c_uint64 + + # close API + # bool k2hdkc_close_chmpx_ex(k2hdkc_chmpx_h handle, bool is_clean_bup) + ret.k2hdkc_close_chmpx_ex.argtypes = [c_uint64, c_bool] + ret.k2hdkc_close_chmpx_ex.restype = c_bool + return ret + + +# Initializes library handlers +_init_library_handle() + + +# Gets library handler +def get_library_handle(): + """Gets C library handles + """ + return _init_library_handle() + + +# Configures the loglevel. +def set_log_level(log_level): + """Sets the log level + """ + LOG.setLevel(log_level) + + +# TODO +# support function to set a logfile as package level function + + +# Configures the layer loglevel. +def set_layer_log_level(log_level): + """Sets the layer log level + """ + if not isinstance(log_level, LayerLogLevel): + TypeError("log_level shoube a LayerLogLevel object") + # 1. gets the current loglevel + # current_log_level = logging.getLevelName(LOG.level) + + global _HANDLE # noqa: pylint: disable=global-statement + if not _HANDLE: + raise RuntimeError("library handle should be defined") + + # 2. sets the each layer loglevel + if LOG.level == logging.NOTSET: + # 1. set network layer silent + _HANDLE['k2hdkc'].k2hdkc_disable_comlog() + # 2. set k2hdkc layer silent + _HANDLE['k2hdkc'].k2hdkc_set_debug_level_silent() + # 3. set chmpx layer silent + _HANDLE['chmpx'].chmpx_set_debug_level_silent() + # 4. set k2hash layer silent + _HANDLE['k2hash'].k2h_set_debug_level_silent() + elif LOG.level == logging.DEBUG: + # 1. set network layer silent + _HANDLE['k2hdkc'].k2hdkc_enable_comlog() + # 2. set k2hdkc layer silent + _HANDLE['k2hdkc'].k2hdkc_set_debug_level_dump() + # 3. set chmpx layer silent + _HANDLE['chmpx'].chmpx_set_debug_level_dump() + # 4. set k2hash layer silent + _HANDLE['k2hash'].k2h_set_debug_level_dump() + elif LOG.level == logging.INFO: + # 1. set network layer silent + _HANDLE['k2hdkc'].k2hdkc_disable_comlog() + # 2. set k2hdkc layer silent + _HANDLE['k2hdkc'].k2hdkc_set_debug_level_message() + # 3. set chmpx layer silent + _HANDLE['chmpx'].chmpx_set_debug_level_message() + # 4. set k2hash layer silent + _HANDLE['k2hash'].k2h_set_debug_level_message() + elif LOG.level == logging.WARNING: + # 1. set network layer silent + _HANDLE['k2hdkc'].k2hdkc_disable_comlog() + # 2. set k2hdkc layer silent + _HANDLE['k2hdkc'].k2hdkc_set_debug_level_warning() + # 3. set chmpx layer silent + _HANDLE['chmpx'].chmpx_set_debug_level_warning() + # 4. set k2hash layer silent + _HANDLE['k2hash'].k2h_set_debug_level_warning() + elif LOG.level == logging.ERROR: + # 1. set network layer silent + _HANDLE['k2hdkc'].k2hdkc_disable_comlog() + # 2. set k2hdkc layer silent + _HANDLE['k2hdkc'].k2hdkc_set_debug_level_error() + # 3. set chmpx layer silent + _HANDLE['chmpx'].chmpx_set_debug_level_error() + # 4. set k2hash layer silent + _HANDLE['k2hash'].k2h_set_debug_level_error() + else: + # level unknown + raise ValueError("unknown logging level:{}".format( + logging.getLevelName(LOG.level))) + + return True + + +# +# import k2hdkc modules +# +from k2hdkc.k2hdkc import K2hdkc + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/src/k2hdkc/k2hdkc.py b/src/k2hdkc/k2hdkc.py new file mode 100644 index 0000000..f2da6db --- /dev/null +++ b/src/k2hdkc/k2hdkc.py @@ -0,0 +1,874 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# +"""K2hdkc Python Driver""" +from __future__ import absolute_import +from copy import deepcopy +import ctypes +from ctypes import pointer, byref, cast, POINTER, Structure +from ctypes import c_bool, c_ubyte, c_size_t, c_short, c_int, c_ulonglong, c_char_p +from ctypes import c_uint8, c_uint16, c_uint32, c_uint64 +import logging +import os +from pathlib import Path +from struct import pack, unpack +import sys +from k2hdkc import TimeUnit +import k2hdkc + +LOG = logging.getLogger(__name__) + + +class K2hdkc: + """ + K2hdkc class provides methods to handle key/value pairs in k2hdkc hash database. + """ + K2H_INVALID_HANDLE = 0 + + def _set_k2h_handle(self): + """Sets the k2h handle""" + # handle = self._libk2hdkc.k2hdkc_open_chmpx( + # c_char_p(self._conf_file.encode())) + # handle = self._libk2hdkc.k2hdkc_open_chmpx_ex( + # c_char_p(self._conf_file.encode()), c_short(self._port), + # c_bool(self._rejoin), c_bool(self._rejoin_forever), + # c_bool(self._clear_backup)) + handle = self._libk2hdkc.k2hdkc_open_chmpx_full( + c_char_p(self._conf_file.encode()), c_short(self._port), + (c_char_p(self._cuk.encode()) if self._cuk else None), + c_bool(self._rejoin), c_bool(self._rejoin_forever), + c_bool(self._clear_backup)) + + if handle == self.__class__.K2H_INVALID_HANDLE: + raise RuntimeError("handle should not be K2H_INVALID_HANDLE") + self._handle = handle + + def __init__(self, + conf_file, + port=8031, + cuk=None, + rejoin=True, + rejoin_forever=True, + clear_backup=True): + """ + K2hdkc constructor. + """ + self._handle = 0 + + if not isinstance(conf_file, str): + raise TypeError("conf_file should be a str object") + if not os.path.exists(Path(conf_file)): + raise RuntimeError("conf_file:{} should exists".format(conf_file)) + if not isinstance(port, int): + raise TypeError("port should be a int object") + if cuk and not isinstance(cuk, str): + raise TypeError("cuk should be a str object") + if not isinstance(rejoin, bool): + raise TypeError("rejoin should be a bool object") + if not isinstance(rejoin_forever, bool): + raise TypeError("rejoin_forever should be a bool object") + if not isinstance(clear_backup, bool): + raise TypeError("clear_backup should be a bool object") + + self._conf_file = conf_file + self._port = port + self._cuk = cuk + self._rejoin = rejoin + self._rejoin_forever = rejoin_forever + self._clear_backup = clear_backup + + try: + # https://docs.python.org/3/library/ctypes.html#ctypes.LibraryLoader.LoadLibrary + self._libc = k2hdkc.get_library_handle()["c"] + if not self._libc: + raise Exception('unable to load c library') + self._libk2hash = k2hdkc.get_library_handle()["k2hash"] + if not self._libk2hash: + raise Exception('unable to load k2hash library') + self._libchmpx = k2hdkc.get_library_handle()["chmpx"] + if not self._libchmpx: + raise Exception('unable to load chmpx library') + self._libk2hdkc = k2hdkc.get_library_handle()["k2hdkc"] + if not self._libk2hdkc: + raise Exception('unable to load k2hdkc library') + except: + LOG.error("Unexpected error:{%s}", sys.exc_info()[0]) + raise + + self._set_k2h_handle() + + @property + def libk2hdkc(self): + """returns libk2hkc handle """ + return self._libk2hdkc + + @property + def libc(self): + """returns libc handle """ + return self._libc + + def set( # noqa: pylint: disable=too-many-branches + self, + key, + val, + clear_subkeys=False, + subkeys=None, + password=None, + expire_duration=None, + time_unit=TimeUnit.SECONDS): + """Sets a key/value pair """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + if not isinstance(val, str): + raise TypeError("val should currently be a str object") + + # checks subkeys + subkeylist = [] + if subkeys: + if not isinstance(subkeys, list) and not isinstance(subkeys, str): + raise TypeError("subkeyes should be a str or list object") + if isinstance(subkeys, list): + subkeylist += subkeys + elif isinstance(subkeys, str): + subkeylist.append(subkeys) + + if password and not isinstance(password, str): + raise TypeError("password should be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + if time_unit and not isinstance(time_unit, TimeUnit): + raise TypeError("time_unit should be a TimeUnit object") + + # 1. calls k2hdkc_pm_set_str_all_wa if subkeylist exists + # bool k2hdkc_pm_set_all_wa(k2hdkc_chmpx_h handle, const unsigned char* pkey, size_t keylength, const unsigned char* pval, size_t vallength, const PK2HDKCKEYPCK pskeypck, int skeypckcnt, const char* encpass, const time_t* expire) + if len(subkeylist) > 0: + keypack_array = (k2hdkc.KeyPack * len(subkeylist))() + i = 0 + for i in subkeylist: + skey = subkeylist[i] + skey_bin = skey.encode() + keypack_array[i].pkey = cast(skey_bin, POINTER(c_ubyte)) + keypack_array[i].length = c_size_t(len(skey_bin) + 1) + keypack_array_pointer = cast(keypack_array, + POINTER(k2hdkc.KeyPack)) + res = self._libk2hdkc.k2hdkc_pm_set_all_wa( + self._handle, c_char_p(key.encode()), c_size_t(len(key) + 1), + c_char_p(val.encode()), c_size_t(len(val) + 1), + (keypack_array_pointer if keypack_array_pointer else None), + len(subkeylist), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + else: + res = self._libk2hdkc.k2hdkc_pm_set_str_value_wa( + self._handle, c_char_p(key.encode()), c_char_p(val.encode()), + c_bool(clear_subkeys), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + + LOG.debug("ret:%s", res) + + # TODO keeps code and subcode or not + # k2hdkc_get_res_code(self._handle) + # k2hdkc_get_res_subcode(self._handle) + return res + + # bool k2hdkc_pm_get_str_value_wp(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, char** ppval); + def get(self, key, password=None): + """Gets the value """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + if password and not isinstance(password, str): + raise TypeError("password should be a str object") + if password and password == "": + raise ValueError("password should not be empty") + ppval = pointer(c_char_p("".encode())) + res = self._libk2hdkc.k2hdkc_pm_get_str_value_wp( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), ppval) + + # TODO keeps code and subcode or not + # k2hdkc_get_res_code(self._handle) + # k2hdkc_get_res_subcode(self._handle) + + if res and ppval.contents.value: + pval = ppval.contents.value.decode() + if ppval.contents: + self._libc.free(ppval.contents) + return pval + return "" + + # bool k2hdkc_pm_set_str_subkey_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* psubkey, const char* pskeyval, bool checkattr, const char* encpass, const time_t* expire) + def add_subkey(self, + key, + subkey, + subval, + check_attr=True, + password=None, + expire_duration=None, + time_unit=TimeUnit.SECONDS): + """Adds a new subkey to a current subkey. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + if not isinstance(subkey, str): + raise TypeError("subkey should be a str object") + if not subkey: + raise ValueError("subkey should not be empty") + if not isinstance(subval, str): + raise TypeError("subval should be a str object") + if check_attr and not isinstance(check_attr, bool): + raise TypeError("check_attr should currently be a bool object") + if password and not isinstance(password, str): + raise TypeError("password should currently be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + if time_unit and not isinstance(time_unit, TimeUnit): + raise TypeError("time_unit should be a TimeUnit object") + + # bool k2hdkc_pm_set_str_subkey_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* psubkey, const char* pskeyval, bool checkattr, const char* encpass, const time_t* expire) + res = self._libk2hdkc.k2hdkc_pm_set_str_subkey_wa( + self._handle, + c_char_p(key.encode()), + c_char_p(subkey.encode()), + c_char_p(subval.encode()), + c_bool(check_attr), + (c_char_p(password.encode()) if password else None), + (pointer(c_uint64(expire_duration)) if expire_duration else None), + ) + + if not res: + LOG.error('error in k2hdkc_pm_set_str_subkey_wa') + return res + + # bool k2hdkc_pm_cas64_str_get_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, uint64_t* pval) + def cas_get(self, key, data_type, password=None, expire_duration=None): # noqa: pylint: disable=too-many-statements,too-many-branches + """Gets a variable from a cluster using a CAS operation. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + # data_ type is either byte, short, int or longlong. + if not data_type: + raise ValueError("data_type should not be empty") + if not isinstance(data_type, k2hdkc.DataType): + raise TypeError("data_type should be a k2hdkc.DataType object") + + if password and not isinstance(password, str): + raise TypeError("password should currently be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + if data_type == k2hdkc.DataType.U_BYTE: + val = c_uint8() + res = self._libk2hdkc.k2hdkc_pm_cas8_str_get_wa( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), + byref(val)) + elif data_type == k2hdkc.DataType.U_SHORT: + val = c_uint16() + res = self._libk2hdkc.k2hdkc_pm_cas16_str_get_wa( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), + byref(val)) + elif data_type == k2hdkc.DataType.U_INT32: + val = c_uint32() + res = self._libk2hdkc.k2hdkc_pm_cas32_str_get_wa( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), + byref(val)) + elif data_type == k2hdkc.DataType.U_LONGLONG: + val = c_ulonglong() + res = self._libk2hdkc.k2hdkc_pm_cas64_str_get_wa( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), + byref(val)) + else: + raise ValueError( + "data_type should be a k2hdkc.DataType object(unknown data_type)" + ) + + if res: + return val.value + LOG.error('error in k2hdkc_pm_cas??_str_get_wa') + return 0 + + # bool k2hdkc_pm_cas_str_decrement_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, const time_t* expire) + def cas_decrement(self, key, password=None, expire_duration=None): # noqa: pylint: disable=too-many-branches + """Decrements a variable in a cluster by using a CAS operation. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + if password and not isinstance(password, str): + raise TypeError("password should currently be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + res = self._libk2hdkc.k2hdkc_pm_cas_str_decrement_wa( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), + (pointer(c_uint64(expire_duration)) if expire_duration else None)) + if not res: + LOG.error('error in k2hdkc_pm_cas_decrement_wa') + return res + + # bool k2hdkc_pm_cas_str_increment_wa(k2hdkc_chmpx_h handle, const char* pkey, const char* encpass, const time_t* expire) + def cas_increment(self, key, password=None, expire_duration=None): # noqa: pylint: disable=too-many-branches + """Increments a variable in a cluster by using a CAS operation. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + if password and not isinstance(password, str): + raise TypeError("password should currently be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + res = self._libk2hdkc.k2hdkc_pm_cas_str_increment_wa( + self._handle, c_char_p(key.encode()), + (c_char_p(password.encode()) if password else None), + (pointer(c_uint64(expire_duration)) if expire_duration else None)) + if not res: + LOG.error('error in k2hdkc_pm_cas_increment_wa') + return res + + # bool k2hdkc_pm_cas8_str_init_wa(k2hdkc_chmpx_h handle, const char* pkey, uint8_t val, const char* encpass, const time_t* expire) + def cas_init(self, key, val=None, password=None, expire_duration=None): # noqa: pylint: disable=too-many-branches + """Initializes a variable in a cluster by using a CAS operation. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + # val type is either byte, short, int or longlong. + if val: + if not isinstance(val, bytes): + raise TypeError("val should be a bytes object") + else: + val = pack('B', 0) # default value is 0 in 8bit. + + if password and not isinstance(password, str): + raise TypeError("password should currently be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + if len(val) == 1: + res = self._libk2hdkc.k2hdkc_pm_cas8_str_init_wa( + self._handle, c_char_p(key.encode()), + c_uint8(unpack('B', val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + elif len(val) == 2: + res = self._libk2hdkc.k2hdkc_pm_cas16_str_init_wa( + self._handle, c_char_p(key.encode()), + c_uint16(unpack('H', val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + elif len(val) == 4: + res = self._libk2hdkc.k2hdkc_pm_cas32_str_init_wa( + self._handle, c_char_p(key.encode()), + c_uint32(unpack('I', val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + elif len(val) == 8: + res = self._libk2hdkc.k2hdkc_pm_cas64_str_init_wa( + self._handle, c_char_p(key.encode()), + c_ulonglong(unpack('Q', val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + if not res: + LOG.error('error in k2hdkc_pm_cas??_str_init_wa') + return res + + # bool k2hdkc_pm_cas8_str_set_wa(k2hdkc_chmpx_h handle, const char* pkey, uint8_t oldval, uint8_t newval, const char* encpass, const time_t* expire) + def cas_set( # noqa: pylint: disable=too-many-branches + self, + key, + old_val, + new_val, + password=None, + expire_duration=None): + """Sets a value in a cluster by using a CAS operation. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + # val type is either byte, short, int or longlong. + if not old_val or not new_val: + raise ValueError("old_val as well as new_val should not be empty") + + if not isinstance(old_val, bytes): + raise TypeError("old_val should be a bytes object") + if not isinstance(new_val, bytes): + raise TypeError("new_val should be a bytes object") + if len(old_val) != len(new_val): + raise ValueError( + "length of old_val and new_val should not be same") + + if password and not isinstance(password, str): + raise TypeError("password should currently be a str object") + if password and password == "": + raise ValueError("password should not be empty") + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a int object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + if len(old_val) == 1: + res = self._libk2hdkc.k2hdkc_pm_cas8_str_set_wa( + self._handle, c_char_p(key.encode()), + c_uint8(unpack('B', old_val)[0]), + c_uint8(unpack('B', new_val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + elif len(old_val) == 2: + res = self._libk2hdkc.k2hdkc_pm_cas16_str_set_wa( + self._handle, c_char_p(key.encode()), + c_uint16(unpack('H', old_val)[0]), + c_uint16(unpack('H', new_val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + elif len(old_val) == 4: + res = self._libk2hdkc.k2hdkc_pm_cas32_str_set_wa( + self._handle, c_char_p(key.encode()), + c_uint32(unpack('I', old_val)[0]), + c_uint32(unpack('I', new_val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + elif len(old_val) == 8: + res = self._libk2hdkc.k2hdkc_pm_cas64_str_set_wa( + self._handle, c_char_p(key.encode()), + c_uint64(unpack('Q', old_val)[0]), + c_uint64(unpack('Q', new_val)[0]), + (c_char_p(password.encode()) if password else None), (pointer( + c_uint64(expire_duration)) if expire_duration else None)) + if not res: + LOG.error('error in k2hdkc_pm_cas??_str_set_wa') + return res + + # bool k2hdkc_pm_clear_str_subkeys(k2hdkc_chmpx_h handle, const char* pkey) + def clear_subkeys(self, key): + """Clears subkeys of a key. Another subkeys that a subkey has will be removed recursively. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + + res = self._libk2hdkc.k2hdkc_pm_clear_str_subkeys( + self._handle, c_char_p(key.encode())) + + if not res: + LOG.error('error in k2hdkc_pm_set_str_subkey_wa') + return res + + # bool k2hdkc_close_chmpx_ex(k2hdkc_chmpx_h handle, bool is_clean_bup) + def close(self): + """Closes the handle + """ + res = self._libk2hdkc.k2hdkc_close_chmpx_ex(self._handle, c_bool(True)) + + if not res: + LOG.error('error in k2hdkc_close_chmpx_ex') + return res + + # PK2HDKCATTRPCK k2hdkc_pm_get_str_direct_attrs(k2hdkc_chmpx_h handle, const char* pkey, int* pattrspckcnt) + def get_attributes(self, key, use_str=True): + """Retrievs attributes of a key. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + + pattrspckcnt = c_int() + res = self._libk2hdkc.k2hdkc_pm_get_str_direct_attrs( + self._handle, c_char_p(key.encode()), byref(pattrspckcnt)) + LOG.debug("type(res):{%s} pattrspckcnt.value{%s}", type(res), + pattrspckcnt.value) + attrs = {} + for i in range(pattrspckcnt.value): + key_buf = ctypes.create_string_buffer(res[i].keylength) + val_buf = ctypes.create_string_buffer(res[i].vallength) + for j in range(res[i].keylength): + key_buf[j] = res[i].pkey[j] + for k in range(res[i].vallength): + val_buf[k] = res[i].pval[k] + if use_str: + # TODO UnicodeDecodeError occurs calling decode() w/o errors='ignore' + attrs[key_buf.value.decode()] = val_buf.value.decode( + errors='ignore') + else: + attrs[key_buf.value] = val_buf.value + return attrs + + # int k2hdkc_pm_get_str_subkeys(k2hdkc_chmpx_h handle, const char* pkey, char*** ppskeyarray) + # char** k2hdkc_pm_get_str_direct_subkeys(k2hdkc_chmpx_h handle, const char* pkey) + # PK2HDKCKEYPCK k2hdkc_pm_get_direct_subkeys(k2hdkc_chmpx_h handle, const unsigned char* pkey, size_t keylength, int* pskeypckcnt) + # ret.k2hdkc_pm_get_direct_subkeys.argtypes = [ + # c_uint64, c_char_p, c_size_t, + # POINTER(c_int) + # ] + # ret.k2hdkc_pm_get_direct_subkeys.restype = POINTER(KeyPack) + def get_subkeys(self, key, use_str=True): + """Retrievs subkeys of a key. + """ + if not isinstance(key, str): + raise TypeError("key should currently be a str object") + if not key: + raise ValueError("key should not be empty") + + pskeypckcnt = c_int() + res = self._libk2hdkc.k2hdkc_pm_get_direct_subkeys( + self._handle, c_char_p(key.encode()), c_size_t(len(key) + 1), + byref(pskeypckcnt)) + LOG.debug("%s", pskeypckcnt.value) + subkeys = [] + for i in range(pskeypckcnt.value): + buf = ctypes.create_string_buffer(res[i].length) + for j in range(res[i].length): + buf[j] = res[i].pkey[j] + if use_str: + subkeys.append(buf.value.decode()) + else: + subkeys.append(buf.value) + return subkeys + + # bool k2hdkc_pm_q_str_pop_wp(k2hdkc_chmpx_h handle, const char* pprefix, bool is_fifo, const char* encpass, const char** ppval) + def queue_get(self, + prefix, + is_fifo=True, + password=None, + expire_duration=None): + """Gets a new element to a queue. + """ + # prefix + if not isinstance(prefix, str): + raise TypeError("prefix should be a string object") + # fifo + if not isinstance(is_fifo, bool): + raise TypeError("fifo should be a boolean object") + # password + if password and not isinstance(password, str): + raise TypeError("password should be a string object") + # expire_duration + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a boolean object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + ppval = c_char_p() + res = self._libk2hdkc.k2hdkc_pm_q_str_pop_wp( + self._handle, c_char_p(prefix.encode()), is_fifo, + (c_char_p(password.encode()) if password else None), byref(ppval)) + + if res and ppval.value: + pval = ppval.value.decode() + if ppval: + self._libc.free(ppval) + return pval + return "" + + # bool k2hdkc_pm_q_str_push_wa(k2hdkc_chmpx_h handle, const char* pprefix, const char* pval, bool is_fifo, bool checkattr, const char* encpass, const time_t* expire) + def queue_put(self, + prefix, + val, + is_fifo=True, + is_check_attr=True, + password=None, + expire_duration=None): + """Adds a new element to a queue. + """ + # prefix + if not isinstance(prefix, str): + raise TypeError("prefix should be a string object") + # val + if not isinstance(val, str): + raise TypeError("val should be a string object") + # fifo + if not isinstance(is_fifo, bool): + raise TypeError("fifo should be a boolean object") + # check_attr + if not isinstance(is_check_attr, bool): + raise TypeError("check_attr should be a boolean object") + # password + if password and not isinstance(password, str): + raise TypeError("password should be a string object") + # expire_duration + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a boolean object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + res = self._libk2hdkc.k2hdkc_pm_q_str_push_wa( + self._handle, c_char_p(prefix.encode()), c_char_p(val.encode()), + is_fifo, is_check_attr, + (c_char_p(password.encode()) if password else None), + (pointer(c_uint64(expire_duration)) if expire_duration else None)) + if res: + LOG.debug("k2hdkc_pm_q_str_push_wa:{%s}", res) + else: + return False + return True + + # bool k2hdkc_pm_keyq_str_pop_wp(k2hdkc_chmpx_h handle, const char* pprefix, bool is_fifo, const char* encpass, const char** ppkey, const char** ppval) + def keyqueue_get(self, + prefix, + is_fifo=True, + password=None, + expire_duration=None): + """Gets a new key/value element from queue. + """ + # prefix + if not isinstance(prefix, str): + raise TypeError("prefix should be a string object") + # fifo + if not isinstance(is_fifo, bool): + raise TypeError("fifo should be a boolean object") + # password + if password and not isinstance(password, str): + raise TypeError("password should be a string object") + # expire_duration + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a boolean object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + + ppkey = c_char_p() + ppval = c_char_p() + res = self._libk2hdkc.k2hdkc_pm_keyq_str_pop_wp( + self._handle, c_char_p(prefix.encode()), is_fifo, + (c_char_p(password.encode()) if password else None), byref(ppkey), + byref(ppval)) + + if res and ppkey.value and ppval.value: + pkey = ppkey.value.decode() + pval = ppval.value.decode() + if ppkey: + self._libc.free(ppkey) + if ppval: + self._libc.free(ppval) + return {pkey: pval} + return {} + + # bool k2hdkc_pm_keyq_str_push_wa(k2hdkc_chmpx_h handle, const char* pprefix, const char* pkey, const char* pval, bool is_fifo, bool checkattr, const char* encpass, const time_t* expire) + def keyqueue_put(self, + prefix, + key, + val, + is_fifo=True, + is_check_attr=True, + password=None, + expire_duration=None): + """Adds a new key/value pair element to a queue. + """ + # prefix + if not isinstance(prefix, str): + raise TypeError("prefix should be a string object") + # key + if not isinstance(key, str): + raise TypeError("key should be a string object") + # val + if not isinstance(val, str): + raise TypeError("val should be a string object") + # fifo + if not isinstance(is_fifo, bool): + raise TypeError("fifo should be a boolean object") + # check_attr + if not isinstance(is_check_attr, bool): + raise TypeError("check_attr should be a boolean object") + # password + if password and not isinstance(password, str): + raise TypeError("password should be a string object") + # expire_duration + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a boolean object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + res = self._libk2hdkc.k2hdkc_pm_keyq_str_push_wa( + self._handle, c_char_p(prefix.encode()), c_char_p(key.encode()), + c_char_p(val.encode()), is_fifo, is_check_attr, + (c_char_p(password.encode()) if password else None), + (pointer(c_uint64(expire_duration)) if expire_duration else None)) + if res: + LOG.debug("k2hdkc_pm_keyq_str_push_wa:{%s}", res) + else: + return False + return True + + # bool k2hdkc_pm_remove_str(k2hdkc_chmpx_h handle, const char* pkey) + def remove(self, key): + """Removes a key from a cluster. + """ + if not isinstance(key, str): + raise TypeError("key should be a str object") + if not key: + raise ValueError("key should not be empty") + res = self._libk2hdkc.k2hdkc_pm_remove_str(self._handle, + c_char_p(key.encode())) + return res + + # bool k2hdkc_pm_remove_str_subkey(k2hdkc_chmpx_h handle, const char* pkey, const char* psubkey, size_t subkeylength, bool is_nest) + def remove_subkeys(self, + key, + subkeys, + nested=False): # NOTE subkeys accept multiple subkeys + """Removes a subkey from the current subkeys. + """ + if not isinstance(key, str): + raise TypeError("key should be a str object") + if not key: + raise ValueError("key should not be empty") + subkeylist = [] + if not isinstance(subkeys, list) and not isinstance(subkeys, str): + raise TypeError("subkeyes should be a str or list object") + if not subkeys: + raise ValueError("subkeys should not be empty") + if isinstance(subkeys, list): + subkeylist += deepcopy(subkeys) + elif isinstance(subkeys, str): + subkeylist.append(subkeys) + if not isinstance(nested, bool): + raise TypeError("nested should be a boolean object") + + for subkey in subkeylist: + if not isinstance(subkey, str): + LOG.warning("subkey should be a str object") + continue + if not subkey: + LOG.warning("subkey should not be empty") + continue + res = self._libk2hdkc.k2hdkc_pm_remove_str_subkey( + self._handle, c_char_p(key.encode()), + c_char_p(subkey.encode()), c_bool(nested)) + if not res: + return False + return True + + # bool k2hdkc_pm_rename_with_parent_str_wa(k2hdkc_chmpx_h handle, const char* poldkey, const char* pnewkey, const char* pparentkey, bool checkattr, const char* encpass, const time_t* expire) + def rename(self, + key, + newkey, + parent_key=None, + is_check_attr=True, + password=None, + expire_duration=None): + """Renames a key in a cluster. + """ + if not isinstance(key, str): + raise TypeError("key should be a str object") + if not key: + raise ValueError("key should not be empty") + if not isinstance(newkey, str): + raise TypeError("newkey should be a str object") + if not newkey: + raise ValueError("newkey should not be empty") + if parent_key and not isinstance(parent_key, str): + raise TypeError("parent_key should be a str object") + # check_attr + if not isinstance(is_check_attr, bool): + raise TypeError("check_attr should be a boolean object") + # password + if password and not isinstance(password, str): + raise TypeError("password should be a string object") + # expire_duration + if expire_duration and not isinstance(expire_duration, int): + raise TypeError("expire_duration should be a boolean object") + if expire_duration and expire_duration <= 0: + raise ValueError("expire_duration should not be positive") + res = self._libk2hdkc.k2hdkc_pm_rename_with_parent_str_wa( + self._handle, c_char_p(key.encode()), c_char_p(newkey.encode()), + (c_char_p(parent_key.encode()) if parent_key else None), + is_check_attr, (c_char_p(password.encode()) if password else None), + (pointer(c_uint64(expire_duration)) if expire_duration else None)) + return res + + def set_subkeys(self, key, subkeys): + """Replaces current subkeys with new one. + """ + if not isinstance(key, str): + raise TypeError("key should be a str object") + if not key: + raise ValueError("key should not be empty") + subkeylist = [] + if not isinstance(subkeys, list) and not isinstance(subkeys, str): + raise TypeError("subkeyes should be a str or list object") + if not subkeys: + raise ValueError("subkeys should not be empty") + if isinstance(subkeys, list): + subkeylist += subkeys + elif isinstance(subkeys, str): + subkeylist.append(subkeys) + + if len(subkeylist) > 0: + keypack_array = (k2hdkc.KeyPack * len(subkeylist))() + i = 0 + for i in subkeylist: + skey = subkeylist[i] + skey_bin = skey.encode() + keypack_array[i].pkey = cast(skey_bin, POINTER(c_ubyte)) + keypack_array[i].length = c_size_t(len(skey_bin) + 1) + keypack_array_pointer = cast(keypack_array, + POINTER(k2hdkc.KeyPack)) + + # bool k2hdkc_pm_set_subkeys(k2hdkc_chmpx_h handle, const unsigned char* pkey, size_t keylength, const PK2HDKCKEYPCK pskeypck, int skeypckcnt) + key_bin = key.encode() + res = self._libk2hdkc.k2hdkc_pm_set_subkeys( + self._handle, cast(key_bin, POINTER(c_ubyte)), + c_size_t(len(key_bin) + 1), + (keypack_array_pointer if keypack_array_pointer else None), + len(subkeylist)) + return res + + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/src/k2hdkc/tests/test_k2hdkc.py b/src/k2hdkc/tests/test_k2hdkc.py new file mode 100644 index 0000000..0ad56d6 --- /dev/null +++ b/src/k2hdkc/tests/test_k2hdkc.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +import unittest +import k2hdkc +import logging +import ctypes +import time +from struct import pack + + +class TestK2hdkc(unittest.TestCase): + def test_K2hdkc_construct(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + db.close() + + def test_K2hdkc_add_subkey(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + subkey = "sub_hello" + subval = "sub_world" + self.assertTrue(db.add_subkey(key, subkey, subval), True) + self.assertTrue(db.get_subkeys(key), [subkey]) + db.close() + + def test_K2hdkc_clear_subkeys(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + subkey = "sub_hello" + subval = "sub_world" + self.assertTrue(db.add_subkey(key, subkey, subval), True) + self.assertTrue(db.get_subkeys(key), [subkey]) + self.assertTrue(db.clear_subkeys(key), True) + # TODO error + # self.assertTrue(db.get_subkeys(key), []) + db.close() + + def test_K2hdkc_get(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + db.close() + + @unittest.skip("skipping because no attrs") + def test_K2hdkc_get_attrs(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + db.close() + + def test_K2hdkc_get_subkeys(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + subkey = "sub_hello" + subval = "sub_world" + self.assertTrue(db.add_subkey(key, subkey, subval), True) + self.assertTrue(db.get_subkeys(key), [subkey]) + db.close() + + def test_K2hdkc_queue_get(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + qkey = "q_hello" + qval = "q_world" + self.assertTrue(db.queue_put(qkey, qval), True) + self.assertTrue(db.queue_get(qkey), qval) + db.close() + + def test_K2hdkc_queue_put(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + qkey = "q_hello" + qval = "q_world" + self.assertTrue(db.queue_put(qkey, qval), True) + self.assertTrue(db.queue_get(qkey), qval) + db.close() + + def test_K2hdkc_keyqueue_get(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + qprefix = "q_prefix" + qkey = "q_hello" + qval = "q_world" + self.assertTrue(db.keyqueue_put(qprefix, qkey, qval), True) + self.assertTrue(db.keyqueue_get(qprefix), {qkey, qval}) + db.close() + + def test_K2hdkc_keyqueue_put(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + qprefix = "q_prefix" + qkey = "q_hello" + qval = "q_world" + self.assertTrue(db.keyqueue_put(qprefix, qkey, qval), True) + self.assertTrue(db.keyqueue_get(qprefix), {qkey, qval}) + db.close() + + def test_K2hdkc_remove(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + self.assertTrue(db.remove(key), True) + self.assertFalse(db.get(key), val) + db.close() + + def test_K2hdkc_remove_subkeys(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + subkey = "sub_hello" + subval = "sub_world" + self.assertTrue(db.add_subkey(key, subkey, subval), True) + self.assertTrue(db.get_subkeys(key), [subkey]) + # TODO error + # self.assertTrue(db.remove_subkeys(key, subkey), True) + db.close() + + def test_K2hdkc_rename(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + newkey = "olleh" + self.assertTrue(db.rename(key, newkey), True) + self.assertTrue(db.get(newkey), val) + db.close() + + def test_K2hdkc_set(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + db.close() + + def test_K2hdkc_set_subkeys(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + subkey = "sub_hello" + subval = "sub_world" + self.assertTrue(db.add_subkey(key, subkey, subval), True) + self.assertTrue(db.get_subkeys(key), [subkey]) + db.close() + + def test_K2hdkc_cas_init(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + cas_key = "cas_hello" + cas_value = 65530 + self.assertTrue(db.cas_init(cas_key, pack('H', cas_value)), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + db.close() + + def test_K2hdkc_cas_get(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + cas_key = "cas_hello" + cas_value = 65530 + self.assertTrue(db.cas_init(cas_key, pack('H', cas_value)), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + db.close() + + def test_K2hdkc_cas_decrement(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + cas_key = "cas_hello" + cas_value = 65530 + self.assertTrue(db.cas_init(cas_key, pack('H', cas_value)), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + self.assertTrue(db.cas_increment(cas_key), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value + 1) + self.assertTrue(db.cas_decrement(cas_key), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + db.close() + + def test_K2hdkc_cas_increment(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + cas_key = "cas_hello" + cas_value = 65530 + self.assertTrue(db.cas_init(cas_key, pack('H', cas_value)), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + self.assertTrue(db.cas_increment(cas_key), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value + 1) + self.assertTrue(db.cas_decrement(cas_key), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + db.close() + + def test_K2hdkc_cas_set(self): + db = k2hdkc.K2hdkc('../cluster/slave.yaml') + self.assertTrue(isinstance(db, k2hdkc.K2hdkc)) + key = "hello" + val = "world" + self.assertTrue(db.set(key, val), True) + self.assertTrue(db.get(key), val) + cas_key = "cas_hello" + cas_value = 65530 + self.assertTrue(db.cas_init(cas_key, pack('H', cas_value)), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + self.assertTrue(db.cas_increment(cas_key), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value + 1) + self.assertTrue(db.cas_decrement(cas_key), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + cas_value) + new_cas_value = 1 + self.assertTrue( + db.cas_set(cas_key, pack('H', cas_value), + pack('H', new_cas_value)), True) + self.assertTrue(db.cas_get(cas_key, k2hdkc.DataType.U_SHORT), + new_cas_value) + db.close() + + +if __name__ == '__main__': + unittest.main() + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +# diff --git a/src/k2hdkc/tests/test_k2hdkc_package.py b/src/k2hdkc/tests/test_k2hdkc_package.py new file mode 100644 index 0000000..33a4d56 --- /dev/null +++ b/src/k2hdkc/tests/test_k2hdkc_package.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# K2hdkc Python Driver under MIT License +# +# Copyright (c) 2022 Yahoo Japan Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Tue Feb 08 2022 +# REVISION: +# + +import unittest +import k2hdkc +import logging + + +class TestK2hdkcPackage(unittest.TestCase): + def test_get_library_handle(self): + libk2hdkc = k2hdkc.get_library_handle() + self.assertTrue(libk2hdkc) + self.assertTrue(isinstance(libk2hdkc, dict)) + self.assertTrue(libk2hdkc['c']) + self.assertTrue(libk2hdkc['k2hdkc']) + + def test_set_log_level(self): + k2hdkc.set_log_level(logging.INFO) + logger = logging.getLogger('k2hdkc') + self.assertEqual(logging.getLevelName(logger.level), 'INFO') + + def test_set_layer_log_level(self): + self.assertEqual( + k2hdkc.set_layer_log_level(k2hdkc.LayerLogLevel.K2HDKC), True) + + +if __name__ == '__main__': + unittest.main() + +# +# Local variables: +# tab-width: 4 +# c-basic-offset: 4 +# End: +# vim600: expandtab sw=4 ts=4 fdm=marker +# vim<600: expandtab sw=4 ts=4 +#