diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..973a7dd --- /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 Python (pip show k2hr3client) + ``` + ``` + +- 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/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..9db2b05 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright (c) 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# 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.12", "3.11", "3.10", "3.9"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + architecture: "x64" + - name: Install dependencies + run: | + make init + shell: sh + - name: Install dependencies in GHA + run: | + python -m pip install --upgrade pip + pipenv install --deploy --dev + - name: Lint with pylint + run: | + pipenv run flake8 src/k2hr3client + pipenv run mypy src/k2hr3client + pipenv run pylint src/k2hr3client + pipenv run python3 setup.py checkdocs + shell: sh + - name: Test with unittest + run: | + pipenv run python3 -m unittest discover src + 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.11' && 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..4c665d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,161 @@ +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +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 +*~ +# +# 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/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..a4edfd2 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,44 @@ +# K2HDKC DBaaS based on Trove +# +# Copyright (c) 2022 Yahoo Japan Corporation +# Copyright 2024 LY 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: +# +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..4e6b093 --- /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..488319e --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,14 @@ +======= +History +======= + +1.0.0 (2024-08-28) +------------------- + +* Supports the other APIs + +0.0.1 (2020-08-28) +------------------- + +* Supports Token, Resource, Policy and Role API + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..373b69a --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2020 Yahoo Japan Corporation +Copyright (c) 2024 LY 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 (including the next +paragraph) 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..b44b033 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,13 @@ +#include AUTHORS.rst +#include CONTRIBUTING.rst +include HISTORY.rst +include LICENSE +include README.rst + +recursive-include k2hr3client * +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fc4de19 --- /dev/null +++ b/Makefile @@ -0,0 +1,156 @@ +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# + +.PHONY: clean clean-test clean-pyc clean-build docs help +.DEFAULT_GOAL := help + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python3 -c "$$BROWSER_PYSCRIPT" + +help: + @python3 -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +init: + python3 -m pip install pipenv + pipenv install --skip-lock + pipenv graph + pipenv install --dev + +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + rm -f VERSION + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr ..mypy_cache/ + rm -f .coverage + rm -fr htmlcov/ + rm -fr .pytest_cache + +lint: ## check style with flake8 + flake8 --version + flake8 src/k2hr3client + mypy src/k2hr3client + pylint src/k2hr3client + python3 setup.py checkdocs + +test: ## run tests quickly with the default Python + python3 -m unittest discover src + +build: ## run build + $ python3 -m pip install --upgrade build + $ python3 -m build + +version: + @rm -f VERSION + @perl -ne 'print if /^[0-9]+.[0-9]+.[0-9]+ \([0-9]{4}-[0-9]{2}-[0-9]{2}\)$$/' HISTORY.rst \ + | head -n 1 | perl -lne 'print $$1 if /^([0-9]+.[0-9]+.[0-9]+) \(.*\)/' > VERSION + +SOURCE_VERSION = $(shell cd src; python3 -c 'import k2hr3client; print(k2hr3client.get_version())') +HISTORY_VERSION = $(shell cat VERSION) + +test-version: version ## builds source and wheel package + @echo 'source ' ${SOURCE_VERSION} + @echo 'history ' ${HISTORY_VERSION} + @if test "${SOURCE_VERSION}" = "${HISTORY_VERSION}" ; then \ + python3 -m unittest discover src; \ + fi + +test-all: lint test-version + +coverage: ## check code coverage quickly with the default Python + coverage run --source src/k2hr3client -m unittest + coverage report -m + coverage xml + coverage html +# $(BROWSER) htmlcov/index.html + +docs: ## generate Sphinx HTML documentation, including API docs + rm -f docs/k2hr3client.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ src/k2hr3client + $(MAKE) -C docs clean + $(MAKE) -C docs html +# $(BROWSER) docs/_build/html/index.html + +servedocs: docs ## compile the docs watching for changes + watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +release: dist ## package and upload a release + twine check dist/* + twine upload dist/* + +test-release: + twine check dist/* + twine upload --repository-url https://test.pypi.org/legacy/ dist/* + +dist: clean version ## builds source and wheel package + @echo 'source ' ${SOURCE_VERSION} + @echo 'history ' ${HISTORY_VERSION} + @if test "${SOURCE_VERSION}" = "${HISTORY_VERSION}" ; then \ + python3 setup.py sdist ; \ + python3 setup.py bdist_wheel ; \ + ls -l dist ; \ + fi + +install: clean ## install the package to the active Python's site-packages + python3 setup.py install + +# +# 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/Pipfile b/Pipfile new file mode 100644 index 0000000..6755bfb --- /dev/null +++ b/Pipfile @@ -0,0 +1,28 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +coverage = "*" +flake8 = "*" +flake8-docstrings = "*" +pep8-naming = "*" +pycodestyle = "*" +mccabe = "*" +sphinx-autoapi = "*" +sphinx-intl = "*" +sphinx-rtd-theme = "*" +sphinx = "*" +watchdog = "*" +wheel = "*" +twine = "*" +pylint = "*" +typed-ast = "*" +astroid = "*" +mypy = "*" +"collective.checkdocs" = "*" +tomli = "*" +dill = "*" + +[packages] diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..82bb59b --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,943 @@ +{ + "_meta": { + "hash": { + "sha256": "a764924c5fc25fcfa8540f19c10267ce76a93189ea281a609052080d1dd94fe2" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "alabaster": { + "hashes": [ + "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", + "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92" + ], + "markers": "python_version >= '3.9'", + "version": "==0.7.16" + }, + "astroid": { + "hashes": [ + "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a", + "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.0'", + "version": "==3.2.4" + }, + "babel": { + "hashes": [ + "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", + "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" + ], + "markers": "python_version >= '3.8'", + "version": "==2.16.0" + }, + "backports.tarfile": { + "hashes": [ + "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", + "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991" + ], + "markers": "python_version < '3.12'", + "version": "==1.2.0" + }, + "certifi": { + "hashes": [ + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.8.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "collective.checkdocs": { + "hashes": [ + "sha256:3a5328257c5224bc72753820c182910d7fb336bc1dba5e09113d48566655e46e" + ], + "version": "==0.2" + }, + "coverage": { + "hashes": [ + "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", + "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", + "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", + "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", + "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", + "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", + "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", + "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", + "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", + "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", + "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", + "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", + "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", + "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", + "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", + "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", + "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", + "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", + "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", + "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", + "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", + "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", + "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", + "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", + "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", + "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", + "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", + "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", + "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", + "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", + "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", + "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", + "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", + "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", + "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", + "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", + "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", + "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", + "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", + "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", + "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", + "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", + "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", + "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", + "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", + "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", + "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", + "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", + "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", + "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", + "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", + "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", + "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", + "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", + "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", + "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", + "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", + "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", + "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", + "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", + "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", + "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", + "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", + "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", + "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", + "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", + "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", + "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", + "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", + "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", + "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", + "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==7.6.1" + }, + "dill": { + "hashes": [ + "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", + "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.3.8" + }, + "docutils": { + "hashes": [ + "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", + "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" + ], + "markers": "python_version >= '3.7'", + "version": "==0.20.1" + }, + "flake8": { + "hashes": [ + "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", + "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.1'", + "version": "==7.1.1" + }, + "flake8-docstrings": { + "hashes": [ + "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af", + "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==1.7.0" + }, + "idna": { + "hashes": [ + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" + ], + "markers": "python_version >= '3.6'", + "version": "==3.8" + }, + "imagesize": { + "hashes": [ + "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", + "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.1" + }, + "importlib-metadata": { + "hashes": [ + "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", + "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5" + ], + "markers": "python_version >= '3.8'", + "version": "==8.4.0" + }, + "isort": { + "hashes": [ + "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", + "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==5.13.2" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", + "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.1" + }, + "jaraco.functools": { + "hashes": [ + "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5", + "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3" + ], + "markers": "python_version >= '3.8'", + "version": "==4.0.2" + }, + "jinja2": { + "hashes": [ + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.4" + }, + "keyring": { + "hashes": [ + "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef", + "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae" + ], + "markers": "python_version >= '3.8'", + "version": "==25.3.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.5" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27", + "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923" + ], + "markers": "python_version >= '3.8'", + "version": "==10.4.0" + }, + "mypy": { + "hashes": [ + "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", + "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce", + "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", + "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b", + "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", + "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", + "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", + "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", + "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86", + "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", + "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", + "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", + "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", + "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", + "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", + "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", + "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", + "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", + "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", + "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", + "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", + "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", + "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", + "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", + "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1", + "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b", + "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.11.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "nh3": { + "hashes": [ + "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", + "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", + "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", + "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", + "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", + "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", + "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", + "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", + "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f", + "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", + "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", + "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", + "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be", + "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50", + "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", + "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe" + ], + "version": "==0.2.18" + }, + "packaging": { + "hashes": [ + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + ], + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pep8-naming": { + "hashes": [ + "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36", + "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.14.1" + }, + "pkginfo": { + "hashes": [ + "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097" + ], + "markers": "python_version >= '3.6'", + "version": "==1.10.0" + }, + "platformdirs": { + "hashes": [ + "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", + "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.2" + }, + "pycodestyle": { + "hashes": [ + "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", + "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.12.1" + }, + "pydocstyle": { + "hashes": [ + "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019", + "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1" + ], + "markers": "python_version >= '3.6'", + "version": "==6.3.0" + }, + "pyflakes": { + "hashes": [ + "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", + "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" + ], + "markers": "python_version >= '3.8'", + "version": "==3.2.0" + }, + "pygments": { + "hashes": [ + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" + ], + "markers": "python_version >= '3.8'", + "version": "==2.18.0" + }, + "pylint": { + "hashes": [ + "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b", + "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.0'", + "version": "==3.2.7" + }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "readme-renderer": { + "hashes": [ + "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311", + "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9" + ], + "markers": "python_version >= '3.8'", + "version": "==43.0" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "rfc3986": { + "hashes": [ + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "rich": { + "hashes": [ + "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc", + "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==13.8.0" + }, + "setuptools": { + "hashes": [ + "sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f", + "sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e" + ], + "markers": "python_version >= '3.8'", + "version": "==74.0.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", + "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" + ], + "version": "==2.2.0" + }, + "sphinx": { + "hashes": [ + "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", + "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.4.7" + }, + "sphinx-autoapi": { + "hashes": [ + "sha256:1596d25504e1fa1839de326b8f6816d97b08dff522aa44d3bb585e270a97c88d", + "sha256:963034d94b0798267f2d2388a3bbcb6845dcc3a9ff1b411c1294d2ced9a8679c" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.3.0" + }, + "sphinx-intl": { + "hashes": [ + "sha256:56ad5f360fae4aa1cb963448c802f141b55c87223bb32a7b29e936620bd1a381", + "sha256:66976a85d31624dfcb564059a6918f90b31669269bfe3f30b2d72e81f225ab20" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.2.0" + }, + "sphinx-rtd-theme": { + "hashes": [ + "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", + "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==2.0.0" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", + "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", + "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", + "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9" + ], + "markers": "python_version >= '3.9'", + "version": "==2.1.0" + }, + "sphinxcontrib-jquery": { + "hashes": [ + "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", + "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae" + ], + "markers": "python_version >= '2.7'", + "version": "==4.1" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", + "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", + "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, + "tomlkit": { + "hashes": [ + "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", + "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79" + ], + "markers": "python_version >= '3.8'", + "version": "==0.13.2" + }, + "twine": { + "hashes": [ + "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==5.1.1" + }, + "typed-ast": { + "hashes": [ + "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10", + "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede", + "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e", + "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c", + "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d", + "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8", + "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e", + "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5", + "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155", + "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4", + "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba", + "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5", + "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a", + "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b", + "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311", + "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769", + "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686", + "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d", + "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2", + "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814", + "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9", + "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b", + "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b", + "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4", + "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd", + "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18", + "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa", + "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6", + "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee", + "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88", + "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4", + "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431", + "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04", + "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d", + "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02", + "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8", + "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437", + "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274", + "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f", + "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a", + "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==1.5.5" + }, + "typing-extensions": { + "hashes": [ + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + ], + "markers": "python_version >= '3.8'", + "version": "==4.12.2" + }, + "urllib3": { + "hashes": [ + "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", + "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.2" + }, + "watchdog": { + "hashes": [ + "sha256:0120b2fa65732797ffa65fa8ee5540c288aa861d91447df298626d6385a24658", + "sha256:01ab36cddc836a0f202c66267daaef92ba5c17c7d6436deff0587bb61234c5c9", + "sha256:0710e9502727f688a7e06d48078545c54485b3d6eb53b171810879d8223c362a", + "sha256:0834c21efa3e767849b09e667274604c7cdfe30b49eb95d794565c53f4db3c1e", + "sha256:109daafc5b0f2a98d1fa9475ff9737eb3559d57b18129a36495e20c71de0b44f", + "sha256:1228cb097e855d1798b550be8f0e9f0cfbac4384f9a3e91f66d250d03e11294e", + "sha256:16c1aa3377bb1f82c5e24277fcbf4e2cac3c4ce46aaaf7212d53caa9076eb7b7", + "sha256:1d17ec7e022c34fa7ddc72aa41bf28c9d1207ffb193df18ba4f6fde453725b3c", + "sha256:1e26f570dd7f5178656affb24d6f0e22ce66c8daf88d4061a27bfb9ac866b40d", + "sha256:22fcad6168fc43cf0e709bd854be5b8edbb0b260f0a6f28f1ea9baa53c6907f7", + "sha256:2aa59fab7ff75281778c649557275ca3085eccbdf825a0e2a5ca3810e977afe5", + "sha256:3c177085c3d210d1c73cb4569442bdaef706ebebc423bd7aed9e90fc12b2e553", + "sha256:3c2d50fdb86aa6df3973313272f5a17eb26eab29ff5a0bf54b6d34597b4dc4e4", + "sha256:4fe6780915000743074236b21b6c37419aea71112af62237881bc265589fe463", + "sha256:663b096368ed7831ac42259919fdb9e0a1f0a8994d972675dfbcca0225e74de1", + "sha256:685931412978d00a91a193d9018fc9e394e565e8e7a0c275512a80e59c6e85f8", + "sha256:6c96b1706430839872a3e33b9370ee3f7a0079f6b828129d88498ad1f96a0f45", + "sha256:6e58eafe9cc5ceebe1562cdb89bacdcd0ef470896e8b0139fe677a5abec243da", + "sha256:78db0fe0336958fc0e1269545c980b6f33d04d184ba191b2800a8b71d3e971a9", + "sha256:7e6b0e9b8a9dc3865d65888b5f5222da4ba9c4e09eab13cff5e305e7b7e7248f", + "sha256:990aedb9e2f336b45a70aed9c014450e7c4a70fd99c5f5b1834d57e1453a177e", + "sha256:b8d747bf6d8fe5ce89cb1a36c3724d1599bd4cde3f90fcba518e6260c7058a52", + "sha256:bc16d448a74a929b896ed9578c25756b2125400b19b3258be8d9a681c7ae8e71", + "sha256:bf3216ec994eabb2212df9861f19056ca0d4cd3516d56cb95801933876519bfe", + "sha256:c2b4d90962639ae7cee371ea3a8da506831945d4418eee090c53bc38e6648dc6", + "sha256:cb59ad83a1700304fc1ac7bc53ae9e5cbe9d60a52ed9bba8e2e2d782a201bb2b", + "sha256:d146331e6b206baa9f6dd40f72b5783ad2302c240df68e7fce196d30588ccf7b", + "sha256:d1acef802916083f2ad7988efc7decf07e46e266916c0a09d8fb9d387288ea12", + "sha256:d76efab5248aafbf8a2c2a63cd7b9545e6b346ad1397af8b862a3bb3140787d8", + "sha256:ff4e957c45c446de34c513eadce01d0b65da7eee47c01dce472dd136124552c9" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==5.0.0" + }, + "wheel": { + "hashes": [ + "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f", + "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.44.0" + }, + "zipp": { + "hashes": [ + "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064", + "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b" + ], + "markers": "python_version >= '3.8'", + "version": "==3.20.1" + } + } +} diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..41f1713 --- /dev/null +++ b/README.rst @@ -0,0 +1,130 @@ +================== +k2hr3client_python +================== + +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://github.com/yahoojapan/k2hr3client_python/blob/master/LICENSE +.. image:: https://img.shields.io/pypi/pyversions/k2hr3client.svg + :target: https://pypi.python.org/pypi/k2hr3client +.. image:: https://img.shields.io/github/forks/yahoojapan/k2hr3client_python.svg + :target: https://github.com/yahoojapan/k2hr3client_python/network +.. image:: https://img.shields.io/github/stars/yahoojapan/k2hr3client_python.svg + :target: https://github.com/yahoojapan/k2hr3client_python/stargazers +.. image:: https://img.shields.io/github/issues/yahoojapan/k2hr3client_python.svg + :target: https://github.com/yahoojapan/k2hr3client_python/issues +.. image:: https://github.com/yahoojapan/k2hr3client_python/workflows/Python%20package/badge.svg + :target: https://github.com/yahoojapan/k2hr3client_python/actions +.. image:: https://readthedocs.org/projects/k2hr3client-python/badge/?version=latest + :target: https://k2hr3client-python.readthedocs.io/en/latest/?badge=latest +.. image:: https://img.shields.io/pypi/v/k2hr3client + :target: https://pypi.org/project/k2hr3client/ + + + +Overview +--------- + +k2hr3client_python is an official Python WebAPI client for `k2hr3`_. + +.. _`k2hr3`: https://k2hr3.antpick.ax/ + +.. image:: https://k2hr3.antpick.ax/images/top_k2hr3.png + + +Install +-------- + +Let's install k2hr3client_python using pip:: + + pip install k2hr3client + + +Usage +------ + +Let's try to get a token and create a resource.:: + + >>> from k2hr3client.token import K2hr3Token + >>> iaas_user = "demo" + >>> iaas_project = "demo" + >>> iaas_token_url = "http://172.24.4.1/identity/v3/auth/tokens" + >>> iaas_token = K2hr3Token.get_openstack_token( + ... iaas_token_url, iaas_user, "password", iaas_project + ... ) + >>> mytoken = K2hr3Token(iaas_project, iaas_token) + >>> + >>> from k2hr3client.http import K2hr3Http + >>> k2hr3_token_url = "http://127.0.0.1:18080" + >>> myhttp = K2hr3Http(k2hr3_token_url) + >>> myhttp.POST(mytoken.create()) + >>> mytoken.token // k2hr3 token + >>> + >>> from k2hr3client.resource import K2hr3Resource + >>> k2hr3_resource_name = "test_resource" + >>> myresource = K2hr3Resource(mytoken.token) + >>> myhttp.POST( + ... myresource.create_conf_resource( + ... name=k2hr3_resource_name, + ... data_type="string", + ... data="testresourcedata", + ... tenant="demo", + ... cluster_name="testcluster", + ... keys={ + ... "cluster-name": "test-cluster", + ... "chmpx-server-port": "8020", + ... "chmpx-server-ctrlport": "8021", + ... "chmpx-slave-ctrlport": "8031"}, + ... alias=[]) + ... ) + >>> myresource.resp.body // {"result":true... + +Development +------------ + +Clone this repository and go into the directory, then run the following command:: + + $ make init + $ pipenv shell + $ make lint test docs build + + +Documents +---------- + +Here are documents including other components. + +`Document top page`_ + +`About K2HR3`_ + +`About AntPickax`_ + +.. _`Document top page`: https://k2hr3client-python.readthedocs.io/ +.. _`About K2HR3`: https://k2hr3.antpick.ax/ +.. _`About AntPickax`: https://antpick.ax + + +Packages +-------- + +Here are packages including other components. + +`k2hr3client(python packages)`_ + +.. _`k2hr3client(python packages)`: https://pypi.org/project/k2hr3client/ + + +License +-------- + +MIT License. See the LICENSE file. + + +AntPickax +--------- + +**k2hr3client_python** is a project by AntPickax_, which is an open source team in `LY Corporation`_. + +.. _AntPickax: https://antpick.ax/ +.. _`LY Corporation`: https://www.lycorp.co.jp/en/company/overview/ + diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..f6c8fe8 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,159 @@ +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# 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 +*~ +# +# 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..9b59039 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright (c) 2022 Yahoo Japan Corporation +# Copyright (c) 2024 LY 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..04dddef --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright (c) 2022 Yahoo Japan Corporation +# Copyright (c) 2024 LY 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 = 'k2hr3client' +copyright = '2020, 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_k2hr3.png b/docs/images/top_k2hr3.png new file mode 100644 index 0000000..8079802 Binary files /dev/null and b/docs/images/top_k2hr3.png differ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..6c00c98 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,19 @@ +Welcome to k2hr3client's documentation! +======================================= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + readme + modules + authors + history + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/k2hr3client.rst b/docs/k2hr3client.rst new file mode 100644 index 0000000..e885d41 --- /dev/null +++ b/docs/k2hr3client.rst @@ -0,0 +1,125 @@ +k2hr3client package +=================== + +Submodules +---------- + +k2hr3client.acr module +---------------------- + +.. automodule:: k2hr3client.acr + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.api module +---------------------- + +.. automodule:: k2hr3client.api + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.exception module +---------------------------- + +.. automodule:: k2hr3client.exception + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.extdata module +-------------------------- + +.. automodule:: k2hr3client.extdata + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.http module +----------------------- + +.. automodule:: k2hr3client.http + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.list module +----------------------- + +.. automodule:: k2hr3client.list + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.policy module +------------------------- + +.. automodule:: k2hr3client.policy + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.resource module +--------------------------- + +.. automodule:: k2hr3client.resource + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.role module +----------------------- + +.. automodule:: k2hr3client.role + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.service module +-------------------------- + +.. automodule:: k2hr3client.service + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.tenant module +------------------------- + +.. automodule:: k2hr3client.tenant + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.token module +------------------------ + +.. automodule:: k2hr3client.token + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.userdata module +--------------------------- + +.. automodule:: k2hr3client.userdata + :members: + :undoc-members: + :show-inheritance: + +k2hr3client.version module +-------------------------- + +.. automodule:: k2hr3client.version + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: k2hr3client + :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..fe35bb2 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/authors.po @@ -0,0 +1,43 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2020, Yahoo Japan Corporation +# Copyright (C) 2024, LY Corporation +# This file is distributed under the same license as the k2hr3client +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hr3client \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 13:58+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: ja\n" +"Language-Team: ja \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.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 "中谷 武史" + diff --git a/docs/locale/ja/LC_MESSAGES/history.po b/docs/locale/ja/LC_MESSAGES/history.po new file mode 100644 index 0000000..22287a7 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/history.po @@ -0,0 +1,43 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2020, Yahoo Japan Corporation +# Copyright (C) 2024, LY Corporation +# This file is distributed under the same license as the k2hr3client +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hr3client \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 13:58+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: ja\n" +"Language-Team: ja \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../../HISTORY.rst:3 +msgid "History" +msgstr "歴史" + +#: ../../../HISTORY.rst:6 +msgid "1.0.0 (2024-08-28)" +msgstr "" + +#: ../../../HISTORY.rst:8 +msgid "Supports the other APIs" +msgstr "他のAPIをサポート" + +#: ../../../HISTORY.rst:11 +msgid "0.0.1 (2020-08-28)" +msgstr "" + +#: ../../../HISTORY.rst:13 +msgid "Supports Token, Resource, Policy and Role API" +msgstr "トークン、リソース、ポリシーおよびロールAPIをサポート" + diff --git a/docs/locale/ja/LC_MESSAGES/index.po b/docs/locale/ja/LC_MESSAGES/index.po new file mode 100644 index 0000000..7b7947f --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/index.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2020, Yahoo Japan Corporation +# Copyright (C) 2024, LY Corporation +# This file is distributed under the same license as the k2hr3client +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hr3client \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 13:58+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: ja\n" +"Language-Team: ja \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../index.rst:4 +msgid "Contents:" +msgstr "内容" + +#: ../../index.rst:2 +msgid "Welcome to k2hr3client's documentation!" +msgstr "k2hr3clientのドキュメントへようこそ" + +#: ../../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 "検索" + diff --git a/docs/locale/ja/LC_MESSAGES/k2hr3client.po b/docs/locale/ja/LC_MESSAGES/k2hr3client.po new file mode 100644 index 0000000..9374b20 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/k2hr3client.po @@ -0,0 +1,656 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2020, Yahoo Japan Corporation +# Copyright (C) 2024, LY Corporation +# This file is distributed under the same license as the k2hr3client +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hr3client \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 13:58+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: ja\n" +"Language-Team: ja \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../k2hr3client.rst:2 +msgid "k2hr3client package" +msgstr "k2hr3clientパッケージ" + +#: ../../k2hr3client.rst:5 +msgid "Submodules" +msgstr "サブモジュール" + +#: ../../k2hr3client.rst:8 +msgid "k2hr3client.acr module" +msgstr "k2hr3client.acrモジュール" + +#: k2hr3client.acr:1 of +msgid "K2HR3 Python Client of ACR API." +msgstr "K2HR3 ACR APIのPythonクライアント" + +#: k2hr3client.acr.K2hr3Acr:1 k2hr3client.extdata.K2hr3Extdata:1 +#: k2hr3client.list.K2hr3List:1 k2hr3client.policy.K2hr3Policy:1 +#: k2hr3client.resource.K2hr3Resource:1 k2hr3client.role.K2hr3Role:1 +#: k2hr3client.service.K2hr3Service:1 k2hr3client.tenant.K2hr3Tenant:1 +#: k2hr3client.token.K2hr3RoleToken:1 k2hr3client.token.K2hr3RoleTokenList:1 +#: k2hr3client.token.K2hr3Token:1 k2hr3client.userdata.K2hr3Userdata:1 +#: k2hr3client.version.K2hr3Version:1 of +msgid "Bases: :py:class:`~k2hr3client.api.K2hr3Api`" +msgstr "k2hr3client.api.K2hr3Apiを継承" + +#: k2hr3client.acr.K2hr3Acr:1 of +msgid "Relationship with K2HR3 ACR API." +msgstr "K2HR3 ACR APIとの関連性" + +#: k2hr3client.acr.K2hr3Acr:3 of +msgid "See https://k2hr3.antpick.ax/api_acr.html" +msgstr "https://k2hr3.antpick.ax/api_acr.html を参照" + +#: k2hr3client.acr.K2hr3Acr.add_member:1 of +msgid "Add the members." +msgstr "メンバーを追加する" + +#: k2hr3client.acr.K2hr3Acr.delete_member:1 of +msgid "Delete the members." +msgstr "メンバーを削除する" + +#: k2hr3client.acr.K2hr3Acr.get_available_resources:1 of +msgid "Show the available resources." +msgstr "利用可能なリソースを表示する" + +#: k2hr3client.acr.K2hr3Acr.r3token:1 k2hr3client.list.K2hr3List.r3token:1 +#: k2hr3client.policy.K2hr3Policy.r3token:1 +#: k2hr3client.resource.K2hr3Resource.r3token:1 +#: k2hr3client.role.K2hr3Role.r3token:1 +#: k2hr3client.service.K2hr3Service.r3token:1 +#: k2hr3client.tenant.K2hr3Tenant.r3token:1 +#: k2hr3client.token.K2hr3RoleToken.r3token:1 +#: k2hr3client.token.K2hr3RoleTokenList.r3token:1 of +msgid "Return the r3token." +msgstr "K2HR3のトークンを返す" + +#: k2hr3client.acr.K2hr3Acr.service:1 +#: k2hr3client.extdata.K2hr3Extdata.extapi_name:1 +#: k2hr3client.extdata.K2hr3Extdata.register_path:1 +#: k2hr3client.extdata.K2hr3Extdata.user_agent:1 +#: k2hr3client.userdata.K2hr3Userdata.userdatapath:1 of +msgid "Return the tenant." +msgstr "テナントを返す" + +#: k2hr3client.acr.K2hr3Acr.show_credential_details:1 of +msgid "Show the credential details." +msgstr "認証クレデンシャルの詳細を表示する" + +#: ../../k2hr3client.rst:16 +msgid "k2hr3client.api module" +msgstr "k2hr3client.apiモジュール" + +#: k2hr3client.api:1 k2hr3client.http:1 of +msgid "k2hr3client - Python library for K2HR3 API." +msgstr "K2HR3 APIのPythonライブラリ" + +#: k2hr3client.api.K2hr3Api:1 of +msgid "Bases: :py:class:`~abc.ABC`" +msgstr "abc.ABCを継承" + +#: k2hr3client.api.K2hr3Api:1 of +msgid "Base class of all K2HR3 WebAPIs." +msgstr "K2HR3 WebAPIの基底クラス" + +#: k2hr3client.api.K2hr3Api.basepath:1 of +msgid "Return the base url path." +msgstr "基本URLパス" + +#: k2hr3client.api.K2hr3Api.body:1 of +msgid "Return the request body." +msgstr "リクエストボディを返す" + +#: k2hr3client.api.K2hr3Api.headers:1 k2hr3client.http.K2hr3Http.headers:1 of +msgid "Return the request headers." +msgstr "リクエストヘッダを返す" + +#: k2hr3client.api.K2hr3Api.resp:1 of +msgid "Return the response struct." +msgstr "レスポンス構造体を返す" + +#: k2hr3client.api.K2hr3Api.set_response:1 of +msgid "Set the API responses in K2hr3Http class." +msgstr "K2hr3Httpクラス内部でAPIレスポンスをセットする" + +#: k2hr3client.api.K2hr3Api.urlparams:1 of +msgid "Return the url params." +msgstr "URLパラメータを返す" + +#: k2hr3client.api.K2hr3Api.version:1 of +msgid "Return the version string." +msgstr "APIバージョン文字列を返す" + +#: k2hr3client.api.K2hr3ApiResponse:1 k2hr3client.http.K2hr3Http:1 +#: k2hr3client.role.K2hr3RoleHost:1 k2hr3client.role.K2hr3RoleHostList:1 of +msgid "Bases: :py:class:`object`" +msgstr "objectクラスを継承" + +#: k2hr3client.api.K2hr3ApiResponse:1 of +msgid "K2hr3ApiResponse stores the response of K2HR3 WebAPI." +msgstr "K2HR3 WebAPIのレスポンスを保存する" + +#: k2hr3client.api.K2hr3ApiResponse:3 of +msgid "The members are set by setter methods only one time." +msgstr "メンバーは一度だけセットされます" + +#: k2hr3client.api.K2hr3ApiResponse.body:1 of +msgid "Return the body." +msgstr "レスポンスボディを返す" + +#: k2hr3client.api.K2hr3ApiResponse.code:1 of +msgid "Return the status code." +msgstr "ステータスコードを返す" + +#: k2hr3client.api.K2hr3ApiResponse.hdrs:1 of +msgid "Return the header." +msgstr "レスポンスヘッダを返す" + +#: k2hr3client.api.K2hr3ApiResponse.url:1 of +msgid "Return the request url." +msgstr "リクエストURLを返す" + +#: k2hr3client.api.K2hr3HTTPMethod:1 k2hr3client.role.K2hr3TokenType:1 +#: k2hr3client.token.K2hr3AuthType:1 of +msgid "Bases: :py:class:`~enum.Enum`" +msgstr "enumクラスを継承" + +#: k2hr3client.api.K2hr3HTTPMethod:1 of +msgid "HTTPMethod." +msgstr "" + +#: ../../k2hr3client.rst:24 +msgid "k2hr3client.exception module" +msgstr "k2hr3client.exceptionモジュール" + +#: k2hr3client.exception:1 of +msgid "K2HR3 Python Client of Exception." +msgstr "K2HR3 Pythonライブラリの例外クラス" + +#: k2hr3client.exception.K2hr3Exception:1 of +msgid "Bases: :py:class:`Exception`" +msgstr "Exceptionクラスを継承" + +#: k2hr3client.exception.K2hr3Exception:1 of +msgid "Exception classes for K2HR3 Python Client." +msgstr "K2HR3 Pythonクライアントの例外クラス" + +#: ../../k2hr3client.rst:32 +msgid "k2hr3client.extdata module" +msgstr "k2hr3client.extdataモジュール" + +#: k2hr3client.extdata:1 of +msgid "k2hr3client - Python library for k2hr3 Extdata API." +msgstr "K2HR3 Extdata APIのPythonクライアント" + +#: k2hr3client.extdata.K2hr3Extdata:1 of +msgid "Relationship with K2HR3 EXTDATA API." +msgstr "K2HR3 Extdata APIとの関連性" + +#: k2hr3client.extdata.K2hr3Extdata:3 of +msgid "See https://k2hr3.antpick.ax/api_extdata.html for details." +msgstr "https://k2hr3.antpick.ax/api_extdata.html を参照する" + +#: k2hr3client.extdata.K2hr3Extdata.acquires_template:1 of +msgid "Set a request to acquire a template." +msgstr "テンプレートを取得するためのリクエストをセットする" + +#: ../../k2hr3client.rst:40 +msgid "k2hr3client.http module" +msgstr "k2hr3client.httpモジュール" + +#: k2hr3client.http.K2hr3Http:1 of +msgid "K2hr3Http sends a http/https request to the K2hr3 WebAPI." +msgstr "K2HR3 WebAPIにHTTP/HTTPSリクエストを送信する" + +#: k2hr3client.http.K2hr3Http:3 of +msgid "Most oif the members are set by setter methods only one time." +msgstr "ほとんどのメンバーは一度だけセットされます" + +#: k2hr3client.http.K2hr3Http.DELETE:1 of +msgid "Send requests by using DELETE Method." +msgstr "DELETEメソッドを使ってリクエストを送信する" + +#: k2hr3client.http.K2hr3Http.GET:1 k2hr3client.http.K2hr3Http.HEAD:1 of +msgid "Send requests by using GET Method." +msgstr "GETメソッドを使ってリクエストを送信する" + +#: k2hr3client.http.K2hr3Http.POST:1 of +msgid "Send requests by using POST Method." +msgstr "POSTメソッドを使ってリクエストを送信する" + +#: k2hr3client.http.K2hr3Http.PUT:1 of +msgid "Send requests by using PUT Method." +msgstr "PUTメソッドを使ってリクエストを送信する" + +#: k2hr3client.http.K2hr3Http.baseurl:1 k2hr3client.http.K2hr3Http.url:1 of +msgid "Returns the url." +msgstr "URLを返す" + +#: k2hr3client.http.K2hr3Http.urlparams:1 of +msgid "Returns the urlparams." +msgstr "URLパラメータを返す" + +#: ../../k2hr3client.rst:48 +msgid "k2hr3client.list module" +msgstr "k2hr3client.listモジュール" + +#: k2hr3client.list:1 of +msgid "K2HR3 Python Client of List API." +msgstr "K2HR3 List APIのPythonクライアント" + +#: k2hr3client.list.K2hr3List:1 of +msgid "Relationship with K2HR3 LIST API." +msgstr "K2HR3 List APIとの関連性" + +#: k2hr3client.list.K2hr3List:3 of +msgid "See https://k2hr3.antpick.ax/api_list.html" +msgstr "https://k2hr3.antpick.ax/api_list.html を参照する" + +#: k2hr3client.list.K2hr3List.get:1 of +msgid "List K2HR3's SERVICE, RESOURCE, POLICY and ROLE in YRN form." +msgstr "YRN形式でK2HR3's SERVICE, RESOURCE, POLICY および ROLEの一覧を表示する" + +#: k2hr3client.list.K2hr3List.service:1 of +msgid "Return the service." +msgstr "サービスを返す" + +#: k2hr3client.list.K2hr3List.validate:1 of +msgid "Validate the objects." +msgstr "オブジェクトの有効性を確認する" + +#: ../../k2hr3client.rst:56 +msgid "k2hr3client.policy module" +msgstr "k2hr3client.policyモジュール" + +#: k2hr3client.policy:1 of +msgid "K2HR3 Python Client of Policy API." +msgstr "K2HR3 Policy APIのPythonクライアント" + +#: k2hr3client.policy.K2hr3Policy:1 of +msgid "Relationship with K2HR3 POLICY API." +msgstr "K2HR3 POLICY APIとの関連性" + +#: k2hr3client.policy.K2hr3Policy:3 of +msgid "See https://k2hr3.antpick.ax/api_policy.html for details." +msgstr "https://k2hr3.antpick.ax/api_policy.html を参照する" + +#: k2hr3client.policy.K2hr3Policy.create:1 of +msgid "Create policies." +msgstr "ポリシーを作成する" + +#: k2hr3client.policy.K2hr3Policy.delete:1 of +msgid "Delete policies." +msgstr "ポリシーを削除する" + +#: k2hr3client.policy.K2hr3Policy.get:1 of +msgid "Get policies." +msgstr "ポリシーを取得する" + +#: k2hr3client.policy.K2hr3Policy.validate:1 of +msgid "Validate policies." +msgstr "ポリシーの有効性を確認する" + +#: ../../k2hr3client.rst:64 +msgid "k2hr3client.resource module" +msgstr "k2hr3client.resourceモジュール" + +#: k2hr3client.resource:1 of +msgid "K2HR3 Python Client of Resource API." +msgstr "K2HR3 Resource APIのPythonクライアント" + +#: k2hr3client.resource.K2hr3Resource:1 of +msgid "Relationship with K2HR3 RESOURCE API." +msgstr "K2HR3 RESOURCE APIとの関連性" + +#: k2hr3client.resource.K2hr3Resource:3 of +msgid "See https://k2hr3.antpick.ax/api_resource.html for details." +msgstr "https://k2hr3.antpick.ax/api_resource.html を参照する" + +#: k2hr3client.resource.K2hr3Resource.create_conf_resource:1 of +msgid "Create the resource." +msgstr "リソースを作成する" + +#: k2hr3client.resource.K2hr3Resource.data:1 of +msgid "Return data." +msgstr "データを返す" + +#: k2hr3client.resource.K2hr3Resource.delete_with_notoken:1 of +msgid "Delete the resource without token." +msgstr "ロールトークン情報なしでリソースを削除する" + +#: k2hr3client.resource.K2hr3Resource.delete_with_roletoken:1 of +msgid "Delete the resource with role token." +msgstr "ロールトークン情報をつけてリソースを削除する" + +#: k2hr3client.resource.K2hr3Resource.delete_with_scopedtoken:1 of +msgid "Delete the resource with scoped token." +msgstr "スコープトークン情報をつけてリソースを削除する" + +#: k2hr3client.resource.K2hr3Resource.get:1 of +msgid "Get the resource." +msgstr "リソースを取得する" + +#: k2hr3client.resource.K2hr3Resource.get_with_roletoken:1 of +msgid "Get the resource with roletoken." +msgstr "ロールトークン情報をつけてリソースを取得する" + +#: k2hr3client.resource.K2hr3Resource.resource_path:1 of +msgid "Return the resource_path." +msgstr "リソースパスを返す" + +#: k2hr3client.resource.K2hr3Resource.roletoken:1 of +msgid "Return the roletoken." +msgstr "ロールトークンを返す" + +#: k2hr3client.resource.K2hr3Resource.validate:1 of +msgid "Validate the resource." +msgstr "リソースの有効性を確認する" + +#: k2hr3client.resource.K2hr3Resource.validate_with_notoken:1 of +msgid "Validate the resource without tokens." +msgstr "トークンなしでリソースの有効性を確認する" + +#: ../../k2hr3client.rst:72 +msgid "k2hr3client.role module" +msgstr "k2hr3client.roleモジュール" + +#: k2hr3client.role:1 of +msgid "K2HR3 Python Client of Role API." +msgstr "K2HR3 Role APIのPythonクライアント" + +#: k2hr3client.role.K2hr3Role:1 of +msgid "Relationship with K2HR3 ROLE API." +msgstr "K2HR3 ROLE APIとの関連性" + +#: k2hr3client.role.K2hr3Role:3 of +msgid "See https://k2hr3.antpick.ax/api_role.html" +msgstr "https://k2hr3.antpick.ax/api_role.html を参照する" + +#: k2hr3client.role.K2hr3Role.add_member:1 of +msgid "Add a member to the role." +msgstr "ロールにメンバーを追加する" + +#: k2hr3client.role.K2hr3Role.add_member_with_roletoken:1 of +msgid "Add members to the role without roletoken." +msgstr "ロールトークンなしでロールにメンバーを追加する" + +#: k2hr3client.role.K2hr3Role.add_members:1 of +msgid "Add members to the role." +msgstr "メンバーをロールに追加する" + +#: k2hr3client.role.K2hr3Role.create:1 k2hr3client.token.K2hr3Token.create:1 of +msgid "Create tokens." +msgstr "トークンを作成する" + +#: k2hr3client.role.K2hr3Role.delete:1 of +msgid "Delete role." +msgstr "ロールを削除する" + +#: k2hr3client.role.K2hr3Role.delete_member:1 of +msgid "Delete host." +msgstr "ホストを削除する" + +#: k2hr3client.role.K2hr3Role.delete_member_wo_roletoken:1 of +msgid "Delete host without roletoken." +msgstr "ロールトークン情報なしでホストを削除する" + +#: k2hr3client.role.K2hr3Role.delete_roletoken:1 of +msgid "Delete roletoken." +msgstr "ロールトークンを削除する" + +#: k2hr3client.role.K2hr3Role.delete_roletoken_with_string:1 of +msgid "Delete roletoken without role." +msgstr "ロール情報なしでロールトークンを削除する" + +#: k2hr3client.role.K2hr3Role.get:1 of +msgid "Show role details." +msgstr "ロールの詳細を表示する" + +#: k2hr3client.role.K2hr3Role.get_token_list:1 of +msgid "Show token list." +msgstr "トークンの一覧を表示する" + +#: k2hr3client.role.K2hr3Role.validate_role:1 of +msgid "Validate role." +msgstr "ロールの有効性を確認する" + +#: k2hr3client.role.K2hr3RoleHost:1 of +msgid "Represent a host of a role." +msgstr "ロールのホストを表す" + +#: k2hr3client.role.K2hr3RoleHost:3 k2hr3client.role.K2hr3RoleHostList:3 of +msgid "NOTE(hiwakaba): This class exists only for backward compatibility." +msgstr "このクラスは後方互換性を維持するためのクラスです" + +#: k2hr3client.role.K2hr3RoleHostList:1 of +msgid "Represent a list of hosts of a role." +msgstr "ロールのホストの一覧を表す" + +#: k2hr3client.role.K2hr3RoleHostList.add_host:1 of +msgid "Add hosts to the list." +msgstr "ロールホスト一覧にホストを追加する" + +#: k2hr3client.role.K2hr3TokenType:1 of +msgid "Represent a type of token." +msgstr "トークンのタイプを表す" + +#: ../../k2hr3client.rst:80 +msgid "k2hr3client.service module" +msgstr "k2hr3client.serviceモジュール" + +#: k2hr3client.service:1 of +msgid "K2HR3 Python Client of Service API." +msgstr "K2HR3 Service APIのPythonクライアント" + +#: k2hr3client.service.K2hr3Service:1 of +msgid "Relationship with K2HR3 SERVICE API." +msgstr "K2HR3 SERVICE APIとの関連性" + +#: k2hr3client.service.K2hr3Service:3 of +msgid "See https://k2hr3.antpick.ax/api_service.html for details." +msgstr "https://k2hr3.antpick.ax/api_service.html を参照する" + +#: k2hr3client.service.K2hr3Service.add_member:1 of +msgid "Add members to services." +msgstr "サービスにメンバーを追加する" + +#: k2hr3client.service.K2hr3Service.create:1 of +msgid "Create services." +msgstr "サービスを作る" + +#: k2hr3client.service.K2hr3Service.delete:1 of +msgid "Delete services." +msgstr "サービスを削除する" + +#: k2hr3client.service.K2hr3Service.delete_tenant:1 of +msgid "Delete tenants." +msgstr "テナントを削除する" + +#: k2hr3client.service.K2hr3Service.get:1 of +msgid "Get services." +msgstr "サービスを取得する" + +#: k2hr3client.service.K2hr3Service.modify:1 of +msgid "Modify services." +msgstr "サービスを変更する" + +#: k2hr3client.service.K2hr3Service.validate:1 of +msgid "Validate services." +msgstr "サービスの有効性を確認する" + +#: ../../k2hr3client.rst:88 +msgid "k2hr3client.tenant module" +msgstr "k2hr3client.tenantモジュール" + +#: k2hr3client.tenant:1 of +msgid "K2HR3 Python Client of Tenant API." +msgstr "K2HR3 Tenant APIのPythonクライアント" + +#: k2hr3client.tenant.K2hr3Tenant:1 of +msgid "Relationship with K2HR3 TENANT API." +msgstr "K2HR3 TENANT APIとの関連性" + +#: k2hr3client.tenant.K2hr3Tenant:3 of +msgid "See https://k2hr3.antpick.ax/api_tenant.html for details." +msgstr "https://k2hr3.antpick.ax/api_tenant.html を参照する" + +#: k2hr3client.tenant.K2hr3Tenant.create:1 of +msgid "Create a new K2HR3 cluster Local Tenant(TENANT)." +msgstr "K2HR3クラスタのローカルテナントを作成する" + +#: k2hr3client.tenant.K2hr3Tenant.delete:1 of +msgid "Completely delete the Local Tenant(TENANT)." +msgstr "K2HR3クラスタのローカルテナントを削除する" + +#: k2hr3client.tenant.K2hr3Tenant.delete_user:1 of +msgid "Make the USER unavailable to the K2HR3 cluster Local Tenant(TENANT)." +msgstr "K2HR3クラスタのローカルテナントに対して無効にする" + +#: k2hr3client.tenant.K2hr3Tenant.get:1 of +msgid "Get the K2HR3 cluster Local Tenant(TENANT) information." +msgstr "K2HR3クラスタのローカルテナントの情報を取得する" + +#: k2hr3client.tenant.K2hr3Tenant.get_tenant_list:1 of +msgid "List the K2HR3 cluster Local Tenant(TENANT)." +msgstr "K2HR3クラスタのローカルテナントの一覧を表示する" + +#: k2hr3client.tenant.K2hr3Tenant.modify:1 of +msgid "Update the K2HR3 cluster Local Tenant(TENANT)." +msgstr "K2HR3 クラスタのローカルテナントを更新する" + +#: k2hr3client.tenant.K2hr3Tenant.validate:1 of +msgid "Check the existence of the K2HR3 cluster Local Tenant(TENANT)." +msgstr "テナントの存在を確認する" + +#: ../../k2hr3client.rst:96 +msgid "k2hr3client.token module" +msgstr "k2hr3client.token モジュール" + +#: k2hr3client:1 k2hr3client.token:1 of +msgid "K2HR3 Python Client of Token API." +msgstr "K2HR3 Token APIのPythonクライアント" + +#: k2hr3client.token.K2hr3AuthType:1 of +msgid "Represent the type of authentication." +msgstr "認証タイプを表す" + +#: k2hr3client.token.K2hr3RoleToken:1 of +msgid "Represent K2hr3 ROLE TOKEN API." +msgstr "K2HR3 ROLE TOKEN APIを表す" + +#: k2hr3client.token.K2hr3RoleToken:3 k2hr3client.token.K2hr3RoleTokenList:3 of +msgid "See https://k2hr3.antpick.ax/api_role.html for details." +msgstr "https://k2hr3.antpick.ax/api_role.html を参照" + +#: k2hr3client.token.K2hr3RoleToken.expire:1 of +msgid "Return the expire." +msgstr "有効期限を返す" + +#: k2hr3client.token.K2hr3RoleToken.role:1 +#: k2hr3client.token.K2hr3RoleTokenList.role:1 of +msgid "Return the role." +msgstr "K2HR3ロールを返す" + +#: k2hr3client.token.K2hr3RoleToken.token:1 +#: k2hr3client.token.K2hr3Token.token:1 of +msgid "Return k2hr3 token." +msgstr "K2HR3トークンを返す" + +#: k2hr3client.token.K2hr3RoleTokenList:1 of +msgid "Represent K2hr3 ROLE TOKEN LIST API." +msgstr "K2HR3 ロールトークン一覧を表す" + +#: k2hr3client.token.K2hr3RoleTokenList.expand:1 of +msgid "Return the expand." +msgstr "expand設定を返す" + +#: k2hr3client.token.K2hr3RoleTokenList.registerpath:1 of +msgid "Set the registerpath." +msgstr "registerpathをセットする" + +#: k2hr3client.token.K2hr3Token:1 of +msgid "Relationship with K2hr3 TOKEN API." +msgstr "K2HR3 TOKEN APIとの関連性" + +#: k2hr3client.token.K2hr3Token:3 of +msgid "See https://k2hr3.antpick.ax/api_token.html for details." +msgstr "https://k2hr3.antpick.ax/api_token.html を参照する" + +#: k2hr3client.token.K2hr3Token.get_openstack_token:1 of +msgid "Get the openstack token." +msgstr "OpenStackのトークンを取得する" + +#: k2hr3client.token.K2hr3Token.iaas_project:1 of +msgid "Return the k2hr3 tenant." +msgstr "K2HR3テナントを返す" + +#: k2hr3client.token.K2hr3Token.iaas_token:1 of +msgid "Return the openstack token." +msgstr "OpenStackのトークンを返す" + +#: k2hr3client.token.K2hr3Token.show:1 of +msgid "Show details of tokens." +msgstr "K2HR3トークンの詳細を表示する" + +#: k2hr3client.token.K2hr3Token.validate:1 of +msgid "Validate tokens." +msgstr "トークンを有効性を確認する" + +#: ../../k2hr3client.rst:104 +msgid "k2hr3client.userdata module" +msgstr "k2hr3client.userdataモジュール" + +#: k2hr3client.userdata:1 of +msgid "K2HR3 Python Client of Userdata API." +msgstr "K2HR3 Userdata APIのPythonクライアント" + +#: k2hr3client.userdata.K2hr3Userdata:1 of +msgid "Relationship with K2HR3 USERDATA API." +msgstr "K2HR3 USERDATA APIとの関連性" + +#: k2hr3client.userdata.K2hr3Userdata:3 of +msgid "See https://k2hr3.antpick.ax/api_userdata.html for details." +msgstr "https://k2hr3.antpick.ax/api_userdata.html を参照する" + +#: k2hr3client.userdata.K2hr3Userdata.provides_userdata_script:1 of +msgid "Get userdata." +msgstr "userdataを取得する" + +#: ../../k2hr3client.rst:112 +msgid "k2hr3client.version module" +msgstr "k2hr3client.versionモジュール" + +#: k2hr3client.version:1 of +msgid "K2HR3 Python Client of Version API." +msgstr "K2HR3 Version APIのPythonクライアント" + +#: k2hr3client.version.K2hr3Version:1 of +msgid "Relationship with K2HR3 VERSION API." +msgstr "K2HR3 Version APIとの関連性" + +#: k2hr3client.version.K2hr3Version:3 of +msgid "See https://k2hr3.antpick.ax/api_version.html for details." +msgstr "https://k2hr3.antpick.ax/api_version.html を参照する" + +#: k2hr3client.version.K2hr3Version.get:1 of +msgid "Get the version." +msgstr "" + +#: ../../k2hr3client.rst:120 +msgid "Module contents" +msgstr "" + diff --git a/docs/locale/ja/LC_MESSAGES/modules.po b/docs/locale/ja/LC_MESSAGES/modules.po new file mode 100644 index 0000000..788adec --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/modules.po @@ -0,0 +1,27 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2020, Yahoo Japan Corporation +# Copyright (C) 2024, LY Corporation +# This file is distributed under the same license as the k2hr3client +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hr3client \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 13:58+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: ja\n" +"Language-Team: ja \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../modules.rst:2 +msgid "k2hr3client" +msgstr "" + diff --git a/docs/locale/ja/LC_MESSAGES/readme.po b/docs/locale/ja/LC_MESSAGES/readme.po new file mode 100644 index 0000000..cef35b5 --- /dev/null +++ b/docs/locale/ja/LC_MESSAGES/readme.po @@ -0,0 +1,111 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2020, Yahoo Japan Corporation +# Copyright (C) 2024, LY Corporation +# This file is distributed under the same license as the k2hr3client +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: k2hr3client \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-09-01 13:58+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: ja\n" +"Language-Team: ja \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../../README.rst:3 +msgid "k2hr3client_python" +msgstr "" + +#: ../../../README.rst:25 +msgid "Overview" +msgstr "概要" + +#: ../../../README.rst:27 +msgid "k2hr3client_python is an official Python WebAPI client for `k2hr3`_." +msgstr "k2hr3client_python はK2HR3の公式Pythonクライアントです" + +#: ../../../README.rst:35 +msgid "Install" +msgstr "インストール" + +#: ../../../README.rst:37 +msgid "Let's install k2hr3client_python using pip::" +msgstr "pipを使ってk2hr3client_pythonをインストールしましょう" + +#: ../../../README.rst:43 +msgid "Usage" +msgstr "使い方" + +#: ../../../README.rst:45 +msgid "Let's try to get a token and create a resource.::" +msgstr "K2HR3トークンを取得して、リソースを作ろう" + +#: ../../../README.rst:82 +msgid "Development" +msgstr "開発" + +#: ../../../README.rst:84 +msgid "" +"Clone this repository and go into the directory, then run the following " +"command::" +msgstr "このレポジトリを複製後、ソースコードのディレクトリに移動し、次のコマンドを実行します" + +#: ../../../README.rst:92 +msgid "Documents" +msgstr "ドキュメント" + +#: ../../../README.rst:94 +msgid "Here are documents including other components." +msgstr "こちらのドキュメントは、他のコンポーネントも含んでいます" + +#: ../../../README.rst:96 +msgid "`Document top page`_" +msgstr "ドキュメントトップ" + +#: ../../../README.rst:98 +msgid "`About K2HR3`_" +msgstr "K2HR3について" + +#: ../../../README.rst:100 +msgid "`About AntPickax`_" +msgstr "AntPickaxについて" + +#: ../../../README.rst:111 +msgid "Packages" +msgstr "パッケージ" + +#: ../../../README.rst:113 +msgid "Here are packages including other components." +msgstr "こちらのドキュメントは、他のコンポーネントも含んでいます" + +#: ../../../README.rst:115 +msgid "`k2hr3client(python packages)`_" +msgstr "k2hr3clientパッケージ" + +#: ../../../README.rst:121 +msgid "License" +msgstr "ライセンス" + +#: ../../../README.rst:123 +msgid "MIT License. See the LICENSE file." +msgstr "" + +#: ../../../README.rst:127 +msgid "AntPickax" +msgstr "" + +#: ../../../README.rst:129 +msgid "" +"**k2hr3client_python** is a project by AntPickax_, which is an open " +"source team in `LY Corporation`_." +msgstr "k2hr3client_pythonは、 `LY Corporation`_ の AntPickax_ によるプロジェクトです。" + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..52035ce --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright (c) 2022 Yahoo Japan Corporation +# Copyright (c) 2024 LY 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..1d9598c --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +k2hr3client +=========== + +.. toctree:: + :maxdepth: 4 + + k2hr3client 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..3fc5655 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright (c) 2022 Yahoo Japan Corporation +# Copyright (c) 2024 LY 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/examples/example_resource.txt b/examples/example_resource.txt new file mode 100644 index 0000000..7fb2d7a --- /dev/null +++ b/examples/example_resource.txt @@ -0,0 +1,145 @@ +{{#!k2hr3template }} +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Takeshi Nakatani +# CREATE: Mon Sep 14 2020 +# REVISION: +# + +# +# GLOBAL SECTION +# +[GLOBAL] +FILEVERSION = 1 +DATE = Thu, 28 May 2020 04:56:20 0000 +GROUP = {{= %cluster-name% }} +MODE = {{= %chmpx-mode% }} +CHMPXIDTYPE = NAME +DELIVERMODE = hash +MAXCHMPX = 256 +REPLICA = 1 +MAXMQSERVER = 8 +MAXMQCLIENT = 128 +MQPERATTACH = 8 +MAXQPERSERVERMQ = 8 +MAXQPERCLIENTMQ = 8 +MAXMQPERCLIENT = 16 +MAXHISTLOG = 0 +{{ if 'SERVER' == %chmpx-mode% }} +PORT = {{= %chmpx-server-port% }} +CTLPORT = {{= %chmpx-server-ctlport% }} +SELFCTLPORT = {{= %chmpx-server-ctlport% }} +{{ else }} +CTLPORT = {{= %chmpx-slave-ctlport% }} +SELFCTLPORT = {{= %chmpx-slave-ctlport% }} +{{ endif }} +SELFCUK = __SELF_INSTANCE_ID__ +RWTIMEOUT = 10000 +RETRYCNT = 500 +CONTIMEOUT = 1000 +MQRWTIMEOUT = 500 +MQRETRYCNT = 10000 +MQACK = no +AUTOMERGE = on +DOMERGE = on +MERGETIMEOUT = 0 +SOCKTHREADCNT = 8 +MQTHREADCNT = 8 +MAXSOCKPOOL = 16 +SOCKPOOLTIMEOUT = 0 +SSL = no +K2HFULLMAP = on +K2HMASKBIT = 4 +K2HCMASKBIT = 8 +K2HMAXELE = 16 + +# +# SERVER NODES SECTION +# +{{ foreach %host_key% in %yrn:yahoo:::__TROVE_K2HDKC_TENANT_NAME__:role:__TROVE_K2HDKC_CLUSTER_NAME__/server/hosts/ip% }} + {{ %one_host% = %yrn:yahoo:::__TROVE_K2HDKC_TENANT_NAME__:role:__TROVE_K2HDKC_CLUSTER_NAME__/server/hosts/ip%{%host_key%} }} +[SVRNODE] +NAME = {{= %one_host%{'host'} }} +CUK = {{= %one_host%{'cuk'} }} +PORT = {{= %chmpx-server-port% }} +CTLPORT = {{= %chmpx-server-ctlport% }} +SSL = no + {{ if %one_host%{'inboundip'} }} +ENDPOINTS = {{= %one_host%{'inboundip'} }}:{{= %chmpx-server-port% }} +CTLENDPOINTS = {{= %one_host%{'inboundip'} }}:{{= %chmpx-server-ctlport% }} +REVERSE_PEERS = {{= %one_host%{'inboundip'} }} + {{ endif }} + {{ if %one_host%{'outboundip'} }} +FORWARD_PEERS = {{= %one_host%{'outboundip'} }} + {{ endif }} +{{ done }} + +# +# SLAVE NODES SECTION +# +{{ if 0 < %yrn:yahoo:::__TROVE_K2HDKC_TENANT_NAME__:role:__TROVE_K2HDKC_CLUSTER_NAME__/slave/hosts/ip%.length }} + {{ foreach %host_key% in %yrn:yahoo:::__TROVE_K2HDKC_TENANT_NAME__:role:__TROVE_K2HDKC_CLUSTER_NAME__/slave/hosts/ip% }} + {{ %one_host% = %yrn:yahoo:::__TROVE_K2HDKC_TENANT_NAME__:role:__TROVE_K2HDKC_CLUSTER_NAME__/slave/hosts/ip%{%host_key%} }} +[SLVNODE] +NAME = {{= %one_host%{'host'} }} +CUK = {{= %one_host%{'cuk'} }} +CTLPORT = {{= %chmpx-slave-ctlport% }} +SSL = no + {{ if %one_host%{'inboundip'} }} +CTLENDPOINTS = {{= %one_host%{'inboundip'} }}:{{= %chmpx-slave-ctlport% }} +REVERSE_PEERS = {{= %one_host%{'inboundip'} }} + {{ endif }} + {{ if %one_host%{'outboundip'} }} +FORWARD_PEERS = {{= %one_host%{'outboundip'} }} + {{ endif }} + {{ done }} +{{ else }} +# +# This is output as a dummy slave node when there are no slave nodes. +# If the slave node definition does not exist, CHMPX will not start. +# To avoid this, register only one localhost as a dummy. +# +[SLVNODE] +NAME = 127.0.0.1 +CUK = dummy_cuk +CTLPORT = {{= %chmpx-slave-ctlport% }} +SSL = no +{{ endif }} + +{{ if 'SERVER' == %chmpx-mode% }} +# +# K2HDKC SECTION +# +[K2HDKC] +K2HTYPE = file +K2HFILE = /var/lib/antpickax/data/k2hdkc.k2h +K2HFULLMAP = on +K2HINIT = no +K2HMASKBIT = 8 +K2HCMASKBIT = 8 +K2HMAXELE = 32 +K2HPAGESIZE = 512 +MAXTHREAD = 20 +{{ endif }} + +# +# 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/examples/identity_api.py b/examples/identity_api.py new file mode 100644 index 0000000..a089fe7 --- /dev/null +++ b/examples/identity_api.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""An example for OpenStack Identity API.""" + +from __future__ import (absolute_import, division, print_function, + unicode_literals) +import argparse +import os +import sys +import json +import urllib.parse +import urllib.request + +here = os.path.dirname(__file__) +src_dir = os.path.join(here, '..') +if os.path.exists(src_dir): + sys.path.append(src_dir) + +IDENTITY_V3_PASSWORD_AUTH_JSON_DATA = """ +{ + "auth": { + "identity": { + "methods": [ + "password" + ], + "password": { + "user": { + "name": "admin", + "domain": { + "name": "Default" + }, + "password": "devstacker" + } + } } + } +} +""" + +IDENTITY_V3_TOKEN_AUTH_JSON_DATA = """ +{ + "auth": { + "identity": { + "methods": [ + "token" + ], + "token": { + "id": "" + } + }, + "scope": { + "project": { + "domain": { + "id": "default" + }, + "name": "" + } + } + } +} +""" + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='k2hr3 token api example') + parser.add_argument('--url', dest='url', default='http://127.0.0.1/identity/v3/auth/tokens', + help='identity api url. ex) http://127.0.0.1/identity/v3/auth/tokens') + parser.add_argument('--user', dest='user', default='demo', help='openstack user') + parser.add_argument('--password', dest='password', default='password', + help='openstack user password') + parser.add_argument('--project', dest='project', default='demo', help='openstack project') + args = parser.parse_args() + + # unscoped token-id + # https://docs.openstack.org/api-ref/identity/v3/index.html#password-authentication-with-unscoped-authorization + url = args.url + python_data = json.loads(IDENTITY_V3_PASSWORD_AUTH_JSON_DATA) + python_data['auth']['identity']['password']['user']['name'] = args.user + python_data['auth']['identity']['password']['user']['password'] = args.password + headers = {'User-Agent': 'hiwkby-sample', 'Content-Type': 'application/json'} + req = urllib.request.Request(url, json.dumps(python_data).encode('ascii'), + headers, method="POST") + with urllib.request.urlopen(req) as res: + unscoped_token_id = dict(res.info()).get('X-Subject-Token') + print('unscoped_token_id:[{}]'.format(unscoped_token_id)) + + # scoped token-id + # https://docs.openstack.org/api-ref/identity/v3/index.html?expanded=#token-authentication-with-scoped-authorization + my_project_name = args.project + python_data = json.loads(IDENTITY_V3_TOKEN_AUTH_JSON_DATA) + python_data['auth']['identity']['token']['id'] = unscoped_token_id + python_data['auth']['scope']['project']['name'] = my_project_name + headers = {'User-Agent': 'hiwkby-sample', 'Content-Type': 'application/json'} + req = urllib.request.Request(url, json.dumps(python_data).encode('ascii'), + headers, method="POST") + with urllib.request.urlopen(req) as res: + scoped_token_id = dict(res.info()).get('X-Subject-Token') + print('scoped_token_id:[{}]'.format(scoped_token_id)) + + sys.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/examples/registerpath.py b/examples/registerpath.py new file mode 100644 index 0000000..ad44566 --- /dev/null +++ b/examples/registerpath.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Token API.""" + +from __future__ import (absolute_import, division, print_function, + unicode_literals) +import argparse +import os +import sys +import json +import urllib.parse +import urllib.request + +here = os.path.dirname(__file__) +src_dir = os.path.join(here, '..') +if os.path.exists(src_dir): + sys.path.append(src_dir) + +from k2hr3client.http import K2hr3Http # type: ignore # pylint: disable=import-error, wrong-import-position +from k2hr3client.token import K2hr3Token # type: ignore # pylint: disable=import-error, wrong-import-position +from k2hr3client.token import K2hr3RoleToken # pylint: disable=import-error, wrong-import-position +from k2hr3client.token import K2hr3RoleTokenList # pylint: disable=import-error, wrong-import-position + +IDENTITY_V3_PASSWORD_AUTH_JSON_DATA = """ +{ + "auth": { + "identity": { + "methods": [ + "password" + ], + "password": { + "user": { + "name": "admin", + "domain": { + "name": "Default" + }, + "password": "devstacker" + } + } } + } +} +""" + +IDENTITY_V3_TOKEN_AUTH_JSON_DATA = """ +{ + "auth": { + "identity": { + "methods": [ + "token" + ], + "token": { + "id": "" + } + }, + "scope": { + "project": { + "domain": { + "id": "default" + }, + "name": "" + } + } + } +} +""" + + +def get_scoped_token_id(url, user, password, project): + """ get a scoped token id from openstack identity. """ + # unscoped token-id + # https://docs.openstack.org/api-ref/identity/v3/index.html#password-authentication-with-unscoped-authorization + python_data = json.loads(IDENTITY_V3_PASSWORD_AUTH_JSON_DATA) + python_data['auth']['identity']['password']['user']['name'] = user + python_data['auth']['identity']['password']['user']['password'] = password + headers = { + 'User-Agent': 'hiwkby-sample', + 'Content-Type': 'application/json' + } + req = urllib.request.Request(url, + json.dumps(python_data).encode('ascii'), + headers, + method="POST") + with urllib.request.urlopen(req) as res: + unscoped_token_id = dict(res.info()).get('X-Subject-Token') + # print('unscoped_token_id:[{}]'.format(unscoped_token_id)) + + # scoped token-id + # https://docs.openstack.org/api-ref/identity/v3/index.html?expanded=#token-authentication-with-scoped-authorization + python_data = json.loads(IDENTITY_V3_TOKEN_AUTH_JSON_DATA) + python_data['auth']['identity']['token']['id'] = unscoped_token_id + python_data['auth']['scope']['project']['name'] = project + headers = { + 'User-Agent': 'hiwkby-sample', + 'Content-Type': 'application/json' + } + req = urllib.request.Request(url, + json.dumps(python_data).encode('ascii'), + headers, + method="POST") + with urllib.request.urlopen(req) as res: + scoped_token_id = dict(res.info()).get('X-Subject-Token') + # print('scoped_token_id:[{}]'.format(scoped_token_id)) + return scoped_token_id + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='k2hr3 token api example') + parser.add_argument( + '--url', + dest='url', + default='http://127.0.0.1/identity/v3/auth/tokens', + help='identity api url. ex) http://127.0.0.1/identity/v3/auth/tokens') + parser.add_argument('--user', + dest='user', + default='demo', + help='openstack user') + parser.add_argument('--password', + dest='password', + default='password', + help='openstack user password') + parser.add_argument('--project', + dest='project', + default='demo', + help='openstack project') + parser.add_argument('--k2hr3_url', + dest='k2hr3_url', + default='http://localhost:18080/v1', + help='k2hr3 api url') + parser.add_argument('--role', + dest='role', + default='k2hdkccluster', + help='k2hr3 rolename') + args = parser.parse_args() + + # 1. Gets a openstack token id from openstack identity server + openstack_token = get_scoped_token_id(args.url, args.user, args.password, + args.project) + + # 2. Gets a k2hr3 token from the openstack token + k2hr3_token = K2hr3Token(args.project, openstack_token) + http = K2hr3Http(args.k2hr3_url) + http.POST(k2hr3_token.create()) + + # 3. Gets a k2hr3 role token from the k2hr3 token + k2hr3_role_token = K2hr3RoleToken(k2hr3_token.token, + role=args.role, + expire=0) + http.GET(k2hr3_role_token) + roletoken = k2hr3_role_token.token + # print("roletoken {}".format(roletoken)) + + # 4. Gets a k2hr3 role token list from the k2hr3 token + k2hr3_role_token_list = K2hr3RoleTokenList(k2hr3_token.token, + role=args.role, + expand=True) + http.GET(k2hr3_role_token_list) + + # 4. Gets the registerpath of the k2hr3 role token using the k2hr3 role token + registerpath = k2hr3_role_token_list.registerpath(roletoken) + print("{}".format(registerpath)) + sys.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/examples/token_api.py b/examples/token_api.py new file mode 100644 index 0000000..e0f8fa7 --- /dev/null +++ b/examples/token_api.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Token API.""" + +from __future__ import (absolute_import, division, print_function, + unicode_literals) +import argparse +import os +import sys + +here = os.path.dirname(__file__) +src_dir = os.path.join(here, '..') +if os.path.exists(src_dir): + sys.path.append(src_dir) + +from k2hr3client.http import K2hr3Http # type: ignore # pylint: disable=import-error, wrong-import-position +from k2hr3client.token import K2hr3Token # type: ignore # pylint: disable=import-error, wrong-import-position +from k2hr3client.token import K2hr3RoleToken # pylint: disable=import-error, wrong-import-position +from k2hr3client.token import K2hr3RoleTokenList # pylint: disable=import-error, wrong-import-position + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='k2hr3 token api example') + parser.add_argument('--url', dest='url', default='http://localhost:18080/v1', + help='k2hr3 api url') + parser.add_argument('--project', dest='project', default='demo', help='openstack project') + parser.add_argument('--token', dest='token', default='foobar', help='openstack token') + args = parser.parse_args() + + # 1. Gets a k2hr3 token from the openstack token + k2hr3_token = K2hr3Token(args.project, args.token) + http = K2hr3Http(args.url) + http.POST(k2hr3_token.create()) + + # 2. Gets a k2hr3 role token from the k2hr3 token + k2hr3_role_token = K2hr3RoleToken( + k2hr3_token.token, + role="k2hdkccluster", + expire=0 + ) + http.GET(k2hr3_role_token) + roletoken = k2hr3_role_token.token + print("roletoken {}".format(roletoken)) + + # 3. Gets a k2hr3 role token list from the k2hr3 token + k2hr3_role_token_list = K2hr3RoleTokenList( + k2hr3_token.token, + role="k2hdkccluster", + expand=True + ) + http.GET(k2hr3_role_token_list) + + # 4. Gets the registerpath of the k2hr3 role token using the k2hr3 role token + registerpath = k2hr3_role_token_list.registerpath(roletoken) + print("registerpath {}".format(registerpath)) + sys.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/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..75f1f29 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,607 @@ +[build-system] +requires = [ "setuptools>=61.0", "wheel" ] +build-backend = "setuptools.build_meta" + +[project] +name = "k2hr3client" +dynamic = ["version"] +requires-python = ">=3.9" +authors = [ + {name = "Hirotaka Wakabayashi", email = "hiwakaba@lycorp.co.jp"}, + {name = "Takeshi Nakatani", email = "ggtakec@gmail.com"}, +] +maintainers = [ + {name = "Hirotaka Wakabayashi", email = "hiwakaba@lycorp.co.jp"}, + {name = "Takeshi Nakatani", email = "ggtakec@gmail.com"}, +] +description = "k2hr3client for python" +readme = "README.rst" +license = {file = "LICENSE"} +keywords = ["k2hr3", "antpickax", "OpenStack", "iaas", "k2hdkc_dbaas"] +classifiers = [ + 'Development Status :: 4 - Beta', + 'Programming Language :: Python', + 'Environment :: Console', + 'Environment :: OpenStack', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.9', +] + +[project.urls] +Homepage = "https://github.com/yahoojapan/k2hr3client_python" +Documentation = "https://k2hr3client-python.readthedocs.org" +Repository = "https://github.com/yahoojapan/k2hr3client_python.git" +"Bug Tracker" = "https://github.com/yahoojapan/k2hr3client_python/issues" +Changelog = "https://github.com/yahoojapan/k2hr3client_python/blob/master/HISTORY.rst" + +[tool.tox] +legacy_tox_ini = """ + [tox] + requires = tox>=4 + env_list = lint, type, py{311} + skip_missing_interpreters = True + + [testenv] + deps = pytest + commands = pytest + package = wheel + + [testenv:lint] + deps = pylint + commands = pylint src/k2hr3client + + [testenv:type] + description = run type checks + deps = + mypy>=0.991 + commands = + mypy {posargs:src} +""" + +[tool.pylint.main] +# 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 = + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint in +# a server-like mode. +# clear-cache-post-run = + +# Always return a 0 (non-error) status code, even if lint errors are found. This +# is primarily useful in continuous integration scripts. +# exit-zero = + +# 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 = + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +# fail-on = + +# Specify a score threshold under which the program will exit with error. +fail-under = 10.0 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +# from-stdin = + +# Files or directories to be skipped. They should be base names, not paths. +ignore = ["CVS"] + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, it +# can't be used as an escape character. +# ignore-paths = + +# Files or directories matching the regular expression patterns are skipped. The +# regex matches against base names, not paths. The default value ignores Emacs +# file locks +# ignore-patterns = + +# 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 = + +# 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, and will cap the count on Windows to +# avoid hangs. +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 = true + +# Minimum Python version to use for version dependent checks. Will default to the +# version used to run pylint. +py-version = "3.9" + +# Discover python modules and packages in the file system subtree. +# recursive = + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +# source-roots = + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode = true + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +# unsafe-load-any-extension = + +[tool.pylint.basic] +# Naming style matching correct argument names. +argument-naming-style = "snake_case" + +# Regular expression matching correct argument names. Overrides argument-naming- +# style. If left empty, argument names will be checked with the set 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. If left empty, attribute names will be checked with the set 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. If left empty, class attribute names will be checked +# with the set naming style. +# class-attribute-rgx = + +# Naming style matching correct class constant names. +class-const-naming-style = "UPPER_CASE" + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +# class-const-rgx = + +# Naming style matching correct class names. +class-naming-style = "PascalCase" + +# Regular expression matching correct class names. Overrides class-naming-style. +# If left empty, class names will be checked with the set 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. If left empty, constant names will be checked with the set 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. If left empty, function names will be checked with the set 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 = + +# Naming style matching correct inline iteration names. +inlinevar-naming-style = "any" + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set 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. If left empty, method names will be checked with the set 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. If left empty, module names will be checked with the set 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"] + +# Regular expression matching correct type alias names. If left empty, type alias +# names will be checked with the set naming style. +# typealias-rgx = + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +# typevar-rgx = + +# Naming style matching correct variable names. +variable-naming-style = "snake_case" + +# Regular expression matching correct variable names. Overrides variable-naming- +# style. If left empty, variable names will be checked with the set naming style. +# variable-rgx = + +[tool.pylint.classes] +# Warn about protected attribute access inside special methods +# check-protected-access-in-special-methods = + +# 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"] + +[tool.pylint.design] +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods = ["HTTPMethod"] + +# List of qualified class names to ignore when counting class parents (see R0901) +# ignored-parents = + +# 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 + +[tool.pylint.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 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +# single-line-class-stmt = + +# Allow the body of an if to be on the same line as the test if there is no else. +# single-line-if-stmt = + +[tool.pylint.imports] +# List of modules that can be imported at any level, not just the top level one. +# allow-any-import-level = + +# Allow explicit reexports by alias from a package __init__. +# allow-reexport-from-package = + +# Allow wildcard imports from modules that define __all__. +# allow-wildcard-with-all = + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules = ["optparse", "tkinter.tix"] + +# Output a graph (.gv or any supported image format) of external dependencies to +# the given file (report RP0402 must not be disabled). +# ext-import-graph = + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be disabled). +# import-graph = + +# Output a graph (.gv or any supported image format) of internal dependencies to +# 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 = + +[tool.pylint.logging] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `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"] + +[tool.pylint."messages control"] +# Only show warnings with the listed confidence levels. Leave empty to show all. +# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence = ["HIGH", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] + +# 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", "cyclic-import", "duplicate-code", "too-many-lines"] + +# 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"] + +[tool.pylint.method_args] +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods = ["requests.api.delete", "requests.api.get", "requests.api.head", "requests.api.options", "requests.api.patch", "requests.api.post", "requests.api.put", "requests.api.request"] + +[tool.pylint.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 = + +[tool.pylint.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"] + +disable = ["cyclic-import", "duplicate-code"] + +[tool.pylint.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= + +# Tells whether to display a full report or only the messages. +# reports = + +# Activate the evaluation score. +score = true + +[tool.pylint.similarities] +# Comments are removed from the similarity computation +ignore-comments = true + +# Docstrings are removed from the similarity computation +ignore-docstrings = true + +# Imports are removed from the similarity computation +# ignore-imports = + +# Signatures are removed from the similarity computation +ignore-signatures = true + +# Minimum lines number of a similarity. +min-similarity-lines = 32 + +[tool.pylint.spelling] +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions = 4 + +# Spelling dictionary name. No available dictionaries : You need to install both +# the python package and the system dependency for enchant to work.. +# spelling-dict = + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:" + +# 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 = + +[tool.pylint.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 +# class is considered mixin if its name matches the mixin-class-rgx option. +# Tells whether to warn about missing members when the owner of the attribute is +# inferred to be None. +ignore-none = true + +# 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 = true + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"] + +# 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"] + +# 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 = true + +# 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 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx = ".*[Mm]ixin" + +# List of decorators that change the signature of a decorated function. +# signature-mutators = + +[tool.pylint.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 = true + +# List of names allowed to shadow builtins +# allowed-redefined-builtins = + +# 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. +ignored-argument-names = "_.*|^ignored_|^unused_" + +# Tells whether we should check for unused import in __init__ files. +# init-import = + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules = ["six.moves", "past.builtins", "future.builtins", "builtins", "io"] + +# +# 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.py b/setup.py new file mode 100644 index 0000000..ddbbab6 --- /dev/null +++ b/setup.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""K2HR3 Python Client.""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +__author__ = 'Hirotaka Wakabayashi ' +__copyright__ = """ + +MIT License + +Copyright (c) 2020 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 (including the next +paragraph) 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. +""" + +# Always prefer setuptools over distutils +from setuptools import setup + + +def get_version(): + """Returns the package version from __ini__.py.""" + from pathlib import Path + from os import path, sep + import re + + here = path.abspath(path.dirname(__file__)) + init_py = Path(sep.join([here, 'src', 'k2hr3client', '__init__.py'])).resolve() + + with init_py.open() as fp: + for line in fp: + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + line.strip(), re.M) + if version_match: + return version_match.group(1) + raise RuntimeError('version expected, but no version found.') + + +setup( + version=get_version(), +) + +# +# 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/k2hr3client/__init__.py b/src/k2hr3client/__init__.py new file mode 100644 index 0000000..d558221 --- /dev/null +++ b/src/k2hr3client/__init__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Token API.""" + +__author__ = 'Hirotaka Wakabayashi ' +__version__ = '1.0.0' + +import sys + +if sys.platform.startswith('win'): + raise ImportError(r'Currently we do not test well on windows') + + +def get_version() -> str: + """Return a version of the package. + + :returns: version + :rtype: str + """ + return __version__ + + +# +# 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/k2hr3client/acr.py b/src/k2hr3client/acr.py new file mode 100644 index 0000000..d639c61 --- /dev/null +++ b/src/k2hr3client/acr.py @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# + +"""K2HR3 Python Client of ACR API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.acr import K2hr3Acr + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # POST a request to add member to K2HR3 ACR API. + myacr = K2hr3Acr(mytoken.token, "service") + myhttp.POST(myacr.add_member("mytenant")) + +""" + + +import json +from typing import Optional + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod +from k2hr3client.exception import K2hr3Exception + +_ACR_API_ADD_MEMBER = """ +{ + "tenant": "" +} +""" + + +class K2hr3Acr(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 ACR API. + + See https://k2hr3.antpick.ax/api_acr.html + """ + + __slots__ = ('_r3token', '_service') + + def __init__(self, r3token: str, service: str): + """Init the members.""" + super().__init__("acr") + self.r3token = r3token + self.service = service + + # following attrs are dynamically set later. + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + self.body = None + self.urlparams = None + # attributes that are unique to this class + self.tenant = None # type: Optional[str] + self.cip = None # type: Optional[str] + self.cport = None # type: Optional[str] + self.ccuk = None # type: Optional[str] + self.crole = None # type: Optional[str] + self.sport = None # type: Optional[str] + self.srole = None # type: Optional[str] + self.scuk = None # type: Optional[str] + + # ---- POST/PUT ---- + # POST http(s)://API SERVER:PORT/v1/acr/service name + # PUT http(s)://API SERVER:PORT/v1/acr/service name + # http(s)://API SERVER:PORT/v1/acr/service name?urlarg + # urlargs tenant=tenant name + def add_member(self, tenant: Optional[str]): + """Add the members.""" + self.api_id = 1 + self.tenant = tenant + return self + + # ---- GET ---- + # GET http(s)://API SERVER:PORT/v1/acr/service name + # http(s)://API SERVER:PORT/v1/acr/service name?urlarg + # cip=client ip address + # cport=client port(optional) + # crole=client role yrn path + # ccuk=client container unique key + # sport=service port(optional) + # srole=service role yrn path + # scuk=service container unique key + def show_credential_details(self): + """Show the credential details.""" + self.api_id = 2 + return self + + def get_available_resources(self, + cip: Optional[str] = None, + cport: Optional[str] = None, + crole: Optional[str] = None, + ccuk: Optional[str] = None, + sport: Optional[str] = None, + srole: Optional[str] = None, + scuk: Optional[str] = None): + """Show the available resources.""" + self.api_id = 3 + self.cip = cip + self.cport = cport + self.crole = crole + self.ccuk = ccuk + self.sport = sport + self.srole = srole + self.scuk = scuk + return self + + # ---- DELETE ---- + # + # DELETE http(s)://API SERVER:PORT/v1/acr/service name + def delete_member(self, tenant: str): + """Delete the members.""" + self.api_id = 4 + self.tenant = tenant + return self + + def __repr__(self) -> str: + """Represent the instance.""" + attrs = [] + values = "" + for attr in ['_r3token', '_tenant']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def r3token(self) -> str: + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + @property # type: ignore + def service(self) -> str: + """Return the tenant.""" + return self._service + + @service.setter + def service(self, val: str): # type: ignore # noqa: F811 + """Set the service.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_service', None) is None: + self._service = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + # Creating the request body + if self.api_id == 1: + python_data = json.loads(_ACR_API_ADD_MEMBER) + python_data['tenant'] = self.tenant + self.body = json.dumps(python_data) + return f'{self.version}/{self.basepath}/{self.service}' + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + self.urlparams = json.dumps({ + 'tenant': self.tenant + }) + return f'{self.version}/{self.basepath}/{self.service}' + if method == K2hr3HTTPMethod.GET: + if self.api_id == 2: + return f'{self.version}/{self.basepath}/{self.service}' + if self.api_id == 3: + self.urlparams = json.dumps({ + 'cip': self.cip, + 'cport': self.cport, + 'crole': self.crole, + 'ccuk': self.ccuk, + 'sport': self.sport, + 'srole': self.srole, + 'scuk': self.scuk, + }) + return f'{self.version}/{self.basepath}/{self.service}' + if method == K2hr3HTTPMethod.DELETE: + if self.api_id == 4: + self.urlparams = json.dumps({ + 'tenant': self.tenant + }) + return f'{self.version}/{self.basepath}/{self.service}' + return None +# +# 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/k2hr3client/api.py b/src/k2hr3client/api.py new file mode 100644 index 0000000..61aea88 --- /dev/null +++ b/src/k2hr3client/api.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# + +""" +k2hr3client - Python library for K2HR3 API. + +.. code-block:: python + + # Import modules from k2hr3client package + from k2hr3client import version + v = version.K2hr3Version() + from k2hr3client import http as khttp + httpreq = khttp.K2hr3Http('http://127.0.0.1:18080') + + # GET the K2hr Version API. + httpreq.GET(v) + print(v.resp) +""" + +import abc +from enum import Enum +import logging +from http.client import HTTPMessage +from typing import Optional + +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + + +# NOTE(hiwakaba): we do not use 3.11's http.HTTPMethod module +# Because we need to support 3.10. +class K2hr3HTTPMethod(Enum): # type: ignore[no-redef] + # pylint: disable=too-few-public-methods + """HTTPMethod.""" + + CONNECT = 1 + DELETE = 2 + GET = 3 + HEAD = 4 + OPTIONS = 5 + PATCH = 6 + POST = 7 + PUT = 8 + TRACE = 9 + + +class K2hr3ApiResponse(): # pylint: disable=too-many-instance-attributes + """K2hr3ApiResponse stores the response of K2HR3 WebAPI. + + The members are set by setter methods only one time. + """ + + __slots__ = ('_code', '_url', '_hdrs', '_body') + + def __init__(self, code=None, url=None, hdrs=None, body=None) -> None: + """Init the members.""" + self.code = code + self.url = url + self.hdrs = hdrs + self.body = body + + def __repr__(self) -> str: + """Represent the members.""" + attrs = [] + values = "" + for attr in ['_hdrs', '_body', '_url', '_code']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) # pylint: disable=consider-using-f-string # noqa + return '' + + @property + def body(self) -> Optional[str]: + """Return the body.""" + return self._body + + @body.setter + def body(self, val: Optional[str]) -> None: + """Set the body that may be empty.""" + if val and isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_body', None) is None: + self._body = val + + @property + def hdrs(self) -> HTTPMessage: + """Return the header.""" + return self._hdrs + + @hdrs.setter + def hdrs(self, val: HTTPMessage) -> None: + """Set the headers that must not be empty.""" + if isinstance(val, HTTPMessage) is False: + raise K2hr3Exception( + f'value type must be http.client.HTTPMessage, not {type(val)}') + if not val: + raise K2hr3Exception("val should not be empty") + if getattr(self, '_hdrs', None) is None: + self._hdrs = val + + @property + def code(self) -> int: + """Return the status code.""" + return self._code + + @code.setter + def code(self, val: int) -> None: + """Set the http status code that must not be empty.""" + if isinstance(val, int) is False: + raise K2hr3Exception(f'val should be int, not {type(val)}') + if not val: + raise K2hr3Exception("val should not be empty") + if getattr(self, '_code', None) is None: + self._code = val + + @property + def url(self) -> str: + """Return the request url.""" + return self._url + + @url.setter + def url(self, val: str) -> None: + """Set the url code that must not be empty.""" + if isinstance(val, str) is False: + raise K2hr3Exception("val should be str, not {type(val)") + if not val: + raise K2hr3Exception("val should not be empty") + if getattr(self, '_url', None) is None: + self._url = val + + +class K2hr3Api(abc.ABC): # pylint: disable=too-many-instance-attributes + """Base class of all K2HR3 WebAPIs.""" + + DEFAULT_VERSION = "v1" + + def __init__(self, basepath: str, params: Optional[str] = None, + hdrs: Optional[dict] = None, body: Optional[str] = None, + version: str = DEFAULT_VERSION) -> None: # noqa + """Init the K2hr3 API members. + + :raise K2hr3Exception: if the val is invalid. + """ + super().__init__() + self.basepath = basepath + self.urlparams = params + self.headers = hdrs + self.body = body + self.version = version + + # following attrs are dynamically set later. + self.resp = None # type: ignore + self.api_id = 0 + + def __repr__(self) -> str: + """Represent the members.""" + attrs = [] + values = "" + for attr in ['_basepath', '_params', '_hdrs', '_body', '_resp', + 'api_id']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property + def basepath(self) -> str: + """Return the base url path.""" + return self._basepath + + @basepath.setter + def basepath(self, val: str) -> None: + """Set the base url path. + + :raise K2hr3Exception: if the val is invalid. + """ + if isinstance(val, str) is False: + raise K2hr3Exception("basepath should be str") + if getattr(self, '_basepath', None) is None: + self._basepath = val + else: + LOG.info("basepath has changed") + self._basepath = val + + # + # getters and setters + # + @property + def urlparams(self) -> Optional[str]: + """Return the url params.""" + return self._params + + @urlparams.setter + def urlparams(self, val: Optional[str]) -> None: # pylint: disable=arguments-differ, invalid-overridden-method # noqa + """Set the params.""" + if getattr(self, '_params', None) is None: + self._params = val + + @property + def headers(self) -> Optional[dict]: + """Return the request headers.""" + return self._hdrs + + @headers.setter + def headers(self, val: Optional[dict]) -> None: # pylint: disable=arguments-differ, invalid-overridden-method # noqa + """Set the headers.""" + if getattr(self, '_hdrs', None) is None: + self._hdrs = val + + @property + def body(self) -> Optional[str]: + """Return the request body.""" + return self._body + + @body.setter + def body(self, val: Optional[str]) -> None: # pylint: disable=arguments-differ, invalid-overridden-method # noqa + """Set the body.""" + if getattr(self, '_body', None) is None: + self._body = val + + @property + def resp(self) -> K2hr3ApiResponse: + """Return the response struct.""" + return self._resp + + @resp.setter + def resp(self, val: K2hr3ApiResponse) -> None: + """Set the response as is.""" + if getattr(self, '_resp', None) is None: + self._resp = val + + @property + def version(self) -> str: + """Return the version string.""" + return self._version + + @version.setter + def version(self, val: str) -> None: + """Set the version as is.""" + self._version = val + + # + # abstract methods that must be implemented in subclasses + # + @abc.abstractmethod + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Sub classes should implement this method.""" + + # + # methods that are invoked from other classes + # + def set_response(self, code: int, url: str, headers: HTTPMessage, + body: Optional[str]) -> None: + """Set the API responses in K2hr3Http class.""" + self._resp = K2hr3ApiResponse(code, url, headers, body) + +# +# 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/k2hr3client/exception.py b/src/k2hr3client/exception.py new file mode 100644 index 0000000..bad48ce --- /dev/null +++ b/src/k2hr3client/exception.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Exception.""" + + +class K2hr3Exception(Exception): + """Exception classes for K2HR3 Python Client.""" + + +# +# 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/k2hr3client/extdata.py b/src/k2hr3client/extdata.py new file mode 100644 index 0000000..87a901e --- /dev/null +++ b/src/k2hr3client/extdata.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +""" +k2hr3client - Python library for k2hr3 Extdata API. + +.. code-block:: python + + # Import modules from k2hr3client package + from k2hr3client import http as khttp + httpreq = khttp.K2hr3Http('http://127.0.0.1:18080') + + from k2hr3client import extdata + example = extdata.K2hr3Extdata("uripath","registerpath","ua 1.0.0") + + # GET the K2hr Extdata API. + httpreq.GET(example.acquires_template()) + print(example.resp) +""" + +import logging +from typing import Optional + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + + +# curl -v \ +# http://127.0.0.1:18080/v1/extdata/test -H 'User-Agent: cloud-init 0.7.9' +# +# +class K2hr3Extdata(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 EXTDATA API. + + See https://k2hr3.antpick.ax/api_extdata.html for details. + """ + + __slots__ = ('_extapi_name', '_register_path', '_user_agent',) + + def __init__(self, extapi_name: str, register_path: str, + user_agent: str) -> None: + """Init the members.""" + super().__init__("extdata") + self.extapi_name = extapi_name + self.register_path = register_path + self.user_agent = user_agent + + # following attrs are dynamically set later. + self.headers = { + + 'Content-Type': 'application/octet-stream', + 'User-Agent': f"{user_agent}", + 'Accept-Encoding': 'gzip', + } + self.body = None # type: ignore + self.urlparams = None # type: ignore + + # ---- GET ---- + # GET(Extdata) http(s)://API SERVER:PORT/v1/extdata/uripath/registerpath + def acquires_template(self) -> None: + """Set a request to acquire a template.""" + self.api_id = 1 + + def __repr__(self) -> str: + """Represent the members.""" + attrs = [] + values = "" + for attr in ['_extapi_name', '_register_path', '_user_agent']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def extapi_name(self) -> str: + """Return the tenant.""" + return self._extapi_name + + @extapi_name.setter + def extapi_name(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the extapi_name.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_extapi_name', None) is None: + self._extapi_name = val + + @property # type: ignore + def register_path(self) -> str: + """Return the tenant.""" + return self._register_path + + @register_path.setter + def register_path(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the extapi_name.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_register_path', None) is None: + self._register_path = val + + @property # type: ignore + def user_agent(self) -> str: + """Return the tenant.""" + return self._user_agent + + @user_agent.setter + def user_agent(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the extapi_name.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_user_agent', None) is None: + self._user_agent = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.GET: + if self.api_id == 1: + return f'{self.version}/{self.basepath}/{self.extapi_name}' \ + f'/{self.register_path}' + return None +# +# 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/k2hr3client/http.py b/src/k2hr3client/http.py new file mode 100644 index 0000000..8e994a0 --- /dev/null +++ b/src/k2hr3client/http.py @@ -0,0 +1,413 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +""" +k2hr3client - Python library for K2HR3 API. + +.. code-block:: python + + # Import modules from k2hr3client package + from k2hr3client import http as khttp + httpreq = khttp.K2hr3Http('http://127.0.0.1:18080') + + from k2hr3client import extdata + example = extdata.K2hr3Extdata("uripath","registerpath","ua 1.0.0") + + # GET the K2hr Extdata API. + httpreq.GET(example.acquires_template()) + print(example.resp) +""" + +from enum import Enum +import json +import logging +import re +import socket +import ssl +import time +from typing import Optional +import urllib +import urllib.parse +import urllib.request +from urllib.error import ContentTooShortError, HTTPError, URLError + +from k2hr3client.api import K2hr3HTTPMethod, K2hr3Api +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + + +class _AgentError(Enum): + NONE = 1 + TEMP = 2 + FATAL = 3 + + +class K2hr3Http(): # pylint: disable=too-many-instance-attributes + """K2hr3Http sends a http/https request to the K2hr3 WebAPI. + + Most of the members are set by setter methods only one time. + """ + + __slots__ = ('_baseurl', '_hdrs', '_timeout_seconds', + '_url', '_urlparams', + '_retry_interval_seconds', '_retries', + '_allow_self_signed_cert') + + def __init__(self, baseurl: str) -> None: + """Init the members.""" + self._set_baseurl(baseurl) + self._timeout_seconds = 30 + self._url = None # type: Optional[str] + self._urlparams = None # type: Optional[str] + self._retry_interval_seconds = 60 # type: int + self._retries = 3 # type: int + self._allow_self_signed_cert = True # type: bool + + def __repr__(self) -> str: + """Represent the members.""" + attrs = [] + values = "" + for attr in ['_baseurl', '_hdrs', '_timeout_seconds', + '_retry_interval_seconds', '_retries', + '_allow_self_signed_cert']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) # pylint: disable=consider-using-f-string # noqa + return '' + + @property + def baseurl(self) -> Optional[str]: + """Returns the url.""" + return self._baseurl + + def _set_baseurl(self, value: Optional[str]) -> None: + """Set the baseurl. + + :raise K2hr3Exception: if the val is invalid. + """ + if isinstance(value, str) is False: + raise K2hr3Exception("value should be str, not {type(value)}") + # scheme + try: + scheme, url_string = value.split('://', maxsplit=2) # type: ignore + except ValueError as verr: + raise K2hr3Exception( + f'scheme should contain ://, not {value}') from verr + if scheme not in ('http', 'https'): + raise K2hr3Exception( + f'scheme should be http or http, not {scheme}') + matches = re.match( + r'(?P[\w|\.]+)?(?P:\d{2,5})?(?P[\w|/]*)?', + url_string) + if matches is None: + raise K2hr3Exception( + f'the argument seems not to be a url string, {value}') + + # domain must be resolved. + domain = matches.group('domain') + if domain is None: + raise K2hr3Exception( + f'url contains no domain, {value}') + try: + # https://github.com/python/cpython/blob/master/Modules/socketmodule.c#L5729 + ipaddress = socket.gethostbyname(domain) + except OSError as error: # resolve failed + raise K2hr3Exception( + f'unresolved domain, {domain} {error}') from error + LOG.debug('%s resolved %s', domain, ipaddress) + + # path(optional) + if matches.group('path') is None: + raise K2hr3Exception( + f'url contains no path, {value}') + path = matches.group('path') + # port(optional) + port = matches.group('port') + LOG.debug('url=%s domain=%s port=%s path=%s', value, domain, port, + path) + if getattr(self, '_baseurl', None) is None: + self._baseurl = value + + @property + def headers(self) -> dict: + """Return the request headers.""" + return self._hdrs # type: ignore + + @headers.deleter + def headers(self) -> None: + """Delete the request headers.""" + self._hdrs = None + + @headers.setter + def headers(self, val: dict) -> None: # pylint: disable=arguments-differ, invalid-overridden-method # noqa + """Set the headers.""" + if getattr(self, '_hdrs', None) is None: + self._hdrs = val # type: ignore + + @property + def url(self) -> Optional[str]: + """Returns the url.""" + return self._url # type: ignore + + @url.deleter + def url(self) -> None: + """Delete the request url.""" + self._url = None + + @url.setter + def url(self, val: Optional[str]) -> None: # type: ignore # noqa: F811 + """Set the url.""" + if isinstance(val, str) is False: + raise K2hr3Exception("basepath should be str") + if getattr(self, '_url', None) is None: + self._url = val + else: + LOG.info("url has changed") + self._url = val + + @property + def urlparams(self) -> Optional[str]: + """Returns the urlparams.""" + return self._urlparams + + @urlparams.deleter + def urlparams(self) -> None: + """Delete the urlparams headers.""" + self._urlparams = None + + @urlparams.setter + def urlparams(self, val: Optional[str]) -> None: # type: ignore # noqa + """Set the urlparams.""" + if getattr(self, '_urlparams', None) is None: + self._urlparams = val + + def _init_request(self) -> None: + """Init the headers and params.""" + del self.headers + self.headers = {'User-Agent': 'K2hr3Http'} + del self.url + del self.urlparams + + def _HTTP_REQUEST_METHOD(self, r3api: K2hr3Api, req: urllib.request.Request) -> bool: # pylint: disable=invalid-name # noqa + agent_error = _AgentError.NONE + try: + ctx = None + if req.type == 'https': + # https://docs.python.jp/3/library/ssl.html#ssl.create_default_context + ctx = ssl.create_default_context() + if self._allow_self_signed_cert: + # https://github.com/python/cpython/blob/master/Lib/ssl.py#L567 + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with urllib.request.urlopen(req, timeout=self._timeout_seconds, + context=ctx) as res: + r3api.set_response(code=res.getcode(), + url=res.geturl(), + headers=res.info(), + body=res.read().decode('utf-8')) + return True + except HTTPError as error: + LOG.error( + 'Could not complete the request. code %s reason %s headers %s', + error.code, error.reason, error.headers) + agent_error = _AgentError.FATAL + except (ContentTooShortError, URLError) as error: + # https://github.com/python/cpython/blob/master/Lib/urllib/error.py#L73 + LOG.error('Could not read the server. reason %s', error.reason) + agent_error = _AgentError.FATAL + except (socket.timeout, OSError) as error: # temporary error + LOG.error('error(OSError, socket) %s', error) + agent_error = _AgentError.TEMP + finally: + if agent_error == _AgentError.TEMP: + self._retries -= 1 # decrement the retries value. + if self._retries >= 0: + LOG.warning('sleeping for %s. remaining retries=%s', + self._retry_interval_seconds, + self._retries) + time.sleep(self._retry_interval_seconds) + self.GET(r3api) + else: + LOG.error("reached the max retry count.") + agent_error = _AgentError.FATAL + + if agent_error == _AgentError.NONE: + LOG.debug('no problem.') + return True + LOG.debug('problem. See the error log.') + return False + + def POST(self, r3api: K2hr3Api) -> bool: # pylint: disable=invalid-name # noqa + """Send requests by using POST Method.""" + self._init_request() + # 1. Constructs request url using K2hr3Api.path property. + r3api_path = r3api._api_path(K2hr3HTTPMethod.POST) # type: ignore # pylint: disable=protected-access # noqa + self.url = f"{self._baseurl}/{r3api_path}" + + # 2. Constructs url parameters using K2hr3Api.urlparams property. + if r3api.headers: + content_type = r3api.headers.get('Content-Type') + if content_type == "application/json": + query = r3api.body + if query: + query = query.encode('ascii') # type: ignore + else: + query = urllib.parse.urlencode(r3api.urlparams) # type: ignore + if query: + query = query.encode('ascii') # type: ignore + self.urlparams = query + + # 3. Constructs headers using K2hr3Api.headers property. + if self._hdrs: + self._hdrs.update(r3api.headers) + + # 4. Sends a request. + req = urllib.request.Request(self.url, data=query, # type: ignore + headers=self._hdrs, # type: ignore # noqa + method="POST") + if req.type not in ('http', 'https'): + LOG.error('http or https, not %s', req.type) + return False + return self._HTTP_REQUEST_METHOD(r3api, req) + + def PUT(self, r3api: K2hr3Api) -> bool: # pylint: disable=invalid-name # noqa + """Send requests by using PUT Method.""" + self._init_request() + # 1. Constructs request url using K2hr3Api.path property. + r3api_path = r3api._api_path(K2hr3HTTPMethod.PUT) # type: ignore # pylint: disable=protected-access # noqa + self.url = f"{self._baseurl}/{r3api_path}" + + # 2. Constructs url parameters using K2hr3Api.urlparams property. + query = urllib.parse.urlencode(json.loads(r3api.urlparams)) # type: ignore # noqa + self.urlparams = query + + # 3. Constructs headers using K2hr3Api.headers property. + if self._hdrs: + self._hdrs.update(r3api.headers) + + # 5. Sends a request. + req = urllib.request.Request("?".join([self.url, self.urlparams]), + data=None, headers=self._hdrs, # type: ignore # noqa + method="PUT") + if req.type not in ('http', 'https'): + LOG.error('http or https, not %s', req.type) + return False + return self._HTTP_REQUEST_METHOD(r3api, req) + + def GET(self, r3api: K2hr3Api) -> bool: # pylint: disable=invalid-name # noqa + """Send requests by using GET Method.""" + self._init_request() + # 1. Constructs request url using K2hr3Api.path property. + r3api_path = r3api._api_path(K2hr3HTTPMethod.GET) # type: ignore # pylint: disable=protected-access # noqa + self.url = f"{self._baseurl}/{r3api_path}" + + # 2. Constructs url parameters using K2hr3Api.params property. + if r3api.urlparams: + if isinstance(r3api.urlparams, dict): + query = urllib.parse.urlencode(r3api.urlparams) + else: + query = urllib.parse.urlencode(json.loads(r3api.urlparams)) + self.urlparams = query + url = self.url + if self.urlparams: + url = "?".join([self.url, self.urlparams]) + + # 3. Constructs headers using K2hr3Api.headers property. + if self._hdrs: + self._hdrs.update(r3api.headers) + + # 4. Sends a request. + req = urllib.request.Request(url, headers=self._hdrs, method="GET") # type: ignore # noqa + if req.type not in ('http', 'https'): + LOG.error('http or https, not %s', req.type) + return False + return self._HTTP_REQUEST_METHOD(r3api, req) + + def HEAD(self, r3api: K2hr3Api) -> bool: # pylint: disable=invalid-name # noqa + """Send requests by using GET Method.""" + self._init_request() + # 1. Constructs request url using K2hr3Api.path property. + r3api_path = r3api._api_path(K2hr3HTTPMethod.HEAD) # type: ignore # pylint: disable=protected-access # noqa + self.url = f"{self._baseurl}/{r3api_path}" + + # 2. Constructs url parameters using K2hr3Api.params property. + if r3api.urlparams: + if isinstance(r3api.urlparams, dict): + query = urllib.parse.urlencode(r3api.urlparams) + else: + query = urllib.parse.urlencode(json.loads(r3api.urlparams)) + self.urlparams = query + url = self.url + if self.urlparams: + url = "?".join([self.url, self.urlparams]) + + # 3. Constructs headers using K2hr3Api.headers property. + if self._hdrs: + self._hdrs.update(r3api.headers) # type: ignore + + # 4. Sends a request. + # NOTE: headers is expected "MutableMapping[str, str]" + req = urllib.request.Request(url, headers=self._hdrs, method="HEAD") # type: ignore # noqa + if req.type not in ('http', 'https'): + LOG.error('http or https, not %s', req.type) + return False + return self._HTTP_REQUEST_METHOD(r3api, req) + + def DELETE(self, r3api: K2hr3Api) -> bool: # pylint: disable=invalid-name # noqa + """Send requests by using DELETE Method.""" + self._init_request() + # 1. Constructs request url using K2hr3Api.path property. + r3api_path = r3api._api_path(K2hr3HTTPMethod.DELETE) # type: ignore # pylint: disable=protected-access # noqa + self.url = f"{self._baseurl}/{r3api_path}" + + # 2. Constructs url parameters using K2hr3Api.params property. + if r3api.urlparams: + if isinstance(r3api.urlparams, dict): + query = urllib.parse.urlencode(r3api.urlparams) + else: + query = urllib.parse.urlencode(json.loads(r3api.urlparams)) + self.urlparams = query + url = self.url + if self.urlparams: + url = "?".join([self.url, self.urlparams]) + + # 3. Constructs headers using K2hr3Api.headers property. + if self._hdrs: + self._hdrs.update(r3api.headers) # type: ignore + + # 4. Sends a request. + # NOTE: headers is expected "MutableMapping[str, str]" + req = urllib.request.Request(url, headers=self._hdrs, method="HEAD") # type: ignore # noqa + if req.type not in ('http', 'https'): + LOG.error('http or https, not %s', req.type) + return False + return self._HTTP_REQUEST_METHOD(r3api, req) + +# +# 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/k2hr3client/list.py b/src/k2hr3client/list.py new file mode 100644 index 0000000..df523f2 --- /dev/null +++ b/src/k2hr3client/list.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of List API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.list import K2hr3List + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # GET a request to get list from K2HR3 List API. + mylist = K2hr3List(mytoken.token, "service") + myhttp.GET(mylist.get()) + mylist.resp.body // {"result":true... + +""" + + +import logging +from typing import Optional + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + + +class K2hr3List(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 LIST API. + + See https://k2hr3.antpick.ax/api_list.html + """ + + __slots__ = ('_r3token', '_service', ) + + def __init__(self, r3token: str, service: str) -> None: + """Init the members.""" + super().__init__("list") + self.r3token = r3token + self.service = service + + # following attrs are dynamically set later. + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + self.body = None # type: ignore + self.urlparams = None + # --- + self.expand = False + + # ---- GET ---- + # GET + # http(s)://API SERVER:PORT/v1/list/service name + # http(s)://API SERVER:PORT/v1/list{/service name}/resource{/root path}?urlarg # noqa + # http(s)://API SERVER:PORT/v1/list{/service name}/policy{/root path}?urlarg # noqa + # http(s)://API SERVER:PORT/v1/list{/service name}/role{/root path}?urlarg + # + # URL Arguments + # expand=true or false(default) + # + def get(self, expand=False): + """List K2HR3's SERVICE, RESOURCE, POLICY and ROLE in YRN form.""" + self.api_id = 1 + self.expand = expand + return self + + # ---- HEAD ---- + # HEAD + # http(s)://API SERVER:PORT/v1/list/service name + # http(s)://API SERVER:PORT/v1/list{/service name}/resource{/root path}?urlarg # noqa + # http(s)://API SERVER:PORT/v1/list{/service name}/resource{/root path}?urlarg # noqa + # http(s)://API SERVER:PORT/v1/list{/service name}/policy{/root path}?urlarg # noqa + # http(s)://API SERVER:PORT/v1/list{/service name}/role{/root path}?urlarg + # + def validate(self): + """Validate the objects.""" + self.api_id = 2 + return self + + def __repr__(self) -> str: + """Represent the instance.""" + attrs = [] + values = "" + for attr in [ + '_r3token', '_service' + ]: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def r3token(self) -> str: + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val: str): # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + @property # type: ignore + def service(self) -> str: + """Return the service.""" + return self._service + + @service.setter + def service(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the service.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be list, not {type(val)}') + if getattr(self, '_service', None) is None: + self._service = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.GET: + if self.api_id == 1: + return f'{self.version}/{self.basepath}/{self.service}' + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 2: + return f'{self.version}/{self.basepath}/{self.service}' + return None +# +# 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/k2hr3client/policy.py b/src/k2hr3client/policy.py new file mode 100644 index 0000000..e06dd55 --- /dev/null +++ b/src/k2hr3client/policy.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Policy API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.policy import K2hr3Policy + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # POST request to get a K2HR3 Policy API. + mypolicy = K2hr3Policy(mytoken.token) + RESOURCE_PATH = "yrn:yahoo:::demo:resource:test_resource" + myhttp.POST( + mypolicy.create( + policy_name="test_policy", + effect="allow", + action=['yrn:yahoo::::action:read'], + resource=[RESOURCE_PATH], + condition=None, + alias=[] + ) + ) + mypolicy.resp.body // {"result":true... + +""" + +import json +import logging +from typing import List, Optional + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod + +LOG = logging.getLogger(__name__) + +_POLICY_API_CREATE_POLICY = """ +{ + "policy": { + "name": "", + "effect": "", + "action": [], + "resource": [], + "condition": null, + "alias": [] + } +} +""" + + +class K2hr3Policy(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 POLICY API. + + See https://k2hr3.antpick.ax/api_policy.html for details. + """ + + __slots__ = ('_r3token',) + + def __init__(self, r3token: str) -> None: + """Init the members.""" + super().__init__("policy") + self.r3token = r3token + + # following attrs are dynamically set later. + if r3token: + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + else: + self.headers = { + 'Content-Type': 'application/json', + } + self.body = None + self.urlparams = None + # attributes that are unique to this class + self.policy_name = None + self.effect = None + self.action = None + self.resource = None + self.condition = None + self.alias = None + self.service = None + self.tenant = None + + # ---- POST/PUT ---- + # POST http(s)://API SERVER:PORT/v1/policy + # PUT http(s)://API SERVER:PORT/v1/policy?urlarg + def create(self, policy_name: str, effect: str, + action: Optional[List[str]], + resource: Optional[List[str]] = None, + condition: Optional[str] = None, + alias: Optional[List[str]] = None): + """Create policies.""" + self.api_id = 1 + # must to process a request further + self.policy_name = policy_name # type: ignore + self.effect = effect # type: ignore + self.action = action # type: ignore + # optionals + self.resource = resource # type: ignore + self.condition = condition # type: ignore + self.alias = alias # type: ignore + return self + + # ---- GET ---- + # GET http(s)://API SERVER:PORT/v1/policy/\ + # policy path or yrn full policy path{?service=service name} # noqa + def get(self, policy_name: str, service: str): + """Get policies.""" + self.api_id = 3 + self.policy_name = policy_name # type: ignore + self.service = service # type: ignore + return self + + # ---- HEAD ---- + # HEAD http(s)://API SERVER:PORT/v1/policy/yrn full policy path?urlarg + def validate(self, policy_name: str, tenant: str, resource: str, + action: str, service: Optional[str] = None): + """Validate policies.""" + self.api_id = 4 + self.policy_name = policy_name # type: ignore + self.tenant = tenant # type: ignore + self.resource = resource # type: ignore + self.action = action # type: ignore + # optionals + self.service = service # type: ignore + return self + + # ---- DELETE ---- + # DELETE http(s)://API SERVER:PORT/v1/policy/policy path or yrn full policy path # noqa + def delete(self, policy_name: str): + """Delete policies.""" + self.api_id = 5 + self.policy_name = policy_name # type: ignore + return self + + def __repr__(self) -> str: + """Represent the instance.""" + attrs = [] + values = "" + for attr in [ + '_r3token' + ]: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def r3token(self) -> str: + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + if self.api_id == 1: + python_data = json.loads(_POLICY_API_CREATE_POLICY) + python_data['policy']['name'] = self.policy_name + python_data['policy']['effect'] = self.effect + python_data['policy']['action'] = self.action + python_data['policy']['resource'] = self.resource + python_data['policy']['alias'] = self.alias + self.body = json.dumps(python_data) + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + self.urlparams = json.dumps({ + 'name': self.policy_name, + 'effect': self.effect, + 'action': self.action, + 'resource': self.resource, + 'alias': self.alias + }) + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.GET: + if self.api_id == 3: + self.urlparams = json.dumps({ + 'service': self.service + }) + return f'{self.version}/{self.basepath}/{self.policy_name}' + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 4: + self.urlparams = json.dumps({ + 'tenant': self.tenant, + 'resource': self.resource, + 'action': self.action, + 'service': self.service + }) + return f'{self.version}/{self.basepath}/{self.policy_name}' + if method == K2hr3HTTPMethod.DELETE: + if self.api_id == 5: + return f'{self.version}/{self.basepath}/{self.policy_name}' + return None + +# +# 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/k2hr3client/resource.py b/src/k2hr3client/resource.py new file mode 100644 index 0000000..9d020e2 --- /dev/null +++ b/src/k2hr3client/resource.py @@ -0,0 +1,487 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Resource API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.resource import K2hr3Resource + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # POST a request to create a resource from K2HR3 Resource API. + myresource = K2hr3Resource(mytoken.token) + myhttp.POST( + myresource.create_conf_resource( + name="test_resource", + data_type="string", + data="testresourcedata", + tenant="demo", + cluster_name="testcluster", + keys={ + "cluster-name": "test-cluster", + "chmpx-server-port": "8020", + "chmpx-server-ctrlport": "8021", + "chmpx-slave-ctrlport": "8031"}, + alias=[]) + ) + myresource.resp.body // {"result":true... + +""" + +import json +import logging +from pathlib import Path +import re +from typing import Optional, Any + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + +_MAX_LINE_LENGTH = 1024 * 8 +_RESOURCE_API_CREATE_RESOURCE = """ +{ + "resource": { + "name": "", + "type": "", + "data": "", + "keys": {}, + "alias": [] + } +} +""" +_RESOURCE_API_CREATE_RESOURCE_WITH_ROLE_TOKEN = """ +{ + "resource": { + "type": "", + "data": "", + "keys": {}, + } +} +""" +_RESOURCE_API_CREATE_RESOURCE_WITH_NO_TOKEN = """ +{ + "resource": { + "port": "", + "cuk": "", + "role": "", + "type": "", + "data": "", + "keys": {}, + } +} +""" + + +class K2hr3Resource(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 RESOURCE API. + + See https://k2hr3.antpick.ax/api_resource.html for details. + """ + + __slots__ = ('_r3token', '_roletoken', '_resource_path', ) + + def __init__(self, r3token: Optional[str] = None, + roletoken: Optional[str] = None, + resource_path: Optional[str] = None) -> None: # pylint: disable=too-many-arguments # noqa + """Init the members.""" + super().__init__("resource") + self.r3token = r3token + self.roletoken = roletoken + self.resource_path = resource_path + + # following attrs are dynamically set later. + if r3token: + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + elif roletoken: + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'R={}'.format(self._roletoken) + } + else: + self.headers = { + 'Content-Type': 'application/json', + } + self.body = None + self.urlparams = None + # attributes that are unique to this class + self.name = None + self.data_type = None + self.keys = None + self.alias = None + self.expand = None + self.service = None + self.port = None + self.cuk = None + self.role = None + self._data = None + + # ---- POST/PUT ---- + # POST http(s)://API SERVER:PORT/v1/resource + # POST http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path # noqa + # POST http(s)://API SERVER:PORT/v1/resource/yrn full resource path + # + # PUT + # http(s)://API SERVER:PORT/v1/resource?urlarg + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # + # name=resource name + # type=data type + # data=resource data + # keys=json key value object + # alias=json alias array + # type=data type + # data=resource data + # keys=json key value object + # + # port=port number + # cuk=container unique key + # role=yrn full role path + # type=data type + # data=resource data + # keys=json key value object + # + def create_conf_resource(self, name: str, data_type: str, data: Any, + tenant: str, cluster_name: str, + keys: Optional[dict], + alias: Optional[list] = None): + """Create the resource.""" + self.api_id = 1 + self.name = name # type: ignore + self.data_type = data_type # type: ignore + self._set_data(data, tenant, cluster_name) # type: ignore + self.keys = keys # type: ignore + self.alias = alias # type: ignore + return self + + # ---- GET ---- + # GET http(s)://API SERVER:PORT/v1/resource/\ + # resource path or yrn full resource path?urlarg # noqa + # GET http(s)://API SERVER:PORT/v1/resource/\ + # resource path or yrn full resource path?urlarg # noqa + # GET http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # expand=true or false + # service=service name + # + # If a role token request header is specified + # type=data type + # keyname=key name + # service=service name + # + # No token specified in the request headers + # port=port number + # cuk=container unique key + # role=yrn full role path + # type=data type + # keyname=key name + # service=service name + # + def get(self, expand: bool = False, + service: Optional[str] = None): + """Get the resource.""" + self.api_id = 3 + self.expand = expand # type: ignore + self.service = service # type: ignore + return self + + def get_with_roletoken(self, data_type: str, keys: Optional[dict], + service: Optional[str] = None): + """Get the resource with roletoken.""" + self.api_id = 4 + self.data_type = data_type # type: ignore + self.keys = keys # type: ignore + self.service = service # type: ignore + return self + + # ---- HEAD ---- + # HEAD + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # + # type=data type + # keyname=key name + # service=service name + # + # port=port number + # cuk=container unique key + # role=yrn full role path + # type=data type + # keyname=_key name + # service=service name + # + def validate(self, data_type: str, keys: Optional[dict], + service: Optional[str] = None): + """Validate the resource.""" + self.api_id = 5 + self.data_type = data_type # type: ignore + self.keys = keys # type: ignore + self.service = service # type: ignore + return self + + def validate_with_notoken(self, port: str, cuk: str, role: str, + data_type: str, keys: Optional[dict], + service: Optional[str] = None): + """Validate the resource without tokens.""" + self.api_id = 6 + self.port = port # type: ignore + self.cuk = cuk # type: ignore + self.role = role # type: ignore + self.data_type = data_type # type: ignore + self.keys = keys # type: ignore + self.service = service # type: ignore + return self + + # DELETE + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # Scoped User Token + # type=data type + # keynames=key name + # aliases=json alias array + # Role Token + # type=data type + # keynames=key name + # No Token + # port=port number + # cuk=container unique key + # role=yrn full role path + # type=data type + # keyname=_key name + def delete_with_scopedtoken(self, data_type: str, + keys: Optional[dict], + alias: Optional[list] = None): + """Delete the resource with scoped token.""" + self.api_id = 7 + self.data_type = data_type # type: ignore + self.keys = keys # type: ignore + self.alias = alias # type: ignore + return self + + def delete_with_roletoken(self, data_type: str, + keys: Optional[dict]): + """Delete the resource with role token.""" + self.api_id = 8 + self.data_type = data_type # type: ignore + self.keys = keys # type: ignore + return self + + def delete_with_notoken(self, port: str, cuk: str, role: str, + data_type: str, keys: Optional[dict]): + """Delete the resource without token.""" + self.api_id = 9 + self.port = port # type: ignore + self.cuk = cuk # type: ignore + self.role = role # type: ignore + self.data_type = data_type # type: ignore + self.keys = keys # type: ignore + return self + + def __repr__(self) -> str: + """Represent instance.""" + attrs = [] + values = "" + for attr in ['_r3token', '_resource_name', '_resource_path']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def data(self) -> str: + """Return data.""" + return self._data # type: ignore + + def _set_data(self, val: Any, projectname: str, clustername: str) -> None: + """Set data.""" + if getattr(self, '_data', None) is None: + self._data = val + if isinstance(val, Path) is False: + self._data = val + else: + self._data = "" # type: ignore + if val.exists() is False: + raise K2hr3Exception(f'path must exist, not {val}') + if val.is_file() is False: + raise K2hr3Exception( + f'path must be a regular file, not {val}') + # NOTE(hiwakaba): val must not be an arbitrary value. + # for backward compat, the resource template is used. + val = "/opt/stack/k2hdkc_dbaas/utils/python-k2hr3client/examples/example_resource.txt" # noqa # pylint: disable=line-too-long + with val.open() as f: # pylint: disable=no-member + line_len = 0 + for line in iter(f.readline, ''): + # 3. replace TROVE_K2HDKC_CLUSTER_NAME with clustername + line = re.sub('__TROVE_K2HDKC_CLUSTER_NAME__', clustername, + line) + # 4. replace TROVE_K2HDKC_TENANT_NAME with projectname + line = re.sub('__TROVE_K2HDKC_TENANT_NAME__', projectname, + line) + line_len += len(line) + if line_len > _MAX_LINE_LENGTH: + raise K2hr3Exception('data too big') + self._data = "".join([self._data, line]) # type: ignore + + @property # type: ignore + def r3token(self): + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val): # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + @property # type: ignore + def roletoken(self): + """Return the roletoken.""" + return self._roletoken + + @roletoken.setter + def roletoken(self, val): # type: ignore # noqa: F811 + """Set the roletoken.""" + if getattr(self, '_roletoken', None) is None: + self._roletoken = val + + @property # type: ignore + def resource_path(self): + """Return the resource_path.""" + return self._resource_path + + @resource_path.setter + def resource_path(self, val): # type: ignore # noqa: F811 + """Set the resource path.""" + if getattr(self, '_resource_path', None) is None: + self._resource_path = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: # pylint: disable=too-many-branches, too-many-return-statements, line-too-long # noqa + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + if self.api_id == 1: + python_data = json.loads(_RESOURCE_API_CREATE_RESOURCE) + python_data['resource']['name'] = self.name + python_data['resource']['type'] = self.data_type + python_data['resource']['data'] = self.data + python_data['resource']['keys'] = self.keys + python_data['resource']['alias'] = self.alias + self.body = json.dumps(python_data) + if self.r3token: + return f'{self.version}/{self.basepath}' + return f'{self.version}/{self.basepath}/{self.resource_path}' + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + self.urlparams = json.dumps({ + 'name': self.name, + 'type': self.data_type, + 'data': self.data, + 'keys': self.keys, + 'alias': self.alias + }) + if self.r3token: + return f'{self.version}/{self.basepath}' + return f'{self.version}/{self.basepath}/{self.resource_path}' + if method == K2hr3HTTPMethod.GET: + if self.api_id == 3: + self.urlparams = json.dumps({ + 'expand': self.expand, + 'service': self.service + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + if self.api_id == 4: + self.urlparams = json.dumps({ + 'type': self.data_type, + 'keys': self.keys, + 'service': self.service + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 5: + self.urlparams = json.dumps({ + 'type': self.data_type, + 'keys': self.keys, + 'service': self.service + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + if self.api_id == 6: + self.urlparams = json.dumps({ + 'port': self.port, + 'cuk': self.cuk, + 'role': self.role, + 'type': self.data_type, + 'keys': self.keys, + 'service': self.service + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + if method == K2hr3HTTPMethod.DELETE: + if self.api_id == 7: + self.urlparams = json.dumps({ + 'type': self.data_type, + 'keynames': self.keys, + 'alias': self.alias + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + if self.api_id == 8: + self.urlparams = json.dumps({ + 'type': self.data_type, + 'keynames': self.keys, + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + if self.api_id == 9: + self.urlparams = json.dumps({ + 'port': self.port, + 'cuk': self.cuk, + 'role': self.role, + 'type': self.data_type, + 'keynames': self.keys, + }) + return f'{self.version}/{self.basepath}/{self.resource_path}' + return None +# +# 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/k2hr3client/role.py b/src/k2hr3client/role.py new file mode 100644 index 0000000..399999d --- /dev/null +++ b/src/k2hr3client/role.py @@ -0,0 +1,496 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Role API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.role import K2hr3Role + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # POST a request to create a role to K2HR3 Role API. + myrole = K2hr3Role(mytoken.token) + myhttp.POST( + myrole.create( + role_name = "test_role", + policies = ['yrn:yahoo:::demo:policy:test_policy'], + alias = [] + ) + ) + myrole.resp.body // {"result":true... + +""" + +from enum import Enum +import json +import logging +from typing import List, Optional + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod + +LOG = logging.getLogger(__name__) + + +_ROLE_API_CREATE_ROLE = """ +{ + "role": { + "name": "", + "policies": [], + "alias": [] + } +} +""" +_ROLE_API_ADD_MEMBER = """ +{ + "host": { + "host": "", + "port": "", + "cuk": "", + "extra": "", + "tag": "", + "inboundip": "", + "outboundip": "" + }, + "clear_hostname": "", + "clear_ips": "" +} +""" +_ROLE_API_ADD_MEMBERS = """ +{ + "host": [{ + "host": "", + "port": "", + "cuk": "", + "extra": "", + "tag": "", + "inboundip": "", + "outboundip": "" + }], + "clear_hostname": "", + "clear_ips": "" +} +""" +_ROLE_API_ADD_MEMBER_USING_ROLETOKEN = """ +{ + "host": { + "port": "", + "cuk": "", + "extra": "", + "tag": "", + "inboundip": "", + "outboundip": "" + } +} +""" + + +class K2hr3RoleHost: # pylint disable=too-few-public-methods + # pylint: disable=too-few-public-methods + """Represent a host of a role. + + NOTE(hiwakaba): This class exists only for backward compatibility. + """ + + def __init__(self, host: str, port: str, cuk: str, extra: str, tag: str, + inboundip: str, outboundip: str): + """Init the members.""" + self.host = host + self.port = port + self.cuk = cuk + self.extra = extra + self.tag = tag + self.inboundip = inboundip + self.outboundip = outboundip + + +class K2hr3RoleHostList: # pylint disable=too-few-public-methods + # pylint: disable=too-few-public-methods + """Represent a list of hosts of a role. + + NOTE(hiwakaba): This class exists only for backward compatibility. + """ + + def __init__(self): + """Init the members.""" + self.hostlist = [] + + def add_host(self, host): + """Add hosts to the list.""" + self.hostlist.append(host) + + +class K2hr3TokenType(Enum): # pylint disable=too-few-public-methods + # pylint: disable=too-few-public-methods + """Represent a type of token.""" + + SCOPED_TOKEN = 1 + ROLE_TOKEN = 2 + NO_TOKEN = 3 + + +class K2hr3Role(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 ROLE API. + + See https://k2hr3.antpick.ax/api_role.html + """ + + __slots__ = ('_r3token',) + + def __init__(self, r3token: str, token_type=K2hr3TokenType.SCOPED_TOKEN): + """Init the members.""" + super().__init__("role") + self.r3token = r3token + + # following attrs are dynamically set later. + if token_type == K2hr3TokenType.SCOPED_TOKEN: + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + elif token_type == K2hr3TokenType.ROLE_TOKEN: + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'R={}'.format(self._r3token) + } + elif token_type == K2hr3TokenType.NO_TOKEN: + self.headers = { + 'Content-Type': 'application/json' + } + self.body = None + self.urlparams = None + # attributes that are unique to this class + self.role_name = None + self.policies = None + self.alias = None + self.host = None + self.clear_hostname = None + self.clear_ips = None + self.hosts = None + self.port = None + self.cuk = None + self.extra = None + self.tag = None + self.inboundip = None + self.outboundip = None + self.expand = None + self.role_token_string = None + + # POST http(s)://API SERVER:PORT/v1/role + # PUT http(s)://API SERVER:PORT/v1/role?urlarg + def create(self, role_name: str, policies: List[str], alias: List[str]): + """Create tokens.""" + self.api_id = 1 + self.role_name = role_name # type: ignore + self.policies = policies # type: ignore + self.alias = alias # type: ignore + return self + + # POST(Add HOST to ROLE) + # http(s)://API SERVER:PORT/v1/role/role path + # http(s)://API SERVER:PORT/v1/role/yrn full path to role + def add_member(self, role_name: str, host: str, + clear_hostname: bool, clear_ips: str): + """Add a member to the role.""" + self.api_id = 3 + self.role_name = role_name # type: ignore + self.host = host # type: ignore + self.clear_hostname = clear_hostname # type: ignore + self.clear_ips = clear_ips # type: ignore + return self + + def add_members(self, role_name: str, hosts: str, + clear_hostname: bool, clear_ips: str): + """Add members to the role.""" + self.api_id = 4 + self.role_name = role_name # type: ignore + self.hosts = hosts # type: ignore + self.clear_hostname = clear_hostname # type: ignore + self.clear_ips = clear_ips # type: ignore + return self + + # PUT(Add HOST to ROLE) + # http(s)://API SERVER:PORT/v1/role/role path?urlarg + # http(s)://API SERVER:PORT/v1/role/yrn full path to role?urlarg + def add_member_with_roletoken(self, role_name: str, port: str, cuk: str, + extra: str, tag: str, + inboundip: str, outboundip: str): + """Add members to the role without roletoken.""" + self.api_id = 5 + self.role_name = role_name # type: ignore + self.port = port # type: ignore + self.cuk = cuk # type: ignore + self.extra = extra # type: ignore + self.tag = tag # type: ignore + self.inboundip = inboundip # type: ignore + self.outboundip = outboundip # type: ignore + return self + + # GET(Create ROLE Token) + # TODO(hiwakaba) + + # GET(Show ROLE details) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path?urlarg + def get(self, role_name: str, expand: bool = True): + """Show role details.""" + self.api_id = 6 + self.role_name = role_name # type: ignore + self.expand = expand # type: ignore + return self + + # GET (Role Token List) + # http(s)://APISERVER:PORT/v1/role/token/list/role path or yrn full role path # noqa + def get_token_list(self, role_name: str, expand: bool = True): + """Show token list.""" + self.api_id = 7 + self.role_name = role_name # type: ignore + self.expand = expand # type: ignore + return self + + # HEAD(Validate ROLE) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path + def validate_role(self, role_name: str): + """Validate role.""" + self.api_id = 8 + self.role_name = role_name # type: ignore + return self + + # DELETE(Delete ROLE) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path?urlarg + def delete(self, role_name: str): + """Delete role.""" + self.api_id = 9 + self.role_name = role_name # type: ignore + return self + + # DELETE(Hostname/IP address deletion-role specification) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path + def delete_member(self, role_name: str, host: str, port: str, cuk: str): + """Delete host.""" + self.api_id = 10 + self.role_name = role_name # type: ignore + self.host = host # type: ignore + self.port = port # type: ignore + self.cuk = cuk # type: ignore + return self + + # DELETE(Hostname/IP address deletion - Role not specified) + def delete_member_wo_roletoken(self, cuk: str): + """Delete host without roletoken.""" + self.api_id = 11 + self.cuk = cuk # type: ignore + return self + + # DELETE (RoleToken deletion - Role specified) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path + def delete_roletoken(self, role_name: str, port: str, cuk: str): + """Delete roletoken.""" + self.api_id = 12 + self.role_name = role_name # type: ignore + self.port = port # type: ignore + self.cuk = cuk # type: ignore + return self + + # DELETE(Delete RoleToken - Role not specified) + def delete_roletoken_with_string(self, role_token_string: str): + """Delete roletoken without role.""" + self.api_id = 13 + self.role_token_string = role_token_string # type: ignore + return self + + def __repr__(self) -> str: + """Represent the instance.""" + attrs = [] + values = "" + for attr in ['_r3token']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def r3token(self) -> str: + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: # pylint: disable=too-many-branches, disable=too-many-return-statements, too-many-statements, line-too-long # noqa + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + if self.api_id == 1: + python_data = json.loads(_ROLE_API_CREATE_ROLE) + python_data['role']['name'] = self.role_name + python_data['role']['policies'] = self.policies + python_data['role']['alias'] = self.alias + self.body = json.dumps(python_data) + # POST http(s)://API SERVER:PORT/v1/role + return f'{self.version}/{self.basepath}' + if self.api_id == 3: + python_data = json.loads(_ROLE_API_ADD_MEMBER) + python_data['host']['host'] = self.host.host # type: ignore[attr-defined] # noqa + python_data['host']['port'] = self.host.port # type: ignore[attr-defined] # noqa + python_data['host']['cuk'] = self.host.cuk # type: ignore[attr-defined] # noqa + python_data['host']['extra'] = self.host.extra # type: ignore[attr-defined] # noqa + python_data['host']['tag'] = self.host.tag # type: ignore[attr-defined] # noqa + python_data['host']['inboundip'] = self.host.inboundip # type: ignore[attr-defined] # noqa # pylint: disable=line-too-long + python_data['host']['outboundip'] = self.host.outboundip # type: ignore[attr-defined] # noqa # pylint: disable=line-too-long + self.body = json.dumps(python_data) + # http(s)://API SERVER:PORT/v1/role/role path + return f'{self.version}/{self.basepath}/{self.role_name}' + # elif (self.api_id == 4): + # python_data = json.loads(_ROLE_API_ADD_MEMBERS) + # # TODO(hiwakaba) Not implemented + # self.body = json.dumps(python_data) + # # http(s)://API SERVER:PORT/v1/role/role path + # return f'{self.basepath}/{self.role_name}' + if self.api_id == 5: + python_data = json.loads(_ROLE_API_ADD_MEMBER_USING_ROLETOKEN) + python_data['host']['port'] = self.host.port # type: ignore[attr-defined] # noqa + python_data['host']['cuk'] = self.host.cuk # type: ignore[attr-defined] # noqa + python_data['host']['extra'] = self.host.extra # type: ignore[attr-defined] # noqa + python_data['host']['tag'] = self.host.tag # type: ignore[attr-defined] # noqa + python_data['host']['inboundip'] = self.host.inboundip # type: ignore[attr-defined] # noqa # pylint: disable=line-too-long + python_data['host']['outboundip'] = self.host.outboundip # type: ignore[attr-defined] # noqa # pylint: disable=line-too-long + self.body = json.dumps(python_data) + # http(s)://API SERVER:PORT/v1/role/role path + return f'{self.version}/{self.basepath}/{self.role_name}' + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + self.urlparams = json.dumps({ + 'name': self.role_name, + 'policies': self.policies, + 'alias': self.alias + }) + # POST http(s)://API SERVER:PORT/v1/role + return f'{self.version}/{self.basepath}' + if self.api_id == 3: + self.urlparams = json.dumps({ + 'host': self.host.host, # type: ignore[attr-defined] + 'port': self.host.port, # type: ignore[attr-defined] + 'cuk': self.host.cuk, # type: ignore[attr-defined] + 'extra': self.host.extra, # type: ignore[attr-defined] + 'tag': self.host.tag, # type: ignore[attr-defined] + 'inboundip': self.host.inboundip, # type: ignore[attr-defined] # noqa + 'outboundip': self.host.outboundip # type: ignore[attr-defined] # noqa + }) + # http(s)://API SERVER:PORT/v1/role/role path + return f'{self.version}/{self.basepath}/{self.role_name}' + if self.api_id == 4: + # TODO(hiwakaba) Not implemented + self.urlparams = json.dumps({ + }) + # http(s)://API SERVER:PORT/v1/role/role path + return f'{self.version}/{self.basepath}/{self.role_name}' + if self.api_id == 5: + self.urlparams = json.dumps({ + 'port': self.port, + 'cuk': self.cuk, + 'extra': self.extra, + 'tag': self.tag, + 'inboundip': self.inboundip, + 'outboundip': self.outboundip + }) + # http(s)://API SERVER:PORT/v1/role/role path + return f'{self.version}/{self.basepath}/{self.role_name}' + if method == K2hr3HTTPMethod.GET: + if self.api_id == 6: + self.urlparams = json.dumps({ + 'expand': self.expand + }) + # GET(Show ROLE details) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path?urlarg # noqa + return f'{self.version}/{self.basepath}/{self.role_name}' + if self.api_id == 7: + self.urlparams = json.dumps({ + 'expand': self.expand + }) + # http(s)://APISERVER:PORT/v1/role/token/list/role path or yrn full role path # noqa + return f'{self.version}/{self.basepath}/token/list/' \ + f'{self.role_name}' + + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 8: + # HEAD(Validate ROLE) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path # noqa + return f'{self.version}/{self.basepath}/{self.role_name}' + + if method == K2hr3HTTPMethod.DELETE: + if self.api_id == 9: + # DELETE(Delete ROLE) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path?urlarg # noqa + return f'{self.version}/{self.basepath}/{self.role_name}' + if self.api_id == 10: + # DELETE(Hostname/IP address deletion-role specification) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path # noqa + self.urlparams = json.dumps({ + 'host': self.host, + 'port': self.port, + 'cuk': self.cuk + }) + return f'{self.version}/{self.basepath}/{self.role_name}' + if self.api_id == 11: + # DELETE(Hostname/IP address deletion-role specification) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path # noqa + self.urlparams = json.dumps({ + 'cuk': self.cuk + }) + return f'{self.version}/{self.basepath}' + if self.api_id == 12: + # DELETE (RoleToken deletion - Role specified) + # http(s)://API SERVER:PORT/v1/role/role path or yrn full role path # noqa + self.urlparams = json.dumps({ + 'port': self.port, + 'cuk': self.cuk + }) + return f'{self.version}/{self.basepath}/{self.role_name}' + if self.api_id == 13: + # DELETE(Delete RoleToken - Role not specified) + # http(s)://API SERVER:PORT/v1/role/token/role token string + return f'{self.version}/{self.basepath}/token/{self.role_token_string}' # noqa + return None +# +# 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/k2hr3client/service.py b/src/k2hr3client/service.py new file mode 100644 index 0000000..bdec56e --- /dev/null +++ b/src/k2hr3client/service.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Service API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.service import K2hr3Service + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # POST a request to create a service to K2HR3 Service API. + myservice = K2hr3Service(mytoken.token, "test_service") + myhttp.POST( + myservice.create( + verify = "http://example.com/verify_url.php" + ) + ) + myservice.resp.body // {"result":true... + +""" + +import json +import logging +from typing import Optional + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod + +LOG = logging.getLogger(__name__) + +_SERVICE_API_CREATE_SERVICE = """ +{ + "name": "", + "verify": "" +} +""" +_SERVICE_API_ADD_MEMBER = """ +{ + "tenant": "", + "clear_tenant": "true/false", + "verify": "" +} +""" +_SERVICE_API_MODIFY_VERIFY = """ +{ + "verify": "" +} +""" + + +class K2hr3Service(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 SERVICE API. + + See https://k2hr3.antpick.ax/api_service.html for details. + """ + + __slots__ = ('_r3token',) + + def __init__(self, r3token: str, service_name: str): + """Init the members.""" + super().__init__("service") + self.r3token = r3token + self.name = service_name + + # following attrs are dynamically set later. + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + self.body = None + self.urlparams = None + # attributes that are unique to this class + self.verify_url = None + self.tenant = None + self.clear_tenant = None + + # ---- POST/PUT ---- + def create(self, verify_url: str): + """Create services.""" + self.api_id = 1 + self.verify_url = verify_url # type: ignore + return self + + def add_member(self, tenant: str, clear_tenant: bool): + """Add members to services.""" + self.api_id = 2 + self.tenant = tenant # type: ignore + self.clear_tenant = clear_tenant # type: ignore + return self + + def modify(self, verify_url: str): + """Modify services.""" + self.api_id = 3 + self.verify_url = verify_url # type: ignore + return self + + # ---- GET ---- + # GET http(s)://API SERVER:PORT/v1/service/service name + def get(self): + """Get services.""" + self.api_id = 4 + return self + + # ---- HEAD ---- + # HEAD http(s)://API SERVER:PORT/v1/service/service name + # http(s)://API SERVER:PORT/v1/service/service name?urlarg + # Validate MEMBER(Optional) tenant=tenant name + def validate(self, tenant: Optional[str] = None): + """Validate services.""" + self.api_id = 5 + self.tenant = tenant # type: ignore + return self + + # ---- DELETE ---- + # DELETE http(s)://API SERVER:PORT/v1/service/service name + # http(s)://API SERVER:PORT/v1/service/service name?urlarg + # Delete MEMBER(Optional) tenant=tenant name(optional) + # + def delete(self): + """Delete services.""" + self.api_id = 6 + return self + + def delete_tenant(self, tenant: str): + """Delete tenants.""" + self.api_id = 7 + self.tenant = tenant # type: ignore + return self + + def __repr__(self): + """Represent the members.""" + attrs = [] + values = "" + for attr in ['_r3token', '_name']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def r3token(self) -> str: + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val: str): # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: # pylint: disable=too-many-branches, disable=too-many-return-statements, line-too-long # noqa + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + if self.api_id == 1: + python_data = json.loads(_SERVICE_API_CREATE_SERVICE) + # { + # "name": + # "verify": + # } + python_data['name'] = self.name + python_data['verify'] = self.verify_url + self.body = json.dumps(python_data) + elif self.api_id == 2: + python_data = json.loads(_SERVICE_API_ADD_MEMBER) + # { + # "tenant": or [, ...] + # "clear_tenant": true/false or undefined + # "verify": + # } + python_data['tenant'] = self.tenant + python_data['clear_tenant'] = self.clear_tenant + self.body = json.dumps(python_data) + elif self.api_id == 3: + python_data = json.loads(_SERVICE_API_MODIFY_VERIFY) + # { + # "tenant": or [, ...] + # "clear_tenant": true/false or undefined + # "verify": + # } + python_data['verify'] = self.verify_url + self.body = json.dumps(python_data) + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + # { + # "tenant": or [, ...] + # "clear_tenant": true/false or undefined + # "verify": + # } + self.urlparams = json.dumps({ + 'name': self.name, + 'verify': self.verify_url + }) + elif self.api_id == 2: + # { + # "name": + # "verify": + # } + self.urlparams = json.dumps({ + 'tenant': self.tenant, + 'clear_tenant': self.clear_tenant + }) + elif self.api_id == 3: + # { + # "name": + # "verify": + # } + self.urlparams = json.dumps({ + 'verify': self.verify_url + }) + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.GET: + if self.api_id == 4: + return f'{self.version}/{self.basepath}/{self.name}' + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 5: + return f'{self.version}/{self.basepath}/{self.name}' + if method == K2hr3HTTPMethod.DELETE: + if self.api_id == 6: + return f'{self.version}/{self.basepath}/{self.name}' + if self.api_id == 7: + self.urlparams = json.dumps({ + 'tenant': self.tenant + }) + return f'{self.version}/{self.basepath}/{self.name}' + return None +# +# 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/k2hr3client/tenant.py b/src/k2hr3client/tenant.py new file mode 100644 index 0000000..cb01d3c --- /dev/null +++ b/src/k2hr3client/tenant.py @@ -0,0 +1,291 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Tenant API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.tenant import K2hr3Tenant + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... + + # POST a request to create a tenant to K2HR3 Tenant API. + mytenant = K2hr3Tenant(mytoken.token) + myhttp.POST( + mytenant.create( + tenant_name = "test_tenant", + users = ["demo"], + desc = "test_tenant description", + display = "test tenant display" + ) + ) + mytenant.resp.body // {"result":true... + +""" + +import json +import logging +from typing import List, Optional + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod + +LOG = logging.getLogger(__name__) + +_TENANT_API_CREATE_TENANT = """ +{ + "tenant": { + "name": "", + "desc": "", + "display": "", + "users": "" + } +} +""" + +_TENANT_API_UPDATE_TENANT = """ +{ + "tenant": { + "id": "", + "desc": "", + "display": "", + "users": "" + } +} +""" + + +class K2hr3Tenant(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 TENANT API. + + See https://k2hr3.antpick.ax/api_tenant.html for details. + """ + + __slots__ = ('_r3token',) + + def __init__(self, r3token: str): + """Init the members.""" + super().__init__("tenant") + self.r3token = r3token + + # following attrs are dynamically set later. + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + self.body = None + self.urlparams = None + # attributes that are unique to this class + self.tenant_name = None + self.users = None + self.desc = None + self.display = None + self.tenant_id = None + self.expand = None + + # POST(create) http(s)://API SERVER:PORT/v1/tenant + # { + # tenant: { + # name: + # desc: + # display: + # users: [, ...] + # } + # } + # PUT(Create) http(s)://API SERVER:PORT/v1/tenant?name=tenant name&… # noqa + def create(self, tenant_name: str, users: Optional[List[str]], + desc: Optional[str], display: Optional[str]): + """Create a new K2HR3 cluster Local Tenant(TENANT).""" + self.api_id = 1 + self.tenant_name = tenant_name # type: ignore + self.users = users # type: ignore + self.desc = desc # type: ignore + self.display = display # type: ignore + return self + + # POST (Update) http(s)://API SERVER:PORT/v1/tenant/tenant name + # { + # tenant: { + # id: + # desc: + # display: + # users: [, ...] + # } + # } + # PUT (Update) http(s)://API SERVER:PORT/v1/tenant/tenant name?id=tenant id # noqa + def modify(self, tenant_name: str, tenant_id: int, + users: Optional[List[str]], desc: Optional[str], + display: Optional[str]): + """Update the K2HR3 cluster Local Tenant(TENANT).""" + self.api_id = 3 + self.tenant_name = tenant_name # type: ignore + self.tenant_id = tenant_id # type: ignore + self.users = users # type: ignore + self.desc = desc # type: ignore + self.display = display # type: ignore + return self + + # GET (List) http(s)://API SERVER:PORT/v1/tenant?expand=true or false + def get_tenant_list(self, expand: bool = False): + """List the K2HR3 cluster Local Tenant(TENANT).""" + self.api_id = 5 + self.expand = expand # type: ignore + return self + + # GET (Tenant) http(s)://API SERVER:PORT/v1/tenant/tenant name + def get(self, tenant_name: str): + """Get the K2HR3 cluster Local Tenant(TENANT) information.""" + self.api_id = 6 + self.tenant_name = tenant_name # type: ignore + return self + + # HEAD http(s)://API SERVER:PORT/v1/tenant/tenant name + def validate(self, tenant_name: str): + """Check the existence of the K2HR3 cluster Local Tenant(TENANT).""" + self.api_id = 7 + self.tenant_name = tenant_name # type: ignore + return self + + # DELETE (Tenant) http(s)://API SERVER:PORT/v1/tenant?tenant=tenant name?id=tenant id # noqa + def delete(self, tenant_name: str, tenant_id: int): + """Completely delete the Local Tenant(TENANT).""" + self.api_id = 8 + self.tenant_name = tenant_name # type: ignore + self.tenant_id = tenant_id # type: ignore + return self + + # DELETE (User) http(s)://API SERVER:PORT/v1/tenant/tenant name?id=tenant id # noqa + def delete_user(self, tenant_name: str, tenant_id: int): + """Make the USER unavailable to the K2HR3 cluster Local Tenant(TENANT).""" # noqa + self.api_id = 9 + self.tenant_name = tenant_name # type: ignore + self.tenant_id = tenant_id # type: ignore + return self + + def __repr__(self) -> str: + """Represent the members.""" + attrs = [] + values = "" + for attr in ['_r3token']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def r3token(self) -> str: + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val: str) -> None: # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: # pylint: disable=too-many-branches, disable=too-many-return-statements, line-too-long # noqa + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + if self.api_id == 1: + python_data = json.loads(_TENANT_API_CREATE_TENANT) + python_data['tenant']['name'] = self.tenant_name + python_data['tenant']['users'] = self.users + python_data['tenant']['desc'] = self.desc + python_data['tenant']['display'] = self.display + self.body = json.dumps(python_data) + return f'{self.version}/{self.basepath}' + if self.api_id == 3: + python_data = json.loads(_TENANT_API_UPDATE_TENANT) + python_data['tenant']['id'] = self.tenant_id + python_data['tenant']['users'] = self.users + python_data['tenant']['desc'] = self.desc + python_data['tenant']['display'] = self.display + self.body = json.dumps(python_data) + return f'{self.version}/{self.basepath}/{self.tenant_name}' + + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + python_data = json.loads(_TENANT_API_CREATE_TENANT) + self.urlparams = json.dumps({ + 'name': self.tenant_name, + 'users': self.users, + 'desc': self.desc, + 'display': self.display + }) + return f'{self.version}/{self.basepath}' + if self.api_id == 3: + python_data = json.loads(_TENANT_API_UPDATE_TENANT) + self.urlparams = json.dumps({ + 'id': self.tenant_id, + 'users': self.users, + 'desc': self.desc, + 'display': self.display + }) + return f'{self.version}/{self.basepath}/{self.tenant_name}' + + if method == K2hr3HTTPMethod.GET: + if self.api_id == 5: + self.urlparams = json.dumps({ + 'expand': self.expand + }) + return f'{self.version}/{self.basepath}' + if self.api_id == 6: + return f'{self.version}/{self.basepath}/{self.tenant_name}' + + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 7: + return f'{self.version}/{self.basepath}/{self.tenant_name}' + + if method == K2hr3HTTPMethod.DELETE: + if self.api_id == 8: + self.urlparams = json.dumps({ + 'tenant': self.tenant_name, + 'id': self.tenant_id + }) + return f'{self.version}/{self.basepath}' + if self.api_id == 9: + self.urlparams = json.dumps({ + 'id': self.tenant_id + }) + return f'{self.version}/{self.basepath}/{self.tenant_name}' + return None +# +# 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/k2hr3client/token.py b/src/k2hr3client/token.py new file mode 100644 index 0000000..41fce31 --- /dev/null +++ b/src/k2hr3client/token.py @@ -0,0 +1,483 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Token API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + + iaas_project = "demo" + iaas_token = "gAAAAA..." + mytoken = K2hr3Token(iaas_project, iaas_token) + + # POST a request to create a token to K2HR3 Token API. + myhttp = K2hr3Http("http://127.0.0.1:18080") + myhttp.POST(mytoken.create()) + mytoken.token // gAAAAA... +""" + +from enum import Enum +import json +import logging +from typing import Optional +import urllib.parse +import urllib.request + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + +_TOKEN_API_CREATE_TOKEN_TYPE1 = """ +{ + "auth": { + "tenantName": "", + "passwordCredentials": { + "username": "", + "password": "" + } + } +} +""" +_TOKEN_API_CREATE_TOKEN_TYPE2 = """ +{ + "auth": { + "tenantName": "" + } +} +""" +IDENTITY_V3_PASSWORD_AUTH_JSON_DATA = """ +{ + "auth": { + "identity": { + "methods": [ + "password" + ], + "password": { + "user": { + "name": "admin", + "domain": { + "name": "Default" + }, + "password": "devstacker" + } + } } + } +} +""" +IDENTITY_V3_TOKEN_AUTH_JSON_DATA = """ +{ + "auth": { + "identity": { + "methods": [ + "token" + ], + "token": { + "id": "" + } + }, + "scope": { + "project": { + "domain": { + "id": "default" + }, + "name": "" + } + } + } +} +""" + + +class K2hr3AuthType(Enum): + """Represent the type of authentication.""" + + CREDENTIAL = 1 + TOKEN = 2 + + +class K2hr3Token(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2hr3 TOKEN API. + + See https://k2hr3.antpick.ax/api_token.html for details. + """ + + __slots__ = ('_tenant', '_openstack_token', ) + + def __init__(self, iaas_project, iaas_token, + auth_type=K2hr3AuthType.TOKEN, + version=K2hr3Api.DEFAULT_VERSION): + """Init the members. + + :param iaas_project: configuration file path + :type iaas_project: str + :raises K2hr3Exception: if invalid augments exist + """ + super().__init__("user/tokens") + self.iaas_project = iaas_project + self.iaas_token = iaas_token + + # following attrs are dynamically set later. + if auth_type == K2hr3AuthType.TOKEN: + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._openstack_token) + } + elif auth_type == K2hr3AuthType.CREDENTIAL: + self.headers = { + 'Content-Type': 'application/json' + } + self.body = None + self.urlparams = None + + # optional attributes that are unique to this class + self.user = None + self.password = None + + # ---- POST/PUT ---- + # POST http(s)://API SERVER:PORT/v1/user/tokens + # PUT http(s)://API SERVER:PORT/v1/user/tokens?urlarg + def create(self, user=None, password=None): + """Create tokens.""" + self.api_id = 1 + self.user = user + self.password = password + return self + + # ---- GET ---- + # GET http(s)://API SERVER:PORT/v1/user/tokens + def show(self): + """Show details of tokens.""" + self.api_id = 2 + return self + + # ---- HEAD ---- + # HEAD http(s)://API SERVER:PORT/v1/user/tokens + def validate(self): + """Validate tokens.""" + self.api_id = 3 + return self + + def __repr__(self): + """Represent the instance.""" + attrs = [] + values = "" + for attr in [ + '_tenant' + ]: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def iaas_project(self): + """Return the k2hr3 tenant.""" + return self._tenant + + @iaas_project.setter + def iaas_project(self, val): # type: ignore # noqa: F811 + """Set the k2hr3 tenant.""" + if getattr(self, '_tenant', None) is None: + self._tenant = val + + @property # type: ignore + def iaas_token(self): + """Return the openstack token.""" + return self._openstack_token + + @iaas_token.setter + def iaas_token(self, val): # type: ignore # noqa: F811 + """Set the openstack token.""" + if getattr(self, '_openstack_token', None) is None: + self._openstack_token = val + + @property + def token(self): + """Return k2hr3 token.""" + python_data = json.loads(self.resp.body) + return python_data.get('token') + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.POST: + if self.api_id == 1: + if self.user and self.password: + python_data = json.loads(_TOKEN_API_CREATE_TOKEN_TYPE1) + python_data['auth']['user'] = self.user + python_data['auth']['password'] = self.password + python_data['auth']['tenantName'] = self.iaas_project + else: + python_data = json.loads(_TOKEN_API_CREATE_TOKEN_TYPE2) + python_data['auth']['tenantName'] = self.iaas_project + self.body = json.dumps(python_data) + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.PUT: + if self.api_id == 1: + if self.user and self.password: + self.urlparams = json.dumps({ + 'user': self.user, + 'password': self.password, + 'tenantname': self.iaas_project + }) + else: + self.urlparams = json.dumps({ + 'tenantname': self.iaas_project + }) + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.GET: + if self.api_id == 2: + return f'{self.version}/{self.basepath}' + if method == K2hr3HTTPMethod.HEAD: + if self.api_id == 3: + return f'{self.version}/{self.basepath}' + return None + + # + # the other methods + # + @staticmethod + def get_openstack_token(identity_url, user, password, project): + """Get the openstack token.""" + # unscoped token-id + # https://docs.openstack.org/api-ref/identity/v3/index.html#password-authentication-with-unscoped-authorization + python_data = json.loads(IDENTITY_V3_PASSWORD_AUTH_JSON_DATA) + python_data['auth']['identity']['password']['user']['name'] = user + python_data['auth']['identity']['password']['user']['password'] = password # noqa + headers = { + 'User-Agent': 'k2hr3client-python', + 'Content-Type': 'application/json' + } + req = urllib.request.Request(identity_url, + json.dumps(python_data).encode('ascii'), + headers, method="POST") + unscoped_token_id = "" + with urllib.request.urlopen(req) as res: + unscoped_token_id = dict(res.info()).get('X-Subject-Token') + if not unscoped_token_id: + return None + + # scoped token-id + # https://docs.openstack.org/api-ref/identity/v3/index.html?expanded=#token-authentication-with-scoped-authorization + python_data = json.loads(IDENTITY_V3_TOKEN_AUTH_JSON_DATA) + python_data['auth']['identity']['token']['id'] = unscoped_token_id + python_data['auth']['scope']['project']['name'] = project + headers = { + 'User-Agent': 'k2hr3client-python', + 'Content-Type': 'application/json' + } + req = urllib.request.Request(identity_url, + json.dumps(python_data).encode('ascii'), + headers, method="POST") + with urllib.request.urlopen(req) as res: + scoped_token_id = dict(res.info()).get('X-Subject-Token') + return scoped_token_id + return None + + +class K2hr3RoleToken(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Represent K2hr3 ROLE TOKEN API. + + See https://k2hr3.antpick.ax/api_role.html for details. + """ + + __slots__ = ('_r3token', '_role', '_expand') + + def __init__(self, r3token, role, expire): + """Init the members.""" + super().__init__("role/token") + self.r3token = r3token + self.role = role + # path should be "role/token/$roletoken". + self.path = "/".join([self.basepath, self.role]) + self.expire = expire + self.params = json.dumps({'expire': self._expire}) + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + + def __repr__(self): + """Represent the instance.""" + attrs = [] + values = "" + for attr in ['_r3token', '_role', '_expand']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def role(self): + """Return the role.""" + return self._role + + @role.setter + def role(self, val): # type: ignore # noqa: F811 + """Set the token.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_role', None) is None: + self._role = val + + @property # type: ignore + def expire(self): + """Return the expire.""" + return self._expire + + @expire.setter + def expire(self, val): # type: ignore # noqa: F811 + """Set the expire.""" + if isinstance(val, int) is False: + raise K2hr3Exception(f'value type must be int, not {type(val)}') + if getattr(self, '_expire', None) is None: + self._expire = val + + @property # type: ignore + def r3token(self): + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val): # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + @property + def token(self): + """Return k2hr3 token.""" + python_data = json.loads(self.resp.body) + return python_data.get('token') + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.GET: + return f'{self.version}/{self.path}' + return None + + +class K2hr3RoleTokenList(K2hr3Api): # pylint: disable=too-many-instance-attributes # noqa + """Represent K2hr3 ROLE TOKEN LIST API. + + See https://k2hr3.antpick.ax/api_role.html for details. + """ + + __slots__ = ('_r3token', '_role', '_expand') + + def __init__(self, r3token, role, expand): + """Init the members.""" + super().__init__("role/token/list") + self.r3token = r3token + self.role = role + self.expand = expand + + # headers should include r3token + self.headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U={}'.format(self._r3token) + } + # path should be "role/token/$roletoken". + self.path = "/".join([self.basepath, self.role]) + + def __repr__(self): + """Represent the instance.""" + attrs = [] + values = "" + for attr in ['_r3token', '_role', '_expand']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def role(self): + """Return the role.""" + return self._role + + @role.setter + def role(self, val): # type: ignore # noqa: F811 + """Set the role.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_role', None) is None: + self._role = val + + @property # type: ignore + def expand(self): + """Return the expand.""" + return self._expand + + @expand.setter + def expand(self, val): # type: ignore # noqa: F811 + """Set the expand.""" + if isinstance(val, bool) is False: + raise K2hr3Exception(f'value type must be bool, not {type(val)}') + if getattr(self, '_expand', None) is None: + self._expand = val + + @property # type: ignore + def r3token(self): + """Return the r3token.""" + return self._r3token + + @r3token.setter + def r3token(self, val): # type: ignore # noqa: F811 + """Set the r3token.""" + if getattr(self, '_r3token', None) is None: + self._r3token = val + + def registerpath(self, roletoken): + """Set the registerpath.""" + python_data = json.loads(self.resp.body) + return python_data['tokens'][roletoken]['registerpath'] + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.GET: + return f'{self.version}/{self.path}' + return None + + +# +# 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/k2hr3client/userdata.py b/src/k2hr3client/userdata.py new file mode 100644 index 0000000..1598d78 --- /dev/null +++ b/src/k2hr3client/userdata.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Userdata API. + +.. code-block:: python + + # Import modules from k2hr3client package. + from k2hr3client.token import K2hr3Token + from k2hr3client.http import K2hr3Http + from k2hr3client.userdata import K2hr3Userdata + + # GET a userdatascript from K2HR3 Userdata API. + userdata_path = "testpath" + myuserdata = K2hr3Userdata(userdata_path) + myhttp.GET( + myuserdata.provides_userdata_script() + ) + myuserdata.resp.body // {"result":true... + +""" +import logging +from typing import Optional + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod +from k2hr3client.exception import K2hr3Exception + +LOG = logging.getLogger(__name__) + + +class K2hr3Userdata(K2hr3Api): # pylint: disable=too-many-instance-attributes # noqa + """Relationship with K2HR3 USERDATA API. + + See https://k2hr3.antpick.ax/api_userdata.html for details. + + """ + + __slots__ = ('_userdatapath',) + + def __init__(self, userdatapath: str): + """Init the members.""" + super().__init__("userdata") + self.userdatapath = userdatapath + + # following attrs are dynamically set later. + self.headers = { + 'Content-Type': 'application/octet-stream', + 'User-Agent': 'Cloud-Init 0.7.9', + } + self.body = None + self.urlparams = None + + # ---- GET ---- + # GET(Userdata) http(s)://API SERVER:PORT/v1/userdata/userdata_path + def provides_userdata_script(self): + """Get userdata.""" + self.api_id = 1 + return self + + def __repr__(self) -> str: + """Represent the instance.""" + attrs = [] + values = "" + for attr in ['_userdatapath']: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + @property # type: ignore + def userdatapath(self) -> str: + """Return the tenant.""" + return self._userdatapath + + @userdatapath.setter + def userdatapath(self, val) -> None: # type: ignore # noqa: F811 + """Set the userdatapath.""" + if isinstance(val, str) is False: + raise K2hr3Exception(f'value type must be str, not {type(val)}') + if getattr(self, '_userdatapath', None) is None: + self._userdatapath = val + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.GET: + if self.api_id == 1: + return f'{self.version}/{self.basepath}/{self.userdatapath}' + return None +# +# 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/k2hr3client/version.py b/src/k2hr3client/version.py new file mode 100644 index 0000000..92953fb --- /dev/null +++ b/src/k2hr3client/version.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +# +"""K2HR3 Python Client of Version API. + +.. code-block:: python + + # Import modules from k2hr3client package + from k2hr3client import version + v = version.K2hr3Version() + from k2hr3client import http as khttp + httpreq = khttp.K2hr3Http('http://127.0.0.1:18080') + + # GET the K2hr Version API. + httpreq.GET(v) + print(v.resp) + +""" + +import logging +from typing import Optional + + +from k2hr3client.api import K2hr3Api, K2hr3HTTPMethod + +LOG = logging.getLogger(__name__) + + +class K2hr3Version(K2hr3Api): # pylint: disable=too-many-instance-attributes + """Relationship with K2HR3 VERSION API. + + See https://k2hr3.antpick.ax/api_version.html for details. + """ + + __slots__ = ('_name',) + + def __init__(self, version=""): + """Init the members.""" + super().__init__("", version=version) + self.name = version + + # following attrs are dynamically set later. + self.headers = { + 'Content-Type': 'application/json', + } + self.body = None + self.urlparams = None + + # ---- GET ---- + # + # GET http(s)://API SERVER:PORT/ + # GET http(s)://API SERVER:PORT/v1 + # + def get(self): + """Get the version.""" + self.api_id = 1 + + def __repr__(self): + """Represent the members.""" + attrs = [] + values = "" + for attr in [ + 'name' + ]: + val = getattr(self, attr, None) + if val: + attrs.append((attr, repr(val))) + values = ', '.join(['%s=%s' % i for i in attrs]) + return '' + + # + # abstract methos that must be implemented in subclasses + # + def _api_path(self, method: K2hr3HTTPMethod) -> Optional[str]: + """Get the request url path.""" + if method == K2hr3HTTPMethod.GET: + if self.api_id == 1: + return f'{self.version}' + return None +# +# 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/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..6e95b86 --- /dev/null +++ b/src/tests/__init__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client. + +This file is needed to run tests simply like: +$ python -m unittest discover + +All of the test files must be a package importable from the top-level directory of the project. +https://docs.python.org/3.6/library/unittest.html#test-discovery +""" +__author__ = 'Hirotaka Wakabayashi ' +__version__ = '1.0.0' + +# Disables the k2hr3client library logs by failure assetion tests. +import logging +logging.getLogger('k2hr3client').addHandler(logging.NullHandler()) + +# +# 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/tests/test_acr.py b/src/tests/test_acr.py new file mode 100644 index 0000000..f86735b --- /dev/null +++ b/src/tests/test_acr.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import json +import logging +import unittest +from unittest.mock import patch +import urllib.parse + +from k2hr3client import http as khttp +from k2hr3client import acr as kacr + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Acr(unittest.TestCase): + """Tests the K2hr3ApiResponse class. + + Simple usage(this class only): + $ python -m unittest tests/test_acr.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.service = "testservicez" + self.newtenant = "newtenant" + + def tearDown(self): + """Tears down a test case.""" + + def test_acr_construct(self): + """Creates a K2hr3ApiResponse instance.""" + acr = kacr.K2hr3Acr("token", service=self.service) + self.assertIsInstance(acr, kacr.K2hr3Acr) + + def test_acr_repr(self): + """Represent a K2hr3ApiResponse instance.""" + myacr = kacr.K2hr3Acr("token", self.service) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(myacr), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_acr_add_member_using_post(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myacr = kacr.K2hr3Acr("token", self.service) + self.assertEqual(myacr.r3token, "token") + self.assertEqual(myacr.service, self.service) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + myacr.add_member(self.newtenant) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(myacr)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/acr/{self.service}") + # 2. assert URL params + self.assertEqual(myacr.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + # 4. assert Request body + python_data = json.loads(kacr._ACR_API_ADD_MEMBER) + python_data['tenant'] = self.newtenant + body = json.dumps(python_data) + self.assertEqual(myacr.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_acr_add_member_using_put(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myacr = kacr.K2hr3Acr("token", self.service) + self.assertEqual(myacr.r3token, "token") + self.assertEqual(myacr.service, self.service) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + myacr.add_member(self.newtenant) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myacr)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/acr/{self.service}") + # 2. assert URL params + s_s_urlparams = {'tenant': self.newtenant} + self.assertEqual(myacr.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + # 4. assert Request body + self.assertEqual(myacr.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_acr_show_credential_details_using_get(self, + mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myacr = kacr.K2hr3Acr("token", self.service) + self.assertEqual(myacr.r3token, "token") + self.assertEqual(myacr.service, self.service) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + myacr.show_credential_details() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myacr)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/acr/{self.service}") + # 2. assert URL params + self.assertEqual(myacr.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + # 4. assert Request body + self.assertEqual(myacr.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_acr_get_available_resources_using_get(self, + mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myacr = kacr.K2hr3Acr("token", self.service) + self.assertEqual(myacr.r3token, "token") + self.assertEqual(myacr.service, self.service) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + s_s_urlparams = { + 'cip': 'mycip', + 'cport': 'mycport', + 'crole': 'mycrole', + 'ccuk': 'myccuk', + 'sport': 'mysport', + 'srole': 'mysrole', + 'scuk': 'myscuk', + } + myacr.get_available_resources( + s_s_urlparams['cip'], + s_s_urlparams['cport'], + s_s_urlparams['crole'], + s_s_urlparams['ccuk'], + s_s_urlparams['sport'], + s_s_urlparams['srole'], + s_s_urlparams['scuk']) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myacr)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/acr/{self.service}") + # 2. assert URL params + self.assertEqual(myacr.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + # 4. assert Request body + self.assertEqual(myacr.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_acr_delete_member_using_get(self, mock_HTTP_REQUEST_METHOD): + myacr = kacr.K2hr3Acr("token", self.service) + self.assertEqual(myacr.r3token, "token") + self.assertEqual(myacr.service, self.service) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + myacr.delete_member(self.newtenant) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myacr)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/acr/{self.service}") + # 2. assert URL params + s_s_urlparams = {'tenant': self.newtenant} + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(myacr.urlparams, json.dumps(s_s_urlparams)) + self.assertEqual(httpreq.urlparams, s_urlparams) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myacr.headers, headers) + # 4. assert Request body + self.assertEqual(myacr.body, None) + + +# +# 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/tests/test_api.py b/src/tests/test_api.py new file mode 100644 index 0000000..ef821c4 --- /dev/null +++ b/src/tests/test_api.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import logging +import unittest +from http.client import HTTPMessage + +from k2hr3client.api import K2hr3ApiResponse + +LOG = logging.getLogger(__name__) + + +class TestK2hr3ApiResponse(unittest.TestCase): + """Tests the K2hr3ApiResponse class. + + Simple usage(this class only): + $ python -m unittest tests/test_r3token.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3apiresponse_construct(self): + """Creates a K2hr3ApiResponse instance.""" + hdrs = HTTPMessage() + hdrs['mime-version'] = '1.0' + response = K2hr3ApiResponse(code=200, url="http://localhost:18080", hdrs=hdrs, body=None) + self.assertIsInstance(response, K2hr3ApiResponse) + + def test_k2hr3apiresponse_repr(self): + """Represent a K2hr3ApiResponse instance.""" + hdrs = HTTPMessage() + hdrs['mime-version'] = '1.0' + response = K2hr3ApiResponse(code=200, url="http://localhost:18080", hdrs=hdrs, body=None) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(response), '') + + +# +# 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/tests/test_extdata.py b/src/tests/test_extdata.py new file mode 100644 index 0000000..1a872e9 --- /dev/null +++ b/src/tests/test_extdata.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import logging +import unittest +from unittest.mock import patch + +from k2hr3client import http as khttp +from k2hr3client import extdata as kextdata + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Extdata(unittest.TestCase): + """Tests the K2hr3ApiResponse class. + + Simple usage(this class only): + $ python -m unittest tests/test_extdata.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.extapi_name = "uripath" + self.register_path = "registerpath" + self.user_agent = "allowed_useragent 1.0.0" + + def tearDown(self): + """Tears down a test case.""" + + def test_extdata_construct(self): + """Creates a K2hr3ApiResponse instance.""" + myextdata = kextdata.K2hr3Extdata(self.extapi_name, self.register_path, + self.user_agent) + self.assertIsInstance(myextdata, kextdata.K2hr3Extdata) + + def test_extdata_repr(self): + """Represent a K2hr3ApiResponse instance.""" + myextdata = kextdata.K2hr3Extdata(self.extapi_name, self.register_path, + self.user_agent) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(myextdata), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_extdata_acquries_template_using_get(self, + mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myextdata = kextdata.K2hr3Extdata(self.extapi_name, self.register_path, + self.user_agent) + myextdata.acquires_template() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myextdata)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/extdata/{self.extapi_name}/{self.register_path}") # noqa + # 2. assert URL params + self.assertEqual(myextdata.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/octet-stream', + 'User-Agent': f"{self.user_agent}", + 'Accept-Encoding': 'gzip', + } + self.assertEqual(myextdata.headers, headers) + # 4. assert Request body + self.assertEqual(myextdata.body, None) + +# +# 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/tests/test_list.py b/src/tests/test_list.py new file mode 100644 index 0000000..cea7a13 --- /dev/null +++ b/src/tests/test_list.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import logging +import unittest +from unittest.mock import patch + +from k2hr3client import http as khttp +from k2hr3client import list as klist + +LOG = logging.getLogger(__name__) + + +class TestK2hr3List(unittest.TestCase): + """Tests the K2hr3ApiResponse class. + + Simple usage(this class only): + $ python -m unittest tests/test_list.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.service = "testservice" + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3list_construct(self): + """Creates a K2hr3ApiResponse instance.""" + mylist = klist.K2hr3List("token", self.service) + self.assertIsInstance(mylist, klist.K2hr3List) + + def test_k2hr3list_repr(self): + """Represent a K2hr3ApiResponse instance.""" + mylist = klist.K2hr3List("token", self.service) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(mylist), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_k2hr3list_service_get_ok(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + mylist = klist.K2hr3List("token", self.service) + self.assertEqual(mylist.r3token, "token") + self.assertEqual(mylist.service, self.service) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mylist.headers, headers) + mylist.get() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(mylist)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/list/{self.service}") + # 2. assert URL params + self.assertEqual(mylist.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mylist.headers, headers) + # 4. assert Request body + self.assertEqual(mylist.body, None) + + +# +# 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/tests/test_policy.py b/src/tests/test_policy.py new file mode 100644 index 0000000..f593250 --- /dev/null +++ b/src/tests/test_policy.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import json +import logging +import unittest +from unittest.mock import patch +import urllib.parse + +from k2hr3client import http as khttp +from k2hr3client import policy as kpolicy + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Policy(unittest.TestCase): + """Tests the K2hr3Policy class. + + Simple usage(this class only): + $ python -m unittest tests/test_resource.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + RESOURCE_PATH = "yrn:yahoo:::demo:resource:my_resource" + self.token = "token" + self.service = "testservice" + self.policy_name = "testpolicy" + self.tenant = "demo" + self.effect = 'allow' + self.action = ['yrn:yahoo::::action:read'] + self.resource = [RESOURCE_PATH] + self.condition = None + self.alias = [] + + def tearDown(self): + """Tears down a test case.""" + + def test_policy_construct(self): + """Creates a K2hr3Policy instance.""" + mypolicy = kpolicy.K2hr3Policy(self.token) + self.assertIsInstance(mypolicy, kpolicy.K2hr3Policy) + + def test_policy_repr(self): + """Represent a K2hr3Policy instance.""" + mypolicy = kpolicy.K2hr3Policy(self.token) + self.assertRegex(repr(mypolicy), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_policy_create_policy_using_post(self, mock_HTTP_REQUEST_METHOD): + mypolicy = kpolicy.K2hr3Policy(self.token) + """Get root path.""" + self.assertEqual(mypolicy.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + mypolicy.create( + self.policy_name, self.effect, self.action, + self.resource, self.condition, self.alias) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(mypolicy)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/policy") + # 2. assert URL params + self.assertEqual(mypolicy.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + # 4. assert Request body + import json + python_data = json.loads(kpolicy._POLICY_API_CREATE_POLICY) + python_data['policy']['name'] = self.policy_name + python_data['policy']['effect'] = self.effect + python_data['policy']['action'] = self.action + python_data['policy']['resource'] = self.resource + python_data['policy']['alias'] = self.alias + body = json.dumps(python_data) + self.assertEqual(mypolicy.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_policy_create_policy_using_put(self, mock_HTTP_REQUEST_METHOD): + mypolicy = kpolicy.K2hr3Policy(self.token) + """Get root path.""" + self.assertEqual(mypolicy.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + mypolicy.create( + self.policy_name, self.effect, self.action, + self.resource, self.condition, self.alias) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(mypolicy)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/policy") + # 2. assert URL params + s_s_urlparams = { + 'name': self.policy_name, + 'effect': self.effect, + 'action': self.action, + 'resource': self.resource, + 'alias': self.alias + } + self.assertEqual(mypolicy.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + # 4. assert Request body + self.assertEqual(mypolicy.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_policy_get_using_get(self, mock_HTTP_REQUEST_METHOD): + mypolicy = kpolicy.K2hr3Policy(self.token) + """Get root path.""" + self.assertEqual(mypolicy.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + mypolicy.get(self.policy_name, self.service) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(mypolicy)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/policy/{self.policy_name}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'service': self.service + } + self.assertEqual(mypolicy.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + # 4. assert Request body + self.assertEqual(mypolicy.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_policy_validate_using_head(self, mock_HTTP_REQUEST_METHOD): + mypolicy = kpolicy.K2hr3Policy(self.token) + """Get root path.""" + self.assertEqual(mypolicy.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + mypolicy.validate( + self.policy_name, self.tenant, self.resource, self.action, + self.service + ) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(mypolicy)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/policy/{self.policy_name}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'tenant': self.tenant, + 'resource': self.resource, + 'action': self.action, + 'service': self.service + } + self.assertEqual(mypolicy.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + # 4. assert Request body + self.assertEqual(mypolicy.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_policy_delete_using_delete(self, mock_HTTP_REQUEST_METHOD): + mypolicy = kpolicy.K2hr3Policy(self.token) + """Get root path.""" + self.assertEqual(mypolicy.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + mypolicy.delete(self.policy_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(mypolicy)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/policy/{self.policy_name}") # noqa + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mypolicy.headers, headers) + # 4. assert Request body + self.assertEqual(mypolicy.body, None) + +# +# 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/tests/test_resource.py b/src/tests/test_resource.py new file mode 100644 index 0000000..13687ac --- /dev/null +++ b/src/tests/test_resource.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" +import json +import logging +import unittest +from unittest.mock import patch +import urllib.parse + +from k2hr3client import http as khttp +from k2hr3client import resource as kresource + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Resource(unittest.TestCase): + """Tests the K2hr3Resource class. + + Simple usage(this class only): + $ python -m unittest tests/test_resource.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.resource_path = "test_resource_path" + self.name = "test_resource" + # self.project = "test_project" + self.data_type = 'string' + self.data = "testresourcedata" + self.tenant = "mytenant" + self.cluster_name = "mycluster" + self.keys = { + "cluster-name": "testcluster", + "chmpx-server-port": "8020", + "chmpx-server-ctrlport": "8021", + "chmpx-slave-ctrlport": "8031" + } + self.expand = True + self.service = 'test_service' + self.alias = [] + + self.port = 3000 + self.cuk = "testcuk" + self.role = "testrole" + self.data_type = "test_datatype" + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3resource_construct(self): + """Creates a K2hr3Resoiurce instance.""" + resource = kresource.K2hr3Resource("token") + self.assertIsInstance(resource, kresource.K2hr3Resource) + + def test_k2hr3resource_repr(self): + """Represent a K2hr3Resource instance.""" + resource = kresource.K2hr3Resource("token") + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(resource), '') + + # + # TestCases using PUT Requests + # + # PUT + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_resource_create_resource_using_put(self, + mock_HTTP_REQUEST_METHOD): + myresource = kresource.K2hr3Resource("token") + self.assertEqual(myresource.r3token, "token") + """ root path.""" + myresource.create_conf_resource( + self.name, + self.data_type, + self.data, + self.tenant, + self.cluster_name, + self.keys, + self.alias) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myresource)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/resource") + # 2. assert URL params + s_s_urlparams = { + 'name': self.name, + 'type': self.data_type, + 'data': self.data, + 'keys': self.keys, + 'alias': self.alias + } + self.assertEqual(myresource.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myresource.headers, headers) + # 4. assert Request body + self.assertEqual(myresource.body, None) + + # + # TestCases using GET Requests + # + # GET + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_resource_get_resource_using_get(self, mock_HTTP_REQUEST_METHOD): + myresource = kresource.K2hr3Resource("token", + resource_path=self.resource_path) + self.assertEqual(myresource.r3token, "token") + """Get root path.""" + myresource.get(self.expand, self.service) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myresource)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/resource/{self.resource_path}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'expand': self.expand, + 'service': self.service + } + self.assertEqual(myresource.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myresource.headers, headers) + # 4. assert Request body + self.assertEqual(myresource.body, None) + + # + # TestCases using GET Requests + # + # GET + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_resource_get_resource_with_roletoken_using_get( + self, mock_HTTP_REQUEST_METHOD): + myresource = kresource.K2hr3Resource(roletoken="token", + resource_path=self.resource_path) + self.assertEqual(myresource.roletoken, "token") + """Get root path.""" + myresource.get_with_roletoken(self.data_type, self.keys, self.service) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myresource)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/resource/{self.resource_path}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'type': self.data_type, + 'keys': self.keys, + 'service': self.service + } + self.assertEqual(myresource.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'R=token' + } + self.assertEqual(myresource.headers, headers) + # 4. assert Request body + self.assertEqual(myresource.body, None) + + # + # TestCases using HEAD Requests + # + # HEAD + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/resource path or yrn full resource path?urlarg # noqa + # http(s)://API SERVER:PORT/v1/resource/yrn full resource path?urlarg + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_resource_validate_resource_using_head(self, + mock_HTTP_REQUEST_METHOD): + myresource = kresource.K2hr3Resource("token", + resource_path=self.resource_path) + """Get root path.""" + self.assertEqual(myresource.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myresource.headers, headers) + myresource.validate(self.data_type, self.keys, self.service) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(myresource)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/resource/{self.resource_path}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'type': self.data_type, + 'keys': self.keys, + 'service': self.service + } + self.assertEqual(myresource.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myresource.headers, headers) + # 4. assert Request body + self.assertEqual(myresource.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_resource_validate_resource_without_token_using_head( + self, mock_HTTP_REQUEST_METHOD): + myresource = kresource.K2hr3Resource(resource_path=self.resource_path) + """Get root path.""" + self.assertEqual(myresource.r3token, None) + headers = { + 'Content-Type': 'application/json', + } + self.assertEqual(myresource.headers, headers) + myresource.validate_with_notoken( + self.port, + self.cuk, + self.role, + self.data_type, + self.keys, + self.service) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(myresource)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/resource/{self.resource_path}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'port': self.port, + 'cuk': self.cuk, + 'role': self.role, + 'type': self.data_type, + 'keys': self.keys, + 'service': self.service + } + self.assertEqual(myresource.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + } + self.assertEqual(myresource.headers, headers) + # 4. assert Request body + self.assertEqual(myresource.body, None) + +# +# 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/tests/test_role.py b/src/tests/test_role.py new file mode 100644 index 0000000..b20daec --- /dev/null +++ b/src/tests/test_role.py @@ -0,0 +1,491 @@ +# -*- Coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import json +import logging +import unittest +from unittest.mock import patch +import urllib + +from k2hr3client import http as khttp +from k2hr3client import role as krole + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Role(unittest.TestCase): + """Tests the K2hr3Role class. + + Simple usage(this class only): + $ python -m unittest tests/test_role.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.token = "testtoken" + self.role_name = "testrole" + POLICY_PATH = "yrn:yahoo:::demo:policy:my_policy" + self.policies = [POLICY_PATH] + self.alias = [] + self.host = krole.K2hr3RoleHost( + 'localhost', '1024', 'testcuk', 'testextra', + 'testtag', '10.0.0.1', '172.24.4.1' + ) + self.clear_hostname = False + self.clear_ips = False + self.role_token_string = "teststring" + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3role_construct(self): + """Creates a K2hr3Resoiurce instance.""" + myrole = krole.K2hr3Role(self.token) + self.assertIsInstance(myrole, krole.K2hr3Role) + + def test_k2hr3role_repr(self): + """Represent a K2hr3Role instance.""" + myrole = krole.K2hr3Role(self.token) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(myrole), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_create_role_using_post(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=testtoken' + } + self.assertEqual(myrole.headers, headers) + myrole.create(self.role_name, self.policies, self.alias) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role") + # 2. assert URL params + self.assertEqual(myrole.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + import json + python_data = json.loads(krole._ROLE_API_CREATE_ROLE) + python_data['role']['name'] = self.role_name + python_data['role']['policies'] = self.policies + python_data['role']['alias'] = self.alias + body = json.dumps(python_data) + self.assertEqual(myrole.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_create_role_using_put(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=testtoken' + } + self.assertEqual(myrole.headers, headers) + myrole.create(self.role_name, self.policies, self.alias) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role") + # 2. assert URL params + s_s_urlparams = { + 'name': self.role_name, + 'policies': self.policies, + 'alias': self.alias + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_add_member_using_post(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=testtoken' + } + self.assertEqual(myrole.headers, headers) + myrole.add_member(self.role_name, self.host, self.clear_hostname, + self.clear_ips) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + self.assertEqual(myrole.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + import json + python_data = json.loads(krole._ROLE_API_ADD_MEMBER) + python_data['host']['host'] = self.host.host + python_data['host']['port'] = self.host.port + python_data['host']['cuk'] = self.host.cuk + python_data['host']['extra'] = self.host.extra + python_data['host']['tag'] = self.host.tag + python_data['host']['inboundip'] = self.host.inboundip + python_data['host']['outboundip'] = self.host.outboundip + body = json.dumps(python_data) + self.assertEqual(myrole.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_add_member_using_put(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + myrole.add_member(self.role_name, self.host, self.policies, self.alias) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + s_s_urlparams = { + 'host': self.host.host, + 'port': self.host.port, + 'cuk': self.host.cuk, + 'extra': self.host.extra, + 'tag': self.host.tag, + 'inboundip': self.host.inboundip, + 'outboundip': self.host.outboundip + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_add_member_with_roletoken_using_put( + self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token, krole.K2hr3TokenType.ROLE_TOKEN) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'R={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.add_member_with_roletoken( + self.role_name, self.host.port, self.host.cuk, self.host.extra, + self.host.tag, self.host.inboundip, self.host.outboundip) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + s_s_urlparams = { + 'port': self.host.port, + 'cuk': self.host.cuk, + 'extra': self.host.extra, + 'tag': self.host.tag, + 'inboundip': self.host.inboundip, + 'outboundip': self.host.outboundip + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_get(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.get(self.role_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + s_s_urlparams = { + 'expand': True + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_get_token_list(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.get_token_list(self.role_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/token/list/{self.role_name}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'expand': True + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_validate(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.validate_role(self.role_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_delete_role(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.delete(self.role_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_delete_member(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + myrole.delete_member(self.role_name, self.host.host, self.host.port, + self.host.cuk) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + s_s_urlparams = { + 'host': self.host.host, + 'port': self.host.port, + 'cuk': self.host.cuk, + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_delete_member_with_roletoken(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token, krole.K2hr3TokenType.NO_TOKEN) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json' + } + self.assertEqual(myrole.headers, headers) + + myrole.delete_member_wo_roletoken(self.host.cuk) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role") + # 2. assert URL params + s_s_urlparams = { + 'cuk': self.host.cuk, + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_delete_roletoken(self, mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token, krole.K2hr3TokenType.ROLE_TOKEN) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'R={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.delete_roletoken(self.role_name, self.host.port, self.host.cuk) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/{self.role_name}") + # 2. assert URL params + s_s_urlparams = { + 'port': self.host.port, + 'cuk': self.host.cuk, + } + self.assertEqual(myrole.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_role_delete_roletoken_with_usertoken(self, + mock_HTTP_REQUEST_METHOD): + myrole = krole.K2hr3Role(self.token) + """Get root path.""" + self.assertEqual(myrole.r3token, self.token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.token}' + } + self.assertEqual(myrole.headers, headers) + + myrole.delete_roletoken_with_string(self.role_token_string) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myrole)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/role/token/{self.role_token_string}") # noqa + # 2. assert URL params + s_s_urlparams = None + self.assertEqual(httpreq.urlparams, s_s_urlparams) + # 3. assert Request headers + self.assertEqual(myrole.headers, headers) + # 4. assert Request body + self.assertEqual(myrole.body, None) + +# +# 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/tests/test_service.py b/src/tests/test_service.py new file mode 100644 index 0000000..fb858f9 --- /dev/null +++ b/src/tests/test_service.py @@ -0,0 +1,418 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import json +import logging +import unittest +from unittest.mock import patch +import urllib.parse + +from k2hr3client import http as khttp +from k2hr3client import service as kservice + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Service(unittest.TestCase): + """Tests the K2hr3Service class. + + Simple usage(this class only): + $ python -m unittest tests/test_service.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.name = "testservice" + self.verify = '[{"name":"testresource2","type":"string","data":"testresource_str2","keys":{}}]' # noqa + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3role_construct(self): + """Creates a K2hr3Service instance.""" + myservice = kservice.K2hr3Service("token", self.name) + self.assertIsInstance(myservice, kservice.K2hr3Service) + + def test_k2hr3role_repr(self): + """Represent a K2hr3Service instance.""" + myservice = kservice.K2hr3Service("token", self.name) + self.assertRegex(repr(myservice), '') + + # + # TestCases using POST Requests + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_create_service_using_post(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token", service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + verify = '[{"name":"testresource2","type":"string","data":"testresource_str2","keys":{}}]' # noqa + myservice.create(verify_url=verify) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service") + # 2. assert URL params + self.assertEqual(myservice.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + python_data = json.loads(kservice._SERVICE_API_CREATE_SERVICE) + python_data['name'] = self.name + python_data['verify'] = self.verify + body = json.dumps(python_data) + self.assertEqual(myservice.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_add_member_using_post(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token", service_name=self.name) + """Get root path.""" + tenant = "mytenant" + clear_tenant = False + myservice.add_member(tenant=tenant, clear_tenant=clear_tenant) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service") + # 2. assert URL params + self.assertEqual(myservice.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + python_data = json.loads(kservice._SERVICE_API_ADD_MEMBER) + python_data['tenant'] = tenant + python_data['clear_tenant'] = False + body = json.dumps(python_data) + self.assertEqual(myservice.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_modify_using_post(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + verify = '[{"name":"testresource2","type":"string","data":"testresource_str2","keys":{}}]' # noqa + myservice.modify(verify) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(myservice)) + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service") + # 2. assert URL params + self.assertEqual(myservice.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + python_data = json.loads(kservice._SERVICE_API_MODIFY_VERIFY) + python_data['verify'] = self.verify + body = json.dumps(python_data) + self.assertEqual(myservice.body, body) + + # + # TestCases using PUT Requests + # + # Create SERVICE + # PUT http(s)://API SERVER:PORT/v1/service?name=service name&verify=verify url # noqa + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_create_service_using_put(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + verify = '[{"name":"testresource2","type":"string","data":"testresource_str2","keys":{}}]' # noqa + myservice.create(verify_url=verify) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service") + # 2. assert URL params + s_s_urlparams = { + 'name': self.name, + 'verify': verify + } + self.assertEqual(myservice.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + + # + # TestCases using PUT Requests + # + # Add MEMBER to SERVICE + # PUT http(s)://API SERVER:PORT/v1/service/service name?tenant=tenant name + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_add_member_using_put(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + tenant = "mytenant" + clear_tenant = False + myservice.add_member(tenant=tenant, clear_tenant=clear_tenant) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service") + # 2. assert URL params + s_s_urlparams = { + 'tenant': tenant, + 'clear_tenant': clear_tenant + } + self.assertEqual(myservice.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + + # + # TestCases using PUT Requests + # + # Modify VERIFY URL + # PUT http(s)://API SERVER:PORT/v1/service?name=service name&verify=verify url # noqa + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_modify_using_put(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + verify = '[{"name":"testresource2","type":"string","data":"testresource_str2","keys":{}}]' # noqa + myservice.modify(verify_url=verify) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service") + # 2. assert URL params + s_s_urlparams = { + 'verify': verify + } + self.assertEqual(myservice.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + + # + # TestCases using PUT Requests + # + # GET http(s)://API SERVER:PORT/v1/service/service name + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_get_service_using_get(self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + myservice.get() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service/{self.name}") # noqa + # 2. assert URL params + self.assertEqual(myservice.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + + # + # TestCases using PUT Requests + # + # HEAD http(s)://API SERVER:PORT/v1/service/service name http(s)://API SERVER:PORT/v1/service/service name?urlarg # noqa + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_validate_service_using_head(self, + mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + myservice.validate() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service/{self.name}") # noqa + # 2. assert URL params + self.assertEqual(myservice.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + + # + # TestCases using PUT Requests + # + # DELETE http(s)://API SERVER:PORT/v1/service/service name http(s)://API SERVER:PORT/v1/service/service name?urlarg # noqa + # + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_delete_service_using_delete(self, + mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + myservice.delete() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service/{self.name}") # noqa + # 2. assert URL params + self.assertEqual(myservice.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_service_delete_service_using_delete_tenant( + self, mock_HTTP_REQUEST_METHOD): + myservice = kservice.K2hr3Service("token",service_name=self.name) + """Get root path.""" + self.assertEqual(myservice.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + tenant = "mytenant" + myservice.delete_tenant(tenant) + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(myservice)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/service/{self.name}") # noqa + # 2. assert URL params + s_s_urlparams = { + 'tenant': tenant + } + self.assertEqual(myservice.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(myservice.headers, headers) + # 4. assert Request body + self.assertEqual(myservice.body, None) + +# +# 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/tests/test_tenant.py b/src/tests/test_tenant.py new file mode 100644 index 0000000..898aa93 --- /dev/null +++ b/src/tests/test_tenant.py @@ -0,0 +1,380 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" +import json +import logging +import unittest +from unittest.mock import patch +import urllib.parse + +from k2hr3client import http as khttp +from k2hr3client import tenant as ktenant + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Tenant(unittest.TestCase): + """Tests the K2hr3Tenant class. + + Simple usage(this class only): + $ python -m unittest tests/test_tenant.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.token = "token" + self.tenant_name = "testtenant" + self.tenant_id = "123" + self.users = ["demo"] + self.desc = "test description" + self.display = "Demone" + + def tearDown(self): + """Tears down a test case.""" + + def test_tenant_construct(self): + """Creates a K2hr3Tenant instance.""" + mytenant = ktenant.K2hr3Tenant(self.token) + self.assertIsInstance(mytenant, ktenant.K2hr3Tenant) + + def test_tenant_repr(self): + """Represent a K2hr3Tenant instance.""" + mytenant = ktenant.K2hr3Tenant(self.token) + self.assertRegex(repr(mytenant), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_create_tenant_using_post(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.create( + self.tenant_name, self.users, self.desc, self.display) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/tenant") + # 2. assert URL params + self.assertEqual(mytenant.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + import json + python_data = json.loads(ktenant._TENANT_API_CREATE_TENANT) + python_data['tenant']['name'] = self.tenant_name + python_data['tenant']['desc'] = self.desc + python_data['tenant']['display'] = self.display + python_data['tenant']['users'] = self.users + body = json.dumps(python_data) + self.assertEqual(mytenant.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_create_tenant_using_put(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.create( + self.tenant_name, self.users, self.desc, self.display) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/tenant") + # 2. assert URL params + s_s_urlparams = { + 'name': self.tenant_name, + 'users': self.users, + 'desc': self.desc, + 'display': self.display, + } + self.assertEqual(mytenant.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_modify_tenant_using_post(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.modify(self.tenant_name, self.tenant_id, self.users, + self.desc, self.display) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/tenant/{self.tenant_name}") + # 2. assert URL params + self.assertEqual(mytenant.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + import json + python_data = json.loads(ktenant._TENANT_API_UPDATE_TENANT) + python_data['tenant']['id'] = self.tenant_id + python_data['tenant']['desc'] = self.desc + python_data['tenant']['display'] = self.display + python_data['tenant']['users'] = self.users + body = json.dumps(python_data) + self.assertEqual(mytenant.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_modify_tenant_using_put(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.modify(self.tenant_name, self.tenant_id, self.users, + self.desc, self.display) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/tenant/{self.tenant_name}") + # 2. assert URL params + s_s_urlparams = { + 'id': self.tenant_id, + 'users': self.users, + 'desc': self.desc, + 'display': self.display, + } + self.assertEqual(mytenant.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_get_tenant_list(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.get_tenant_list() + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/tenant") + # 2. assert URL params + s_s_urlparams = { + 'expand': False + } + self.assertEqual(mytenant.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_get_tenant(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.get(self.tenant_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/tenant/{self.tenant_name}") + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_validate_using_head(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.validate(self.tenant_name) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/tenant/{self.tenant_name}") + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_delete_using_delete(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.delete(self.tenant_name, self.tenant_id) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/tenant") + # 2. assert URL params + s_s_urlparams = { + 'tenant': self.tenant_name, + 'id': self.tenant_id + } + self.assertEqual(mytenant.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_tenant_delete_user(self, mock_HTTP_REQUEST_METHOD): + mytenant = ktenant.K2hr3Tenant(self.token) + """Get root path.""" + self.assertEqual(mytenant.r3token, "token") + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + mytenant.delete_user(self.tenant_name, self.tenant_id) + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.DELETE(mytenant)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/tenant/{self.tenant_name}") + # 2. assert URL params + s_s_urlparams = { + 'id': self.tenant_id + } + self.assertEqual(mytenant.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': 'U=token' + } + self.assertEqual(mytenant.headers, headers) + # 4. assert Request body + self.assertEqual(mytenant.body, None) + +# +# 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/tests/test_token.py b/src/tests/test_token.py new file mode 100644 index 0000000..ca9b8c3 --- /dev/null +++ b/src/tests/test_token.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import json +import logging +import unittest +from unittest.mock import patch +import urllib.parse + +from k2hr3client import http as khttp +from k2hr3client import token as ktoken + +LOG = logging.getLogger(__name__) + + +class TestK2hr3token(unittest.TestCase): + """Tests the K2hr3token class. + + Simple usage(this class only): + $ python -m unittest tests/test_token.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.iaas_project = "my_project" + self.iaas_token = "my_iaas_token" + self.r3token = "my_r3_token" + self.role = "my_role" + self.expire = 0 + self.expand = True + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3token_construct(self): + """Creates a K2hr3Token instance.""" + mytoken = ktoken.K2hr3Token(self.iaas_project, self.iaas_token) + self.assertIsInstance(mytoken, ktoken.K2hr3Token) + + def test_k2hr3token_repr(self): + """Represent a K2hr3Token instance.""" + mytoken = ktoken.K2hr3Token(self.iaas_project, self.iaas_token) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(mytoken), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_token_create_using_post(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + mytoken = ktoken.K2hr3Token(self.iaas_project, self.iaas_token) + self.assertEqual(mytoken.iaas_project, self.iaas_project) + self.assertEqual(mytoken.iaas_token, self.iaas_token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.iaas_token}' + } + self.assertEqual(mytoken.headers, headers) + mytoken.create() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.POST(mytoken)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/user/tokens") + # 2. assert URL params + self.assertEqual(mytoken.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.iaas_token}' + } + self.assertEqual(mytoken.headers, headers) + # 4. assert Request body + python_data = json.loads(ktoken._TOKEN_API_CREATE_TOKEN_TYPE2) + python_data['auth']['tenantName'] = self.iaas_project + body = json.dumps(python_data) + self.assertEqual(mytoken.body, body) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_token_create_using_put(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + mytoken = ktoken.K2hr3Token(self.iaas_project, self.iaas_token) + self.assertEqual(mytoken.iaas_project, self.iaas_project) + self.assertEqual(mytoken.iaas_token, self.iaas_token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.iaas_token}' + } + self.assertEqual(mytoken.headers, headers) + mytoken.create() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.PUT(mytoken)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/user/tokens") + # 2. assert URL params + s_s_urlparams = {'tenantname': self.iaas_project} + self.assertEqual(mytoken.urlparams, json.dumps(s_s_urlparams)) + s_urlparams = urllib.parse.urlencode(s_s_urlparams) + self.assertEqual(httpreq.urlparams, f"{s_urlparams}") + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.iaas_token}' + } + self.assertEqual(mytoken.headers, headers) + # 4. assert Request body + self.assertEqual(mytoken.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_token_show_credential_details_using_get( + self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + mytoken = ktoken.K2hr3Token(self.iaas_project, self.iaas_token) + self.assertEqual(mytoken.iaas_project, self.iaas_project) + self.assertEqual(mytoken.iaas_token, self.iaas_token) + mytoken.show() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(mytoken)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/user/tokens") + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.iaas_token}' + } + self.assertEqual(mytoken.headers, headers) + # 4. assert Request body + self.assertEqual(mytoken.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_validate(self, mock_HTTP_REQUEST_METHOD): + mytoken = ktoken.K2hr3Token(self.iaas_project, self.iaas_token) + """Get root path.""" + self.assertEqual(mytoken.iaas_project, self.iaas_project) + self.assertEqual(mytoken.iaas_token, self.iaas_token) + headers = { + 'Content-Type': 'application/json', + 'x-auth-token': f'U={self.iaas_token}' + } + self.assertEqual(mytoken.headers, headers) + + mytoken.validate() + + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.HEAD(mytoken)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1/user/tokens") + # 2. assert URL params + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + self.assertEqual(mytoken.headers, headers) + # 4. assert Request body + self.assertEqual(mytoken.body, None) + + +# +# 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/tests/test_userdata.py b/src/tests/test_userdata.py new file mode 100644 index 0000000..8877594 --- /dev/null +++ b/src/tests/test_userdata.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import logging +import unittest +from unittest.mock import patch + +from k2hr3client import http as khttp +from k2hr3client import userdata as kuserdata + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Userdata(unittest.TestCase): + """Tests the K2hr3ApiResponse class. + + Simple usage(this class only): + $ python -m unittest tests/test_userdata.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.userdatapath = "example_userdata_path" + + def tearDown(self): + """Tears down a test case.""" + + def test_userdata_construct(self): + """Creates a K2hr3ApiResponse instance.""" + myuserdata = kuserdata.K2hr3Userdata(userdatapath=self.userdatapath) + self.assertIsInstance(myuserdata, kuserdata.K2hr3Userdata) + + def test_userdata_repr(self): + """Represent a K2hr3ApiResponse instance.""" + myuserdata = kuserdata.K2hr3Userdata(self.userdatapath) + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(myuserdata), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_userdata_provides_userdata_script_using_get( + self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myuserdata = kuserdata.K2hr3Userdata(self.userdatapath) + myuserdata.provides_userdata_script() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myuserdata)) + + # 1. assert URL + self.assertEqual(httpreq.url, + f"{self.base_url}/v1/userdata/{self.userdatapath}") + # 2. assert URL params + self.assertEqual(myuserdata.urlparams, None) + self.assertEqual(httpreq.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/octet-stream', + 'User-Agent': 'Cloud-Init 0.7.9', + } + self.assertEqual(myuserdata.headers, headers) + # 4. assert Request body + self.assertEqual(myuserdata.body, None) + +# +# 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/tests/test_version.py b/src/tests/test_version.py new file mode 100644 index 0000000..fd40fa4 --- /dev/null +++ b/src/tests/test_version.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# +# K2HDKC DBaaS based on Trove +# +# Copyright 2020 Yahoo Japan Corporation +# Copyright 2024 LY Corporation +# +# K2HDKC DBaaS is a Database as a Service compatible with Trove which +# is DBaaS for OpenStack. +# Using K2HR3 as backend and incorporating it into Trove to provide +# DBaaS functionality. K2HDKC, K2HR3, CHMPX and K2HASH are components +# provided as AntPickax. +# +# For the full copyright and license information, please view +# the license file that was distributed with this source code. +# +# AUTHOR: Hirotaka Wakabayashi +# CREATE: Mon Sep 14 2020 +# REVISION: +# +"""Test Package for K2hr3 Python Client.""" + +import logging +import unittest +from unittest.mock import patch + +from k2hr3client import http as khttp +from k2hr3client import version as kversion + +LOG = logging.getLogger(__name__) + + +class TestK2hr3Version(unittest.TestCase): + """Tests the K2hr3ApiResponse class. + + Simple usage(this class only): + $ python -m unittest tests/test_version.py + + Simple usage(all): + $ python -m unittest tests + """ + def setUp(self): + """Sets up a test case.""" + self.base_url = "http://127.0.0.1:18080" + self.name = "" + + def tearDown(self): + """Tears down a test case.""" + + def test_k2hr3version_construct(self): + """Creates a K2hr3ApiResponse instance.""" + myversion = kversion.K2hr3Version() + self.assertIsInstance(myversion, kversion.K2hr3Version) + + def test_k2hr3version_repr(self): + """Represent a K2hr3ApiResponse instance.""" + myversion = kversion.K2hr3Version() + # Note: The order of _error and _code is unknown! + self.assertRegex(repr(myversion), '') + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_k2hr3version_root_get_ok(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myversion = kversion.K2hr3Version() + self.assertEqual(myversion.name, "") + myversion.get() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myversion)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/") + # 2. assert URL params + self.assertEqual(myversion.urlparams, None) + # 3. assert Request header + headers = { + 'Content-Type': 'application/json', + } + self.assertEqual(myversion.headers, headers) + # 4. assert Request body + self.assertEqual(myversion.body, None) + + @patch('k2hr3client.http.K2hr3Http._HTTP_REQUEST_METHOD') + def test_k2hr3version_root_get_v1_ok(self, mock_HTTP_REQUEST_METHOD): + """Get root path.""" + myversion = kversion.K2hr3Version("v1") + self.assertEqual(myversion.name, "v1") + myversion.get() + httpreq = khttp.K2hr3Http(self.base_url) + self.assertTrue(httpreq.GET(myversion)) + + # 1. assert URL + self.assertEqual(httpreq.url, f"{self.base_url}/v1") + # 2. assert URL params + self.assertEqual(myversion.urlparams, None) + # 3. assert Request headers + headers = { + 'Content-Type': 'application/json', + } + self.assertEqual(myversion.headers, headers) + # 4. assert Request body + self.assertEqual(myversion.body, None) + +# +# 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 +#