Skip to content

Commit 1d9d78a

Browse files
authored
Merge pull request #4 from nside/openapi-key-type
Propagate primary key type through openapi
2 parents 62e9691 + 666b5b0 commit 1d9d78a

File tree

4 files changed

+94
-29
lines changed

4 files changed

+94
-29
lines changed

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.2.0',
9+
version='1.3.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/database.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def get_primary_key(self, table_name):
1414
columns = self.cursor.fetchall()
1515
for column in columns:
1616
if column[5]: # The 6th item in the tuple is 1 if the column is the primary key, 0 otherwise
17-
return column[1] # The 2nd item in the tuple is the column name
18-
return None
17+
return column[1], column[2] # The 2nd item in the tuple is the column name, the 3rd item is the column type
18+
return None, None
1919

2020
def get_records(self, table_name, page, per_page):
2121
offset = (page - 1) * per_page
@@ -25,7 +25,7 @@ def get_records(self, table_name, page, per_page):
2525
return records
2626

2727
def get_record(self, table_name, key):
28-
primary_key = self.get_primary_key(table_name)
28+
primary_key, _ = self.get_primary_key(table_name)
2929
self.cursor.execute(f"SELECT * FROM {table_name} WHERE {primary_key} = ?;", (key,))
3030
row = self.cursor.fetchone()
3131
if row is None:
@@ -41,13 +41,13 @@ def create_record(self, table_name, data):
4141
self.conn.commit()
4242

4343
def update_record(self, table_name, key, data):
44-
primary_key = self.get_primary_key(table_name)
44+
primary_key, _ = self.get_primary_key(table_name)
4545
set_clause = ', '.join(f"{column} = ?" for column in data.keys())
4646
self.cursor.execute(f"UPDATE {table_name} SET {set_clause} WHERE {primary_key} = ?;", tuple(data.values()) + (key,))
4747
self.conn.commit()
4848

4949
def delete_record(self, table_name, key):
50-
primary_key = self.get_primary_key(table_name)
50+
primary_key, _ = self.get_primary_key(table_name)
5151
self.cursor.execute(f"DELETE FROM {table_name} WHERE {primary_key} = ?;", (key,))
5252
self.conn.commit()
5353

sqlite2rest/openapi.py

+87-22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,89 @@
22
from openapi_spec_validator import validate_spec
33
import yaml
44

5-
def generate_openapi_spec():
5+
def get_operation_summary(method):
6+
return {
7+
'GET': 'Retrieve all records from',
8+
'POST': 'Create a new record in',
9+
'PUT': 'Update a record in',
10+
'DELETE': 'Delete a record from',
11+
'PATCH': 'Partially update a record in',
12+
'TRACE': 'Trace a request to'
13+
}.get(method, 'Perform operation on')
14+
15+
def add_paging_parameters(operation_obj):
16+
operation_obj["parameters"] = [
17+
{
18+
"name": "page",
19+
"in": "query",
20+
"description": "Page number to retrieve",
21+
"required": False,
22+
"schema": {
23+
"type": "integer",
24+
"default": 1
25+
}
26+
},
27+
{
28+
"name": "per_page",
29+
"in": "query",
30+
"description": "Number of records per page",
31+
"required": False,
32+
"schema": {
33+
"type": "integer",
34+
"default": 10
35+
}
36+
}
37+
]
38+
39+
def add_operation_to_path(path_item, method, rule_str, primary_key_type):
40+
operation = get_operation_summary(method)
41+
table_name = rule_str.split('/')[1]
42+
operation_obj = {
43+
"summary": f"{operation} the {table_name} table",
44+
"responses": {
45+
"200": {
46+
"description": "OK"
47+
}
48+
}
49+
}
50+
if method == 'GET':
51+
if '<id>' in rule_str:
52+
operation_obj["parameters"] = [
53+
{
54+
"name": "id",
55+
"in": "path",
56+
"description": "The ID of the record to retrieve",
57+
"required": True,
58+
"schema": {
59+
"type": primary_key_type,
60+
}
61+
}
62+
]
63+
else:
64+
add_paging_parameters(operation_obj)
65+
path_item[method.lower()] = operation_obj
66+
67+
def sqlite_type_to_openapi_type(sqlite_type):
68+
"""
69+
Convert SQLite data types to OpenAPI data types.
70+
"""
71+
sqlite_type = sqlite_type.upper()
72+
if sqlite_type in ["INT", "INTEGER", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT", "UNSIGNED BIG INT", "INT2", "INT8"]:
73+
return "integer"
74+
elif sqlite_type in ["REAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT"]:
75+
return "number"
76+
elif sqlite_type in ["TEXT", "CHARACTER", "VARCHAR", "VARYING CHARACTER", "NCHAR", "NATIVE CHARACTER", "NVARCHAR", "CLOB"]:
77+
return "string"
78+
elif sqlite_type in ["BLOB"]:
79+
return "string", "byte"
80+
elif sqlite_type in ["BOOLEAN"]:
81+
return "boolean"
82+
elif sqlite_type in ["DATE", "DATETIME"]:
83+
return "string", "date-time"
84+
else:
85+
return "string"
86+
87+
def generate_openapi_spec(db):
688
# Basic OpenAPI spec
789
spec = {
890
"openapi": "3.0.0",
@@ -28,33 +110,16 @@ def generate_openapi_spec():
28110
# Add an operation object for each method
29111
for method in rule.methods:
30112
if method in ['GET', 'POST', 'PUT', 'DELETE']:
31-
operation = {
32-
'GET': 'Retrieve all records from',
33-
'POST': 'Create a new record in',
34-
'PUT': 'Update a record in',
35-
'DELETE': 'Delete a record from',
36-
'PATCH': 'Partially update a record in',
37-
'TRACE': 'Trace a request to'
38-
}.get(method, 'Perform operation on')
39-
40113
table_name = str(rule).split('/')[1]
41-
42-
path_item[method.lower()] = {
43-
"summary": f"{operation} the {table_name} table",
44-
"responses": {
45-
"200": {
46-
"description": "OK"
47-
}
48-
}
49-
}
114+
_, primary_key_type = db.get_primary_key(table_name)
115+
add_operation_to_path(path_item, method, str(rule), sqlite_type_to_openapi_type(primary_key_type))
50116

51117
# Validate the spec
52118
validate_spec(spec)
53119

54120
# Return the spec as a dictionary
55121
return spec
56122

57-
def get_openapi_spec():
58-
spec = generate_openapi_spec()
123+
def get_openapi_spec(db):
124+
spec = generate_openapi_spec(db)
59125
return yaml.dump(spec)
60-

sqlite2rest/routes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ def delete_record(id):
5858
@app.route('/openapi.yaml', methods=['GET'])
5959
def openapi():
6060
app.logger.info('Getting OpenAPI specification')
61-
spec = get_openapi_spec()
61+
spec = get_openapi_spec(get_database())
6262
return spec, 200, {'Content-Type': 'text/vnd.yaml'}
6363

0 commit comments

Comments
 (0)