Skip to content

Commit 75e2091

Browse files
committed
feat(python): add support to aggregate multiple queries within a single response
Part of #17
1 parent 10a4915 commit 75e2091

File tree

4 files changed

+68
-12
lines changed

4 files changed

+68
-12
lines changed

examples/js/endpoints.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
counter:
88
type: integer
99

10+
- path: /v1/categories/stat
11+
get:
12+
aggregated_queries:
13+
total: SELECT COUNT(*) FROM categories
14+
in_russian: SELECT COUNT(*) FROM categories WHERE name_ru IS NOT NULL
15+
in_english: SELECT COUNT(*) FROM categories WHERE name IS NOT NULL
16+
fully_translated: SELECT COUNT(*) FROM categories WHERE name IS NOT NULL AND name_ru IS NOT NULL
17+
1018
- path: /v1/collections/:collectionId/categories/count
1119
get:
1220
query: >-

examples/python/routes.py

+24
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,30 @@ def get_v1_categories_count():
2626
finally:
2727
conn.close()
2828

29+
@router.get('/v1/categories/stat')
30+
def get_v1_categories_stat():
31+
conn = psycopg2.connect(
32+
database = os.getenv('DB_NAME'),
33+
user = os.getenv('DB_USER'),
34+
password = os.getenv('DB_PASSWORD'),
35+
host = os.getenv('DB_HOST', 'localhost'),
36+
port = 5432)
37+
try:
38+
with conn:
39+
with conn.cursor(cursor_factory = psycopg2.extras.DictCursor) as cur:
40+
result = {}
41+
cur.execute('SELECT COUNT(*) FROM categories')
42+
result['total'] = cur.fetchone()[0]
43+
cur.execute('SELECT COUNT(*) FROM categories WHERE name_ru IS NOT NULL')
44+
result['in_russian'] = cur.fetchone()[0]
45+
cur.execute('SELECT COUNT(*) FROM categories WHERE name IS NOT NULL')
46+
result['in_english'] = cur.fetchone()[0]
47+
cur.execute('SELECT COUNT(*) FROM categories WHERE name IS NOT NULL AND name_ru IS NOT NULL')
48+
result['fully_translated'] = cur.fetchone()[0]
49+
return result
50+
finally:
51+
conn.close()
52+
2953
@router.get('/v1/collections/{collectionId}/categories/count')
3054
def get_v1_collections_collection_id_categories_count(collectionId):
3155
conn = psycopg2.connect(

src/cli.js

+2
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ const createEndpoints = async (destDir, lang, config) => {
138138
let queries = []
139139
if (method.query) {
140140
queries.push(method.query)
141+
} else if (method.aggregated_queries) {
142+
queries = Object.values(method.aggregated_queries)
141143
}
142144
queries.forEach(query => {
143145
const sql = removePlaceholders(flattenQuery(query));

src/templates/routes.py.ejs

+34-12
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,26 @@ endpoints.forEach(function(endpoint) {
3434
const hasGetMany = method.name === 'get_list'
3535
const pythonMethodName = generate_method_name(method.name, path)
3636
37-
let queries = []
37+
const queriesWithNames = []
3838
if (method.query) {
39-
queries.push(method.query)
39+
queriesWithNames.push({ "result" : method.query })
40+
} else if (method.aggregated_queries) {
41+
for (const [key, value] of Object.entries(method.aggregated_queries)) {
42+
queriesWithNames.push({ [key]: value })
43+
}
4044
}
4145
42-
queries = queries.map(query => {
43-
const sql = convertToPsycopgNamedArguments(formatQuery(query))
44-
const params = extractParamsFromQuery(query);
45-
const formattedParams = params.length > 0
46-
// [ "p.categoryId" ] => [ '"categoryId": categoryId' ]
47-
? ', { ' + params.map(param => param.substring(2)).map(param => `"${param}": ${param}`).join(', ') + ' }'
48-
: ''
49-
return ({ sql : sql, formattedParams: formattedParams })
46+
const queries = []
47+
queriesWithNames.forEach(queryWithName => {
48+
for (const [name, query] of Object.entries(queryWithName)) {
49+
const sql = convertToPsycopgNamedArguments(formatQuery(query))
50+
const params = extractParamsFromQuery(query);
51+
const formattedParams = params.length > 0
52+
// [ "p.categoryId" ] => [ '"categoryId": categoryId' ]
53+
? ', { ' + params.map(param => param.substring(2)).map(param => `"${param}": ${param}`).join(', ') + ' }'
54+
: ''
55+
queries.push({ [name]: { sql : sql, formattedParams: formattedParams }})
56+
}
5057
})
5158
5259
if (hasGetOne || hasGetMany) {
@@ -62,14 +69,29 @@ def <%- pythonMethodName %>(<%- paramsFromPath.join(', ') %>):
6269
port = 5432)
6370
try:
6471
with conn:
72+
<% if (queries.length > 1) { /* we can omit cursor_factory but in this case we might get an unused import */-%>
73+
with conn.cursor(cursor_factory = psycopg2.extras.DictCursor) as cur:
74+
result = {}
75+
<% queries.forEach(queryInfo => {
76+
for (const [name, query] of Object.entries(queryInfo)) {
77+
-%>
78+
cur.execute('<%- query.sql %>'<%- query.formattedParams %>)
79+
result['<%- name %>'] = cur.fetchone()[0]
80+
<% }
81+
})
82+
-%>
83+
return result
84+
<%
85+
} else {
86+
const query = queries[0].result
87+
-%>
6588
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
66-
<% queries.forEach(query => { -%>
6789
cur.execute('<%- query.sql %>'<%- query.formattedParams %>)
6890
result = cur.fetchone()
6991
if result is None:
7092
raise HTTPException(status_code=404)
7193
return result
72-
<% }) -%>
94+
<% } -%>
7395
finally:
7496
conn.close()
7597
<%

0 commit comments

Comments
 (0)