Skip to content

Commit 9f429ca

Browse files
author
Abhishek Singh
committed
v1.0.2 changes
1. Support for contextmanager in SQLiteClient 2. PyPy3 platform in build matrix 3. Unittest cases for backup 4. Deleted travis.yml. Going forward only github actions will be used for building and testing 5. Readme updates
1 parent 9b02098 commit 9f429ca

File tree

11 files changed

+168
-59
lines changed

11 files changed

+168
-59
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ example.db
1616
venv/
1717
start_server.py
1818
run_client.py
19+
.github
1920

.github/workflows/sqlite_build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
fail-fast: false
1010
matrix:
1111
os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-20.04]
12-
python-version: [3.6, 3.7, 3.8, 3.9]
12+
python-version: [3.6, 3.7, 3.8, 3.9, '3.10.0-beta.3', 'pypy3']
1313

1414
steps:
1515
- name: Checkout

.travis.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
# sqlite_rx [![Downloads](https://pepy.tech/badge/sqlite-rx)](https://pepy.tech/project/sqlite-rx) [![Travis](https://travis-ci.org/aosingh/sqlite_rx.svg?branch=master)](https://travis-ci.org/aosingh/sqlite_rx) [![PyPI version](https://badge.fury.io/py/sqlite-rx.svg)](https://pypi.python.org/pypi/sqlite-rx) [![Coverage Status](https://coveralls.io/repos/github/aosingh/sqlite_rx/badge.svg?branch=master)](https://coveralls.io/github/aosingh/sqlite_rx?branch=master)
1+
# sqlite_rx [![Downloads](https://pepy.tech/badge/sqlite-rx)](https://pepy.tech/project/sqlite-rx) [![sqlite-rx](https://github.com/aosingh/sqlite-rx/actions/workflows/sqlite_build.yaml/badge.svg)]
2+
3+
[![PyPI version](https://badge.fury.io/py/sqlite-rx.svg)](https://pypi.python.org/pypi/sqlite-rx)
4+
25
[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)]((https://www.python.org/downloads/release/python-370/)) [![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/) [![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/)
36
[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)]((https://www.python.org/downloads/release/python-390/))
47
[![PyPy3.6](https://img.shields.io/badge/python-PyPy3.6-blue.svg)](https://www.pypy.org/index.html)
8+
59
## Background
610

711
[SQLite](https://www.sqlite.org/index.html) is a lightweight database written in C.
8-
The Python programming language has in-built support to interact with the database(locally) which is either stored on disk or in memory.
12+
The Python programming language has in-built support to interact with the database (locally) which is either stored on disk or in memory.
913

10-
## Introducing sqlite_rx - SQLite remote query execution
14+
## sqlite_rx
1115
With `sqlite_rx`, clients should be able to communicate with an `SQLiteServer` in a fast, simple and secure manner and execute queries remotely.
1216

1317
Key Features
1418

1519
- Python Client and Server for [SQLite](https://www.sqlite.org/index.html) database built using [ZeroMQ](http://zguide.zeromq.org/page:all) as the transport layer and [msgpack](https://msgpack.org/index.html) for serialization/deserialization.
16-
- Supports authentication using [ZeroMQ Authentication Protocol (ZAP)](https://rfc.zeromq.org/spec:27/ZAP/)
17-
- Supports encryption using [CurveZMQ](http://curvezmq.org/)
18-
- Allows the users to define a generic authorization policy during server startup
19-
20+
- Authentication using [ZeroMQ Authentication Protocol (ZAP)](https://rfc.zeromq.org/spec:27/ZAP/)
21+
- Encryption using [CurveZMQ](http://curvezmq.org/)
22+
- Generic authorization policy during server startup
23+
- Schedule regular backups
2024

2125

2226
# Install
@@ -58,6 +62,7 @@ def main():
5862
server = SQLiteServer(database=":memory:",
5963
bind_address="tcp://127.0.0.1:5000")
6064
server.start()
65+
server.join()
6166

6267
if __name__ == '__main__':
6368
main()

docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33

44
sqlite_server:
55
image: aosingh/sqlite_rx
6-
command: sqlite-server --log-level DEBUG --database /data/database.db --backup-database /data/backup.db
6+
command: sqlite-server --log-level DEBUG --database /data/main.db --backup-database /data/backup.db
77
ports:
88
- 5001:5000
99
volumes:

sqlite_rx/client.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import socket
44
import threading
55
import zlib
6-
from pprint import pformat
76

87
import msgpack
98
import zmq
@@ -161,7 +160,6 @@ def execute(self,
161160
socks = dict(self._poller.poll(request_timeout))
162161
if socks.get(self._client) == zmq.POLLIN:
163162
response = self._recv_response()
164-
LOG.debug("Response %s", pformat(response))
165163
return response
166164
else:
167165
LOG.warning("No response from server, retrying...")
@@ -175,6 +173,12 @@ def execute(self,
175173
self._send_request(request)
176174

177175
raise SQLiteRxConnectionError("No response after retrying. Abandoning Request")
176+
177+
def __enter__(self):
178+
return self
179+
180+
def __exit__(self, exc_type, exc_value, traceback):
181+
self.shutdown()
178182

179183
def shutdown(self):
180184
try:
@@ -183,10 +187,10 @@ def shutdown(self):
183187
self._poller.unregister(self._client)
184188
except zmq.ZMQError as e:
185189
if e.errno in (zmq.EINVAL,
186-
zmq.EPROTONOSUPPORT,
187-
zmq.ENOCOMPATPROTO,
188-
zmq.EADDRINUSE,
189-
zmq.EADDRNOTAVAIL,):
190+
zmq.EPROTONOSUPPORT,
191+
zmq.ENOCOMPATPROTO,
192+
zmq.EADDRINUSE,
193+
zmq.EADDRNOTAVAIL,):
190194
LOG.error("ZeroMQ Transportation endpoint was not setup")
191195

192196
elif e.errno in (zmq.ENODEV, zmq.ENOTSOCK,):

sqlite_rx/server.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
import sys
77
import traceback
88
import zlib
9-
from pprint import pformat
109
from signal import SIGTERM, SIGINT, signal
1110

1211
from typing import List, Union, Callable
1312

14-
import billiard as multiprocess
13+
import billiard as multiprocessing
1514
import msgpack
1615
import zmq
1716
from sqlite_rx import get_version
@@ -31,7 +30,7 @@
3130
__all__ = ['SQLiteServer']
3231

3332

34-
class SQLiteZMQProcess(multiprocess.Process):
33+
class SQLiteZMQProcess(multiprocessing.Process):
3534

3635
def __init__(self, *args, **kwargs):
3736
"""The :class: ``sqlite_rx.server.SQLiteServer`` is intended to run as an isolated process.
@@ -181,18 +180,12 @@ def handle_signal(self, signum, frame):
181180
self.socket.close()
182181
self.loop.stop()
183182

184-
# self.terminate()
185-
186-
# If backup process was started then wait for the backup thread to finish.
187183
if self.back_up_recurring_thread:
188-
# self.back_up_recurring_thread.join()
189-
# pthread_kill(self.back_up_recurring_thread.get_ident())
190184
self.back_up_recurring_thread.cancel()
191185

192186
os._exit(os.EX_OK)
193187

194188

195-
196189
def run(self):
197190
LOG.info("Setting up signal handlers")
198191

@@ -254,7 +247,6 @@ def __call__(self, message: List):
254247
self._rep_stream.send(zlib.compress(msgpack.dumps(result)))
255248

256249
def execute(self, message: dict, *args, **kwargs):
257-
LOG.debug("Request received is %s", pformat(message))
258250
execute_many = message['execute_many']
259251
execute_script = message['execute_script']
260252
error = None

sqlite_rx/tests/backup/__init__.py

Whitespace-only changes.

sqlite_rx/tests/backup/conftest.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
import platform
3+
import signal
4+
import tempfile
5+
import pytest
6+
7+
import sqlite3
8+
import logging.config
9+
10+
from collections import namedtuple
11+
from sqlite_rx import backup, get_default_logger_settings
12+
from sqlite_rx.client import SQLiteClient
13+
from sqlite_rx.server import SQLiteServer
14+
15+
logging.config.dictConfig(get_default_logger_settings(level="DEBUG"))
16+
17+
LOG = logging.getLogger(__file__)
18+
19+
backup_event = namedtuple('backup_event', ('client', 'backup_database'))
20+
21+
22+
@pytest.fixture(scope="module")
23+
def plain_client():
24+
auth_config = {
25+
sqlite3.SQLITE_OK: {
26+
sqlite3.SQLITE_DROP_TABLE
27+
}
28+
}
29+
with tempfile.NamedTemporaryFile() as back_db_file, tempfile.NamedTemporaryFile() as main_db_file:
30+
server = SQLiteServer(bind_address="tcp://127.0.0.1:5003",
31+
database=main_db_file.name,
32+
auth_config=auth_config,
33+
backup_database=back_db_file.name,
34+
backup_interval=1)
35+
36+
client = SQLiteClient(connect_address="tcp://127.0.0.1:5003")
37+
38+
event = backup_event(client=client, backup_database=back_db_file.name)
39+
40+
server.start()
41+
42+
LOG.info("Started Test SQLiteServer")
43+
44+
yield event
45+
46+
if platform.system().lower() == 'windows':
47+
os.system("taskkill /F /pid "+str(server.pid))
48+
else:
49+
os.kill(server.pid, signal.SIGINT)
50+
51+
server.join()
52+
client.shutdown()
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import sys
2+
import platform
3+
import sqlite3
4+
import os
5+
import time
6+
7+
from sqlite_rx import backup
8+
9+
sqlite_error_prefix = "sqlite3.OperationalError"
10+
11+
if platform.python_implementation() == "PyPy":
12+
sqlite_error_prefix = "_sqlite3.OperationalError"
13+
14+
15+
def test_not_present(plain_client):
16+
result = plain_client.client.execute('SELECT * FROM IDOLS')
17+
expected_result = {
18+
'items': [],
19+
'error': {
20+
'message': '{0}: no such table: IDOLS'.format(sqlite_error_prefix),
21+
'type': '{0}'.format(sqlite_error_prefix)}}
22+
assert type(result) == dict
23+
assert result == expected_result
24+
25+
26+
27+
def test_table_creation(plain_client):
28+
result = plain_client.client.execute('CREATE TABLE stocks (date text, trans text, symbol text, qty real, price real)')
29+
expected_result = {"error": None, 'items': []}
30+
assert result == expected_result
31+
32+
33+
def test_table_rows_insertion(plain_client):
34+
purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
35+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
36+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
37+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
38+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
39+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
40+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
41+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
42+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
43+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
44+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
45+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
46+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
47+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
48+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
49+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
50+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
51+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
52+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
53+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
54+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
55+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
56+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
57+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
58+
('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
59+
('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
60+
('2006-04-06', 'SELL', 'XOM', 500, 53.00),
61+
]
62+
63+
result = plain_client.client.execute('INSERT INTO stocks VALUES (?,?,?,?,?)', *purchases, execute_many=True)
64+
expected_result = {'error': None, 'items': [], 'rowcount': 27}
65+
assert result == expected_result
66+
67+
if (sys.version_info.major == 3 and sys.version_info.minor >= 7):
68+
time.sleep(1) # wait for the backup thread to finish backing up.
69+
70+
backup_database = plain_client.backup_database
71+
backup_connection = sqlite3.connect(database=backup_database,
72+
isolation_level=None,
73+
check_same_thread=False)
74+
assert os.path.exists(backup_database) == True
75+
result = backup_connection.execute("SELECT * FROM stocks").fetchall()
76+
assert len(result) == 27
77+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import sys
2+
import pytest
3+
4+
from sqlite_rx.exception import SQLiteRxBackUpError
5+
from sqlite_rx.server import SQLiteServer
6+
7+
8+
def test_backup_exception():
9+
10+
if not (sys.version_info.major == 3 and sys.version_info.minor >= 7):
11+
with pytest.raises(SQLiteRxBackUpError):
12+
server = SQLiteServer(bind_address="tcp://127.0.0.1:5002", database=":memory:", backup_database='backup.db')

0 commit comments

Comments
 (0)