Skip to content

Commit c4c2f61

Browse files
authored
Merge pull request #1 from nside/1.1.0
1.1.0
2 parents 46e9409 + f195aff commit c4c2f61

File tree

7 files changed

+109
-31
lines changed

7 files changed

+109
-31
lines changed

.github/workflows/publish.yaml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Publish Python 🐍 distributions 📦 to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
build-n-publish:
9+
name: Build and publish Python 🐍 distributions 📦 to PyPI
10+
runs-on: ubuntu-18.04
11+
12+
steps:
13+
- uses: actions/checkout@v2
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: 3.8
19+
20+
- name: Install pypa/build
21+
run: python -m pip install build
22+
23+
- name: Build a binary wheel and a source tarball
24+
run: python -m build --sdist --wheel --outdir dist/
25+
26+
- name: Publish distribution 📦 to PyPI
27+
uses: pypa/gh-action-pypi-publish@release/v1
28+
with:
29+
user: __token__
30+
password: ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/test.yaml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Run Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Check out code
10+
uses: actions/checkout@v2
11+
12+
- name: Set up Python 3.8
13+
uses: actions/setup-python@v2
14+
with:
15+
python-version: 3.8
16+
17+
- name: Install package
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install -e .
21+
22+
- name: Run tests
23+
run: |
24+
python -m unittest discover tests

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
setup(
88
name='sqlite2rest',
9-
version='1.0.0',
9+
version='1.1.0',
1010
description='A Python library for creating a RESTful API from an SQLite database using Flask.',
1111
author='Denis Laprise',
1212
author_email='[email protected]',

sqlite2rest/app.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import logging
22
import sys
33
from flask import Flask, g
4+
from .database import Database
45
from .routes import setup_routes
56

67
def create_app(database_uri):
78
# Initialize Flask app
89
app = Flask(__name__)
910

10-
setup_routes(app, database_uri)
11+
def get_db():
12+
if 'db' not in g:
13+
g.db = Database(database_uri)
14+
return g.db
15+
tables = Database(database_uri).get_tables()
16+
setup_routes(app, tables, get_db)
1117

1218
# Configure logging
1319
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
1420
app.logger.addHandler(logging.StreamHandler(sys.stderr))
1521
app.logger.setLevel(logging.DEBUG)
1622

23+
@app.teardown_appcontext
24+
def teardown_db(exception):
25+
db = g.pop('db', None)
26+
27+
if db is not None:
28+
db.close()
29+
1730
return app

sqlite2rest/database.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class Database:
44
def __init__(self, db_uri):
5-
self.conn = sqlite3.connect(db_uri, check_same_thread=False)
5+
self.conn = sqlite3.connect(db_uri)
66
self.cursor = self.conn.cursor()
77

88
def get_tables(self):
@@ -19,12 +19,16 @@ def get_primary_key(self, table_name):
1919

2020
def get_records(self, table_name):
2121
self.cursor.execute(f"SELECT * FROM {table_name};")
22-
return self.cursor.fetchall()
22+
col_names = [description[0] for description in self.cursor.description]
23+
records = [dict(zip(col_names, record)) for record in self.cursor.fetchall()]
24+
return records
2325

2426
def get_record(self, table_name, key):
2527
primary_key = self.get_primary_key(table_name)
2628
self.cursor.execute(f"SELECT * FROM {table_name} WHERE {primary_key} = ?;", (key,))
27-
return self.cursor.fetchone()
29+
col_names = [description[0] for description in self.cursor.description]
30+
record = dict(zip(col_names, self.cursor.fetchone()))
31+
return record
2832

2933
def create_record(self, table_name, data):
3034
columns = ', '.join(data.keys())
@@ -43,3 +47,5 @@ def delete_record(self, table_name, key):
4347
self.cursor.execute(f"DELETE FROM {table_name} WHERE {primary_key} = ?;", (key,))
4448
self.conn.commit()
4549

50+
def close(self):
51+
self.conn.close()

sqlite2rest/routes.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
from flask import jsonify, request
2-
from .database import Database
32
from .openapi import get_openapi_spec
43

5-
def setup_routes(app, database_uri):
6-
tables = Database(database_uri).get_tables()
7-
4+
def setup_routes(app, tables, get_database):
85
def create_get_records_fn(table_name):
96
def get_records():
107
app.logger.info(f'Getting records for table {table_name}')
11-
records = Database(database_uri).get_records(table_name)
8+
records = get_database().get_records(table_name)
129
return jsonify(records), 200, {'Content-Type': 'application/json'}
1310
get_records.__name__ = f'get_records_{table_name}'
1411
return get_records
@@ -17,7 +14,7 @@ def create_create_record_fn(table_name):
1714
def create_record():
1815
data = request.get_json()
1916
app.logger.info(f'Creating record in table {table_name} with data {data}')
20-
Database(database_uri).create_record(table_name, data)
17+
get_database().create_record(table_name, data)
2118
return jsonify({'message': 'Record created.'}), 201, {'Content-Type': 'application/json'}
2219
create_record.__name__ = f'create_record_{table_name}'
2320
return create_record
@@ -26,15 +23,15 @@ def create_update_record_fn(table_name):
2623
def update_record(id):
2724
data = request.get_json()
2825
app.logger.info(f'Updating record with id {id} in table {table_name} with data {data}')
29-
Database(database_uri).update_record(table_name, id, data)
26+
get_database().update_record(table_name, id, data)
3027
return jsonify({'message': 'Record updated.'}), 200, {'Content-Type': 'application/json'}
3128
update_record.__name__ = f'update_record_{table_name}'
3229
return update_record
3330

3431
def create_delete_record_fn(table_name):
3532
def delete_record(id):
3633
app.logger.info(f'Deleting record with id {id} from table {table_name}')
37-
Database(database_uri).delete_record(table_name, id)
34+
get_database().delete_record(table_name, id)
3835
return jsonify({'message': 'Record deleted.'}), 200, {'Content-Type': 'application/json'}
3936
delete_record.__name__ = f'delete_record_{table_name}'
4037
return delete_record

tests/test_routes.py

+26-18
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,61 @@
1-
import unittest
21
import json
32
import os
4-
import shutil
5-
from sqlite2rest import create_app
3+
from sqlite2rest import Database, create_app
4+
import unittest
65

76
class TestRoutes(unittest.TestCase):
87
@classmethod
98
def setUpClass(cls):
10-
# Copy the database file before running the tests
11-
shutil.copyfile('data/chinook.db', 'test_chinook.db')
9+
# Use a database file for testing
10+
cls.db_uri = 'test.db'
11+
cls.db = Database(cls.db_uri)
12+
13+
# Create a test table
14+
cls.db.cursor.execute('CREATE TABLE Artist (ArtistId INTEGER PRIMARY KEY, Name TEXT);')
15+
cls.db.conn.commit()
1216

1317
@classmethod
1418
def tearDownClass(cls):
15-
# Delete the copied database file after running the tests
16-
os.remove('test_chinook.db')
19+
# Delete the database file after running the tests
20+
os.remove('test.db')
1721

1822
def setUp(self):
19-
# Use the copied database file for testing
20-
self.app = create_app('test_chinook.db')
23+
# Create the Flask app
24+
self.app = create_app(self.db_uri)
2125
self.client = self.app.test_client()
2226

23-
def test_get(self):
27+
def test_0get(self):
2428
response = self.client.get('/Artist')
2529
self.assertEqual(response.status_code, 200)
2630
artists = json.loads(response.data)
2731
self.assertIsInstance(artists, list)
32+
self.assertEqual(artists, [])
2833

2934
def test_create(self):
30-
response = self.client.post('/Artist', json={'Name': 'Test Artist'})
35+
response = self.client.post('/Artist', json={'ArtistId': 1, 'Name': 'Test Artist'})
3136
self.assertEqual(response.status_code, 201)
3237
self.assertEqual(json.loads(response.data), {'message': 'Record created.'})
3338

39+
# Verify the creation by reading it back
40+
response = self.client.get('/Artist')
41+
self.assertEqual(response.status_code, 200)
42+
artists = json.loads(response.data)
43+
self.assertEqual(artists, [{'ArtistId': 1, 'Name': 'Test Artist'}])
44+
3445
def test_update(self):
3546
# First, create a record to update
36-
self.client.post('/Artist', json={'Name': 'Test Artist'})
47+
self.client.post('/Artist', json={'ArtistId': 2, 'Name': 'Test Artist'})
3748

3849
# Then, update the record
39-
response = self.client.put('/Artist/1', json={'Name': 'Updated Artist'})
50+
response = self.client.put('/Artist/2', json={'Name': 'Updated Artist'})
4051
self.assertEqual(response.status_code, 200)
4152
self.assertEqual(json.loads(response.data), {'message': 'Record updated.'})
4253

4354
def test_delete(self):
4455
# First, create a record to delete
45-
self.client.post('/Artist', json={'Name': 'Test Artist'})
56+
self.client.post('/Artist', json={'ArtistId': 3, 'Name': 'Test Artist'})
4657

4758
# Then, delete the record
48-
response = self.client.delete('/Artist/1')
59+
response = self.client.delete('/Artist/3')
4960
self.assertEqual(response.status_code, 200)
5061
self.assertEqual(json.loads(response.data), {'message': 'Record deleted.'})
51-
52-
if __name__ == '__main__':
53-
unittest.main()

0 commit comments

Comments
 (0)