diff --git a/.coveragerc b/.coveragerc index 71acd05..950aeb3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,7 +5,7 @@ source = sqlite_rx concurrency = thread multiprocessing -debug = +debug = multiproc omit = /*/tests/* @@ -14,8 +14,8 @@ omit = /home/travis/virtualenv/* ./sqlite_rx/run_client.py ./sqlite_rx/start_server.py -# plugins = +# plugins = # billiard_coverage_plugin [html] -directory = htmlcov \ No newline at end of file +directory = htmlcov diff --git a/.dockerignore b/.dockerignore index bfa440f..6a39bc9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,4 +18,3 @@ start_server.py run_client.py curve_client.py .github - diff --git a/.github/workflows/sqlite_build.yaml b/.github/workflows/sqlite_build.yaml index 8153b3d..6d3c853 100644 --- a/.github/workflows/sqlite_build.yaml +++ b/.github/workflows/sqlite_build.yaml @@ -9,14 +9,14 @@ jobs: fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.8', 'pypy-3.9', 'pypy3.10'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', 'pypy3.10', 'pypy3.11'] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -25,11 +25,9 @@ jobs: python3 -m pip install --upgrade pip setuptools pip install pytest coverage pip install -e . - pip install coverage - + - name: Run Unittests run: | coverage run -m pytest --verbose sqlite_rx/tests coverage combine coverage report -m - diff --git a/.gitignore b/.gitignore index 31c0139..a1a065d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,3 @@ start_server.py run_client.py curve_client.py .pypy3venv/ - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3f3609a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + args: ['--maxkb=2000'] +- repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.4.2 + hooks: + - id: black +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.291 + hooks: + - id: ruff + args: ["check", "--select", "I", "--fix"] diff --git a/Dockerfile b/Dockerfile index ea3cac2..6754ef6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,25 @@ -FROM python:3.10.5-slim as base +FROM python:3-slim AS base +ARG USERNAME=sqliteuser +ARG UID=1001 +ARG GID=1001 +ARG HOME_DIR=/home/${USERNAME} + +# Upgrade OS packages +RUN set -ex \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Create the group and user +RUN groupadd --gid ${GID} ${USERNAME} \ + && useradd -m -u ${UID} -g ${GID} -d ${HOME_DIR} -s /bin/bash ${USERNAME} + +RUN chown ${USER_NAME}:${USERNAME} ${HOME_DIR} + +FROM base AS builder COPY . /sqlite_rx WORKDIR /svc @@ -10,10 +30,13 @@ RUN pip install wheel && pip wheel --wheel-dir=/svc/wheels /sqlite_rx[cli] RUN rm -rf /sqlite_rx -FROM python:3.10.5-slim +FROM base -COPY --from=base /svc /svc +COPY --from=builder /svc /svc WORKDIR /svc RUN pip install --upgrade pip -RUN pip install --no-index /svc/wheels/*.whl \ No newline at end of file +RUN pip install --no-index /svc/wheels/*.whl + +USER ${USERNAME} +WORKDIR ${HOME_DIR} diff --git a/README.md b/README.md index 4df2079..59a5da4 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,22 @@ -# sqlite_rx +# sqlite_rx [![PyPI version](https://badge.fury.io/py/sqlite-rx.svg)](https://pypi.python.org/pypi/sqlite-rx) [![sqlite-rx](https://github.com/aosingh/sqlite_rx/actions/workflows/sqlite_build.yaml/badge.svg)](https://github.com/aosingh/sqlite_rx/actions) [![Downloads](https://pepy.tech/badge/sqlite-rx)](https://pepy.tech/project/sqlite-rx) -[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/) -[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)]((https://www.python.org/downloads/release/python-390/)) [![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-3100/) [![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3110/) [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/) +[![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3130/) +[![Python 3.14](https://img.shields.io/badge/python-3.14-blue.svg)](https://www.python.org/downloads/release/python-3140/) - -[![PyPy3.8](https://img.shields.io/badge/python-PyPy3.8-blue.svg)](https://www.pypy.org/download.html) -[![PyPy3.9](https://img.shields.io/badge/python-PyPy3.9-blue.svg)](https://www.pypy.org/download.html) +[![PyPy3.10](https://img.shields.io/badge/python-PyPy3.10-blue.svg)](https://www.pypy.org/download.html) +[![PyPy3.11](https://img.shields.io/badge/python-PyPy3.11-blue.svg)](https://www.pypy.org/download.html) #### For documentation, usage and examples refer [https://aosingh.github.io/sqlite_rx/](https://aosingh.github.io/sqlite_rx/) # Introduction -[SQLite](https://www.sqlite.org/index.html) is a lightweight database written in C. +[SQLite](https://www.sqlite.org/index.html) is a lightweight database written in C. Python has in-built support to interact with the database (locally) which is either stored on disk or in memory. With `sqlite_rx`, clients should be able to communicate with an `SQLiteServer` in a fast, simple and secure manner and execute queries remotely. @@ -47,10 +46,10 @@ from sqlite_rx.server import SQLiteServer def main(): - # database is a path-like object giving the pathname - # of the database file to be opened. - - # You can use ":memory:" to open a database connection to a database + # database is a path-like object giving the pathname + # of the database file to be opened. + + # You can use ":memory:" to open a database connection to a database # that resides in RAM instead of on disk server = SQLiteServer(database=":memory:", @@ -85,4 +84,3 @@ with client: {'error': None, 'items': []} ``` - diff --git a/docker-compose.yaml b/docker-compose.yaml index deda4b2..c376380 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,4 +10,4 @@ services: - data:/data volumes: - data: {} \ No newline at end of file + data: {} diff --git a/docker_examples/docker-compose-curvezmq.yaml b/docker_examples/docker-compose-curvezmq.yaml index 171194f..4529051 100644 --- a/docker_examples/docker-compose-curvezmq.yaml +++ b/docker_examples/docker-compose-curvezmq.yaml @@ -11,4 +11,4 @@ services: - /Users/as/.curve:/root/.curve volumes: - data: {} \ No newline at end of file + data: {} diff --git a/docker_examples/docker-compose-ondisk.yaml b/docker_examples/docker-compose-ondisk.yaml index 1762cc9..d444ee5 100644 --- a/docker_examples/docker-compose-ondisk.yaml +++ b/docker_examples/docker-compose-ondisk.yaml @@ -10,4 +10,4 @@ services: - data:/data volumes: - data: {} \ No newline at end of file + data: {} diff --git a/docker_examples/docker-compose-pypy.yaml b/docker_examples/docker-compose-pypy.yaml index 898fef7..1596518 100644 --- a/docker_examples/docker-compose-pypy.yaml +++ b/docker_examples/docker-compose-pypy.yaml @@ -10,4 +10,4 @@ services: - data:/data volumes: - data: {} \ No newline at end of file + data: {} diff --git a/docker_examples/docker-compose-zapcurvezmq.yaml b/docker_examples/docker-compose-zapcurvezmq.yaml index 0359c2c..4094d42 100644 --- a/docker_examples/docker-compose-zapcurvezmq.yaml +++ b/docker_examples/docker-compose-zapcurvezmq.yaml @@ -11,4 +11,4 @@ services: - /Users/as/.curve:/root/.curve volumes: - data: {} \ No newline at end of file + data: {} diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile index 82cac71..5aa02a1 100644 --- a/dockerfiles/Dockerfile +++ b/dockerfiles/Dockerfile @@ -17,4 +17,3 @@ WORKDIR /svc RUN pip install --upgrade pip RUN pip install --no-index --find-links=/svc/wheels sqlite_rx - diff --git a/dockerfiles/Dockerfile_pypy3 b/dockerfiles/Dockerfile_pypy3 index 608d138..6a4bddc 100644 --- a/dockerfiles/Dockerfile_pypy3 +++ b/dockerfiles/Dockerfile_pypy3 @@ -8,4 +8,4 @@ COPY . /sqlite_rx WORKDIR /sqlite_rx RUN pypy3 -m pip install -r requirements.txt -RUN pypy3 setup.py install \ No newline at end of file +RUN pypy3 setup.py install diff --git a/pyproject.toml b/pyproject.toml index 41ad39b..2c092ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,101 @@ [build-system] requires = ['setuptools >= 40.8.0', 'wheel'] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "setuptools.build_meta" + +[project] +name = "sqlite_rx" +dynamic = ["version"] +description = "Python SQLite Client and Server" +readme = {file = "README.md", content-type = "text/markdown"} +requires-python = ">=3.10" +authors = [ + {name="Abhishek Singh", email="abhishek.singh20141@gmail.com"} +] +maintainers =[ + {name="Abhishek Singh", email="abhishek.singh20141@gmail.com"} +] +keywords = [ + "sqlite", + "server", + "client", + "secure" +] +license = "MIT" +license-files = ["LICENSE"] +classifiers = [ + "Topic :: Database :: Database Engines/Servers", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Education", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Operating System :: POSIX :: Linux", + "Operating System :: Unix", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS" +] +dependencies = [ + "msgpack", + "tornado", + "pyzmq", + "billiard" +] + +[project.optional-dependencies] +test = [ + "coverage", + "pytest", +] +cli = [ + "click", + "rich", + "pygments", +] + +[project.scripts] +curve-keygen = "sqlite_rx.cli.keygen:main" +sqlite-server = "sqlite_rx.cli.server:main" +sqlite-client = "sqlite_rx.cli.client:main" + + +[project.urls] +Homepage = "https://github.com/aosingh/sqlite_rx" +Repository = "https://github.com/aosingh/sqlite_rx" +Issues = "https://github.com/aosingh/sqlite_rx/issues" +Documentation = "https://aosingh.github.io/sqlite_rx/" +CI = "https://github.com/aosingh/sqlite_rx/actions" + +[tool.setuptools] +packages = ["sqlite_rx", "sqlite_rx.cli"] + +[tool.setuptools.dynamic] +version = { attr = "sqlite_rx.version.__version__" } + +[tool.black] +line-length = 79 +target-version = ["py310", "py311", "py312", "py313"] +required-version = 24 + +[tool.ruff] +line-length = 79 +target-version = "py310" +per-file-ignores = { "__init__.py" = ["F401"] } + +[tool.pytest.ini_options] +minversion = "8.3.0" +testpaths = [ + "tests" +] + +[tools.coverage.run] +branch = true +concurrency = "multiprocessing" +parallel = true +source = "sqlite_rx" diff --git a/requirements.txt b/requirements.txt index 759e0ec..8246422 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -billiard==4.2.1 -click==8.1.7 -msgpack==1.1.0 -pyzmq==26.2.0 -tornado==6.4.2 +billiard +click +msgpack +pyzmq +tornado diff --git a/requirements_dev.txt b/requirements_dev.txt index 65512fa..ad20f1a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,3 +5,4 @@ wheel pytest setuptools twine +pre-commit diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c1c9de3..0000000 --- a/setup.cfg +++ /dev/null @@ -1,79 +0,0 @@ -[metadata] -name = sqlite_rx -version = 1.2.2 -description = Python SQLite Client and Server -long_description = file: README.md -long_description_content_type = text/markdown -keywords = sqlite, client, server, fast, secure -url = https://aosingh.github.io/sqlite_rx/ -license = MIT License -classifiers = - Topic :: Database :: Database Engines/Servers - Development Status :: 5 - Production/Stable - Intended Audience :: Education - Intended Audience :: Developers - Intended Audience :: Science/Research - Intended Audience :: System Administrators - License :: OSI Approved :: MIT License - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Programming Language :: Python :: 3.13 - Operating System :: POSIX :: Linux - Operating System :: Unix - Operating System :: Microsoft :: Windows - Operating System :: MacOS -author = Abhishek Singh -author_email = abhishek.singh20141@gmail.com -maintainer = Abhishek Singh -maintainer_email = abhishek.singh20141@gmail.com -project_urls = - Documentation = https://aosingh.github.io/sqlite_rx/ - Source = https://github.com/aosingh/sqlite_rx - Bug Tracker = https://github.com/aosingh/sqlite_rx/issues - CI = https://github.com/aosingh/sqlite_rx/actions - Release Notes = https://github.com/aosingh/sqlite_rx/releases - License = https://github.com/aosingh/sqlite_rx/blob/master/LICENSE - - -[options] -zip_safe = False -packages = find: -package_dir = - sqlite_rx=sqlite_rx -include_package_data = True -scripts = - bin/curve-keygen -install_requires = - billiard==4.2.1 - msgpack==1.1.0 - pyzmq==26.2.0 - tornado==6.4.2 -test_require = - pytest - coverage -python_requires = >=3.8 - -[options.packages.find] -where = sqlite_rx -exclude = tests - -[options.entry_points] -console_scripts = - sqlite-server = sqlite_rx.cli.server:main - sqlite-client = sqlite_rx.cli.client:main - -[options.extras_require] -cli = - click==8.1.7 - rich==13.9.3 - pygments==2.18.0 - -[coverage:run] -branch = True -concurrency = multiprocessing -parallel = True -source = sqlite_rx diff --git a/setup.py b/setup.py deleted file mode 100644 index 0030574..0000000 --- a/setup.py +++ /dev/null @@ -1,99 +0,0 @@ -import sys -from os import path - -from setuptools import find_packages, setup - - -if sys.version_info < (3, 8): - print("Error: sqlite-rx does not support this version of Python.") - print("Please upgrade to Python 3.8 or higher.") - sys.exit(1) - -this_directory = path.abspath(path.dirname(__file__)) - -with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: - long_description = f.read() - -VERSION = '1.2.2' -DISTNAME = 'sqlite_rx' -LICENSE = 'MIT License' -AUTHOR = 'Abhishek Singh' -MAINTAINER = 'Abhishek Singh' -MAINTAINER_EMAIL = 'abhishek.singh20141@gmail.com' -DESCRIPTION = 'Python SQLite Client and Server' -URL = 'https://github.com/aosingh/sqlite_rx' - -PACKAGES = ['sqlite_rx'] - -INSTALL_REQUIRES = ['msgpack==1.1.0', - 'pyzmq==26.2.0', - 'tornado==6.4.2', - 'billiard==4.2.1'] - -CLI_REQUIRES = ['click==8.1.7', 'rich==13.9.3', 'pygments==2.18.0'] - -TEST_REQUIRE = ['pytest', - 'coverage'] - -classifiers = [ - 'Topic :: Database :: Database Engines/Servers', - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Education', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Operating System :: POSIX :: Linux', - 'Operating System :: Unix', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS' -] -keywords = 'sqlite client server fast secure' - -project_urls = {"Documentation": "https://aosingh.github.io/sqlite_rx/", - "Source": "https://github.com/aosingh/sqlite_rx", - "Bug Tracker": "https://github.com/aosingh/sqlite_rx/issues", - "CI": "https://github.com/aosingh/sqlite_rx/actions", - "Release Notes": "https://github.com/aosingh/sqlite_rx/releases", - "License": "https://github.com/aosingh/sqlite_rx/blob/master/LICENSE"} - - -setup( - name=DISTNAME, - long_description=long_description, - long_description_content_type='text/markdown', - author=AUTHOR, - author_email=MAINTAINER_EMAIL, - maintainer=MAINTAINER, - maintainer_email=MAINTAINER_EMAIL, - description=DESCRIPTION, - license=LICENSE, - url=URL, - project_urls=project_urls, - version=VERSION, - scripts=['bin/curve-keygen'], - entry_points={ - 'console_scripts': [ - 'sqlite-server=sqlite_rx.cli.server:main', - 'sqlite-client=sqlite_rx.cli.client:main' - ] - }, - extras_require={ - 'cli': CLI_REQUIRES - }, - packages=find_packages(exclude=("tests",)), - package_dir={'sqlite_rx': 'sqlite_rx'}, - install_requires=INSTALL_REQUIRES, - test_require=TEST_REQUIRE, - include_package_data=True, - classifiers=classifiers, - keywords=keywords, - python_requires='>=3.8' -) diff --git a/sqlite_rx/__init__.py b/sqlite_rx/__init__.py index 940dded..2adae1b 100644 --- a/sqlite_rx/__init__.py +++ b/sqlite_rx/__init__.py @@ -5,39 +5,39 @@ def get_default_logger_settings(level: str = "DEBUG"): return { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'standard': { - '()': 'logging.Formatter', - 'format': '%(asctime)s - %(levelname)s - [%(name)s:%(funcName)s:%(lineno)d] %(message)s' + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "()": "logging.Formatter", + "format": "%(asctime)s - %(levelname)s - [%(name)s:%(funcName)s:%(lineno)d] %(message)s", }, }, - 'handlers': { - 'default': { - 'level': level, - 'formatter': 'standard', - 'class': 'logging.StreamHandler', - 'stream': 'ext://sys.stdout', # Default is stderr + "handlers": { + "default": { + "level": level, + "formatter": "standard", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", # Default is stderr }, }, - 'loggers': { - '': { # root logger - 'handlers': ['default'], - 'level': level, - 'propagate': False + "loggers": { + "": { # root logger + "handlers": ["default"], + "level": level, + "propagate": False, }, - 'sqlite_rx': { - 'handlers': ['default'], - 'level': level, - 'propagate': False + "sqlite_rx": { + "handlers": ["default"], + "level": level, + "propagate": False, }, - '__main__': { # if __name__ == '__main__' - 'handlers': ['default'], - 'level': level, - 'propagate': False + "__main__": { # if __name__ == '__main__' + "handlers": ["default"], + "level": level, + "propagate": False, }, - } + }, } diff --git a/sqlite_rx/auth.py b/sqlite_rx/auth.py index be57537..40d6977 100644 --- a/sqlite_rx/auth.py +++ b/sqlite_rx/auth.py @@ -6,12 +6,12 @@ import zmq import zmq.auth -from sqlite_rx.exception import SQLiteRxAuthConfigError +from sqlite_rx.exception import SQLiteRxAuthConfigError LOG = logging.getLogger(__name__) -__all__ = ['Authorizer', 'KeyGenerator', 'KeyMonkey', 'DEFAULT_AUTH_CONFIG'] +__all__ = ["Authorizer", "KeyGenerator", "KeyMonkey", "DEFAULT_AUTH_CONFIG"] # Default Authorization Config DEFAULT_AUTH_CONFIG = { @@ -35,7 +35,6 @@ sqlite3.SQLITE_REINDEX, sqlite3.SQLITE_ANALYZE, }, - sqlite3.SQLITE_DENY: { sqlite3.SQLITE_DELETE, sqlite3.SQLITE_DROP_INDEX, @@ -47,11 +46,7 @@ sqlite3.SQLITE_DROP_TRIGGER, sqlite3.SQLITE_DROP_VIEW, }, - - sqlite3.SQLITE_IGNORE: { - sqlite3.SQLITE_PRAGMA - } - + sqlite3.SQLITE_IGNORE: {sqlite3.SQLITE_PRAGMA}, } @@ -82,11 +77,13 @@ class during server startup. This class represents a callable passed to the sqli self.valid_return_values = { sqlite3.SQLITE_IGNORE, sqlite3.SQLITE_OK, - sqlite3.SQLITE_DENY} + sqlite3.SQLITE_DENY, + } if any(k not in self.valid_return_values for k in self.config.keys()): raise SQLiteRxAuthConfigError( "Allowed return values are: " - "sqlite3.SQLITE_OK(0), sqlite3.SQLITE_DENY(1), sqlite3.SQLITE_IGNORE(2)") + "sqlite3.SQLITE_OK(0), sqlite3.SQLITE_DENY(1), sqlite3.SQLITE_IGNORE(2)" + ) def __call__(self, action: int, *args, **kwargs) -> int: """Returns the permission for the passed ``action`` @@ -108,9 +105,7 @@ def __call__(self, action: int, *args, **kwargs) -> int: class KeyGenerator: - def __init__(self, - key_id: str = "id_curve", - destination_dir: str = None): + def __init__(self, key_id: str = "id_curve", destination_dir: str = None): """Generates curve public and private keys required for encryption. This class should not be used by users to generate keys. Use the script ``curve-keygen`` @@ -129,16 +124,20 @@ def __init__(self, """ self.my_id = key_id - self.curvedir = destination_dir if destination_dir else os.path.join( - os.path.expanduser("~"), ".curve") + self.curvedir = ( + destination_dir + if destination_dir + else os.path.join(os.path.expanduser("~"), ".curve") + ) self.public_key = os.path.join( - self.curvedir, "{}.key".format(self.my_id)) + self.curvedir, "{}.key".format(self.my_id) + ) self.private_key = os.path.join( - self.curvedir, - "{}.key_secret".format( - self.my_id)) + self.curvedir, "{}.key_secret".format(self.my_id) + ) self.authorized_clients_dir = os.path.join( - self.curvedir, "authorized_clients") + self.curvedir, "authorized_clients" + ) def generate(self): """ @@ -166,7 +165,8 @@ def generate(self): os.chmod(self.authorized_clients_dir, 0o700) server_public_file, server_secret_file = zmq.auth.create_certificates( - self.curvedir, self.my_id) + self.curvedir, self.my_id + ) LOG.info(server_public_file) LOG.info(server_secret_file) os.chmod(self.public_key, 0o600) @@ -177,9 +177,7 @@ def generate(self): class KeyMonkey: - def __init__(self, - key_id: str = "id_curve", - destination_dir: str = None): + def __init__(self, key_id: str = "id_curve", destination_dir: str = None): """Setup secure client or server using the CurveZMQ This class expects the following keys depending on whether you want to setup a secure server @@ -199,14 +197,22 @@ def __init__(self, """ self.my_id = key_id - self.curvedir = destination_dir if destination_dir else os.path.join(os.path.expanduser("~"), ".curve") - self.public_key = os.path.join(self.curvedir, "{}.key".format(self.my_id)) - self.private_key = os.path.join(self.curvedir, "{}.key_secret".format(self.my_id)) - self.authorized_clients_dir = os.path.join(self.curvedir, "authorized_clients") - - def setup_secure_server(self, - server, - bind_address: str): + self.curvedir = ( + destination_dir + if destination_dir + else os.path.join(os.path.expanduser("~"), ".curve") + ) + self.public_key = os.path.join( + self.curvedir, "{}.key".format(self.my_id) + ) + self.private_key = os.path.join( + self.curvedir, "{}.key_secret".format(self.my_id) + ) + self.authorized_clients_dir = os.path.join( + self.curvedir, "authorized_clients" + ) + + def setup_secure_server(self, server, bind_address: str): """ Use this method to setup a secure server. @@ -219,21 +225,28 @@ def setup_secure_server(self, """ try: - server.curve_publickey, server.curve_secretkey = zmq.auth.load_certificate(self.private_key) + server.curve_publickey, server.curve_secretkey = ( + zmq.auth.load_certificate(self.private_key) + ) server.curve_server = True - LOG.info("Secure setup completed using on %s using curve key %s", bind_address, self.my_id) + LOG.info( + "Secure setup completed using on %s using curve key %s", + bind_address, + self.my_id, + ) return server except IOError: - LOG.exception("Couldn't load the private key: %s", self.private_key) + LOG.exception( + "Couldn't load the private key: %s", self.private_key + ) raise except Exception: LOG.exception("Exception while setting up CURVECP") raise - def setup_secure_client(self, - client, - connect_address: str, - servername: str): + def setup_secure_client( + self, client, connect_address: str, servername: str + ): """ Use this method to setup a secure client. Clients also need the servername to look for server's public key @@ -251,19 +264,31 @@ def setup_secure_client(self, """ try: - client.curve_publickey, client.curve_secretkey = zmq.auth.load_certificate(self.private_key) + client.curve_publickey, client.curve_secretkey = ( + zmq.auth.load_certificate(self.private_key) + ) except IOError: - LOG.exception("Couldn't load the client private key: %s", self.private_key) + LOG.exception( + "Couldn't load the client private key: %s", self.private_key + ) raise else: # Clients need server's public key for encryption try: - client.curve_serverkey, _ = zmq.auth.load_certificate(os.path.join(self.curvedir, f"{servername}.key")) + client.curve_serverkey, _ = zmq.auth.load_certificate( + os.path.join(self.curvedir, f"{servername}.key") + ) except IOError: LOG.exception( - "Couldn't load the server public key %s ", os.path.join(self.curvedir, f"{servername}.key")) + "Couldn't load the server public key %s ", + os.path.join(self.curvedir, f"{servername}.key"), + ) raise else: - LOG.info("Client connecting to %s (key %s) using curve key '%s'.", - connect_address, servername, self.my_id) + LOG.info( + "Client connecting to %s (key %s) using curve key '%s'.", + connect_address, + servername, + self.my_id, + ) return client diff --git a/sqlite_rx/backup.py b/sqlite_rx/backup.py index e21d9ac..56f114c 100644 --- a/sqlite_rx/backup.py +++ b/sqlite_rx/backup.py @@ -1,9 +1,8 @@ import logging.config -import threading import platform import sqlite3 import sys - +import threading from typing import Any LOG = logging.getLogger(__name__) @@ -11,10 +10,10 @@ def is_backup_supported(): - if sys.platform.startswith('win'): + if sys.platform.startswith("win"): return False - if platform.python_implementation().lower() == 'pypy': + if platform.python_implementation().lower() == "pypy": return False return True @@ -31,7 +30,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any: def progress(status, remaining, total): copied = total - remaining - LOG.info('Copied %s of %s pages', copied, total) + LOG.info("Copied %s of %s pages", copied, total) source = sqlite3.connect(self.src) backup = sqlite3.connect(self.target) @@ -39,7 +38,9 @@ def progress(status, remaining, total): with backup: source.backup(backup, pages=self.pages, progress=progress) - LOG.info("Finished Backup: Source %s , Target %s ", self.src, self.target) + LOG.info( + "Finished Backup: Source %s , Target %s ", self.src, self.target + ) class RecurringTimer(threading.Timer): diff --git a/sqlite_rx/cli/client.py b/sqlite_rx/cli/client.py index c872935..5a67a70 100644 --- a/sqlite_rx/cli/client.py +++ b/sqlite_rx/cli/client.py @@ -1,5 +1,4 @@ import logging.config - import typing import click @@ -8,13 +7,11 @@ import rich.progress import rich.syntax import rich.table - from rich import print_json -from sqlite_rx import get_default_logger_settings, __version__ +from sqlite_rx import __version__, get_default_logger_settings from sqlite_rx.client import SQLiteClient - LOG = logging.getLogger(__name__) @@ -22,37 +19,55 @@ def print_help(): console = rich.console.Console() console.print("[bold]sqlite-client :paw_prints:", justify="center") console.print() - console.print("A simple, fast and secure client for the SQLite database.", justify="center") + console.print( + "A simple, fast and secure client for the SQLite database.", + justify="center", + ) console.print() - console.print("Usage: [bold]sqlite-client [OPTIONS] exec[/bold] [cyan] [/cyan] ", justify="left") + console.print( + "Usage: [bold]sqlite-client [OPTIONS] exec[/bold] [cyan] [/cyan] ", + justify="left", + ) console.print() table = rich.table.Table.grid(padding=1, pad_edge=True) table.add_column("Parameter", no_wrap=True, justify="left", style="bold") table.add_column("Description") - table.add_row("-l, --log-level [bold][cyan]LOG_LEVEL", - "CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET\n" - "Default level is [bold][cyan]CRITICAL") - table.add_row('-a, --connect-address [bold][cyan]tcp://:', - "The host and port on which to connect\n" - "Default value is tcp://0.0.0.0:5000") - table.add_row("--zap/--no-zap", - "Enable/Disable ZAP Authentication\n" - "Default value is [bold][cyan]False") - table.add_row('--curvezmq/--no-curvezmq', - "Enable/Disable CurveZMQ\n" - "Default value is [bold][cyan]False") - table.add_row("-d --curve-dir [cyan]PATH", - "Path to the Curve key directory\n" - "Default value is [italic][bold][cyan]~/.curve") - table.add_row("-c --client-key-id [cyan]CURVE KEY ID", - "Client's Curve Key ID") + table.add_row( + "-l, --log-level [bold][cyan]LOG_LEVEL", + "CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET\n" + "Default level is [bold][cyan]CRITICAL", + ) + table.add_row( + "-a, --connect-address [bold][cyan]tcp://:", + "The host and port on which to connect\n" + "Default value is tcp://0.0.0.0:5000", + ) + table.add_row( + "--zap/--no-zap", + "Enable/Disable ZAP Authentication\n" + "Default value is [bold][cyan]False", + ) + table.add_row( + "--curvezmq/--no-curvezmq", + "Enable/Disable CurveZMQ\n" "Default value is [bold][cyan]False", + ) + table.add_row( + "-d --curve-dir [cyan]PATH", + "Path to the Curve key directory\n" + "Default value is [italic][bold][cyan]~/.curve", + ) + table.add_row( + "-c --client-key-id [cyan]CURVE KEY ID", "Client's Curve Key ID" + ) table.add_row("--help", "Show this message and exit.") console.print(table) -def handle_help(ctx: click.Context, - param: typing.Union[click.Option, click.Parameter], - value: typing.Any) -> None: +def handle_help( + ctx: click.Context, + param: typing.Union[click.Option, click.Parameter], + value: typing.Any, +) -> None: if not value or ctx.resilient_parsing: return print_help() @@ -60,67 +75,87 @@ def handle_help(ctx: click.Context, @click.group(add_help_option=False, invoke_without_command=True) -@click.version_option(__version__, '-v', '--version', message='%(version)s') -@click.option('--log-level', - '-l', - default='CRITICAL', - help="Logging level", - type=click.Choice("CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET".split()), - show_default=True) -@click.option('--connect-address', - '-a', - default='tcp://0.0.0.0:5000', - help='Address on which to connect to the SQLiteServer', - show_default=True) -@click.option('--curvezmq/--no-curvezmq', - help='True if you want to enable CurveZMQ encryption', - default=False, - show_default=True) -@click.option('--curve-dir', - '-d', - help='Curve Key directory', - default=None) -@click.option('--client-key-id', - '-c', - type=click.STRING, - help='Client key ID', - default=None) -@click.option('--server-key-id', - '-s', - type=click.STRING, - help='Server key ID', - default=None) -@click.option("--help", - is_flag=True, - is_eager=True, - expose_value=False, - callback=handle_help, - help="Show this message and exit.") +@click.version_option(__version__, "-v", "--version", message="%(version)s") +@click.option( + "--log-level", + "-l", + default="CRITICAL", + help="Logging level", + type=click.Choice( + "CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET".split() + ), + show_default=True, +) +@click.option( + "--connect-address", + "-a", + default="tcp://0.0.0.0:5000", + help="Address on which to connect to the SQLiteServer", + show_default=True, +) +@click.option( + "--curvezmq/--no-curvezmq", + help="True if you want to enable CurveZMQ encryption", + default=False, + show_default=True, +) +@click.option("--curve-dir", "-d", help="Curve Key directory", default=None) +@click.option( + "--client-key-id", + "-c", + type=click.STRING, + help="Client key ID", + default=None, +) +@click.option( + "--server-key-id", + "-s", + type=click.STRING, + help="Server key ID", + default=None, +) +@click.option( + "--help", + is_flag=True, + is_eager=True, + expose_value=False, + callback=handle_help, + help="Show this message and exit.", +) @click.pass_context -def main(ctx, log_level, connect_address, curvezmq, curve_dir, client_key_id, server_key_id): +def main( + ctx, + log_level, + connect_address, + curvezmq, + curve_dir, + client_key_id, + server_key_id, +): logging.config.dictConfig(get_default_logger_settings(level=log_level)) - client = SQLiteClient(connect_address=connect_address, - use_encryption=curvezmq, - curve_dir=curve_dir, - client_curve_id=client_key_id, - server_curve_id=server_key_id) + client = SQLiteClient( + connect_address=connect_address, + use_encryption=curvezmq, + curve_dir=curve_dir, + client_curve_id=client_key_id, + server_curve_id=server_key_id, + ) ctx.obj = client -@main.command(name='exec', add_help_option=False) -@click.argument('query') -@click.option("--help", - is_flag=True, - is_eager=True, - expose_value=False, - callback=handle_help, - help="Show this message and exit.") +@main.command(name="exec", add_help_option=False) +@click.argument("query") +@click.option( + "--help", + is_flag=True, + is_eager=True, + expose_value=False, + callback=handle_help, + help="Show this message and exit.", +) @click.pass_context def execute_query(ctx, query): client = ctx.obj client.execute(query=query) result = client.execute(query=query) print_json(data=result, indent=2) - - - diff --git a/sqlite_rx/cli/keygen.py b/sqlite_rx/cli/keygen.py new file mode 100644 index 0000000..4433915 --- /dev/null +++ b/sqlite_rx/cli/keygen.py @@ -0,0 +1,33 @@ +import argparse +import logging +import socket +import sys + +import zmq.auth + +from sqlite_rx.auth import KeyGenerator + +logging.basicConfig(stream=sys.stdout, level=logging.INFO) + +LOG = logging.getLogger(__name__) + + +def main(): + """ + Modeled after ssh-keygen. + Implementation idea borrowed from : https://github.com/danielrobbins/ibm-dw-zeromq-2/blob/master/curve-keygen + """ + if zmq.zmq_version_info() < (4, 0): + raise RuntimeError( + "Security is not supported in libzmq version < 4.0. libzmq version {0}".format( + zmq.zmq_version() + ) + ) + mode = "client" + parser = argparse.ArgumentParser() + parser.add_argument("--mode", default=mode, help="`client` or `server`") + args = parser.parse_args() + key_id = "id_{}_{}_curve".format(args.mode, socket.gethostname()) + LOG.info("Generating keys in %s", mode) + kg = KeyGenerator(key_id=key_id) + kg.generate() diff --git a/sqlite_rx/cli/server.py b/sqlite_rx/cli/server.py index 77c6e07..26e7725 100644 --- a/sqlite_rx/cli/server.py +++ b/sqlite_rx/cli/server.py @@ -1,8 +1,6 @@ import logging.config - -import typing import platform - +import typing from pprint import pformat import click @@ -12,56 +10,77 @@ import rich.syntax import rich.table -from sqlite_rx import get_default_logger_settings, __version__ +from sqlite_rx import __version__, get_default_logger_settings from sqlite_rx.server import SQLiteServer - LOG = logging.getLogger(__name__) def print_help(): console = rich.console.Console() - console.print(f"[bold]sqlite-server[/bold] :paw_prints:", justify="center") + console.print( + f"[bold]sqlite-server[/bold] :paw_prints:", justify="center" + ) console.print() - console.print("A simple, fast and secure server for the SQLite database.", justify="center") + console.print( + "A simple, fast and secure server for the SQLite database.", + justify="center", + ) console.print() - console.print("Usage: [bold]sqlite-server[/bold] [cyan][OPTIONS][/cyan] ", justify="left") + console.print( + "Usage: [bold]sqlite-server[/bold] [cyan][OPTIONS][/cyan] ", + justify="left", + ) console.print() table = rich.table.Table.grid(padding=1, pad_edge=True) table.add_column("Parameter", no_wrap=True, justify="left", style="bold") table.add_column("Description") - table.add_row("-l, --log-level [cyan]LOG_LEVEL", - "CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET\n" - "Default value is [bold][cyan]INFO") - table.add_row('-a, --tcp-address [cyan]tcp://:', - "The host and port on which to listen for TCP connections\n" - "Default value is [bold][cyan]tcp://0.0.0.0:5000") - table.add_row("-d --database [cyan]PATH", - "Path to the database\n" - "You can use :memory: for an in-memory database\n" - "Default value is [bold][cyan]:memory:") - table.add_row("--zap/--no-zap", - "Enable/Disable ZAP Authentication\n" - "Default value is [bold][cyan]False") - table.add_row('--curvezmq/--no-curvezmq', - "Enable/Disable CurveZMQ\n" - "Default value is [bold][cyan]False") - table.add_row("-c --curve-dir [cyan]PATH", - "Path to the Curve key directory\n" - "Default value is [bold][cyan]~/.curve") - table.add_row("-k --key-id [cyan]CURVE KEY ID", - "Server's Curve Key ID") - table.add_row("-b --backup-database [cyan]PATH", - "Path to the backup database") - table.add_row("-i --backup-interval [cyan]FLOAT", - "Backup interval in seconds") + table.add_row( + "-l, --log-level [cyan]LOG_LEVEL", + "CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET\n" + "Default value is [bold][cyan]INFO", + ) + table.add_row( + "-a, --tcp-address [cyan]tcp://:", + "The host and port on which to listen for TCP connections\n" + "Default value is [bold][cyan]tcp://0.0.0.0:5000", + ) + table.add_row( + "-d --database [cyan]PATH", + "Path to the database\n" + "You can use :memory: for an in-memory database\n" + "Default value is [bold][cyan]:memory:", + ) + table.add_row( + "--zap/--no-zap", + "Enable/Disable ZAP Authentication\n" + "Default value is [bold][cyan]False", + ) + table.add_row( + "--curvezmq/--no-curvezmq", + "Enable/Disable CurveZMQ\n" "Default value is [bold][cyan]False", + ) + table.add_row( + "-c --curve-dir [cyan]PATH", + "Path to the Curve key directory\n" + "Default value is [bold][cyan]~/.curve", + ) + table.add_row("-k --key-id [cyan]CURVE KEY ID", "Server's Curve Key ID") + table.add_row( + "-b --backup-database [cyan]PATH", "Path to the backup database" + ) + table.add_row( + "-i --backup-interval [cyan]FLOAT", "Backup interval in seconds" + ) table.add_row("--help", "Show this message and exit.") console.print(table) -def handle_help(ctx: click.Context, - param: typing.Union[click.Option, click.Parameter], - value: typing.Any) -> None: +def handle_help( + ctx: click.Context, + param: typing.Union[click.Option, click.Parameter], + value: typing.Any, +) -> None: if not value or ctx.resilient_parsing: return print_help() @@ -69,84 +88,104 @@ def handle_help(ctx: click.Context, @click.command(add_help_option=False) -@click.version_option(__version__, '-v', '--version', message='%(version)s') -@click.option('--log-level', - '-l', - default='INFO', - help="Logging level", - type=click.Choice("CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET".split()), - show_default=True) -@click.option('--tcp-address', - '-a', - default='tcp://0.0.0.0:5000', - type=click.STRING, - help='The host and port on which to listen for TCP connections', - show_default=True) -@click.option('--database', - '-d', - type=click.STRING, - default=':memory:', - help='Path like object giving the database name\n' - 'You can use `:memory:` for an in-memory database', - show_default=True) -@click.option('--zap/--no-zap', - help='True, if you want to enable ZAP authentication', - default=False, - show_default=True) -@click.option('--curvezmq/--no-curvezmq', - help='True, if you want to enable CurveZMQ encryption', - default=False, - show_default=True) -@click.option('--curve-dir', - '-d', - type=click.Path(exists=True), - help='Curve Key directory', - default=None) -@click.option('--key-id', - '-k', - type=click.STRING, - help='Server key ID', - default=None) -@click.option('--backup-database', - '-b', - help='Path to the backup database', - default=None, - type=str, - show_default=True) -@click.option('--backup-interval', - '-i', - help='Backup interval in seconds', - default=600.0, - type=click.FLOAT, - show_default=True) -@click.option("--help", - is_flag=True, - is_eager=True, - expose_value=False, - callback=handle_help, - help="Show this message and exit.") -def main(log_level, - tcp_address, - database, - zap, - curvezmq, - curve_dir, - key_id, - backup_database, - backup_interval): +@click.version_option(__version__, "-v", "--version", message="%(version)s") +@click.option( + "--log-level", + "-l", + default="INFO", + help="Logging level", + type=click.Choice( + "CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET".split() + ), + show_default=True, +) +@click.option( + "--tcp-address", + "-a", + default="tcp://0.0.0.0:5000", + type=click.STRING, + help="The host and port on which to listen for TCP connections", + show_default=True, +) +@click.option( + "--database", + "-d", + type=click.STRING, + default=":memory:", + help="Path like object giving the database name\n" + "You can use `:memory:` for an in-memory database", + show_default=True, +) +@click.option( + "--zap/--no-zap", + help="True, if you want to enable ZAP authentication", + default=False, + show_default=True, +) +@click.option( + "--curvezmq/--no-curvezmq", + help="True, if you want to enable CurveZMQ encryption", + default=False, + show_default=True, +) +@click.option( + "--curve-dir", + "-d", + type=click.Path(exists=True), + help="Curve Key directory", + default=None, +) +@click.option( + "--key-id", "-k", type=click.STRING, help="Server key ID", default=None +) +@click.option( + "--backup-database", + "-b", + help="Path to the backup database", + default=None, + type=str, + show_default=True, +) +@click.option( + "--backup-interval", + "-i", + help="Backup interval in seconds", + default=600.0, + type=click.FLOAT, + show_default=True, +) +@click.option( + "--help", + is_flag=True, + is_eager=True, + expose_value=False, + callback=handle_help, + help="Show this message and exit.", +) +def main( + log_level, + tcp_address, + database, + zap, + curvezmq, + curve_dir, + key_id, + backup_database, + backup_interval, +): logging.config.dictConfig(get_default_logger_settings(level=log_level)) LOG.info("Python Platform %s", platform.python_implementation()) kwargs = { - 'bind_address': tcp_address, - 'database': database, - 'curve_dir': curve_dir, - 'use_zap_auth': zap, - 'use_encryption': curvezmq, - 'server_curve_id': key_id, - 'backup_database': backup_database, - 'backup_interval': backup_interval + "bind_address": tcp_address, + "database": database, + "curve_dir": curve_dir, + "use_zap_auth": zap, + "use_encryption": curvezmq, + "server_curve_id": key_id, + "backup_database": backup_database, + "backup_interval": backup_interval, } - LOG.info('Args %s', pformat(kwargs)) + LOG.info("Args %s", pformat(kwargs)) server = SQLiteServer(**kwargs) server.start() server.join() diff --git a/sqlite_rx/client.py b/sqlite_rx/client.py index 152d995..5a1bf3a 100644 --- a/sqlite_rx/client.py +++ b/sqlite_rx/client.py @@ -6,15 +6,15 @@ import msgpack import zmq + from sqlite_rx.auth import KeyMonkey from sqlite_rx.exception import ( SQLiteRxCompressionError, SQLiteRxConnectionError, - SQLiteRxTransportError, SQLiteRxSerializationError, + SQLiteRxTransportError, ) - DEFAULT_REQUEST_TIMEOUT = 2500 REQUEST_RETRIES = 5 @@ -23,18 +23,20 @@ LOG = logging.getLogger(__name__) -__all__ = ['SQLiteClient'] +__all__ = ["SQLiteClient"] class SQLiteClient(threading.local): - def __init__(self, - connect_address: str, - use_encryption: bool = False, - curve_dir: str = None, - client_curve_id: str = None, - server_curve_id: str = None, - context=None): + def __init__( + self, + connect_address: str, + use_encryption: bool = False, + curve_dir: str = None, + client_curve_id: str = None, + server_curve_id: str = None, + context=None, + ): """ A thin and reliable client to send query execution requests to a remote :class: `sqlite_rx.server.SQLiteServer` @@ -49,12 +51,22 @@ def __init__(self, context: `zmq.Context` """ - self.client_id = "python@{}_{}".format(socket.gethostname(), threading.get_ident()) + self.client_id = "python@{}_{}".format( + socket.gethostname(), threading.get_ident() + ) self._context = context or zmq.Context.instance() self._connect_address = connect_address self._encrypt = use_encryption - self.server_curve_id = server_curve_id if server_curve_id else "id_server_{}_curve".format(socket.gethostname()) - client_curve_id = client_curve_id if client_curve_id else "id_client_{}_curve".format(socket.gethostname()) + self.server_curve_id = ( + server_curve_id + if server_curve_id + else "id_server_{}_curve".format(socket.gethostname()) + ) + client_curve_id = ( + client_curve_id + if client_curve_id + else "id_client_{}_curve".format(socket.gethostname()) + ) self._keymonkey = KeyMonkey(client_curve_id, destination_dir=curve_dir) self._client = self._init_client() @@ -63,7 +75,9 @@ def _init_client(self): client = self._context.socket(zmq.REQ) if self._encrypt: LOG.debug("requests will be encrypted; will load CurveZMQ keys") - client = self._keymonkey.setup_secure_client(client, self._connect_address, self.server_curve_id) + client = self._keymonkey.setup_secure_client( + client, self._connect_address, self.server_curve_id + ) client.connect(self._connect_address) self._poller = zmq.Poller() self._poller.register(client, zmq.POLLIN) @@ -86,7 +100,9 @@ def _send_request(self, request): def _recv_response(self): try: - response = msgpack.loads(zlib.decompress(self._client.recv()), raw=False) + response = msgpack.loads( + zlib.decompress(self._client.recv()), raw=False + ) except zmq.ZMQError: LOG.exception("Exception while receiving message") raise SQLiteRxTransportError("ZMQ receive error") @@ -98,10 +114,7 @@ def _recv_response(self): raise SQLiteRxSerializationError("msgpack deserialization error") return response - def execute(self, - query: str, - *args, - **kwargs) -> dict: + def execute(self, query: str, *args, **kwargs) -> dict: """Synchronous which will send the `query` and the parameters to a remote SQLiteServer instance, wait for the response and return the response to the caller. @@ -133,21 +146,25 @@ def execute(self, """ LOG.info("Executing query %s for client %s", query, self.client_id) - request_retries = kwargs.pop('retries', REQUEST_RETRIES) - execute_many = kwargs.pop('execute_many', False) - execute_script = kwargs.pop('execute_script', False) - request_timeout = kwargs.pop('request_timeout', DEFAULT_REQUEST_TIMEOUT) + request_retries = kwargs.pop("retries", REQUEST_RETRIES) + execute_many = kwargs.pop("execute_many", False) + execute_script = kwargs.pop("execute_script", False) + request_timeout = kwargs.pop( + "request_timeout", DEFAULT_REQUEST_TIMEOUT + ) # Do some client side validations. if execute_script and execute_many: - raise ValueError("Both `execute_script` and `execute_many` cannot be True") + raise ValueError( + "Both `execute_script` and `execute_many` cannot be True" + ) request = { "client_id": self.client_id, "query": query, "params": args, "execute_many": execute_many, - "execute_script": execute_script + "execute_script": execute_script, } expect_reply = True @@ -171,11 +188,13 @@ def execute(self, self._client = self._init_client() self._send_request(request) - raise SQLiteRxConnectionError("No response after retrying. Abandoning Request") - + raise SQLiteRxConnectionError( + "No response after retrying. Abandoning Request" + ) + def __enter__(self): return self - + def __exit__(self, exc_type, exc_value, traceback): self.cleanup() @@ -185,17 +204,26 @@ def cleanup(self): self._client.close() self._poller.unregister(self._client) except zmq.ZMQError as e: - if e.errno in (zmq.EINVAL, - zmq.EPROTONOSUPPORT, - zmq.ENOCOMPATPROTO, - zmq.EADDRINUSE, - zmq.EADDRNOTAVAIL,): + if e.errno in ( + zmq.EINVAL, + zmq.EPROTONOSUPPORT, + zmq.ENOCOMPATPROTO, + zmq.EADDRINUSE, + zmq.EADDRNOTAVAIL, + ): LOG.error("ZeroMQ Transportation endpoint was not setup") elif e.errno in (zmq.ENOTSOCK,): - LOG.error("ZeroMQ request was made against a non-existent device or invalid socket") - - elif e.errno in (zmq.ETERM, zmq.EMTHREAD,): - LOG.error("ZeroMQ context is not a state to handle this request for socket") + LOG.error( + "ZeroMQ request was made against a non-existent device or invalid socket" + ) + + elif e.errno in ( + zmq.ETERM, + zmq.EMTHREAD, + ): + LOG.error( + "ZeroMQ context is not a state to handle this request for socket" + ) except Exception: LOG.exception("Exception while shutting down SQLiteClient") diff --git a/sqlite_rx/exception.py b/sqlite_rx/exception.py index 3ba0957..4c093e8 100644 --- a/sqlite_rx/exception.py +++ b/sqlite_rx/exception.py @@ -1,4 +1,3 @@ - class SQLiteRxError(Exception): pass @@ -26,5 +25,6 @@ class SQLiteRxCompressionError(SQLiteRxError): class SQLiteRxConnectionError(SQLiteRxError): pass + class SQLiteRxBackUpError(SQLiteRxError): - pass \ No newline at end of file + pass diff --git a/sqlite_rx/server.py b/sqlite_rx/server.py index 7e0e814..defe4a9 100644 --- a/sqlite_rx/server.py +++ b/sqlite_rx/server.py @@ -7,28 +7,26 @@ import threading import traceback import zlib -from signal import SIGTERM, SIGINT, signal - -from typing import List, Union, Callable +from signal import SIGINT, SIGTERM, signal +from typing import Callable, List, Union import billiard as multiprocessing import msgpack import zmq -from sqlite_rx import get_version -from sqlite_rx.auth import Authorizer, KeyMonkey -from sqlite_rx.backup import SQLiteBackUp, RecurringTimer, is_backup_supported -from sqlite_rx.exception import SQLiteRxBackUpError -from sqlite_rx.exception import SQLiteRxZAPSetupError from tornado import ioloop, version from zmq.auth.asyncio import AsyncioAuthenticator from zmq.eventloop import zmqstream +from sqlite_rx import get_version +from sqlite_rx.auth import Authorizer, KeyMonkey +from sqlite_rx.backup import RecurringTimer, SQLiteBackUp, is_backup_supported +from sqlite_rx.exception import SQLiteRxBackUpError, SQLiteRxZAPSetupError PARENT_DIR = os.path.dirname(__file__) LOG = logging.getLogger(__name__) -__all__ = ['SQLiteServer'] +__all__ = ["SQLiteServer"] class SQLiteZMQProcess(multiprocessing.Process): @@ -52,14 +50,16 @@ def setup(self): self.context = zmq.Context() self.loop = ioloop.IOLoop() - def stream(self, - sock_type, - address: str, - callback: Callable = None, - use_encryption: bool = False, - server_curve_id: str = None, - curve_dir: str = None, - use_zap: bool = False): + def stream( + self, + sock_type, + address: str, + callback: Callable = None, + use_encryption: bool = False, + server_curve_id: str = None, + curve_dir: str = None, + use_zap: bool = False, + ): """ Method used to setup a ZMQ stream which will be bound to a ZMQ.REP socket. @@ -83,20 +83,35 @@ def stream(self, if use_encryption or use_zap: - server_curve_id = server_curve_id if server_curve_id else "id_server_{}_curve".format(socket.gethostname()) - keymonkey = KeyMonkey(key_id=server_curve_id, destination_dir=curve_dir) + server_curve_id = ( + server_curve_id + if server_curve_id + else "id_server_{}_curve".format(socket.gethostname()) + ) + keymonkey = KeyMonkey( + key_id=server_curve_id, destination_dir=curve_dir + ) if use_encryption: LOG.info("Setting up encryption using CurveCP") - self.socket = keymonkey.setup_secure_server(self.socket, address) + self.socket = keymonkey.setup_secure_server( + self.socket, address + ) if use_zap: if not use_encryption: - raise SQLiteRxZAPSetupError("ZAP requires CurveZMQ(use_encryption = True) to be enabled. Exiting") + raise SQLiteRxZAPSetupError( + "ZAP requires CurveZMQ(use_encryption = True) to be enabled. Exiting" + ) self.auth = AsyncioAuthenticator(self.context) - LOG.info("ZAP enabled. \n Authorizing clients in %s.", keymonkey.authorized_clients_dir) - self.auth.configure_curve(domain="*", location=keymonkey.authorized_clients_dir) + LOG.info( + "ZAP enabled. \n Authorizing clients in %s.", + keymonkey.authorized_clients_dir, + ) + self.auth.configure_curve( + domain="*", location=keymonkey.authorized_clients_dir + ) self.auth.start() self.socket.bind(address) @@ -109,17 +124,20 @@ def stream(self, class SQLiteServer(SQLiteZMQProcess): - def __init__(self, - bind_address: str, - database: Union[bytes, str], - auth_config: dict = None, - curve_dir: str = None, - server_curve_id: str = None, - use_encryption: bool = False, - use_zap_auth: bool = False, - backup_database: Union[bytes, str] = None, - backup_interval: int = 4, - *args, **kwargs): + def __init__( + self, + bind_address: str, + database: Union[bytes, str], + auth_config: dict = None, + curve_dir: str = None, + server_curve_id: str = None, + use_encryption: bool = False, + use_zap_auth: bool = False, + backup_database: Union[bytes, str] = None, + backup_interval: int = 4, + *args, + **kwargs, + ): """ SQLiteServer runs as an isolated python process. @@ -145,10 +163,14 @@ def __init__(self, if backup_database is not None: if not is_backup_supported(): - raise SQLiteRxBackUpError(f"SQLite backup is not supported on {sys.platform} or {platform.python_implementation()}") + raise SQLiteRxBackUpError( + f"SQLite backup is not supported on {sys.platform} or {platform.python_implementation()}" + ) sqlite_backup = SQLiteBackUp(src=database, target=backup_database) - self.back_up_recurring_thread = RecurringTimer(function=sqlite_backup, interval=backup_interval) + self.back_up_recurring_thread = RecurringTimer( + function=sqlite_backup, interval=backup_interval + ) self.back_up_recurring_thread.daemon = True def setup(self): @@ -158,16 +180,20 @@ def setup(self): """ super().setup() # Depending on the initialization parameters either get a plain stream or secure stream. - self.rep_stream = self.stream(zmq.REP, - self._bind_address, - use_encryption=self._encrypt, - use_zap=self._zap_auth, - server_curve_id=self.server_curve_id, - curve_dir=self.curve_dir) + self.rep_stream = self.stream( + zmq.REP, + self._bind_address, + use_encryption=self._encrypt, + use_zap=self._zap_auth, + server_curve_id=self.server_curve_id, + curve_dir=self.curve_dir, + ) # Register the callback. - self.rep_stream.on_recv(QueryStreamHandler(self.rep_stream, - self._database, - self._auth_config)) + self.rep_stream.on_recv( + QueryStreamHandler( + self.rep_stream, self._database, self._auth_config + ) + ) def handle_signal(self, signum, frame): LOG.info("SQLiteServer %s PID %s received %r", self, self.pid, signum) @@ -176,7 +202,7 @@ def handle_signal(self, signum, frame): self.rep_stream.close() self.socket.close() self.loop.stop() - + if self.back_up_recurring_thread: self.back_up_recurring_thread.cancel() raise SystemExit() @@ -193,19 +219,23 @@ def run(self): LOG.info("SQLiteServer (Tornado) i/o loop started..") LOG.info("Backup thread %s", self.back_up_recurring_thread) - if self.back_up_recurring_thread and not self.back_up_recurring_thread.is_alive(): + if ( + self.back_up_recurring_thread + and not self.back_up_recurring_thread.is_alive() + ): self.back_up_recurring_thread.start() - LOG.info("Ready to accept client connections on %s", self._bind_address) + LOG.info( + "Ready to accept client connections on %s", self._bind_address + ) self.loop.start() class QueryStreamHandler: - def __init__(self, - rep_stream, - database: Union[bytes, str], - auth_config: dict = None): + def __init__( + self, rep_stream, database: Union[bytes, str], auth_config: dict = None + ): """ Executes SQL queries and send results back on the `zmq.REP` stream @@ -215,10 +245,10 @@ def __init__(self, auth_config: A dictionary describing what actions are authorized, denied or ignored. """ - self._connection = sqlite3.connect(database=database, - isolation_level=None, - check_same_thread=False) - self._connection.execute('pragma journal_mode=wal') + self._connection = sqlite3.connect( + database=database, isolation_level=None, check_same_thread=False + ) + self._connection.execute("pragma journal_mode=wal") self._connection.set_authorizer(Authorizer(config=auth_config)) self._cursor = self._connection.cursor() self._rep_stream = rep_stream @@ -227,7 +257,12 @@ def __init__(self, def capture_exception(): exc_type, exc_value, exc_tb = sys.exc_info() exc_type_string = "%s.%s" % (exc_type.__module__, exc_type.__name__) - error = {"type": exc_type_string, "message": traceback.format_exception_only(exc_type, exc_value)[-1].strip()} + error = { + "type": exc_type_string, + "message": traceback.format_exception_only(exc_type, exc_value)[ + -1 + ].strip(), + } return error def __call__(self, message: List): @@ -238,50 +273,48 @@ def __call__(self, message: List): except Exception: LOG.exception("exception while preparing response") error = self.capture_exception() - result = {"items": [], - "error": error} + result = {"items": [], "error": error} self._rep_stream.send(zlib.compress(msgpack.dumps(result))) def execute(self, message: dict, *args, **kwargs): - execute_many = message['execute_many'] - execute_script = message['execute_script'] + execute_many = message["execute_many"] + execute_script = message["execute_script"] error = None try: if execute_script: LOG.debug("Query Mode: Execute Script") - self._cursor.executescript(message['query']) - elif execute_many and message['params']: + self._cursor.executescript(message["query"]) + elif execute_many and message["params"]: LOG.debug("Query Mode: Execute Many") - self._cursor.executemany(message['query'], message['params']) - elif message['params']: + self._cursor.executemany(message["query"], message["params"]) + elif message["params"]: LOG.debug("Query Mode: Conditional Params") - self._cursor.execute(message['query'], message['params']) + self._cursor.execute(message["query"], message["params"]) else: LOG.debug("Query Mode: Default No params") - self._cursor.execute(message['query']) + self._cursor.execute(message["query"]) except Exception: - LOG.exception("Exception while executing query %s", message['query']) + LOG.exception( + "Exception while executing query %s", message["query"] + ) error = self.capture_exception() - result = { - "items": [], - "error": error - } + result = {"items": [], "error": error} if error: return zlib.compress(msgpack.dumps(result)) try: - result['items'] = list(self._cursor.fetchall()) + result["items"] = list(self._cursor.fetchall()) # If rowcount attribute is set on the cursor object include it in the response if self._cursor.rowcount > -1: - result['rowcount'] = self._cursor.rowcount + result["rowcount"] = self._cursor.rowcount # If lastrowid attribute is set on the cursor include it in the response if self._cursor.lastrowid: - result['lastrowid'] = self._cursor.lastrowid + result["lastrowid"] = self._cursor.lastrowid return zlib.compress(msgpack.dumps(result)) except Exception: LOG.exception("Exception while collecting rows") - result['error'] = self.capture_exception() + result["error"] = self.capture_exception() return zlib.compress(msgpack.dumps(result)) diff --git a/sqlite_rx/tests/__init__.py b/sqlite_rx/tests/__init__.py index 45106ab..4123468 100644 --- a/sqlite_rx/tests/__init__.py +++ b/sqlite_rx/tests/__init__.py @@ -1,10 +1,8 @@ +import logging.config import os - import socket - import tempfile from contextlib import contextmanager -import logging.config from sqlite_rx import get_default_logger_settings from sqlite_rx.auth import KeyGenerator @@ -16,14 +14,18 @@ @contextmanager def get_server_auth_files(): - """Generate Temporary Private and Public keys for ZAP and CurveZMQ SQLiteServer - - """ + """Generate Temporary Private and Public keys for ZAP and CurveZMQ SQLiteServer""" with tempfile.TemporaryDirectory() as curve_dir: LOG.info("Curve dir is %s", curve_dir) server_key_id = "id_server_{}_curve".format(socket.gethostname()) - key_generator = KeyGenerator(destination_dir=curve_dir, key_id=server_key_id) + key_generator = KeyGenerator( + destination_dir=curve_dir, key_id=server_key_id + ) key_generator.generate() - server_public_key = os.path.join(curve_dir, "{}.key".format(server_key_id)) - server_private_key = os.path.join(curve_dir, "{}.key_secret".format(server_key_id)) + server_public_key = os.path.join( + curve_dir, "{}.key".format(server_key_id) + ) + server_private_key = os.path.join( + curve_dir, "{}.key_secret".format(server_key_id) + ) yield curve_dir, server_key_id, server_public_key, server_private_key diff --git a/sqlite_rx/tests/backup/conftest.py b/sqlite_rx/tests/backup/conftest.py index 0420fd1..d3462ec 100644 --- a/sqlite_rx/tests/backup/conftest.py +++ b/sqlite_rx/tests/backup/conftest.py @@ -1,14 +1,14 @@ +import logging.config import os -import sys import platform import signal +import sqlite3 +import sys import tempfile -import pytest +from collections import namedtuple -import sqlite3 -import logging.config +import pytest -from collections import namedtuple from sqlite_rx import get_default_logger_settings from sqlite_rx.backup import is_backup_supported from sqlite_rx.client import SQLiteClient @@ -18,36 +18,42 @@ LOG = logging.getLogger(__file__) -backup_event = namedtuple('backup_event', ('client', 'backup_database', 'main_database')) +backup_event = namedtuple( + "backup_event", ("client", "backup_database", "main_database") +) @pytest.fixture(scope="module") def plain_client(): - auth_config = { - sqlite3.SQLITE_OK: { - sqlite3.SQLITE_DROP_TABLE - } - } + auth_config = {sqlite3.SQLITE_OK: {sqlite3.SQLITE_DROP_TABLE}} with tempfile.TemporaryDirectory() as base_dir: - main_db_file = os.path.join(base_dir, 'main.db') - backup_db_file = os.path.join(base_dir, 'backup.db') + main_db_file = os.path.join(base_dir, "main.db") + backup_db_file = os.path.join(base_dir, "backup.db") if is_backup_supported(): - server = SQLiteServer(bind_address="tcp://127.0.0.1:5003", - database=main_db_file, - auth_config=auth_config, - backup_database=backup_db_file, - backup_interval=1) + server = SQLiteServer( + bind_address="tcp://127.0.0.1:5003", + database=main_db_file, + auth_config=auth_config, + backup_database=backup_db_file, + backup_interval=1, + ) else: - server = SQLiteServer(bind_address="tcp://127.0.0.1:5003", - database=main_db_file, - auth_config=auth_config) - + server = SQLiteServer( + bind_address="tcp://127.0.0.1:5003", + database=main_db_file, + auth_config=auth_config, + ) + client = SQLiteClient(connect_address="tcp://127.0.0.1:5003") - event = backup_event(client=client, backup_database=backup_db_file, main_database=main_db_file) + event = backup_event( + client=client, + backup_database=backup_db_file, + main_database=main_db_file, + ) server.start() @@ -55,10 +61,10 @@ def plain_client(): yield event - if platform.system().lower() == 'windows': - os.system("taskkill /F /pid "+str(server.pid)) + if platform.system().lower() == "windows": + os.system("taskkill /F /pid " + str(server.pid)) else: os.kill(server.pid, signal.SIGINT) server.join() - client.cleanup() \ No newline at end of file + client.cleanup() diff --git a/sqlite_rx/tests/backup/test_queries.py b/sqlite_rx/tests/backup/test_queries.py index 57fcb93..2a35788 100644 --- a/sqlite_rx/tests/backup/test_queries.py +++ b/sqlite_rx/tests/backup/test_queries.py @@ -1,10 +1,11 @@ -import sys +import os import platform import sqlite3 -import os +import sys import time from sqlite_rx.backup import is_backup_supported + sqlite_error_prefix = "sqlite3.OperationalError" if platform.python_implementation() == "PyPy": @@ -12,64 +13,72 @@ def test_not_present(plain_client): - result = plain_client.client.execute('SELECT * FROM IDOLS') + result = plain_client.client.execute("SELECT * FROM IDOLS") expected_result = { - 'items': [], - 'error': { - 'message': '{0}: no such table: IDOLS'.format(sqlite_error_prefix), - 'type': '{0}'.format(sqlite_error_prefix)}} + "items": [], + "error": { + "message": "{0}: no such table: IDOLS".format(sqlite_error_prefix), + "type": "{0}".format(sqlite_error_prefix), + }, + } assert type(result) == dict assert result == expected_result - + def test_table_creation(plain_client): - result = plain_client.client.execute('CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)') - expected_result = {"error": None, 'items': []} + result = plain_client.client.execute( + "CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)" + ) + expected_result = {"error": None, "items": []} assert result == expected_result def test_table_rows_insertion(plain_client): - purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ] + purchases = [ + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ] - result = plain_client.client.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', *purchases, execute_many=True) - expected_result = {'error': None, 'items': [], 'rowcount': 27} + result = plain_client.client.execute( + "INSERT INTO stocks VALUES (?,?,?,?,?)", *purchases, execute_many=True + ) + expected_result = {"error": None, "items": [], "rowcount": 27} assert result == expected_result if is_backup_supported(): time.sleep(1) # wait for the backup thread to finish backing up. backup_database = plain_client.backup_database - backup_connection = sqlite3.connect(database=backup_database, - isolation_level=None, - check_same_thread=False) + backup_connection = sqlite3.connect( + database=backup_database, + isolation_level=None, + check_same_thread=False, + ) assert os.path.exists(backup_database) is True result = backup_connection.execute("SELECT * FROM stocks").fetchall() assert len(result) == 27 - \ No newline at end of file diff --git a/sqlite_rx/tests/curezmq/conftest.py b/sqlite_rx/tests/curezmq/conftest.py index 6405aa7..d50bf9e 100644 --- a/sqlite_rx/tests/curezmq/conftest.py +++ b/sqlite_rx/tests/curezmq/conftest.py @@ -1,12 +1,12 @@ -import pytest - +import logging.config import os import platform -import socket import shutil import signal +import socket import sqlite3 -import logging.config + +import pytest from sqlite_rx import get_default_logger_settings from sqlite_rx.auth import KeyGenerator @@ -22,42 +22,52 @@ @pytest.fixture(scope="module") def curvezmq_client(): with get_server_auth_files() as auth_files: - curve_dir, server_key_id, server_public_key, server_private_key = auth_files + curve_dir, server_key_id, server_public_key, server_private_key = ( + auth_files + ) client_key_id = "id_client_{}_curve".format(socket.gethostname()) - key_generator = KeyGenerator(destination_dir=curve_dir, key_id=client_key_id) + key_generator = KeyGenerator( + destination_dir=curve_dir, key_id=client_key_id + ) key_generator.generate() - client_public_key = os.path.join(curve_dir, "{}.key".format(client_key_id)) - client_private_key = os.path.join(curve_dir, "{}.key_secret".format(client_key_id)) - shutil.copyfile(client_public_key, os.path.join(curve_dir, - 'authorized_clients', - "{}.key".format(client_key_id))) - auth_config = { - sqlite3.SQLITE_OK: { - sqlite3.SQLITE_DROP_TABLE - } - } - server = SQLiteServer(bind_address="tcp://127.0.0.1:5002", - use_encryption=True, - curve_dir=curve_dir, - server_curve_id=server_key_id, - auth_config=auth_config, - database=":memory:") + client_public_key = os.path.join( + curve_dir, "{}.key".format(client_key_id) + ) + client_private_key = os.path.join( + curve_dir, "{}.key_secret".format(client_key_id) + ) + shutil.copyfile( + client_public_key, + os.path.join( + curve_dir, "authorized_clients", "{}.key".format(client_key_id) + ), + ) + auth_config = {sqlite3.SQLITE_OK: {sqlite3.SQLITE_DROP_TABLE}} + server = SQLiteServer( + bind_address="tcp://127.0.0.1:5002", + use_encryption=True, + curve_dir=curve_dir, + server_curve_id=server_key_id, + auth_config=auth_config, + database=":memory:", + ) # server.daemon = True - client = SQLiteClient(connect_address="tcp://127.0.0.1:5002", - server_curve_id=server_key_id, - client_curve_id=client_key_id, - curve_dir=curve_dir, - use_encryption=True) + client = SQLiteClient( + connect_address="tcp://127.0.0.1:5002", + server_curve_id=server_key_id, + client_curve_id=client_key_id, + curve_dir=curve_dir, + use_encryption=True, + ) server.start() # server.join() LOG.info("Started Test SQLiteServer") yield client - if platform.system().lower() == 'windows': - os.system("taskkill /F /pid "+str(server.pid)) + if platform.system().lower() == "windows": + os.system("taskkill /F /pid " + str(server.pid)) else: os.kill(server.pid, signal.SIGINT) server.join() client.cleanup() - diff --git a/sqlite_rx/tests/curezmq/test_queries.py b/sqlite_rx/tests/curezmq/test_queries.py index e393273..7828b96 100644 --- a/sqlite_rx/tests/curezmq/test_queries.py +++ b/sqlite_rx/tests/curezmq/test_queries.py @@ -7,53 +7,59 @@ def test_not_present(curvezmq_client): - result = curvezmq_client.execute('SELECT * FROM IDOLS') + result = curvezmq_client.execute("SELECT * FROM IDOLS") expected_result = { - 'items': [], - 'error': { - 'message': '{0}: no such table: IDOLS'.format(sqlite_error_prefix), - 'type': '{0}'.format(sqlite_error_prefix)}} + "items": [], + "error": { + "message": "{0}: no such table: IDOLS".format(sqlite_error_prefix), + "type": "{0}".format(sqlite_error_prefix), + }, + } assert type(result) == dict assert result == expected_result def test_table_creation(curvezmq_client): - result = curvezmq_client.execute('CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)') - expected_result = {"error": None, 'items': []} + result = curvezmq_client.execute( + "CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)" + ) + expected_result = {"error": None, "items": []} assert result == expected_result def test_table_rows_insertion(curvezmq_client): - purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ] - - result = curvezmq_client.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', *purchases, execute_many=True) - expected_result = {'error': None, 'items': [], 'rowcount': 27} - assert result == expected_result + purchases = [ + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ] + result = curvezmq_client.execute( + "INSERT INTO stocks VALUES (?,?,?,?,?)", *purchases, execute_many=True + ) + expected_result = {"error": None, "items": [], "rowcount": 27} + assert result == expected_result diff --git a/sqlite_rx/tests/error/conftest.py b/sqlite_rx/tests/error/conftest.py index 7693a42..d69df48 100644 --- a/sqlite_rx/tests/error/conftest.py +++ b/sqlite_rx/tests/error/conftest.py @@ -1,10 +1,10 @@ -import pytest import logging.config +import pytest + from sqlite_rx import get_default_logger_settings from sqlite_rx.client import SQLiteClient - logging.config.dictConfig(get_default_logger_settings(level="DEBUG")) LOG = logging.getLogger("error_sqlite_client") diff --git a/sqlite_rx/tests/error/test_connection.py b/sqlite_rx/tests/error/test_connection.py index 4fd922f..a57b9aa 100644 --- a/sqlite_rx/tests/error/test_connection.py +++ b/sqlite_rx/tests/error/test_connection.py @@ -7,4 +7,6 @@ def test_client_connection_error(error_client): retries = 2 timeout_ms = 1 with pytest.raises(SQLiteRxConnectionError): - error_client.execute("SELECT * FROM IDOLS", retries=retries, request_timeout=timeout_ms) + error_client.execute( + "SELECT * FROM IDOLS", retries=retries, request_timeout=timeout_ms + ) diff --git a/sqlite_rx/tests/misc/test_backup_exception.py b/sqlite_rx/tests/misc/test_backup_exception.py index 8631614..866671f 100644 --- a/sqlite_rx/tests/misc/test_backup_exception.py +++ b/sqlite_rx/tests/misc/test_backup_exception.py @@ -1,4 +1,5 @@ import sys + import pytest from sqlite_rx.exception import SQLiteRxBackUpError @@ -9,4 +10,8 @@ def test_backup_exception(): if not (sys.version_info.major == 3 and sys.version_info.minor >= 7): with pytest.raises(SQLiteRxBackUpError): - server = SQLiteServer(bind_address="tcp://127.0.0.1:5002", database=":memory:", backup_database='backup.db') \ No newline at end of file + server = SQLiteServer( + bind_address="tcp://127.0.0.1:5002", + database=":memory:", + backup_database="backup.db", + ) diff --git a/sqlite_rx/tests/misc/test_curvekeygen.py b/sqlite_rx/tests/misc/test_curvekeygen.py index 1a4e75b..87cc760 100644 --- a/sqlite_rx/tests/misc/test_curvekeygen.py +++ b/sqlite_rx/tests/misc/test_curvekeygen.py @@ -4,12 +4,17 @@ from sqlite_rx.auth import KeyGenerator + def test_generation_of_curve_keys(): with tempfile.TemporaryDirectory() as destination_dir: key_id = "id_client_{}_curve".format(socket.gethostname()) - key_generator = KeyGenerator(destination_dir=destination_dir, key_id=key_id) + key_generator = KeyGenerator( + destination_dir=destination_dir, key_id=key_id + ) key_generator.generate() public_key = os.path.join(destination_dir, "{}.key".format(key_id)) - private_key = os.path.join(destination_dir, "{}.key_secret".format(key_id)) + private_key = os.path.join( + destination_dir, "{}.key_secret".format(key_id) + ) assert os.path.exists(public_key) == True assert os.path.exists(private_key) == True diff --git a/sqlite_rx/tests/plain/conftest.py b/sqlite_rx/tests/plain/conftest.py index 23c40f4..57c3648 100644 --- a/sqlite_rx/tests/plain/conftest.py +++ b/sqlite_rx/tests/plain/conftest.py @@ -1,10 +1,10 @@ +import logging.config import os import platform import signal -import pytest - import sqlite3 -import logging.config + +import pytest from sqlite_rx import get_default_logger_settings from sqlite_rx.client import SQLiteClient @@ -17,15 +17,13 @@ @pytest.fixture(scope="module") def plain_client(): - auth_config = { - sqlite3.SQLITE_OK: { - sqlite3.SQLITE_DROP_TABLE - } - } - server = SQLiteServer(bind_address="tcp://127.0.0.1:5003", - database=":memory:", - auth_config=auth_config) - + auth_config = {sqlite3.SQLITE_OK: {sqlite3.SQLITE_DROP_TABLE}} + server = SQLiteServer( + bind_address="tcp://127.0.0.1:5003", + database=":memory:", + auth_config=auth_config, + ) + # server.daemon = True client = SQLiteClient(connect_address="tcp://127.0.0.1:5003") @@ -34,10 +32,9 @@ def plain_client(): # server.join() LOG.info("Started Test SQLiteServer") yield client - if platform.system().lower() == 'windows': - os.system("taskkill /F /pid "+str(server.pid)) + if platform.system().lower() == "windows": + os.system("taskkill /F /pid " + str(server.pid)) else: os.kill(server.pid, signal.SIGINT) server.join() client.cleanup() - \ No newline at end of file diff --git a/sqlite_rx/tests/plain/test_queries.py b/sqlite_rx/tests/plain/test_queries.py index 26acf51..9905d5a 100644 --- a/sqlite_rx/tests/plain/test_queries.py +++ b/sqlite_rx/tests/plain/test_queries.py @@ -3,136 +3,156 @@ PYPY = True if platform.python_implementation() == "PyPy" else False - def test_table_creation(plain_client): - result = plain_client.execute('CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)') - expected_result = {"error": None, 'items': []} + result = plain_client.execute( + "CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)" + ) + expected_result = {"error": None, "items": []} assert result == expected_result def test_table_rows_insertion(plain_client): - purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ] - - result = plain_client.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', *purchases, execute_many=True) - expected_result = {'error': None, 'items': [], 'rowcount': 27} + purchases = [ + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ] + + result = plain_client.execute( + "INSERT INTO stocks VALUES (?,?,?,?,?)", *purchases, execute_many=True + ) + expected_result = {"error": None, "items": [], "rowcount": 27} assert result == expected_result def test_table_not_present(plain_client): - result = plain_client.execute('SELECT * FROM IDOLS') + result = plain_client.execute("SELECT * FROM IDOLS") assert type(result) == dict def test_select_before_update(plain_client): - purchases = [['2006-03-28', 'BUY', 'IBM', 1000, 45.00], - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ] - result = plain_client.execute('SELECT * FROM stocks') - expected_result = {'error': None, 'items': [list(purchase) for purchase in purchases], 'lastrowid': 27} + purchases = [ + ["2006-03-28", "BUY", "IBM", 1000, 45.00], + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ] + result = plain_client.execute("SELECT * FROM stocks") + expected_result = { + "error": None, + "items": [list(purchase) for purchase in purchases], + "lastrowid": 27, + } assert result == expected_result def test_update(plain_client): - args = ('IBM',) - result = plain_client.execute('UPDATE stocks SET price = 480 where symbol = ?', *args) - expected_result = {'error': None, 'items': [], 'rowcount': 9, 'lastrowid': 27} + args = ("IBM",) + result = plain_client.execute( + "UPDATE stocks SET price = 480 where symbol = ?", *args + ) + expected_result = { + "error": None, + "items": [], + "rowcount": 9, + "lastrowid": 27, + } assert result == expected_result def test_select(plain_client): - purchases = [['2006-03-28', 'BUY', 'IBM', 1000, 480.00], - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 480.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ] - result = plain_client.execute('SELECT * FROM stocks') - expected_result = {'error': None, 'items': [list(purchase) for purchase in purchases], 'lastrowid': 27} + purchases = [ + ["2006-03-28", "BUY", "IBM", 1000, 480.00], + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 480.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ] + result = plain_client.execute("SELECT * FROM stocks") + expected_result = { + "error": None, + "items": [list(purchase) for purchase in purchases], + "lastrowid": 27, + } assert result == expected_result def test_sql_script(plain_client): - script = '''CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, phone TEXT); + script = """CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, phone TEXT); CREATE TABLE accounts(id INTEGER PRIMARY KEY, description TEXT); - INSERT INTO users(name, phone) VALUES ('John', '5557241'), - ('Adam', '5547874'), ('Jack', '5484522');''' - expected_result = {"error": None, 'items': [], 'lastrowid': 27} + INSERT INTO users(name, phone) VALUES ('John', '5557241'), + ('Adam', '5547874'), ('Jack', '5484522');""" + expected_result = {"error": None, "items": [], "lastrowid": 27} result = plain_client.execute(script, execute_script=True) assert expected_result == result - diff --git a/sqlite_rx/tests/zap/conftest.py b/sqlite_rx/tests/zap/conftest.py index 5e74c57..4267080 100644 --- a/sqlite_rx/tests/zap/conftest.py +++ b/sqlite_rx/tests/zap/conftest.py @@ -1,11 +1,11 @@ -import pytest - +import logging.config import os import platform -import socket import shutil +import socket import sqlite3 -import logging.config + +import pytest from sqlite_rx import get_default_logger_settings from sqlite_rx.auth import KeyGenerator @@ -13,54 +13,62 @@ from sqlite_rx.server import SQLiteServer from sqlite_rx.tests import get_server_auth_files - logging.config.dictConfig(get_default_logger_settings(level="DEBUG")) LOG = logging.getLogger(__file__) import signal -@pytest.fixture(scope='module') + +@pytest.fixture(scope="module") def zap_client(): with get_server_auth_files() as auth_files: - curve_dir, server_key_id, server_public_key, server_private_key = auth_files + curve_dir, server_key_id, server_public_key, server_private_key = ( + auth_files + ) client_key_id = "id_client_{}_curve".format(socket.gethostname()) - key_generator = KeyGenerator(destination_dir=curve_dir, key_id=client_key_id) + key_generator = KeyGenerator( + destination_dir=curve_dir, key_id=client_key_id + ) key_generator.generate() - client_public_key = os.path.join(curve_dir, "{}.key".format(client_key_id)) - client_private_key = os.path.join(curve_dir, "{}.key_secret".format(client_key_id)) - shutil.copyfile(client_public_key, os.path.join(curve_dir, - 'authorized_clients', - "{}.key".format(client_key_id))) + client_public_key = os.path.join( + curve_dir, "{}.key".format(client_key_id) + ) + client_private_key = os.path.join( + curve_dir, "{}.key_secret".format(client_key_id) + ) + shutil.copyfile( + client_public_key, + os.path.join( + curve_dir, "authorized_clients", "{}.key".format(client_key_id) + ), + ) - auth_config = { - sqlite3.SQLITE_OK : { - sqlite3.SQLITE_DROP_TABLE - } - } - server = SQLiteServer(bind_address="tcp://127.0.0.1:5001", - use_zap_auth=True, - use_encryption=True, - curve_dir=curve_dir, - server_curve_id=server_key_id, - auth_config=auth_config, - database=":memory:") + auth_config = {sqlite3.SQLITE_OK: {sqlite3.SQLITE_DROP_TABLE}} + server = SQLiteServer( + bind_address="tcp://127.0.0.1:5001", + use_zap_auth=True, + use_encryption=True, + curve_dir=curve_dir, + server_curve_id=server_key_id, + auth_config=auth_config, + database=":memory:", + ) - client = SQLiteClient(connect_address="tcp://127.0.0.1:5001", - server_curve_id=server_key_id, - client_curve_id=client_key_id, - curve_dir=curve_dir, - use_encryption=True) - + client = SQLiteClient( + connect_address="tcp://127.0.0.1:5001", + server_curve_id=server_key_id, + client_curve_id=client_key_id, + curve_dir=curve_dir, + use_encryption=True, + ) server.start() LOG.info("Started Test SQLiteServer") yield client - if platform.system().lower() == 'windows': - os.system("taskkill /F /pid "+str(server.pid)) + if platform.system().lower() == "windows": + os.system("taskkill /F /pid " + str(server.pid)) else: os.kill(server.pid, signal.SIGINT) server.join() client.cleanup() - - diff --git a/sqlite_rx/tests/zap/test_queries.py b/sqlite_rx/tests/zap/test_queries.py index 740fe5b..fb1a5f4 100644 --- a/sqlite_rx/tests/zap/test_queries.py +++ b/sqlite_rx/tests/zap/test_queries.py @@ -7,51 +7,58 @@ def test_table_not_present(zap_client): - result = zap_client.execute('SELECT * FROM IDOLS') + result = zap_client.execute("SELECT * FROM IDOLS") expected_result = { - 'items': [], - 'error': { - 'message': '{0}: no such table: IDOLS'.format(sqlite_error_prefix), - 'type': '{0}'.format(sqlite_error_prefix)}} + "items": [], + "error": { + "message": "{0}: no such table: IDOLS".format(sqlite_error_prefix), + "type": "{0}".format(sqlite_error_prefix), + }, + } assert type(result) == dict assert result == expected_result def test_table_creation(zap_client): - result = zap_client.execute('CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)') - expected_result = {"error": None, 'items': []} + result = zap_client.execute( + "CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)" + ) + expected_result = {"error": None, "items": []} assert result == expected_result def test_table_rows_insertion(zap_client): - purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ('2006-03-28', 'BUY', 'IBM', 1000, 45.00), - ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00), - ('2006-04-06', 'SELL', 'XOM', 500, 53.00), - ] - result = zap_client.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', *purchases, execute_many=True) - expected_result = {'error': None, 'items': [], 'rowcount': 27} + purchases = [ + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ("2006-03-28", "BUY", "IBM", 1000, 45.00), + ("2006-04-05", "BUY", "MSFT", 1000, 72.00), + ("2006-04-06", "SELL", "XOM", 500, 53.00), + ] + result = zap_client.execute( + "INSERT INTO stocks VALUES (?,?,?,?,?)", *purchases, execute_many=True + ) + expected_result = {"error": None, "items": [], "rowcount": 27} assert result == expected_result diff --git a/sqlite_rx/version.py b/sqlite_rx/version.py new file mode 100644 index 0000000..67bc602 --- /dev/null +++ b/sqlite_rx/version.py @@ -0,0 +1 @@ +__version__ = "1.3.0"