diff --git a/lib/codegen/codegen.dart b/lib/codegen/codegen.dart index a004c2dc..35f11fcc 100644 --- a/lib/codegen/codegen.dart +++ b/lib/codegen/codegen.dart @@ -1,5 +1,6 @@ import 'package:apidash/codegen/kotlin/pkg_okhttp.dart'; import 'python/pkg_http_client.dart'; +import 'package:apidash/codegen/python/pkg_request.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/models/models.dart' show RequestModel; @@ -18,6 +19,8 @@ class Codegen { return KotlinOkHttpCodeGen().getCode(requestModel); case CodegenLanguage.pythonHttpClient: return PythonHttpClient().getCode(requestModel); + case CodegenLanguage.pythonRequests: + return PythonRequestCodeGen().getCode(requestModel, defaultUriScheme); default: throw ArgumentError('Invalid codegenLanguage'); } diff --git a/lib/codegen/python/pkg_request.dart b/lib/codegen/python/pkg_request.dart new file mode 100644 index 00000000..e25c9d76 --- /dev/null +++ b/lib/codegen/python/pkg_request.dart @@ -0,0 +1,104 @@ +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 + +def main(): + url = '{{url}}'{{params}}{{body}}{{headers}} + + response = requests.{{method}}( + url{{request_params}}{{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.reason) + +main() +'''; + + String? getCode(RequestModel requestModel, String defaultUriScheme) { + try { + bool hasHeaders = false; + bool hasBody = false; + bool hasParams = false; + + String url = requestModel.url; + if (!url.contains('://') && url.isNotEmpty) { + url = '$defaultUriScheme://$url'; + } + + var paramsList = requestModel.requestParams; + String params = ''; + if (paramsList != null) { + for (var param in paramsList) { + if (param.k.isNotEmpty) { + hasParams = true; + params += '\n "${param.k}": "${param.v}",'; + } + } + } + + var method = requestModel.method.name.toLowerCase(); + + var requestBody = requestModel.requestBody; + String requestBodyString = ''; + if (requestBody != null && requestBody.isNotEmpty) { + hasBody = true; + var bodyType = requestModel.requestBodyContentType; + if (bodyType == ContentType.json) { + int index = requestBody.lastIndexOf("}"); + index--; + while (requestBody[index] == " " || requestBody[index] == "\n") { + index--; + } + if (requestBody[index] == ",") { + requestBody = requestBody.substring(0, index) + + requestBody.substring(index + 1); + } + } + requestBodyString = "data = '''$requestBody'''"; + } + + var headersList = requestModel.requestHeaders; + String headers = ''; + if (headersList != null || hasBody) { + 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; + } + + var template = jj.Template(kPythonTemplate); + var pythonCode = template.render({ + 'url': url, + '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' : '', + 'request_body': hasBody ? ', data=data' : '', + }); + + return pythonCode; + } catch (e) { + return null; + } + } +} diff --git a/lib/consts.dart b/lib/consts.dart index 3a32fff7..f93773bd 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -233,7 +233,8 @@ const kDefaultContentType = ContentType.json; enum CodegenLanguage { dartHttp("Dart (http)", "dart", "dart"), kotlinOkHttp("Kotlin (okhttp3)", "java", "kt"), - pythonHttpClient("Python (http.client)", "python", "py"); + pythonHttpClient("Python (http.client)", "python", "py"), + pythonRequests("Python (requests)"); const CodegenLanguage(this.label, this.codeHighlightLang, this.ext); final String label; diff --git a/test/codegen/python_request_codegen_test.dart b/test/codegen/python_request_codegen_test.dart new file mode 100644 index 00000000..a74f50ac --- /dev/null +++ b/test/codegen/python_request_codegen_test.dart @@ -0,0 +1,179 @@ +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 + +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 + +def main(): + url = 'https://jsonplaceholder.typicode.com/posts' + + data = '''{"title": "foo","body": "bar","userId": 1}''' + + 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 + +def main(): + url = 'https://jsonplaceholder.typicode.com/posts/1' + + data = '''{"title": "foo","body": "bar","userId": 1}''' + + 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 + +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 + +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); + }); + }); +}