diff --git a/lib/codegen/codegen.dart b/lib/codegen/codegen.dart index d39e4d10..f6fdaa5f 100644 --- a/lib/codegen/codegen.dart +++ b/lib/codegen/codegen.dart @@ -18,36 +18,64 @@ class Codegen { String defaultUriScheme, { String? boundary, }) { - if (requestModel.isFormDataRequest) { - boundary = boundary ?? getNewUuid(); - } switch (codegenLanguage) { case CodegenLanguage.curl: - return cURLCodeGen().getCode(requestModel, defaultUriScheme); + return cURLCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.har: - return HARCodeGen().getCode(requestModel, defaultUriScheme); + return HARCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.dartHttp: - return DartHttpCodeGen().getCode(requestModel, defaultUriScheme); + return DartHttpCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.dartDio: - return DartDioCodeGen().getCode(requestModel, defaultUriScheme); + return DartDioCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.jsAxios: - return AxiosCodeGen().getCode(requestModel, defaultUriScheme); + return AxiosCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.jsFetch: - return FetchCodeGen().getCode(requestModel, defaultUriScheme); + return FetchCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.nodejsAxios: - return AxiosCodeGen(isNodeJs: true) - .getCode(requestModel, defaultUriScheme); + return AxiosCodeGen(isNodeJs: true).getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.nodejsFetch: - return FetchCodeGen(isNodeJs: true) - .getCode(requestModel, defaultUriScheme); + return FetchCodeGen(isNodeJs: true).getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.kotlinOkHttp: - return KotlinOkHttpCodeGen().getCode(requestModel, defaultUriScheme); + return KotlinOkHttpCodeGen().getCode( + requestModel, + defaultUriScheme, + ); case CodegenLanguage.pythonHttpClient: - return PythonHttpClientCodeGen() - .getCode(requestModel, defaultUriScheme, boundary); + return PythonHttpClientCodeGen().getCode( + requestModel, + defaultUriScheme, + boundary: boundary ?? getNewUuid(), + ); case CodegenLanguage.pythonRequests: - return PythonRequestsCodeGen() - .getCode(requestModel, defaultUriScheme, boundary); + return PythonRequestsCodeGen().getCode( + requestModel, + defaultUriScheme, + boundary: boundary, + ); } } } diff --git a/lib/codegen/codegen_utils.dart b/lib/codegen/codegen_utils.dart new file mode 100644 index 00000000..2d7a1846 --- /dev/null +++ b/lib/codegen/codegen_utils.dart @@ -0,0 +1,15 @@ +String jsonToPyDict(String jsonString) { + Map replaceWithMap = { + "null": "None", + "true": "True", + "false": "False" + }; + String pyDict = jsonString; + for (var k in replaceWithMap.keys) { + RegExp regExp = RegExp(k + r'(?=([^"]*"[^"]*")*[^"]*$)'); + pyDict = pyDict.replaceAllMapped(regExp, (match) { + return replaceWithMap[match.group(0)] ?? match.group(0)!; + }); + } + return pyDict; +} diff --git a/lib/codegen/python/requests.dart b/lib/codegen/python/requests.dart index b9ea1bca..8a700122 100644 --- a/lib/codegen/python/requests.dart +++ b/lib/codegen/python/requests.dart @@ -1,15 +1,14 @@ import 'dart:io'; -import 'dart:convert'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart' - show getValidRequestUri, padMultilineString, stripUriParams; + show getValidRequestUri, stripUriParams, getFilenameFromPath; import 'package:apidash/models/models.dart' show RequestModel; +import '../codegen_utils.dart'; class PythonRequestsCodeGen { final String kTemplateStart = """import requests -{% if isFormDataRequest %}import mimetypes -from codecs import encode +{% if hasFormData %}from requests_toolbelt.multipart.encoder import MultipartEncoder {% endif %} url = '{{url}}' @@ -20,7 +19,6 @@ url = '{{url}}' params = {{params}} """; - int kParamsPadding = 9; String kTemplateBody = """ @@ -39,44 +37,27 @@ payload = {{body}} headers = {{headers}} """; - String kTemplateFormHeaderContentType = ''' -multipart/form-data; boundary={{boundary}}'''; - - int kHeadersPadding = 10; String kTemplateRequest = """ response = requests.{{method}}(url """; - final String kStringFormDataBody = r''' + final String kTemplateFormDataBody = r''' -def build_data_list(fields): - dataList = [] - for field in fields: - name = field.get('name', '') - value = field.get('value', '') - type_ = field.get('type', 'text') +payload = MultipartEncoder({ +{{formdata_payload}} +}{% if boundary != '' %}, + boundary="{{boundary}}" +{% endif %}) - dataList.append(encode('--{{boundary}}')) - if type_ == 'text': - dataList.append(encode(f'Content-Disposition: form-data; name="{name}"')) - dataList.append(encode('Content-Type: text/plain')) - dataList.append(encode('')) - dataList.append(encode(value)) - elif type_ == 'file': - dataList.append(encode(f'Content-Disposition: form-data; name="{name}"; filename="{value}"')) - dataList.append(encode(f'Content-Type: {mimetypes.guess_type(value)[0] or "application/octet-stream"}')) - dataList.append(encode('')) - dataList.append(open(value, 'rb').read()) - dataList.append(encode('--{{boundary}}--')) - dataList.append(encode('')) - return dataList - -dataList = build_data_list({{fields_list}}) -payload = b'\r\n'.join(dataList) '''; + String kTemplateFormDataRowText = r""" "{{name}}": "{{value}}","""; + + String kTemplateFormDataRowFile = + r""" "{{name}}": ("{{filename}}", open("{{path}}", "rb")),"""; + String kStringRequestParams = """, params=params"""; String kStringRequestBody = """, data=payload"""; @@ -91,11 +72,18 @@ print('Status Code:', response.status_code) print('Response Body:', response.text) """; + String kStringFormDataContentType = "payload.content_type"; + + String refactorHeaderString(String headerString) { + return headerString.replaceAll( + '"$kStringFormDataContentType"', kStringFormDataContentType); + } + String? getCode( RequestModel requestModel, - String defaultUriScheme, + String defaultUriScheme, { String? boundary, - ) { + }) { try { String result = ""; bool hasQuery = false; @@ -117,7 +105,7 @@ print('Response Body:', response.text) var templateStartUrl = jj.Template(kTemplateStart); result += templateStartUrl.render({ "url": stripUriParams(uri), - 'isFormDataRequest': requestModel.isFormDataRequest + 'hasFormData': requestModel.hasFormData }); if (uri.hasQuery) { @@ -126,77 +114,85 @@ print('Response Body:', response.text) hasQuery = true; var templateParams = jj.Template(kTemplateParams); var paramsString = kEncoder.convert(params); - paramsString = padMultilineString(paramsString, kParamsPadding); result += templateParams.render({"params": paramsString}); } } - var method = requestModel.method; - var requestBody = requestModel.requestBody; - if (kMethodsWithBody.contains(method) && requestBody != null) { - var contentLength = utf8.encode(requestBody).length; - if (contentLength > 0) { - if (requestModel.requestBodyContentType == ContentType.json) { - hasJsonBody = true; - var templateBody = jj.Template(kTemplateJson); - result += templateBody.render({"body": requestBody}); - } else if (!requestModel.isFormDataRequest) { - hasBody = true; - var templateBody = jj.Template(kTemplateBody); - result += templateBody.render({"body": requestBody}); + if (requestModel.hasFormData) { + hasBody = true; + List formdataPayload = []; + for (var item in requestModel.formDataList) { + if (item.type == FormDataType.text) { + formdataPayload.add(jj.Template(kTemplateFormDataRowText).render({ + "name": item.name, + "value": item.value, + })); + } + if (item.type == FormDataType.file) { + formdataPayload.add(jj.Template(kTemplateFormDataRowFile).render({ + "name": item.name, + "filename": getFilenameFromPath(item.value), + "path": item.value, + })); } } + var formDataBodyData = jj.Template(kTemplateFormDataBody); + result += formDataBodyData.render( + { + "formdata_payload": formdataPayload.join("\n"), + "boundary": boundary ?? '', + }, + ); + } else if (requestModel.hasJsonData) { + hasJsonBody = true; + var templateBody = jj.Template(kTemplateJson); + var pyDict = jsonToPyDict(requestModel.requestBody ?? ""); + result += templateBody.render({"body": pyDict}); + } else if (requestModel.hasTextData) { + hasBody = true; + var templateBody = jj.Template(kTemplateBody); + result += templateBody.render({"body": requestModel.requestBody}); } var headersList = requestModel.enabledRequestHeaders; if (headersList != null || hasBody) { var headers = requestModel.enabledHeadersMap; - if (requestModel.isFormDataRequest) { - var formHeaderTemplate = - jj.Template(kTemplateFormHeaderContentType); - headers[HttpHeaders.contentTypeHeader] = formHeaderTemplate.render({ - "boundary": boundary, - }); - } - if (headers.isNotEmpty || hasBody) { - hasHeaders = true; - if (hasBody) { + if (hasBody) { + if (requestModel.hasFormData) { + headers[HttpHeaders.contentTypeHeader] = + kStringFormDataContentType; + } else { headers[HttpHeaders.contentTypeHeader] = requestModel.requestBodyContentType.header; } + } + if (headers.isNotEmpty) { + hasHeaders = true; var headersString = kEncoder.convert(headers); - headersString = padMultilineString(headersString, kHeadersPadding); + headersString = refactorHeaderString(headersString); var templateHeaders = jj.Template(kTemplateHeaders); result += templateHeaders.render({"headers": headersString}); } } - if (requestModel.isFormDataRequest) { - var formDataBodyData = jj.Template(kStringFormDataBody); - result += formDataBodyData.render( - { - "fields_list": json.encode(requestModel.formDataMapList), - "boundary": boundary, - }, - ); - } + var templateRequest = jj.Template(kTemplateRequest); result += templateRequest.render({ - "method": method.name.toLowerCase(), + "method": requestModel.method.name.toLowerCase(), }); if (hasQuery) { result += kStringRequestParams; } - if (hasBody || requestModel.isFormDataRequest) { + if (hasBody) { result += kStringRequestBody; } - if (hasJsonBody || requestModel.isFormDataRequest) { + if (hasJsonBody) { result += kStringRequestJson; } - if (hasHeaders || requestModel.isFormDataRequest) { + if (hasHeaders) { result += kStringRequestHeaders; } diff --git a/test/codegen/python_requests_codegen_test.dart b/test/codegen/python_requests_codegen_test.dart index 3ed0a38a..babe65d6 100644 --- a/test/codegen/python_requests_codegen_test.dart +++ b/test/codegen/python_requests_codegen_test.dart @@ -26,8 +26,8 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/country/data' params = { - "code": "US" - } + "code": "US" +} response = requests.get(url, params=params) @@ -44,8 +44,8 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/country/data' params = { - "code": "IND" - } + "code": "IND" +} response = requests.get(url, params=params) @@ -62,12 +62,12 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/humanize/social' params = { - "num": "8700000", - "digits": "3", - "system": "SS", - "add_space": "true", - "trailing_zeros": "true" - } + "num": "8700000", + "digits": "3", + "system": "SS", + "add_space": "true", + "trailing_zeros": "true" +} response = requests.get(url, params=params) @@ -84,8 +84,8 @@ print('Response Body:', response.text) url = 'https://api.github.com/repos/foss42/apidash' headers = { - "User-Agent": "Test Agent" - } + "User-Agent": "Test Agent" +} response = requests.get(url, headers=headers) @@ -102,12 +102,12 @@ print('Response Body:', response.text) url = 'https://api.github.com/repos/foss42/apidash' params = { - "raw": "true" - } + "raw": "true" +} headers = { - "User-Agent": "Test Agent" - } + "User-Agent": "Test Agent" +} response = requests.get(url, params=params, headers=headers) @@ -138,12 +138,12 @@ print('Response Body:', response.text) url = 'https://api.github.com/repos/foss42/apidash' params = { - "raw": "true" - } + "raw": "true" +} headers = { - "User-Agent": "Test Agent" - } + "User-Agent": "Test Agent" +} response = requests.get(url, params=params, headers=headers) @@ -160,9 +160,9 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/humanize/social' params = { - "num": "8700000", - "add_space": "true" - } + "num": "8700000", + "add_space": "true" +} response = requests.get(url, params=params) @@ -179,8 +179,8 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/humanize/social' headers = { - "User-Agent": "Test Agent" - } + "User-Agent": "Test Agent" +} response = requests.get(url, headers=headers) @@ -201,13 +201,13 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/humanize/social' params = { - "num": "8700000", - "digits": "3" - } + "num": "8700000", + "digits": "3" +} headers = { - "User-Agent": "Test Agent" - } + "User-Agent": "Test Agent" +} response = requests.get(url, params=params, headers=headers) @@ -274,8 +274,8 @@ payload = r'''{ }''' headers = { - "content-type": "text/plain" - } + "content-type": "text/plain" +} response = requests.post(url, data=payload, headers=headers) @@ -292,7 +292,12 @@ print('Response Body:', response.text) url = 'https://api.apidash.dev/case/lower' payload = { -"text": "I LOVE Flutter" +"text": "I LOVE Flutter", +"flag": None, +"male": True, +"female": False, +"no": 1.2, +"arr": ["null", "true", "false", None] } response = requests.post(url, json=payload) @@ -314,8 +319,8 @@ payload = { } headers = { - "User-Agent": "Test Agent" - } + "User-Agent": "Test Agent" +} response = requests.post(url, json=payload, headers=headers) @@ -325,6 +330,166 @@ print('Response Body:', response.text) expect(pythonRequestsCodeGen.getCode(requestModelPost3, "https"), expectedCode); }); + + test('POST 4', () { + const expectedCode = r"""import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +url = 'https://api.apidash.dev/io/form' + +payload = MultipartEncoder({ + "text": "API", + "sep": "|", + "times": "3", +}) + +headers = { + "content-type": payload.content_type +} + +response = requests.post(url, data=payload, headers=headers) + +print('Status Code:', response.status_code) +print('Response Body:', response.text) +"""; + expect(pythonRequestsCodeGen.getCode(requestModelPost4, "https"), + expectedCode); + }); + + test('POST 5', () { + const expectedCode = r"""import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +url = 'https://api.apidash.dev/io/form' + +payload = MultipartEncoder({ + "text": "API", + "sep": "|", + "times": "3", +}) + +headers = { + "User-Agent": "Test Agent", + "content-type": payload.content_type +} + +response = requests.post(url, data=payload, headers=headers) + +print('Status Code:', response.status_code) +print('Response Body:', response.text) +"""; + expect(pythonRequestsCodeGen.getCode(requestModelPost5, "https"), + expectedCode); + }); + + test('POST 6', () { + const expectedCode = r"""import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +url = 'https://api.apidash.dev/io/img' + +payload = MultipartEncoder({ + "token": "xyz", + "imfile": ("1.png", open("/Documents/up/1.png", "rb")), +}) + +headers = { + "content-type": payload.content_type +} + +response = requests.post(url, data=payload, headers=headers) + +print('Status Code:', response.status_code) +print('Response Body:', response.text) +"""; + expect(pythonRequestsCodeGen.getCode(requestModelPost6, "https"), + expectedCode); + }); + + test('POST 7', () { + const expectedCode = r"""import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +url = 'https://api.apidash.dev/io/img' + +payload = MultipartEncoder({ + "token": "xyz", + "imfile": ("1.png", open("/Documents/up/1.png", "rb")), +}) + +headers = { + "content-type": payload.content_type +} + +response = requests.post(url, data=payload, headers=headers) + +print('Status Code:', response.status_code) +print('Response Body:', response.text) +"""; + expect(pythonRequestsCodeGen.getCode(requestModelPost7, "https"), + expectedCode); + }); + + test('POST 8', () { + const expectedCode = r"""import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +url = 'https://api.apidash.dev/io/form' + +params = { + "size": "2", + "len": "3" +} + +payload = MultipartEncoder({ + "text": "API", + "sep": "|", + "times": "3", +}) + +headers = { + "content-type": payload.content_type +} + +response = requests.post(url, params=params, data=payload, headers=headers) + +print('Status Code:', response.status_code) +print('Response Body:', response.text) +"""; + expect(pythonRequestsCodeGen.getCode(requestModelPost8, "https"), + expectedCode); + }); + + test('POST 9', () { + const expectedCode = r"""import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +url = 'https://api.apidash.dev/io/img' + +params = { + "size": "2", + "len": "3" +} + +payload = MultipartEncoder({ + "token": "xyz", + "imfile": ("1.png", open("/Documents/up/1.png", "rb")), +}) + +headers = { + "User-Agent": "Test Agent", + "Keep-Alive": "true", + "content-type": payload.content_type +} + +response = requests.post(url, params=params, data=payload, headers=headers) + +print('Status Code:', response.status_code) +print('Response Body:', response.text) +"""; + expect(pythonRequestsCodeGen.getCode(requestModelPost9, "https"), + expectedCode); + }); }); group('PUT Request', () {