From 4aa617c7992c6397dd66d1528d83c7f52720e07b Mon Sep 17 00:00:00 2001 From: Mixel2004 <84668201+Mixel2004@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:59:43 +0530 Subject: [PATCH 1/6] Python Code Generator Added. --- lib/codegen/python/pkg_request.dart | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 lib/codegen/python/pkg_request.dart diff --git a/lib/codegen/python/pkg_request.dart b/lib/codegen/python/pkg_request.dart new file mode 100644 index 00000000..046aa3bd --- /dev/null +++ b/lib/codegen/python/pkg_request.dart @@ -0,0 +1,88 @@ +import 'package:jinja/jinja.dart' as jj; +import 'package:apidash/models/models.dart' show RequestModel; + +class PythonRequestCodeGen { + String kPythonTemplate = ''' +import requests + +def main(): + url = '{{url}}' + + {{params}} + + {{body}} + + headers = {{headers}} + + response = requests.{{method}}(url{{request_headers}}{{request_body}}) + + status_code = response.status_code + if 200 <= status_code < 300: + print('Status Code:', status_code) + print('Response Body:', response.text) + else: + print('Error Status Code:', status_code) + print('Error Response Body:', response.text) + +main() +'''; + + String? getCode(RequestModel requestModel, String defaultUriScheme) { + try { + bool hasHeaders = false; + bool hasBody = false; + + String url = requestModel.url; + if (!url.contains('://') && url.isNotEmpty) { + url = '$defaultUriScheme://$url'; + } + + var paramsList = requestModel.requestParams; + String params = ''; + if (paramsList != null) { + params = ''; + for (var param in paramsList) { + params += '${param.k} = ${param.v}\n '; + } + } + + var method = requestModel.method.name.toLowerCase(); + + var requestBody = requestModel.requestBody; + String requestBodyString = ''; + if (requestBody != null) { + hasBody = true; + requestBodyString = "data = '''$requestBody'''\n "; + } + + var headersList = requestModel.requestHeaders; + String headers = ''; + if (headersList != null || hasBody) { + headers = ''; + for (var header in headersList ?? []) { + headers += "'${header.k}': '${header.v}',\n "; + } + if (hasBody) { + headers += + "'Content-Type': '${requestModel.requestBodyContentType}',\n "; + } + hasHeaders = headers.isNotEmpty; + } + + var template = jj.Template(kPythonTemplate); + var pythonCode = template.render({ + 'url': url, + 'params': params, + 'body': requestBodyString, + 'headers': '{\n $headers}', + 'method': method, + 'request_headers': hasHeaders ? 'headers=headers,\n ' : '', + 'request_body': hasBody ? 'data=data,\n ' : '', + }); + + return pythonCode; + } catch (e) { + return null; + } + } +} From 934a0036004737e67fce0468aa15ed82998c0120 Mon Sep 17 00:00:00 2001 From: Mixel2004 <84668201+Mixel2004@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:09:57 +0530 Subject: [PATCH 2/6] Updated Python into the Language Dropdown --- lib/codegen/codegen.dart | 3 +++ lib/consts.dart | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/codegen/codegen.dart b/lib/codegen/codegen.dart index eb5bb0ef..5eb60121 100644 --- a/lib/codegen/codegen.dart +++ b/lib/codegen/codegen.dart @@ -1,4 +1,5 @@ import 'package:apidash/codegen/kotlin/pkg_okhttp.dart'; +import 'package:apidash/codegen/python/pkg_request.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/models/models.dart' show RequestModel; @@ -15,6 +16,8 @@ class Codegen { return DartHttpCodeGen().getCode(requestModel, defaultUriScheme); case CodegenLanguage.kotlinOkHttp: return KotlinOkHttpCodeGen().getCode(requestModel); + case CodegenLanguage.pythonRequests: + return PythonRequestCodeGen().getCode(requestModel, defaultUriScheme); default: throw ArgumentError('Invalid codegenLanguage'); } diff --git a/lib/consts.dart b/lib/consts.dart index f1390a3c..ac956bc0 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -230,6 +230,7 @@ const kDefaultContentType = ContentType.json; enum CodegenLanguage { dartHttp("Dart (http)"), + pythonRequests("Python (requests)"), kotlinOkHttp("Kotlin (OkHttp)"); const CodegenLanguage(this.label); From e5ab9b84d093d82ce4658868d571efbd368bd600 Mon Sep 17 00:00:00 2001 From: Mixel2004 <84668201+Mixel2004@users.noreply.github.com> Date: Mon, 26 Jun 2023 00:47:58 +0530 Subject: [PATCH 3/6] Fixed the bug --- lib/codegen/python/pkg_request.dart | 52 ++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/lib/codegen/python/pkg_request.dart b/lib/codegen/python/pkg_request.dart index 046aa3bd..9ea93b9e 100644 --- a/lib/codegen/python/pkg_request.dart +++ b/lib/codegen/python/pkg_request.dart @@ -1,20 +1,26 @@ import 'package:jinja/jinja.dart' as jj; import 'package:apidash/models/models.dart' show RequestModel; +import 'package:apidash/utils/utils.dart' show padMultilineString, rowsToMap; +import 'package:apidash/consts.dart'; class PythonRequestCodeGen { + int kHeadersPadding = 16; String kPythonTemplate = ''' import requests +import json def main(): url = '{{url}}' - {{params}} + params = {{params}} {{body}} headers = {{headers}} - response = requests.{{method}}(url{{request_headers}}{{request_body}}) + response = requests.{{method}}( + url{{request_params}}{{request_headers}}{{request_body}} + ) status_code = response.status_code if 200 <= status_code < 300: @@ -31,6 +37,7 @@ main() try { bool hasHeaders = false; bool hasBody = false; + bool hasParams = false; String url = requestModel.url; if (!url.contains('://') && url.isNotEmpty) { @@ -40,9 +47,9 @@ main() var paramsList = requestModel.requestParams; String params = ''; if (paramsList != null) { - params = ''; + hasParams = true; for (var param in paramsList) { - params += '${param.k} = ${param.v}\n '; + params += '\n "${param.k}": "${param.v}",'; } } @@ -52,19 +59,31 @@ main() String requestBodyString = ''; if (requestBody != null) { hasBody = true; - requestBodyString = "data = '''$requestBody'''\n "; + var bodyType = requestModel.requestBodyContentType; + if (bodyType == ContentType.text) { + requestBodyString = "data = '''$requestBody'''\n"; + } else if (bodyType == ContentType.json) { + int index = requestBody.lastIndexOf("}"); + if (requestBody[index - 1] == ",") { + requestBody = requestBody.substring(0, index - 1) + + requestBody.substring(index); + } + requestBodyString = + "data = '''$requestBody''' \ndata = json.loads(data)\n"; + } } var headersList = requestModel.requestHeaders; String headers = ''; if (headersList != null || hasBody) { - headers = ''; - for (var header in headersList ?? []) { - headers += "'${header.k}': '${header.v}',\n "; - } - if (hasBody) { - headers += - "'Content-Type': '${requestModel.requestBodyContentType}',\n "; + var head = rowsToMap(requestModel.requestHeaders) ?? {}; + if (head.isNotEmpty || hasBody) { + if (hasBody) { + head["content-type"] = + kContentTypeMap[requestModel.requestBodyContentType] ?? ""; + } + headers = kEncoder.convert(head); + headers = padMultilineString(headers, kHeadersPadding); } hasHeaders = headers.isNotEmpty; } @@ -72,12 +91,13 @@ main() var template = jj.Template(kPythonTemplate); var pythonCode = template.render({ 'url': url, - 'params': params, + 'params': '{$params \n }', 'body': requestBodyString, - 'headers': '{\n $headers}', + 'headers': headers, 'method': method, - 'request_headers': hasHeaders ? 'headers=headers,\n ' : '', - 'request_body': hasBody ? 'data=data,\n ' : '', + 'request_params': hasParams ? ', params=params' : '', + 'request_headers': hasHeaders ? ', headers=headers' : '', + 'request_body': hasBody ? ', data=data' : '', }); return pythonCode; From ac4a2160bf14eeadf4af1ee93504cde03e939968 Mon Sep 17 00:00:00 2001 From: Mixel2004 <84668201+Mixel2004@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:32:23 +0530 Subject: [PATCH 4/6] Prevent generation of unnecessary lines in Python codegen --- lib/codegen/python/pkg_request.dart | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/codegen/python/pkg_request.dart b/lib/codegen/python/pkg_request.dart index 9ea93b9e..7c2c8376 100644 --- a/lib/codegen/python/pkg_request.dart +++ b/lib/codegen/python/pkg_request.dart @@ -10,13 +10,7 @@ import requests import json def main(): - url = '{{url}}' - - params = {{params}} - - {{body}} - - headers = {{headers}} + url = '{{url}}'{{params}}{{body}}{{headers}} response = requests.{{method}}( url{{request_params}}{{request_headers}}{{request_body}} @@ -28,7 +22,7 @@ def main(): print('Response Body:', response.text) else: print('Error Status Code:', status_code) - print('Error Response Body:', response.text) + print('Error Response Body:', response.reason) main() '''; @@ -47,9 +41,11 @@ main() var paramsList = requestModel.requestParams; String params = ''; if (paramsList != null) { - hasParams = true; for (var param in paramsList) { - params += '\n "${param.k}": "${param.v}",'; + if (param.k.isNotEmpty) { + hasParams = true; + params += '\n "${param.k}": "${param.v}",'; + } } } @@ -57,19 +53,23 @@ main() var requestBody = requestModel.requestBody; String requestBodyString = ''; - if (requestBody != null) { + if (requestBody != null && requestBody.isNotEmpty) { hasBody = true; var bodyType = requestModel.requestBodyContentType; if (bodyType == ContentType.text) { requestBodyString = "data = '''$requestBody'''\n"; } else if (bodyType == ContentType.json) { int index = requestBody.lastIndexOf("}"); - if (requestBody[index - 1] == ",") { - requestBody = requestBody.substring(0, index - 1) + - requestBody.substring(index); + index--; + while (requestBody[index] == " " || requestBody[index] == "\n") { + index--; + } + if (requestBody[index] == ",") { + requestBody = requestBody.substring(0, index) + + requestBody.substring(index + 1); } requestBodyString = - "data = '''$requestBody''' \ndata = json.loads(data)\n"; + "data = '''$requestBody''' \n data = json.loads(data)\n"; } } @@ -91,9 +91,9 @@ main() var template = jj.Template(kPythonTemplate); var pythonCode = template.render({ 'url': url, - 'params': '{$params \n }', - 'body': requestBodyString, - 'headers': headers, + 'params': hasParams ? '\n\n params = {$params \n }' : '', + 'body': hasBody ? '\n\n $requestBodyString' : '', + 'headers': hasHeaders ? '\n\n headers = $headers' : '', 'method': method, 'request_params': hasParams ? ', params=params' : '', 'request_headers': hasHeaders ? ', headers=headers' : '', From 1b143af680dd108dc1fe0f90505fce2646c483b1 Mon Sep 17 00:00:00 2001 From: Mixel2004 <84668201+Mixel2004@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:34:46 +0530 Subject: [PATCH 5/6] Add test for python (request) codegen --- test/codegen/python_request_codegen_test.dart | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 test/codegen/python_request_codegen_test.dart diff --git a/test/codegen/python_request_codegen_test.dart b/test/codegen/python_request_codegen_test.dart new file mode 100644 index 00000000..a4c86096 --- /dev/null +++ b/test/codegen/python_request_codegen_test.dart @@ -0,0 +1,188 @@ +import 'package:apidash/codegen/python/pkg_request.dart'; +import 'package:apidash/models/models.dart' show KVRow, RequestModel; +import 'package:test/test.dart'; +import 'package:apidash/consts.dart'; + +void main() { + group('PythonRequestCodeGen', () { + final pythonRequestCodeGen = PythonRequestCodeGen(); + + test('getCode returns valid code for GET request', () { + const requestModel = RequestModel( + url: 'https://jsonplaceholder.typicode.com/todos/1', + method: HTTPVerb.get, + id: '', + ); + const expectedCode = """import requests +import json + +def main(): + url = 'https://jsonplaceholder.typicode.com/todos/1' + + response = requests.get( + url + ) + + status_code = response.status_code + if 200 <= status_code < 300: + print('Status Code:', status_code) + print('Response Body:', response.text) + else: + print('Error Status Code:', status_code) + print('Error Response Body:', response.reason) + +main()"""; + expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode); + }); + + test('getCode returns valid code for POST request', () { + const requestModel = RequestModel( + url: 'https://jsonplaceholder.typicode.com/posts', + method: HTTPVerb.post, + requestBody: '{"title": "foo","body": "bar","userId": 1}', + requestBodyContentType: ContentType.json, + id: '1', + ); + const expectedCode = """import requests +import json + +def main(): + url = 'https://jsonplaceholder.typicode.com/posts' + + data = '''{"title": "foo","body": "bar","userId": 1}''' + data = json.loads(data) + + + headers = { + "content-type": "application/json" + } + + response = requests.post( + url, headers=headers, data=data + ) + + status_code = response.status_code + if 200 <= status_code < 300: + print('Status Code:', status_code) + print('Response Body:', response.text) + else: + print('Error Status Code:', status_code) + print('Error Response Body:', response.reason) + +main()"""; + expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode); + }); + + test('getCode returns valid code for DELETE request', () { + const requestModel = RequestModel( + url: 'https://jsonplaceholder.typicode.com/posts/1', + method: HTTPVerb.delete, + requestBody: '{"title": "foo","body": "bar","userId": 1}', + requestBodyContentType: ContentType.json, + id: '1', + ); + const expectedCode = """import requests +import json + +def main(): + url = 'https://jsonplaceholder.typicode.com/posts/1' + + data = '''{"title": "foo","body": "bar","userId": 1}''' + data = json.loads(data) + + + headers = { + "content-type": "application/json" + } + + response = requests.delete( + url, headers=headers, data=data + ) + + status_code = response.status_code + if 200 <= status_code < 300: + print('Status Code:', status_code) + print('Response Body:', response.text) + else: + print('Error Status Code:', status_code) + print('Error Response Body:', response.reason) + +main()"""; + expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode); + }); + + test('getCode returns valid code for HEAD request', () { + const requestModel = RequestModel( + url: 'https://jsonplaceholder.typicode.com/posts/1', + method: HTTPVerb.head, + id: '1', + ); + const expectedCode = """import requests +import json + +def main(): + url = 'https://jsonplaceholder.typicode.com/posts/1' + + response = requests.head( + url + ) + + status_code = response.status_code + if 200 <= status_code < 300: + print('Status Code:', status_code) + print('Response Body:', response.text) + else: + print('Error Status Code:', status_code) + print('Error Response Body:', response.reason) + +main()"""; + expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode); + }); + + test( + 'getCode returns valid code for requests with headers and query parameters', + () { + const requestModel = RequestModel( + url: 'https://jsonplaceholder.typicode.com/posts', + method: HTTPVerb.get, + requestParams: [ + KVRow('userId', 1), + ], + requestHeaders: [ + KVRow('Custom-Header-1', 'Value-1'), + KVRow('Custom-Header-2', 'Value-2') + ], + id: '1', + ); + const expectedCode = """import requests +import json + +def main(): + url = 'https://jsonplaceholder.typicode.com/posts' + + params = { + "userId": "1", + } + + headers = { + "Custom-Header-1": "Value-1", + "Custom-Header-2": "Value-2" + } + + response = requests.get( + url, params=params, headers=headers + ) + + status_code = response.status_code + if 200 <= status_code < 300: + print('Status Code:', status_code) + print('Response Body:', response.text) + else: + print('Error Status Code:', status_code) + print('Error Response Body:', response.reason) + +main()"""; + expect(pythonRequestCodeGen.getCode(requestModel, 'https'), expectedCode); + }); + }); +} From 1e97618fe3d4da18bb0bd480cf0848707718a1a0 Mon Sep 17 00:00:00 2001 From: Mixel2004 <84668201+Mixel2004@users.noreply.github.com> Date: Sat, 8 Jul 2023 22:39:55 +0530 Subject: [PATCH 6/6] fix: Removed Json which was causing error --- lib/codegen/python/pkg_request.dart | 8 ++------ test/codegen/python_request_codegen_test.dart | 13 ++----------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/lib/codegen/python/pkg_request.dart b/lib/codegen/python/pkg_request.dart index 7c2c8376..e25c9d76 100644 --- a/lib/codegen/python/pkg_request.dart +++ b/lib/codegen/python/pkg_request.dart @@ -7,7 +7,6 @@ class PythonRequestCodeGen { int kHeadersPadding = 16; String kPythonTemplate = ''' import requests -import json def main(): url = '{{url}}'{{params}}{{body}}{{headers}} @@ -56,9 +55,7 @@ main() if (requestBody != null && requestBody.isNotEmpty) { hasBody = true; var bodyType = requestModel.requestBodyContentType; - if (bodyType == ContentType.text) { - requestBodyString = "data = '''$requestBody'''\n"; - } else if (bodyType == ContentType.json) { + if (bodyType == ContentType.json) { int index = requestBody.lastIndexOf("}"); index--; while (requestBody[index] == " " || requestBody[index] == "\n") { @@ -68,9 +65,8 @@ main() requestBody = requestBody.substring(0, index) + requestBody.substring(index + 1); } - requestBodyString = - "data = '''$requestBody''' \n data = json.loads(data)\n"; } + requestBodyString = "data = '''$requestBody'''"; } var headersList = requestModel.requestHeaders; diff --git a/test/codegen/python_request_codegen_test.dart b/test/codegen/python_request_codegen_test.dart index a4c86096..a74f50ac 100644 --- a/test/codegen/python_request_codegen_test.dart +++ b/test/codegen/python_request_codegen_test.dart @@ -14,7 +14,6 @@ void main() { id: '', ); const expectedCode = """import requests -import json def main(): url = 'https://jsonplaceholder.typicode.com/todos/1' @@ -44,14 +43,11 @@ main()"""; id: '1', ); const expectedCode = """import requests -import json def main(): url = 'https://jsonplaceholder.typicode.com/posts' - data = '''{"title": "foo","body": "bar","userId": 1}''' - data = json.loads(data) - + data = '''{"title": "foo","body": "bar","userId": 1}''' headers = { "content-type": "application/json" @@ -82,14 +78,11 @@ main()"""; id: '1', ); const expectedCode = """import requests -import json def main(): url = 'https://jsonplaceholder.typicode.com/posts/1' - data = '''{"title": "foo","body": "bar","userId": 1}''' - data = json.loads(data) - + data = '''{"title": "foo","body": "bar","userId": 1}''' headers = { "content-type": "application/json" @@ -118,7 +111,6 @@ main()"""; id: '1', ); const expectedCode = """import requests -import json def main(): url = 'https://jsonplaceholder.typicode.com/posts/1' @@ -155,7 +147,6 @@ main()"""; id: '1', ); const expectedCode = """import requests -import json def main(): url = 'https://jsonplaceholder.typicode.com/posts'