Skip to content

Commit bda5ed8

Browse files
committed
chore(python): implement get request with path parameters
Part of #16
1 parent 06f48b0 commit bda5ed8

File tree

3 files changed

+47
-17
lines changed

3 files changed

+47
-17
lines changed

examples/python/routes.py

+21-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22
import psycopg2
3+
import psycopg2.extras
34

4-
from fastapi import APIRouter
5+
from fastapi import APIRouter, HTTPException
56

67
router = APIRouter()
78

@@ -16,14 +17,17 @@ def get_v1_categories_count():
1617
port = 5432)
1718
try:
1819
with conn:
19-
with conn.cursor() as cur:
20+
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
2021
cur.execute('SELECT COUNT(*) AS counter FROM categories')
21-
return cur.fetchone()[0]
22+
result = cur.fetchone()
23+
if result is None:
24+
raise HTTPException(status_code=404)
25+
return result
2226
finally:
2327
conn.close()
2428

2529
@router.get('/v1/collections/{collectionId}/categories/count')
26-
def get_v1_collections_collection_id_categories_count():
30+
def get_v1_collections_collection_id_categories_count(collectionId):
2731
conn = psycopg2.connect(
2832
database = os.getenv('DB_NAME'),
2933
user = os.getenv('DB_USER'),
@@ -32,9 +36,12 @@ def get_v1_collections_collection_id_categories_count():
3236
port = 5432)
3337
try:
3438
with conn:
35-
with conn.cursor() as cur:
36-
cur.execute('SELECT COUNT(DISTINCT s.category_id) AS counter FROM collections_series cs JOIN series s ON s.id = cs.series_id WHERE cs.collection_id = :collectionId')
37-
return cur.fetchone()[0]
39+
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
40+
cur.execute('SELECT COUNT(DISTINCT s.category_id) AS counter FROM collections_series cs JOIN series s ON s.id = cs.series_id WHERE cs.collection_id = %(collectionId)s', { "collectionId": collectionId })
41+
result = cur.fetchone()
42+
if result is None:
43+
raise HTTPException(status_code=404)
44+
return result
3845
finally:
3946
conn.close()
4047

@@ -47,7 +54,7 @@ def post_v1_categories():
4754
pass
4855

4956
@router.get('/v1/categories/{categoryId}')
50-
def get_v1_categories_category_id():
57+
def get_v1_categories_category_id(categoryId):
5158
conn = psycopg2.connect(
5259
database = os.getenv('DB_NAME'),
5360
user = os.getenv('DB_USER'),
@@ -56,9 +63,12 @@ def get_v1_categories_category_id():
5663
port = 5432)
5764
try:
5865
with conn:
59-
with conn.cursor() as cur:
60-
cur.execute('SELECT id , name , name_ru , slug FROM categories WHERE id = :categoryId')
61-
return cur.fetchone()[0]
66+
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
67+
cur.execute('SELECT id , name , name_ru , slug FROM categories WHERE id = %(categoryId)s', { "categoryId": categoryId })
68+
result = cur.fetchone()
69+
if result is None:
70+
raise HTTPException(status_code=404)
71+
return result
6272
finally:
6373
conn.close()
6474

src/cli.js

+4
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ const createEndpoints = async (destDir, lang, config) => {
164164
// "... WHERE id = :p.id" => [ "p.id" ]
165165
"extractParamsFromQuery": (query) => query.match(/(?<=:)[pb]\.\w+/g) || [],
166166

167+
// "/categories/:categoryId" => [ "categoryId" ]
168+
// (used only with FastAPI)
169+
"extractParamsFromPath": (query) => query.match(/(?<=:)\w+/g) || [],
170+
167171
// [ "p.page", "b.num" ] => '"page": req.params.page, "num": req.body.num'
168172
// (used only with Express)
169173
"formatParamsAsJavaScriptObject": (params) => {

src/templates/routes.py.ejs

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22
import psycopg2
3+
import psycopg2.extras
34

4-
from fastapi import APIRouter
5+
from fastapi import APIRouter, HTTPException
56

67
router = APIRouter()
78

@@ -12,6 +13,12 @@ function generate_method_name(method, path) {
1213
return `${method}${name}`
1314
}
1415
16+
// "INSERT INTO ... VALUES(:categoryId)" => "INSERT INTO ... VALUES(%(categoryId)s)"
17+
// See: https://www.psycopg.org/docs/usage.html#passing-parameters-to-sql-queries
18+
function convertToPsycopgNamedArguments(sql) {
19+
return sql.replace(/:([_a-zA-Z]+)/g, '%($1)s')
20+
}
21+
1522
// "/categories/:categoryId" => "/categories/{categoryId}"
1623
function convertToFastApiPath(path) {
1724
return path.replace(/:([_a-zA-Z]+)/g, '{$1}')
@@ -20,17 +27,23 @@ function convertToFastApiPath(path) {
2027
2128
endpoints.forEach(function(endpoint) {
2229
const path = convertToFastApiPath(endpoint.path)
30+
const paramsFromPath = extractParamsFromPath(endpoint.path)
2331
2432
endpoint.methods.forEach(function(method) {
2533
const hasGetOne = method.name === 'get'
2634
const hasGetMany = method.name === 'get_list'
2735
const pythonMethodName = generate_method_name(method.name, path)
28-
const sql = formatQuery(method.query)
36+
const sql = convertToPsycopgNamedArguments(formatQuery(method.query))
37+
const params = extractParamsFromQuery(method.query);
38+
const formattedParams = params.length > 0
39+
// [ "p.categoryId" ] => [ '"categoryId": categoryId' ]
40+
? ', { ' + params.map(param => param.substring(2)).map(param => `"${param}": ${param}`).join(', ') + ' }'
41+
: ''
2942
3043
if (hasGetOne || hasGetMany) {
3144
%>
3245
@router.get('<%- path %>')
33-
def <%- pythonMethodName %>():
46+
def <%- pythonMethodName %>(<%- paramsFromPath.join(', ') %>):
3447
<% if (hasGetOne) { -%>
3548
conn = psycopg2.connect(
3649
database = os.getenv('DB_NAME'),
@@ -40,9 +53,12 @@ def <%- pythonMethodName %>():
4053
port = 5432)
4154
try:
4255
with conn:
43-
with conn.cursor() as cur:
44-
cur.execute('<%- sql %>')
45-
return cur.fetchone()[0]
56+
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
57+
cur.execute('<%- sql %>'<%- formattedParams %>)
58+
result = cur.fetchone()
59+
if result is None:
60+
raise HTTPException(status_code=404)
61+
return result
4662
finally:
4763
conn.close()
4864
<%

0 commit comments

Comments
 (0)