From 80ca67cfdac0008282ef756cc069f275883a1c44 Mon Sep 17 00:00:00 2001 From: Jyhess Date: Fri, 11 Jan 2019 16:35:21 +0100 Subject: [PATCH] Python AIOHTTP server generator (#1470) * Astract factory for generators based on connexion * Add aiohttp server generator * Fix flask tests * Normalize python-flask folder names --- bin/openapi3/python-flask-petstore-python2.sh | 24 +- bin/openapi3/python-flask-petstore.sh | 23 +- bin/python-flask-all.sh | 4 - bin/python-flask-petstore-python2.sh | 34 - ...e.sh => python-server-aiohttp-petstore.sh} | 19 +- bin/python-server-all.sh | 5 + bin/python-server-flask-petstore-python2.sh | 50 + bin/python-server-flask-petstore.sh | 50 + bin/windows/python2-flask-petstore.bat | 2 +- bin/windows/python3-flask-petstore.bat | 2 +- .../openapitools/codegen/CodegenSecurity.java | 3 + .../openapitools/codegen/DefaultCodegen.java | 8 + .../PythonAbstractConnexionServerCodegen.java | 908 ++++++++++++++++++ .../PythonAiohttpConnexionServerCodegen.java | 49 + .../PythonFlaskConnexionServerCodegen.java | 698 +------------- .../org.openapitools.codegen.CodegenConfig | 1 + .../resources/python-aiohttp/README.mustache | 46 + .../__init__.mustache | 0 .../python-aiohttp/__init__main.mustache | 12 + .../python-aiohttp/__init__model.mustache | 5 + .../python-aiohttp/__init__test.mustache | 0 .../python-aiohttp/__main__.mustache | 6 + .../python-aiohttp/base_model_.mustache | 66 ++ .../python-aiohttp/conftest.mustache | 17 + .../python-aiohttp/controller.mustache | 104 ++ .../python-aiohttp/controller_test.mustache | 68 ++ .../resources/python-aiohttp/model.mustache | 162 ++++ .../openapi.mustache | 0 .../param_type.mustache | 0 .../python-aiohttp/requirements.mustache | 3 + .../security_controller_.mustache | 59 ++ .../python-aiohttp/test-requirements.mustache | 6 + .../resources/python-aiohttp/util.mustache | 130 +++ .../Dockerfile.mustache | 0 .../README.mustache | 0 .../resources/python-flask/__init__.mustache | 0 .../__init__model.mustache | 0 .../__init__test.mustache | 0 .../__main__.mustache | 0 .../base_model_.mustache | 0 .../controller.mustache | 0 .../controller_test.mustache | 22 +- .../dockerignore.mustache | 0 .../encoder.mustache | 0 .../git_push.sh.mustache | 0 .../gitignore.mustache | 0 .../model.mustache | 7 + .../resources/python-flask/openapi.mustache | 1 + .../python-flask/param_type.mustache | 1 + .../requirements.mustache | 2 +- .../security_controller_.mustache | 90 ++ .../setup.mustache | 0 .../test-requirements.mustache | 0 .../tox.mustache | 2 +- .../travis.mustache | 0 .../util.mustache | 0 pom.xml | 3 + .../.openapi-generator-ignore | 0 .../.openapi-generator/VERSION | 0 .../server/petstore/python-aiohttp/Makefile | 18 + .../server/petstore/python-aiohttp/README.md | 46 + .../python-aiohttp/openapi_server/__main__.py | 6 + .../openapi_server/controllers}/__init__.py | 0 .../controllers/pet_controller.py | 114 +++ .../controllers/security_controller_.py | 29 + .../controllers/store_controller.py | 52 + .../controllers/user_controller.py | 107 +++ .../openapi_server/models/__init__.py | 9 + .../openapi_server/models/api_response.py | 110 +++ .../openapi_server/models/base_model_.py | 66 ++ .../openapi_server/models/category.py | 85 ++ .../openapi_server/models/order.py | 193 ++++ .../openapi_server/models/pet.py | 199 ++++ .../openapi_server/models/tag.py | 85 ++ .../openapi_server/models/user.py | 237 +++++ .../openapi_server/openapi/openapi.yaml | 792 +++++++++++++++ .../python-aiohttp/openapi_server/util.py | 130 +++ .../server/petstore/python-aiohttp/pom.xml | 46 + .../petstore/python-aiohttp/requirements.txt | 3 + .../python-aiohttp/test-requirements.txt | 6 + .../petstore/python-aiohttp/test_python3.sh | 32 + .../tests}/__init__.py | 0 .../petstore/python-aiohttp/tests/conftest.py | 17 + .../tests/test_pet_controller.py | 200 ++++ .../tests/test_store_controller.py | 76 ++ .../tests/test_user_controller.py | 149 +++ .../.dockerignore | 0 .../.gitignore | 0 .../.openapi-generator-ignore | 0 .../.openapi-generator/VERSION | 0 .../.travis.yml | 0 .../Dockerfile | 0 .../petstore/python-flask-python2/Makefile | 20 + .../README.md | 0 .../git_push.sh | 0 .../openapi_server/__init__.py | 0 .../openapi_server/__main__.py | 0 .../openapi_server/controllers/__init__.py | 0 .../controllers/pet_controller.py | 0 .../controllers/security_controller_.py | 48 + .../controllers/store_controller.py | 0 .../controllers/user_controller.py | 0 .../openapi_server/encoder.py | 0 .../openapi_server/models/__init__.py | 0 .../openapi_server/models/api_response.py | 0 .../openapi_server/models/base_model_.py | 0 .../openapi_server/models/category.py | 0 .../openapi_server/models/order.py | 0 .../openapi_server/models/pet.py | 2 + .../openapi_server/models/tag.py | 0 .../openapi_server/models/user.py | 0 .../openapi_server/openapi/openapi.yaml | 21 +- .../openapi_server/test/__init__.py | 0 .../test/test_pet_controller.py | 93 +- .../test/test_store_controller.py | 32 +- .../test/test_user_controller.py | 50 +- .../openapi_server/util.py | 0 .../petstore/python-flask-python2/pom.xml | 46 + .../requirements.txt | 2 +- .../setup.py | 0 .../test-requirements.txt | 0 .../python-flask-python2/test_python2.sh | 32 + .../tox.ini | 2 +- .../.dockerignore | 0 .../.gitignore | 0 .../python-flask/.openapi-generator-ignore | 23 + .../python-flask/.openapi-generator/VERSION | 1 + .../.travis.yml | 0 .../Dockerfile | 0 samples/server/petstore/python-flask/Makefile | 20 + .../README.md | 0 .../git_push.sh | 0 .../python-flask/openapi_server/__init__.py | 0 .../openapi_server/__main__.py | 0 .../openapi_server/controllers/__init__.py | 0 .../controllers/pet_controller.py | 0 .../controllers/security_controller_.py | 48 + .../controllers/store_controller.py | 0 .../controllers/user_controller.py | 0 .../openapi_server/encoder.py | 0 .../openapi_server/models/__init__.py | 0 .../openapi_server/models/api_response.py | 0 .../openapi_server/models/base_model_.py | 0 .../openapi_server/models/category.py | 0 .../openapi_server/models/order.py | 0 .../openapi_server/models/pet.py | 2 + .../openapi_server/models/tag.py | 0 .../openapi_server/models/user.py | 0 .../openapi_server/openapi/openapi.yaml | 21 +- .../openapi_server/test/__init__.py | 0 .../test/test_pet_controller.py | 93 +- .../test/test_store_controller.py | 32 +- .../test/test_user_controller.py | 50 +- .../openapi_server/util.py | 0 samples/server/petstore/python-flask/pom.xml | 46 + .../requirements.txt | 2 +- .../{flaskConnexion => python-flask}/setup.py | 0 .../test-requirements.txt | 0 .../petstore/python-flask/test_python3.sh | 32 + .../{flaskConnexion => python-flask}/tox.ini | 2 +- 160 files changed, 5404 insertions(+), 815 deletions(-) delete mode 100755 bin/python-flask-all.sh delete mode 100755 bin/python-flask-petstore-python2.sh rename bin/{python-flask-petstore.sh => python-server-aiohttp-petstore.sh} (52%) create mode 100755 bin/python-server-all.sh create mode 100755 bin/python-server-flask-petstore-python2.sh create mode 100755 bin/python-server-flask-petstore.sh create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAiohttpConnexionServerCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/README.mustache rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-aiohttp}/__init__.mustache (100%) create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/__init__main.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/__init__model.mustache rename samples/server/petstore/flaskConnexion-python2/openapi_server/__init__.py => modules/openapi-generator/src/main/resources/python-aiohttp/__init__test.mustache (100%) create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/__main__.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/base_model_.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/conftest.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/controller.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/controller_test.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/model.mustache rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-aiohttp}/openapi.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-aiohttp}/param_type.mustache (100%) create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/requirements.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/security_controller_.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/test-requirements.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-aiohttp/util.mustache rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/Dockerfile.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/README.mustache (100%) rename samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/__init__.py => modules/openapi-generator/src/main/resources/python-flask/__init__.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/__init__model.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/__init__test.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/__main__.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/base_model_.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/controller.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/controller_test.mustache (65%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/dockerignore.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/encoder.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/git_push.sh.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/gitignore.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/model.mustache (98%) create mode 100644 modules/openapi-generator/src/main/resources/python-flask/openapi.mustache create mode 100644 modules/openapi-generator/src/main/resources/python-flask/param_type.mustache rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/requirements.mustache (87%) create mode 100644 modules/openapi-generator/src/main/resources/python-flask/security_controller_.mustache rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/setup.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/test-requirements.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/tox.mustache (68%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/travis.mustache (100%) rename modules/openapi-generator/src/main/resources/{flaskConnexion => python-flask}/util.mustache (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-aiohttp}/.openapi-generator-ignore (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-aiohttp}/.openapi-generator/VERSION (100%) create mode 100644 samples/server/petstore/python-aiohttp/Makefile create mode 100644 samples/server/petstore/python-aiohttp/README.md create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/__main__.py rename samples/server/petstore/{flaskConnexion/openapi_server => python-aiohttp/openapi_server/controllers}/__init__.py (100%) create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/controllers/pet_controller.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/controllers/security_controller_.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/controllers/store_controller.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/controllers/user_controller.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/__init__.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/api_response.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/base_model_.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/category.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/order.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/pet.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/tag.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/models/user.py create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/openapi/openapi.yaml create mode 100644 samples/server/petstore/python-aiohttp/openapi_server/util.py create mode 100644 samples/server/petstore/python-aiohttp/pom.xml create mode 100644 samples/server/petstore/python-aiohttp/requirements.txt create mode 100644 samples/server/petstore/python-aiohttp/test-requirements.txt create mode 100755 samples/server/petstore/python-aiohttp/test_python3.sh rename samples/server/petstore/{flaskConnexion/openapi_server/controllers => python-aiohttp/tests}/__init__.py (100%) create mode 100644 samples/server/petstore/python-aiohttp/tests/conftest.py create mode 100644 samples/server/petstore/python-aiohttp/tests/test_pet_controller.py create mode 100644 samples/server/petstore/python-aiohttp/tests/test_store_controller.py create mode 100644 samples/server/petstore/python-aiohttp/tests/test_user_controller.py rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/.dockerignore (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/.gitignore (100%) rename samples/server/petstore/{flaskConnexion => python-flask-python2}/.openapi-generator-ignore (100%) rename samples/server/petstore/{flaskConnexion => python-flask-python2}/.openapi-generator/VERSION (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/.travis.yml (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/Dockerfile (100%) create mode 100644 samples/server/petstore/python-flask-python2/Makefile rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/README.md (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/git_push.sh (100%) create mode 100644 samples/server/petstore/python-flask-python2/openapi_server/__init__.py rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/__main__.py (100%) create mode 100644 samples/server/petstore/python-flask-python2/openapi_server/controllers/__init__.py rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/controllers/pet_controller.py (100%) create mode 100644 samples/server/petstore/python-flask-python2/openapi_server/controllers/security_controller_.py rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/controllers/store_controller.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/controllers/user_controller.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/encoder.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/__init__.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/api_response.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/base_model_.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/category.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/order.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/pet.py (98%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/tag.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/models/user.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/openapi/openapi.yaml (97%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/test/__init__.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/test/test_pet_controller.py (59%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/test/test_store_controller.py (65%) rename samples/server/petstore/{flaskConnexion => python-flask-python2}/openapi_server/test/test_user_controller.py (70%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/openapi_server/util.py (100%) create mode 100644 samples/server/petstore/python-flask-python2/pom.xml rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/requirements.txt (82%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/setup.py (100%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/test-requirements.txt (100%) create mode 100755 samples/server/petstore/python-flask-python2/test_python2.sh rename samples/server/petstore/{flaskConnexion-python2 => python-flask-python2}/tox.ini (85%) rename samples/server/petstore/{flaskConnexion => python-flask}/.dockerignore (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/.gitignore (100%) create mode 100644 samples/server/petstore/python-flask/.openapi-generator-ignore create mode 100644 samples/server/petstore/python-flask/.openapi-generator/VERSION rename samples/server/petstore/{flaskConnexion => python-flask}/.travis.yml (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/Dockerfile (100%) create mode 100644 samples/server/petstore/python-flask/Makefile rename samples/server/petstore/{flaskConnexion => python-flask}/README.md (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/git_push.sh (100%) create mode 100644 samples/server/petstore/python-flask/openapi_server/__init__.py rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/__main__.py (100%) create mode 100644 samples/server/petstore/python-flask/openapi_server/controllers/__init__.py rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/controllers/pet_controller.py (100%) create mode 100644 samples/server/petstore/python-flask/openapi_server/controllers/security_controller_.py rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/controllers/store_controller.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/controllers/user_controller.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/encoder.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/__init__.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/api_response.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/base_model_.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/category.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/order.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/pet.py (98%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/tag.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/models/user.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/openapi/openapi.yaml (97%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/test/__init__.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/test/test_pet_controller.py (59%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/test/test_store_controller.py (65%) rename samples/server/petstore/{flaskConnexion-python2 => python-flask}/openapi_server/test/test_user_controller.py (70%) rename samples/server/petstore/{flaskConnexion => python-flask}/openapi_server/util.py (100%) create mode 100644 samples/server/petstore/python-flask/pom.xml rename samples/server/petstore/{flaskConnexion => python-flask}/requirements.txt (79%) rename samples/server/petstore/{flaskConnexion => python-flask}/setup.py (100%) rename samples/server/petstore/{flaskConnexion => python-flask}/test-requirements.txt (100%) create mode 100755 samples/server/petstore/python-flask/test_python3.sh rename samples/server/petstore/{flaskConnexion => python-flask}/tox.ini (89%) diff --git a/bin/openapi3/python-flask-petstore-python2.sh b/bin/openapi3/python-flask-petstore-python2.sh index f804f1a69dc..c98569d10c4 100755 --- a/bin/openapi3/python-flask-petstore-python2.sh +++ b/bin/openapi3/python-flask-petstore-python2.sh @@ -26,9 +26,25 @@ then fi # if you've executed sbt assembly previously it will use that instead. -export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" -#ags="generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -DsupportPython2=true $@" -ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -c bin/supportPython2.json -D service $@" +input=modules/openapi-generator/src/test/resources/3_0/petstore.yaml +out_folder=samples/server/openapi3/petstore/python-flask-python2 +resources=modules/openapi-generator/src/main/resources/python-flask + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t $resources -i $input -g python-flask -o $out_folder -c bin/supportPython2.json -D service $@" + +rm -rf $out_folder/.openapi* +rm -rf $out_folder/openapi_server +rm $out_folder/.dockerignore +rm $out_folder/.gitignore +rm $out_folder/.travis.yml +rm $out_folder/Dockerfile +rm $out_folder/git_push.sh +rm $out_folder/README.md +rm $out_folder/requirements.txt +rm $out_folder/setup.py +rm $out_folder/test-requirements.txt +rm $out_folder/tox.ini -rm -rf samples/server/petstore/flaskConnexion-python2/* java $JAVA_OPTS -jar $executable $ags diff --git a/bin/openapi3/python-flask-petstore.sh b/bin/openapi3/python-flask-petstore.sh index 8fce1e0c8cf..473c69a7b2c 100755 --- a/bin/openapi3/python-flask-petstore.sh +++ b/bin/openapi3/python-flask-petstore.sh @@ -26,8 +26,25 @@ then fi # if you've executed sbt assembly previously it will use that instead. -export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" -ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion -Dservice $@" +input=modules/openapi-generator/src/test/resources/3_0/petstore.yaml +out_folder=samples/server/openapi3/petstore/python-flask +resources=modules/openapi-generator/src/main/resources/python-flask + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t $resources -i $input -g python-flask -o $out_folder -Dservice $@" + +rm -rf $out_folder/.openapi* +rm -rf $out_folder/openapi_server +rm $out_folder/.dockerignore +rm $out_folder/.gitignore +rm $out_folder/.travis.yml +rm $out_folder/Dockerfile +rm $out_folder/git_push.sh +rm $out_folder/README.md +rm $out_folder/requirements.txt +rm $out_folder/setup.py +rm $out_folder/test-requirements.txt +rm $out_folder/tox.ini -rm -rf samples/server/petstore/flaskConnexion/* java $JAVA_OPTS -jar $executable $ags diff --git a/bin/python-flask-all.sh b/bin/python-flask-all.sh deleted file mode 100755 index ed4ca88d286..00000000000 --- a/bin/python-flask-all.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -./bin/python-flask-petstore.sh -./bin/python-flask-petstore-python2.sh diff --git a/bin/python-flask-petstore-python2.sh b/bin/python-flask-petstore-python2.sh deleted file mode 100755 index 1b427d48f6a..00000000000 --- a/bin/python-flask-petstore-python2.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh - -SCRIPT="$0" -echo "# START SCRIPT: $SCRIPT" - -while [ -h "$SCRIPT" ] ; do - ls=`ls -ld "$SCRIPT"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - SCRIPT="$link" - else - SCRIPT=`dirname "$SCRIPT"`/"$link" - fi -done - -if [ ! -d "${APP_DIR}" ]; then - APP_DIR=`dirname "$SCRIPT"`/.. - APP_DIR=`cd "${APP_DIR}"; pwd` -fi - -executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" - -if [ ! -f "$executable" ] -then - mvn -B clean package -fi - -# if you've executed sbt assembly previously it will use that instead. -export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" -#ags="generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -DsupportPython2=true $@" -ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -c bin/supportPython2.json -D service $@" - -rm -rf samples/server/petstore/flaskConnexion-python2/* -java $JAVA_OPTS -jar $executable $ags diff --git a/bin/python-flask-petstore.sh b/bin/python-server-aiohttp-petstore.sh similarity index 52% rename from bin/python-flask-petstore.sh rename to bin/python-server-aiohttp-petstore.sh index 1f599fe7879..38aae23d5f3 100755 --- a/bin/python-flask-petstore.sh +++ b/bin/python-server-aiohttp-petstore.sh @@ -25,9 +25,20 @@ then mvn -B clean package fi -# if you've executed sbt assembly previously it will use that instead. -export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" -ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion -Dservice $@" +generator=python-aiohttp +input=modules/openapi-generator/src/test/resources/2_0/petstore.yaml +out_folder=samples/server/petstore/$generator +resources=modules/openapi-generator/src/main/resources/$generator + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t $resources -i $input -g $generator -o $out_folder -Dservice $@" + +rm -rf $out_folder/.openapi* +rm -rf $out_folder/openapi_server +rm -rf $out_folder/tests* +rm $out_folder/README.md +rm $out_folder/requirements.txt +rm $out_folder/test-requirements.txt -rm -rf samples/server/petstore/flaskConnexion/* java $JAVA_OPTS -jar $executable $ags diff --git a/bin/python-server-all.sh b/bin/python-server-all.sh new file mode 100755 index 00000000000..5a4f5c75489 --- /dev/null +++ b/bin/python-server-all.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +./bin/python-server-aiohttp-petstore.sh +./bin/python-server-flask-petstore.sh +./bin/python-server-flask-petstore-python2.sh diff --git a/bin/python-server-flask-petstore-python2.sh b/bin/python-server-flask-petstore-python2.sh new file mode 100755 index 00000000000..debbb458a15 --- /dev/null +++ b/bin/python-server-flask-petstore-python2.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +generator=python-flask +input=modules/openapi-generator/src/test/resources/2_0/petstore.yaml +out_folder=samples/server/petstore/$generator-python2 +resources=modules/openapi-generator/src/main/resources/$generator + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t $resources -i $input -g $generator -o $out_folder -c bin/supportPython2.json -D service $@" + +rm -rf $out_folder/.openapi* +rm -rf $out_folder/openapi_server +rm $out_folder/.dockerignore +rm $out_folder/.gitignore +rm $out_folder/.travis.yml +rm $out_folder/Dockerfile +rm $out_folder/git_push.sh +rm $out_folder/README.md +rm $out_folder/requirements.txt +rm $out_folder/setup.py +rm $out_folder/test-requirements.txt +rm $out_folder/tox.ini + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/python-server-flask-petstore.sh b/bin/python-server-flask-petstore.sh new file mode 100755 index 00000000000..915aacd540a --- /dev/null +++ b/bin/python-server-flask-petstore.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +generator=python-flask +input=modules/openapi-generator/src/test/resources/2_0/petstore.yaml +out_folder=samples/server/petstore/$generator +resources=modules/openapi-generator/src/main/resources/$generator + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t $resources -i $input -g $generator -o $out_folder -Dservice $@" + +rm -rf $out_folder/.openapi* +rm -rf $out_folder/openapi_server +rm $out_folder/.dockerignore +rm $out_folder/.gitignore +rm $out_folder/.travis.yml +rm $out_folder/Dockerfile +rm $out_folder/git_push.sh +rm $out_folder/README.md +rm $out_folder/requirements.txt +rm $out_folder/setup.py +rm $out_folder/test-requirements.txt +rm $out_folder/tox.ini + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/windows/python2-flask-petstore.bat b/bin/windows/python2-flask-petstore.bat index d0999b8e92e..cc77dfcd5fc 100755 --- a/bin/windows/python2-flask-petstore.bat +++ b/bin/windows/python2-flask-petstore.bat @@ -5,6 +5,6 @@ If Not Exist %executable% ( ) REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\flaskConnexion-python2 -c bin\supportPython2.json -D service +set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\python-flask-python2 -c bin\supportPython2.json -D service java %JAVA_OPTS% -jar %executable% %ags% diff --git a/bin/windows/python3-flask-petstore.bat b/bin/windows/python3-flask-petstore.bat index 283c896c290..c1677662bd0 100755 --- a/bin/windows/python3-flask-petstore.bat +++ b/bin/windows/python3-flask-petstore.bat @@ -5,6 +5,6 @@ If Not Exist %executable% ( ) REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\flaskConnexion -D service +set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\python-flask -D service java %JAVA_OPTS% -jar %executable% %ags% diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java index 3f267a53045..24d665aacbb 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java @@ -27,7 +27,10 @@ import java.util.Map; public class CodegenSecurity { public String name; public String type; + public String scheme; public Boolean hasMore, isBasic, isOAuth, isApiKey; + // is Basic is true for all http authentication type. Those are to differentiate basic and bearer authentication + public Boolean isBasicBasic, isBasicBearer; public Map vendorExtensions = new HashMap(); // ApiKey specific public String keyParamName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index a41e57378aa..be6c725381b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3230,6 +3230,8 @@ public class DefaultCodegen implements CodegenConfig { cs.name = key; cs.type = securityScheme.getType().toString(); cs.isCode = cs.isPassword = cs.isApplication = cs.isImplicit = false; + cs.isBasicBasic = cs.isBasicBearer = false; + cs.scheme = securityScheme.getScheme(); if (SecurityScheme.Type.APIKEY.equals(securityScheme.getType())) { cs.isBasic = cs.isOAuth = false; @@ -3241,6 +3243,12 @@ public class DefaultCodegen implements CodegenConfig { } else if (SecurityScheme.Type.HTTP.equals(securityScheme.getType())) { cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isOAuth = false; cs.isBasic = true; + if ("basic".equals(securityScheme.getScheme())) { + cs.isBasicBasic = true; + } + else if ("bearer".equals(securityScheme.getScheme())) { + cs.isBasicBearer = true; + } } else if (SecurityScheme.Type.OAUTH2.equals(securityScheme.getType())) { cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = false; cs.isOAuth = true; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java new file mode 100644 index 00000000000..ecfe5964f12 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAbstractConnexionServerCodegen.java @@ -0,0 +1,908 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.PathItem.HttpMethod; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.io.FilenameUtils; +import org.openapitools.codegen.*; +import org.openapitools.codegen.utils.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.File; +import java.util.*; + +import static org.openapitools.codegen.utils.StringUtils.camelize; +import static org.openapitools.codegen.utils.StringUtils.underscore; + +public class PythonAbstractConnexionServerCodegen extends DefaultCodegen implements CodegenConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(PythonAbstractConnexionServerCodegen.class); + + public static final String CONTROLLER_PACKAGE = "controllerPackage"; + public static final String DEFAULT_CONTROLLER = "defaultController"; + public static final String SUPPORT_PYTHON2 = "supportPython2"; + static final String MEDIA_TYPE = "mediaType"; + + protected int serverPort = 8080; + protected String packageName; + protected String packageVersion; + protected String controllerPackage; + protected String defaultController; + protected Map regexModifiers; + protected boolean fixBodyName; + + public PythonAbstractConnexionServerCodegen(String templateDirectory, boolean fixBodyNameValue) { + super(); + fixBodyName = fixBodyNameValue; + modelPackage = "models"; + testPackage = "test"; + + languageSpecificPrimitives.clear(); + languageSpecificPrimitives.add("int"); + languageSpecificPrimitives.add("float"); + languageSpecificPrimitives.add("List"); + languageSpecificPrimitives.add("Dict"); + languageSpecificPrimitives.add("bool"); + languageSpecificPrimitives.add("str"); + languageSpecificPrimitives.add("datetime"); + languageSpecificPrimitives.add("date"); + languageSpecificPrimitives.add("file"); + languageSpecificPrimitives.add("object"); + languageSpecificPrimitives.add("byte"); + languageSpecificPrimitives.add("bytearray"); + + typeMapping.clear(); + typeMapping.put("integer", "int"); + typeMapping.put("float", "float"); + typeMapping.put("number", "float"); + typeMapping.put("long", "int"); + typeMapping.put("double", "float"); + typeMapping.put("array", "List"); + typeMapping.put("map", "Dict"); + typeMapping.put("boolean", "bool"); + typeMapping.put("string", "str"); + typeMapping.put("date", "date"); + typeMapping.put("DateTime", "datetime"); + typeMapping.put("object", "object"); + typeMapping.put("file", "file"); + typeMapping.put("UUID", "str"); + typeMapping.put("byte", "bytearray"); + typeMapping.put("ByteArray", "bytearray"); + + // from https://docs.python.org/3/reference/lexical_analysis.html#keywords + setReservedWordsLowerCase( + Arrays.asList( + // @property + "property", + // python reserved words + "and", "del", "from", "not", "while", "as", "elif", "global", "or", "with", + "assert", "else", "if", "pass", "yield", "break", "except", "import", + "print", "class", "exec", "in", "raise", "continue", "finally", "is", + "return", "def", "for", "lambda", "try", "self", "None", "True", "False", "nonlocal")); + + // set the output folder here + outputFolder = "generated-code/connexion"; + + apiTemplateFiles.put("controller.mustache", ".py"); + modelTemplateFiles.put("model.mustache", ".py"); + apiTestTemplateFiles().put("controller_test.mustache", ".py"); + + /* + * Template Location. This is the location which templates will be read from. The generator + * will use the resource stream to attempt to read the templates. + */ + embeddedTemplateDir = templateDir = templateDirectory; + + /* + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("serverPort", serverPort); + + /* + * Supporting Files. You can write single files for the generator with the + * entire object tree available. If the input file has a suffix of `.mustache + * it will be processed by the template engine. Otherwise, it will be copied + */ + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt")); + supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt")); + + regexModifiers = new HashMap(); + regexModifiers.put('i', "IGNORECASE"); + regexModifiers.put('l', "LOCALE"); + regexModifiers.put('m', "MULTILINE"); + regexModifiers.put('s', "DOTALL"); + regexModifiers.put('u', "UNICODE"); + regexModifiers.put('x', "VERBOSE"); + + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).") + .defaultValue("openapi_server")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.") + .defaultValue("1.0.0")); + cliOptions.add(new CliOption(CONTROLLER_PACKAGE, "controller package"). + defaultValue("controllers")); + cliOptions.add(new CliOption(DEFAULT_CONTROLLER, "default controller"). + defaultValue("default_controller")); + cliOptions.add(new CliOption(SUPPORT_PYTHON2, "support python2"). + defaultValue("false")); + cliOptions.add(new CliOption("serverPort", "TCP port to listen to in app.run"). + defaultValue("8080")); + } + + protected void addSupportingFiles() { + } + + @Override + public void processOpts() { + super.processOpts(); + + if (StringUtils.isEmpty(System.getenv("PYTHON_POST_PROCESS_FILE"))) { + LOGGER.info("Environment variable PYTHON_POST_PROCESS_FILE not defined so the Python code may not be properly formatted. To define it, try 'export PYTHON_POST_PROCESS_FILE=\"/usr/local/bin/yapf -i\"' (Linux/Mac)"); + LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI)."); + } + + //apiTemplateFiles.clear(); + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + setPackageName("openapi_server"); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName); + } + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } else { + setPackageVersion("1.0.0"); + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion); + } + if (additionalProperties.containsKey(CONTROLLER_PACKAGE)) { + this.controllerPackage = additionalProperties.get(CONTROLLER_PACKAGE).toString(); + } else { + this.controllerPackage = "controllers"; + additionalProperties.put(CONTROLLER_PACKAGE, this.controllerPackage); + } + if (additionalProperties.containsKey(DEFAULT_CONTROLLER)) { + this.defaultController = additionalProperties.get(DEFAULT_CONTROLLER).toString(); + } else { + this.defaultController = "default_controller"; + additionalProperties.put(DEFAULT_CONTROLLER, this.defaultController); + } + if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) { + additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE); + typeMapping.put("long", "long"); + } + supportingFiles.add(new SupportingFile("__main__.mustache", packageName, "__main__.py")); + supportingFiles.add(new SupportingFile("util.mustache", packageName, "util.py")); + supportingFiles.add(new SupportingFile("__init__.mustache", packageName + File.separatorChar + controllerPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("security_controller_.mustache", packageName + File.separatorChar + controllerPackage, "security_controller_.py")); + supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("base_model_.mustache", packageName + File.separatorChar + modelPackage, "base_model_.py")); + supportingFiles.add(new SupportingFile("openapi.mustache", packageName + File.separatorChar + "openapi", "openapi.yaml")); + addSupportingFiles(); + + modelPackage = packageName + "." + modelPackage; + controllerPackage = packageName + "." + controllerPackage; + } + + private static String dropDots(String str) { + return str.replaceAll("\\.", "_"); + } + + @Override + public String apiPackage() { + return controllerPackage; + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with help + * tips, parameters here + * + * @return A string value for the help message + */ + @Override + public String getHelp() { + return "Generates a Python server library using the Connexion project. By default, " + + "it will also generate service classes -- which you can disable with the `-Dnoservice` environment variable."; + } + + @Override + public String toApiName(String name) { + if (name == null || name.length() == 0) { + return "DefaultController"; + } + return camelize(name, false) + "Controller"; + } + + @Override + public String toApiFilename(String name) { + return underscore(toApiName(name)); + } + + @Override + public String toApiTestFilename(String name) { + return "test_" + toApiFilename(name); + } + + /** + * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping + * those terms here. This logic is only called if a variable matches the reserved words + * + * @return the escaped term + */ + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; // add an underscore to the name + } + + /** + * Location to write api files. You can use the apiPackage() as defined when the class is + * instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (ModelUtils.isArraySchema(p)) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]"; + } else if (ModelUtils.isMapSchema(p)) { + Schema inner = ModelUtils.getAdditionalProperties(p); + return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String getSchemaType(Schema p) { + String schemaType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(schemaType)) { + type = typeMapping.get(schemaType); + if (languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = toModelName(schemaType); + } + return type; + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + // need vendor extensions for x-openapi-router-controller + Map paths = openAPI.getPaths(); + if (paths != null) { + List pathnames = new ArrayList(paths.keySet()); + for (String pathname : pathnames) { + PathItem path = paths.get(pathname); + // Fix path parameters to be in snake_case + if (pathname.contains("{")) { + String fixedPath = new String(); + for (String token: pathname.substring(1).split("/")) { + if (token.startsWith("{")) { + String snake_case_token = "{" + this.toParamName(token.substring(1, token.length()-1)) + "}"; + if(token != snake_case_token) { + token = snake_case_token; + } + } + fixedPath += "/" + token; + } + if (!fixedPath.equals(pathname)) { + LOGGER.warn("Path '" + pathname + "' is not consistant with Python variable names. It will be replaced by '" + fixedPath + "'"); + paths.remove(pathname); + paths.put(fixedPath, path); + } + } + Map operationMap = path.readOperationsMap(); + if (operationMap != null) { + for (HttpMethod method : operationMap.keySet()) { + Operation operation = operationMap.get(method); + String tag = "default"; + if (operation.getTags() != null && operation.getTags().size() > 0) { + tag = operation.getTags().get(0); + } + String operationId = operation.getOperationId(); + if (operationId == null) { + operationId = getOrGenerateOperationId(operation, pathname, method.toString()); + } + operation.setOperationId(toOperationId(operationId)); + if (operation.getExtensions() == null || operation.getExtensions().get("x-openapi-router-controller") == null) { + operation.addExtension( + "x-openapi-router-controller", + controllerPackage + "." + toApiFilename(tag) + ); + } + if (operation.getParameters() != null) { + for (Parameter parameter: operation.getParameters()) { + String swaggerParameterName = parameter.getName(); + String pythonParameterName = this.toParamName(swaggerParameterName); + if (!swaggerParameterName.equals(pythonParameterName)) { + LOGGER.warn("Parameter name '" + swaggerParameterName + "' is not consistant with Python variable names. It will be replaced by '" + pythonParameterName + "'"); + parameter.setName(pythonParameterName); + } + if (swaggerParameterName.isEmpty()) { + LOGGER.error("Missing parameter name in " + pathname + "." + parameter.getIn()); + } + } + } + RequestBody body = operation.getRequestBody(); + if (fixBodyName && body != null) { + if (body.getExtensions() == null || !body.getExtensions().containsKey("x-body-name")) { + String bodyParameterName = "body"; + if (operation.getExtensions() != null && operation.getExtensions().containsKey("x-codegen-request-body-name")) { + bodyParameterName = (String) operation.getExtensions().get("x-codegen-request-body-name"); + } + else { + // Used by code generator + operation.addExtension("x-codegen-request-body-name", bodyParameterName); + } + // Used by connexion + body.addExtension("x-body-name", bodyParameterName); + } + } + } + } + } + // Sort path names after variable name fix + List fixedPathnames = new ArrayList(paths.keySet()); + Collections.sort(fixedPathnames); + for (String pathname: fixedPathnames) { + PathItem pathItem = paths.remove(pathname); + paths.put(pathname, pathItem); + } + } + addSecurityExtensions(openAPI); + } + + private void addSecurityExtension(SecurityScheme securityScheme, String extensionName, String functionName) { + if (securityScheme.getExtensions() == null || ! securityScheme.getExtensions().containsKey(extensionName)) { + securityScheme.addExtension(extensionName, functionName); + } + } + + private void addSecurityExtensions(OpenAPI openAPI) { + Components components = openAPI.getComponents(); + if (components != null && components.getSecuritySchemes() != null) { + Map securitySchemes = components.getSecuritySchemes(); + for(String securityName: securitySchemes.keySet()) { + SecurityScheme securityScheme = securitySchemes.get(securityName); + String baseFunctionName = controllerPackage + ".security_controller_."; + switch(securityScheme.getType()) { + case APIKEY: + addSecurityExtension(securityScheme, "x-apikeyInfoFunc", baseFunctionName + "info_from_" + securityName); + break; + case HTTP: + if ("basic".equals(securityScheme.getScheme())) { + addSecurityExtension(securityScheme, "x-basicInfoFunc", baseFunctionName + "info_from_" + securityName); + } + else if ("bearer".equals(securityScheme.getScheme())) { + addSecurityExtension(securityScheme, "x-bearerInfoFunc", baseFunctionName + "info_from_" + securityName); + } + break; + case OPENIDCONNECT: + LOGGER.warn("Security type " + securityScheme.getType().toString() + " is not supported by connextion yet"); + case OAUTH2: + addSecurityExtension(securityScheme, "x-tokenInfoFunc", baseFunctionName + "info_from_" + securityName); + addSecurityExtension(securityScheme, "x-scopeValidateFunc", baseFunctionName + "validate_scope_" + securityName); + break; + default: + LOGGER.warn("Unknown security type " + securityScheme.getType().toString()); + } + } + } + } + + @SuppressWarnings("unchecked") + private static List> getOperations(Map objs) { + List> result = new ArrayList>(); + Map apiInfo = (Map) objs.get("apiInfo"); + List> apis = (List>) apiInfo.get("apis"); + for (Map api : apis) { + result.add((Map) api.get("operations")); + } + return result; + } + + private static List> sortOperationsByPath(List ops) { + Multimap opsByPath = ArrayListMultimap.create(); + + for (CodegenOperation op : ops) { + opsByPath.put(op.path, op); + } + + List> opsByPathList = new ArrayList>(); + for (Map.Entry> entry : opsByPath.asMap().entrySet()) { + Map opsByPathEntry = new HashMap(); + opsByPathList.add(opsByPathEntry); + opsByPathEntry.put("path", entry.getKey()); + opsByPathEntry.put("operation", entry.getValue()); + List operationsForThisPath = Lists.newArrayList(entry.getValue()); + operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false; + if (opsByPathList.size() < opsByPath.asMap().size()) { + opsByPathEntry.put("hasMore", "true"); + } + } + + return opsByPathList; + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + generateYAMLSpecFile(objs); + + for (Map operations : getOperations(objs)) { + @SuppressWarnings("unchecked") + List ops = (List) operations.get("operation"); + + List> opsByPathList = sortOperationsByPath(ops); + operations.put("operationsByPath", opsByPathList); + } + return super.postProcessSupportingFileData(objs); + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + // remove dollar sign + name = name.replaceAll("$", ""); + + // if it's all uppper case, convert to lower case + if (name.matches("^[A-Z_]*$")) { + name = name.toLowerCase(Locale.ROOT); + } + + // underscore the variable name + // petId => pet_id + name = underscore(name); + + // remove leading underscore + name = name.replaceAll("^_*", ""); + + // for reserved word or word starting with number, append _ + if (isReservedWord(name) || name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public String toParamName(String name) { + // to avoid conflicts with 'callback' parameter for async call + if ("callback".equals(name)) { + return "param_callback"; + } + + // should be the same as variable name + return toVarName(name); + } + + @Override + public String toModelFilename(String name) { + // underscore the model file name + // PhoneNumber => phone_number + return underscore(dropDots(toModelName(name))); + } + + @Override + public String toModelName(String name) { + name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + // remove dollar sign + name = name.replaceAll("$", ""); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name)); + name = "model_" + name; // e.g. return => ModelReturn (after camelize) + } + + // model name starts with number + if (name.matches("^\\d.*")) { + LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name)); + name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) + } + + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // camelize the model name + // phone_number => PhoneNumber + return camelize(name); + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty (should not occur as an auto-generated method name will be used) + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method name (operationId) not allowed"); + } + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId))); + operationId = "call_" + operationId; + } + + return underscore(sanitizeName(operationId)); + } + + /** + * Return the default value of the property + * + * @param p OpenAPI property object + * @return string presentation of the default value of the property + */ + @Override + public String toDefaultValue(Schema p) { + if (ModelUtils.isBooleanSchema(p)) { + if (p.getDefault() != null) { + if (p.getDefault().toString().equalsIgnoreCase("false")) + return "False"; + else + return "True"; + } + } else if (ModelUtils.isDateSchema(p)) { + // TODO + } else if (ModelUtils.isDateTimeSchema(p)) { + // TODO + } else if (ModelUtils.isNumberSchema(p)) { + if (p.getDefault() != null) { + return p.getDefault().toString(); + } + } else if (ModelUtils.isIntegerSchema(p)) { + if (p.getDefault() != null) { + return p.getDefault().toString(); + } + } else if (ModelUtils.isStringSchema(p)) { + if (p.getDefault() != null) { + return "'" + (String) p.getDefault() + "'"; + } + } + + return null; + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + String example; + + if (p.defaultValue == null) { + example = p.example; + } else { + p.example = p.defaultValue; + return; + } + + String type = p.baseType; + if (type == null) { + type = p.dataType; + } + + if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) { + if (example == null) { + example = p.paramName + "_example"; + } + example = "'" + escapeText(example) + "'"; + } else if ("Integer".equals(type) || "int".equals(type)) { + if (p.minimum != null) { + example = "" + (Integer.valueOf(p.minimum) + 1); + } + if (p.maximum != null) { + example = "" + p.maximum; + } else if (example == null) { + example = "56"; + } + + } else if ("Long".equalsIgnoreCase(type)) { + if (p.minimum != null) { + example = "" + (Long.valueOf(p.minimum) + 1); + } + if (p.maximum != null) { + example = "" + p.maximum; + } else if (example == null) { + example = "789"; + } + } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) { + if (p.minimum != null) { + example = "" + p.minimum; + } else if (p.maximum != null) { + example = "" + p.maximum; + } else if (example == null) { + example = "3.4"; + } + } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) { + if (example == null) { + example = "True"; + } + } else if ("file".equalsIgnoreCase(type)) { + example = "(BytesIO(b'some file data'), 'file.txt')"; + } else if ("Date".equalsIgnoreCase(type)) { + if (example == null) { + example = "2013-10-20"; + } + example = "'" + escapeText(example) + "'"; + } else if ("DateTime".equalsIgnoreCase(type)) { + if (example == null) { + example = "2013-10-20T19:20:30+01:00"; + } + example = "'" + escapeText(example) + "'"; + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = "{}"; + } else { + LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue"); + } + + if (p.items != null && p.items.defaultValue != null) { + example = p.items.defaultValue; + } + if (example == null) { + if (Boolean.TRUE.equals(p.isListContainer)) { + example = "[]"; + } + else { + example = "None"; + } + } else if (Boolean.TRUE.equals(p.isListContainer)) { + if (Boolean.TRUE.equals(p.isBodyParam)) { + example = "[" + example + "]"; + } + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = "{'key': " + example + "}"; + } + + p.example = example; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + + + @Override + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + // remove multiline comment + return input.replace("'''", "'_'_'"); + } + + @Override + public String toModelImport(String name) { + String modelImport; + if (StringUtils.startsWithAny(name, "import", "from")) { + modelImport = name; + } else { + modelImport = "from "; + if (!"".equals(modelPackage())) { + modelImport += modelPackage() + "."; + } + modelImport += toModelFilename(name) + " import " + name; + } + return modelImport; + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + if (StringUtils.isNotEmpty(property.pattern)) { + addImport(model, "import re"); + } + postProcessPattern(property.pattern, property.vendorExtensions); + } + + @Override + public Map postProcessModels(Map objs) { + // process enum in models + return postProcessModelsEnum(objs); + } + + @Override + public Map postProcessAllModels(Map objs) { + Map result = super.postProcessAllModels(objs); + for (Map.Entry entry : result.entrySet()) { + Map inner = (Map) entry.getValue(); + List> models = (List>) inner.get("models"); + for (Map mo : models) { + CodegenModel cm = (CodegenModel) mo.get("model"); + // Add additional filename information for imports + mo.put("pyImports", toPyImports(cm, cm.imports)); + } + } + return result; + } + + private List> toPyImports(CodegenModel cm, Set imports) { + List> pyImports = new ArrayList<>(); + for (String im : imports) { + if (!im.equals(cm.classname)) { + HashMap pyImport = new HashMap<>(); + pyImport.put("import", toModelImport(im)); + pyImports.add(pyImport); + } + } + return pyImports; + } + + @Override + public void postProcessParameter(CodegenParameter parameter) { + postProcessPattern(parameter.pattern, parameter.vendorExtensions); + } + + @Override + public Map postProcessOperationsWithModels(Map objs, List allModels) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + + for (CodegenOperation operation : operationList) { + Map skipTests = new HashMap<>(); + // Set flag to deactivate tests due to connexion issue. + if (operation.consumes != null ) { + if (operation.consumes.size() == 1) { + Map consume = operation.consumes.get(0); + if (! "application/json".equals(consume.get(MEDIA_TYPE))) { + skipTests.put("reason", consume.get(MEDIA_TYPE) + " not supported by Connexion"); + if ("multipart/form-data".equals(consume.get(MEDIA_TYPE))) { + operation.isMultipart = Boolean.TRUE; + } + } + operation.vendorExtensions.put("x-prefered-consume", consume); + } + else if (operation.consumes.size() > 1) { + Map consume = operation.consumes.get(0); + skipTests.put("reason", "Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760"); + operation.vendorExtensions.put("x-prefered-consume", consume); + if ("multipart/form-data".equals(consume.get(MEDIA_TYPE))) { + operation.isMultipart = Boolean.TRUE; + } + } + } + else { + // A body without consumes means '*/*' has been used instead of application/json + if (operation.bodyParam != null) { + Map consume = new HashMap<>(); + consume.put(MEDIA_TYPE, "application/json"); + operation.vendorExtensions.put("x-prefered-consume", consume); + skipTests.put("reason", "*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760"); + } + } + // Choose to consume 'application/json' if available, else choose the last one. + if (operation.produces != null ) { + for (Map produce: operation.produces) { + operation.vendorExtensions.put("x-prefered-produce", produce); + if (produce.get(MEDIA_TYPE).equals("application/json")) { + break; + } + } + } + if (! skipTests.isEmpty()) { + operation.vendorExtensions.put("x-skip-test", skipTests); + } + if (operation.requestBodyExamples != null) { + for (Map example: operation.requestBodyExamples) { + if (example.get("contentType") != null && example.get("contentType").equals("application/json")) { + operation.bodyParam.example = example.get("example"); + } + } + } + } + return objs; + } + + /* + * The openapi pattern spec follows the Perl convention and style of modifiers. Python + * does not support this in as natural a way so it needs to convert it. See + * https://docs.python.org/2/howto/regex.html#compilation-flags for details. + */ + public void postProcessPattern(String pattern, Map vendorExtensions) { + if (pattern != null) { + int i = pattern.lastIndexOf('/'); + + //Must follow Perl /pattern/modifiers convention + if (pattern.charAt(0) != '/' || i < 2) { + throw new IllegalArgumentException("Pattern must follow the Perl " + + "/pattern/modifiers convention. " + pattern + " is not valid."); + } + + String regex = pattern.substring(1, i).replace("'", "\\'"); + List modifiers = new ArrayList(); + + for (char c : pattern.substring(i).toCharArray()) { + if (regexModifiers.containsKey(c)) { + String modifier = regexModifiers.get(c); + modifiers.add(modifier); + } + } + + vendorExtensions.put("x-regex", regex); + vendorExtensions.put("x-modifiers", modifiers); + } + } + + @Override + public void postProcessFile(File file, String fileType) { + if (file == null) { + return; + } + String pythonPostProcessFile = System.getenv("PYTHON_POST_PROCESS_FILE"); + if (StringUtils.isEmpty(pythonPostProcessFile)) { + return; // skip if PYTHON_POST_PROCESS_FILE env variable is not defined + } + + // only process files with py extension + if ("py".equals(FilenameUtils.getExtension(file.toString()))) { + String command = pythonPostProcessFile + " " + file.toString(); + try { + Process p = Runtime.getRuntime().exec(command); + int exitValue = p.waitFor(); + if (exitValue != 0) { + LOGGER.error("Error running the command ({}). Exit value: {}", command, exitValue); + } else { + LOGGER.info("Successfully executed: " + command); + } + } catch (Exception e) { + LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage()); + } + } + } + +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAiohttpConnexionServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAiohttpConnexionServerCodegen.java new file mode 100644 index 00000000000..bcdd450c703 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonAiohttpConnexionServerCodegen.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.File; +import java.util.*; + +public class PythonAiohttpConnexionServerCodegen extends PythonAbstractConnexionServerCodegen { + private static final Logger LOGGER = LoggerFactory.getLogger(PythonAiohttpConnexionServerCodegen.class); + + public PythonAiohttpConnexionServerCodegen() { + super("python-aiohttp", true); + testPackage = "tests"; + embeddedTemplateDir = templateDir = "python-aiohttp"; + } + + /** + * Configures a friendly name for the generator. This will be used by the generator + * to select the library with the -g flag. + * + * @return the friendly name for the generator + */ + @Override + public String getName() { + return "python-aiohttp"; + } + + @Override + protected void addSupportingFiles() { + supportingFiles.add(new SupportingFile("conftest.mustache", testPackage, "conftest.py")); + supportingFiles.add(new SupportingFile("__init__test.mustache", testPackage, "__init__.py")); + } +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java index 498f8abe955..a7aac8d4b6a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonFlaskConnexionServerCodegen.java @@ -1,6 +1,5 @@ /* * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) - * Copyright 2018 SmartBear Software * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,19 +16,7 @@ package org.openapitools.codegen.languages; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.Operation; -import io.swagger.v3.oas.models.PathItem; -import io.swagger.v3.oas.models.PathItem.HttpMethod; -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.Schema; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.io.FilenameUtils; import org.openapitools.codegen.*; -import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; @@ -38,193 +25,11 @@ import java.util.*; import static org.openapitools.codegen.utils.StringUtils.camelize; import static org.openapitools.codegen.utils.StringUtils.underscore; -public class PythonFlaskConnexionServerCodegen extends DefaultCodegen implements CodegenConfig { +public class PythonFlaskConnexionServerCodegen extends PythonAbstractConnexionServerCodegen { private static final Logger LOGGER = LoggerFactory.getLogger(PythonFlaskConnexionServerCodegen.class); - public static final String CONTROLLER_PACKAGE = "controllerPackage"; - public static final String DEFAULT_CONTROLLER = "defaultController"; - public static final String SUPPORT_PYTHON2 = "supportPython2"; - - protected int serverPort = 8080; - protected String packageName; - protected String packageVersion; - protected String controllerPackage; - protected String defaultController; - protected Map regexModifiers; - public PythonFlaskConnexionServerCodegen() { - super(); - modelPackage = "models"; - testPackage = "test"; - - languageSpecificPrimitives.clear(); - languageSpecificPrimitives.add("int"); - languageSpecificPrimitives.add("float"); - languageSpecificPrimitives.add("List"); - languageSpecificPrimitives.add("Dict"); - languageSpecificPrimitives.add("bool"); - languageSpecificPrimitives.add("str"); - languageSpecificPrimitives.add("datetime"); - languageSpecificPrimitives.add("date"); - languageSpecificPrimitives.add("file"); - languageSpecificPrimitives.add("object"); - - typeMapping.clear(); - typeMapping.put("integer", "int"); - typeMapping.put("float", "float"); - typeMapping.put("number", "float"); - typeMapping.put("long", "int"); - typeMapping.put("double", "float"); - typeMapping.put("array", "List"); - typeMapping.put("map", "Dict"); - typeMapping.put("boolean", "bool"); - typeMapping.put("string", "str"); - typeMapping.put("date", "date"); - typeMapping.put("DateTime", "datetime"); - typeMapping.put("object", "object"); - typeMapping.put("file", "file"); - typeMapping.put("UUID", "str"); - - // from https://docs.python.org/3/reference/lexical_analysis.html#keywords - setReservedWordsLowerCase( - Arrays.asList( - // @property - "property", - // python reserved words - "and", "del", "from", "not", "while", "as", "elif", "global", "or", "with", - "assert", "else", "if", "pass", "yield", "break", "except", "import", - "print", "class", "exec", "in", "raise", "continue", "finally", "is", - "return", "def", "for", "lambda", "try", "self", "None", "True", "False", "nonlocal")); - - // set the output folder here - outputFolder = "generated-code/connexion"; - - apiTemplateFiles.put("controller.mustache", ".py"); - modelTemplateFiles.put("model.mustache", ".py"); - apiTestTemplateFiles().put("controller_test.mustache", ".py"); - - /* - * Template Location. This is the location which templates will be read from. The generator - * will use the resource stream to attempt to read the templates. - */ - embeddedTemplateDir = templateDir = "flaskConnexion"; - - /* - * Additional Properties. These values can be passed to the templates and - * are available in models, apis, and supporting files - */ - additionalProperties.put("serverPort", serverPort); - - /* - * Supporting Files. You can write single files for the generator with the - * entire object tree available. If the input file has a suffix of `.mustache - * it will be processed by the template engine. Otherwise, it will be copied - */ - supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); - supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py")); - supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini")); - supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt")); - supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt")); - supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); - supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); - supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml")); - supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile")); - supportingFiles.add(new SupportingFile("dockerignore.mustache", "", ".dockerignore")); - - regexModifiers = new HashMap(); - regexModifiers.put('i', "IGNORECASE"); - regexModifiers.put('l', "LOCALE"); - regexModifiers.put('m', "MULTILINE"); - regexModifiers.put('s', "DOTALL"); - regexModifiers.put('u', "UNICODE"); - regexModifiers.put('x', "VERBOSE"); - - cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).") - .defaultValue("openapi_server")); - cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.") - .defaultValue("1.0.0")); - cliOptions.add(new CliOption(CONTROLLER_PACKAGE, "controller package"). - defaultValue("controllers")); - cliOptions.add(new CliOption(DEFAULT_CONTROLLER, "default controller"). - defaultValue("default_controller")); - cliOptions.add(new CliOption(SUPPORT_PYTHON2, "support python2"). - defaultValue("false")); - cliOptions.add(new CliOption("serverPort", "TCP port to listen to in app.run"). - defaultValue("8080")); - } - - @Override - public void processOpts() { - super.processOpts(); - - if (StringUtils.isEmpty(System.getenv("PYTHON_POST_PROCESS_FILE"))) { - LOGGER.info("Environment variable PYTHON_POST_PROCESS_FILE not defined so the Python code may not be properly formatted. To define it, try 'export PYTHON_POST_PROCESS_FILE=\"/usr/local/bin/yapf -i\"' (Linux/Mac)"); - LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI)."); - } - - //apiTemplateFiles.clear(); - - if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { - setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); - } else { - setPackageName("openapi_server"); - additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName); - } - if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { - setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); - } else { - setPackageVersion("1.0.0"); - additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion); - } - if (additionalProperties.containsKey(CONTROLLER_PACKAGE)) { - this.controllerPackage = additionalProperties.get(CONTROLLER_PACKAGE).toString(); - } else { - this.controllerPackage = "controllers"; - additionalProperties.put(CONTROLLER_PACKAGE, this.controllerPackage); - } - if (additionalProperties.containsKey(DEFAULT_CONTROLLER)) { - this.defaultController = additionalProperties.get(DEFAULT_CONTROLLER).toString(); - } else { - this.defaultController = "default_controller"; - additionalProperties.put(DEFAULT_CONTROLLER, this.defaultController); - } - if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) { - additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE); - typeMapping.put("long", "long"); - } - supportingFiles.add(new SupportingFile("__init__.mustache", packageName, "__init__.py")); - supportingFiles.add(new SupportingFile("__main__.mustache", packageName, "__main__.py")); - supportingFiles.add(new SupportingFile("encoder.mustache", packageName, "encoder.py")); - supportingFiles.add(new SupportingFile("util.mustache", packageName, "util.py")); - supportingFiles.add(new SupportingFile("__init__.mustache", packageName + File.separatorChar + controllerPackage, "__init__.py")); - supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py")); - supportingFiles.add(new SupportingFile("base_model_.mustache", packageName + File.separatorChar + modelPackage, "base_model_.py")); - supportingFiles.add(new SupportingFile("__init__test.mustache", packageName + File.separatorChar + testPackage, "__init__.py")); - supportingFiles.add(new SupportingFile("openapi.mustache", packageName + File.separatorChar + "openapi", "openapi.yaml")); - - modelPackage = packageName + "." + modelPackage; - controllerPackage = packageName + "." + controllerPackage; - testPackage = packageName + "." + testPackage; - } - - private static String dropDots(String str) { - return str.replaceAll("\\.", "_"); - } - - @Override - public String apiPackage() { - return controllerPackage; - } - - /** - * Configures the type of generator. - * - * @return the CodegenType for this generator - * @see org.openapitools.codegen.CodegenType - */ - @Override - public CodegenType getTag() { - return CodegenType.SERVER; + super("python-flask", false); } /** @@ -238,493 +43,18 @@ public class PythonFlaskConnexionServerCodegen extends DefaultCodegen implements return "python-flask"; } - /** - * Returns human-friendly help for the generator. Provide the consumer with help - * tips, parameters here - * - * @return A string value for the help message - */ @Override - public String getHelp() { - return "Generates a Python server library using the Connexion project. By default, " + - "it will also generate service classes -- which you can disable with the `-Dnoservice` environment variable."; + protected void addSupportingFiles() { + supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); + supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile")); + supportingFiles.add(new SupportingFile("dockerignore.mustache", "", ".dockerignore")); + supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py")); + supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini")); + supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); + supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml")); + supportingFiles.add(new SupportingFile("encoder.mustache", packageName, "encoder.py")); + supportingFiles.add(new SupportingFile("__init__test.mustache", packageName + File.separatorChar + testPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__.mustache", packageName, "__init__.py")); + testPackage = packageName + "." + testPackage; } - - @Override - public String toApiName(String name) { - if (name == null || name.length() == 0) { - return "DefaultController"; - } - return camelize(name, false) + "Controller"; - } - - @Override - public String toApiFilename(String name) { - return underscore(toApiName(name)); - } - - @Override - public String toApiTestFilename(String name) { - return "test_" + toApiFilename(name); - } - - /** - * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping - * those terms here. This logic is only called if a variable matches the reserved words - * - * @return the escaped term - */ - @Override - public String escapeReservedWord(String name) { - if (this.reservedWordsMappings().containsKey(name)) { - return this.reservedWordsMappings().get(name); - } - return "_" + name; // add an underscore to the name - } - - /** - * Location to write api files. You can use the apiPackage() as defined when the class is - * instantiated - */ - @Override - public String apiFileFolder() { - return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar); - } - - @Override - public String getTypeDeclaration(Schema p) { - if (ModelUtils.isArraySchema(p)) { - ArraySchema ap = (ArraySchema) p; - Schema inner = ap.getItems(); - return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]"; - } else if (ModelUtils.isMapSchema(p)) { - Schema inner = ModelUtils.getAdditionalProperties(p); - return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]"; - } - return super.getTypeDeclaration(p); - } - - @Override - public String getSchemaType(Schema p) { - String schemaType = super.getSchemaType(p); - String type = null; - if (typeMapping.containsKey(schemaType)) { - type = typeMapping.get(schemaType); - if (languageSpecificPrimitives.contains(type)) { - return type; - } - } else { - type = toModelName(schemaType); - } - return type; - } - - @Override - public void preprocessOpenAPI(OpenAPI openAPI) { - // need vendor extensions for x-openapi-router-controller - Map paths = openAPI.getPaths(); - if (paths != null) { - for (String pathname : paths.keySet()) { - PathItem path = paths.get(pathname); - Map operationMap = path.readOperationsMap(); - if (operationMap != null) { - for (HttpMethod method : operationMap.keySet()) { - Operation operation = operationMap.get(method); - String tag = "default"; - if (operation.getTags() != null && operation.getTags().size() > 0) { - tag = operation.getTags().get(0); - } - String operationId = operation.getOperationId(); - if (operationId == null) { - operationId = getOrGenerateOperationId(operation, pathname, method.toString()); - } - operation.setOperationId(toOperationId(operationId)); - if (operation.getExtensions() == null || operation.getExtensions().get("x-openapi-router-controller") == null) { - operation.addExtension( - "x-openapi-router-controller", - controllerPackage + "." + toApiFilename(tag) - ); - } - } - } - } - } - } - - @SuppressWarnings("unchecked") - private static List> getOperations(Map objs) { - List> result = new ArrayList>(); - Map apiInfo = (Map) objs.get("apiInfo"); - List> apis = (List>) apiInfo.get("apis"); - for (Map api : apis) { - result.add((Map) api.get("operations")); - } - return result; - } - - private static List> sortOperationsByPath(List ops) { - Multimap opsByPath = ArrayListMultimap.create(); - - for (CodegenOperation op : ops) { - opsByPath.put(op.path, op); - } - - List> opsByPathList = new ArrayList>(); - for (Map.Entry> entry : opsByPath.asMap().entrySet()) { - Map opsByPathEntry = new HashMap(); - opsByPathList.add(opsByPathEntry); - opsByPathEntry.put("path", entry.getKey()); - opsByPathEntry.put("operation", entry.getValue()); - List operationsForThisPath = Lists.newArrayList(entry.getValue()); - operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false; - if (opsByPathList.size() < opsByPath.asMap().size()) { - opsByPathEntry.put("hasMore", "true"); - } - } - - return opsByPathList; - } - - @Override - public Map postProcessSupportingFileData(Map objs) { - generateYAMLSpecFile(objs); - - for (Map operations : getOperations(objs)) { - @SuppressWarnings("unchecked") - List ops = (List) operations.get("operation"); - - List> opsByPathList = sortOperationsByPath(ops); - operations.put("operationsByPath", opsByPathList); - } - return super.postProcessSupportingFileData(objs); - } - - @Override - public String toVarName(String name) { - // sanitize name - name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - - // remove dollar sign - name = name.replaceAll("$", ""); - - // if it's all uppper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { - name = name.toLowerCase(Locale.ROOT); - } - - // underscore the variable name - // petId => pet_id - name = underscore(name); - - // remove leading underscore - name = name.replaceAll("^_*", ""); - - // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { - name = escapeReservedWord(name); - } - - return name; - } - - @Override - public String toParamName(String name) { - // to avoid conflicts with 'callback' parameter for async call - if ("callback".equals(name)) { - return "param_callback"; - } - - // should be the same as variable name - return toVarName(name); - } - - @Override - public String toModelFilename(String name) { - // underscore the model file name - // PhoneNumber => phone_number - return underscore(dropDots(toModelName(name))); - } - - @Override - public String toModelName(String name) { - name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - // remove dollar sign - name = name.replaceAll("$", ""); - - // model name cannot use reserved keyword, e.g. return - if (isReservedWord(name)) { - LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name)); - name = "model_" + name; // e.g. return => ModelReturn (after camelize) - } - - // model name starts with number - if (name.matches("^\\d.*")) { - LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name)); - name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) - } - - if (!StringUtils.isEmpty(modelNamePrefix)) { - name = modelNamePrefix + "_" + name; - } - - if (!StringUtils.isEmpty(modelNameSuffix)) { - name = name + "_" + modelNameSuffix; - } - - // camelize the model name - // phone_number => PhoneNumber - return camelize(name); - } - - @Override - public String toOperationId(String operationId) { - // throw exception if method name is empty (should not occur as an auto-generated method name will be used) - if (StringUtils.isEmpty(operationId)) { - throw new RuntimeException("Empty method name (operationId) not allowed"); - } - - // method name cannot use reserved keyword, e.g. return - if (isReservedWord(operationId)) { - LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId))); - operationId = "call_" + operationId; - } - - return underscore(sanitizeName(operationId)); - } - - /** - * Return the default value of the property - * - * @param p OpenAPI property object - * @return string presentation of the default value of the property - */ - @Override - public String toDefaultValue(Schema p) { - if (ModelUtils.isBooleanSchema(p)) { - if (p.getDefault() != null) { - if (p.getDefault().toString().equalsIgnoreCase("false")) - return "False"; - else - return "True"; - } - } else if (ModelUtils.isDateSchema(p)) { - // TODO - } else if (ModelUtils.isDateTimeSchema(p)) { - // TODO - } else if (ModelUtils.isNumberSchema(p)) { - if (p.getDefault() != null) { - return p.getDefault().toString(); - } - } else if (ModelUtils.isIntegerSchema(p)) { - if (p.getDefault() != null) { - return p.getDefault().toString(); - } - } else if (ModelUtils.isStringSchema(p)) { - if (p.getDefault() != null) { - return "'" + (String) p.getDefault() + "'"; - } - } - - return null; - } - - @Override - public void setParameterExampleValue(CodegenParameter p) { - String example; - - if (p.defaultValue == null) { - example = p.example; - } else { - p.example = p.defaultValue; - return; - } - - String type = p.baseType; - if (type == null) { - type = p.dataType; - } - - if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) { - if (example == null) { - example = p.paramName + "_example"; - } - example = "'" + escapeText(example) + "'"; - } else if ("Integer".equals(type) || "int".equals(type)) { - if (p.minimum != null) { - example = "" + (Integer.valueOf(p.minimum) + 1); - } - if (p.maximum != null) { - example = "" + p.maximum; - } else if (example == null) { - example = "56"; - } - - } else if ("Long".equalsIgnoreCase(type)) { - if (p.minimum != null) { - example = "" + (Long.valueOf(p.minimum) + 1); - } - if (p.maximum != null) { - example = "" + p.maximum; - } else if (example == null) { - example = "789"; - } - } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) { - if (p.minimum != null) { - example = "" + p.minimum; - } else if (p.maximum != null) { - example = "" + p.maximum; - } else if (example == null) { - example = "3.4"; - } - } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) { - if (example == null) { - example = "True"; - } - } else if ("file".equalsIgnoreCase(type)) { - example = "(BytesIO(b'some file data'), 'file.txt')"; - } else if ("Date".equalsIgnoreCase(type)) { - if (example == null) { - example = "2013-10-20"; - } - example = "'" + escapeText(example) + "'"; - } else if ("DateTime".equalsIgnoreCase(type)) { - if (example == null) { - example = "2013-10-20T19:20:30+01:00"; - } - example = "'" + escapeText(example) + "'"; - } else if (!languageSpecificPrimitives.contains(type)) { - // type is a model class, e.g. User - example = type + "()"; - } else { - LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue"); - } - - if (p.items != null && p.items.defaultValue != null) { - example = p.items.defaultValue; - } - if (example == null) { - example = "None"; - } else if (Boolean.TRUE.equals(p.isListContainer)) { - if (Boolean.TRUE.equals(p.isBodyParam)) { - example = "[" + example + "]"; - } - } else if (Boolean.TRUE.equals(p.isMapContainer)) { - example = "{'key': " + example + "}"; - } - - p.example = example; - } - - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - public void setPackageVersion(String packageVersion) { - this.packageVersion = packageVersion; - } - - - @Override - public String escapeQuotationMark(String input) { - // remove ' to avoid code injection - return input.replace("'", ""); - } - - @Override - public String escapeUnsafeCharacters(String input) { - // remove multiline comment - return input.replace("'''", "'_'_'"); - } - - @Override - public String toModelImport(String name) { - String modelImport; - if (StringUtils.startsWithAny(name, "import", "from")) { - modelImport = name; - } else { - modelImport = "from "; - if (!"".equals(modelPackage())) { - modelImport += modelPackage() + "."; - } - modelImport += toModelFilename(name) + " import " + name; - } - return modelImport; - } - - @Override - public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { - if (StringUtils.isNotEmpty(property.pattern)) { - addImport(model, "import re"); - } - postProcessPattern(property.pattern, property.vendorExtensions); - } - - @Override - public Map postProcessModels(Map objs) { - // process enum in models - return postProcessModelsEnum(objs); - } - - @Override - public void postProcessParameter(CodegenParameter parameter) { - postProcessPattern(parameter.pattern, parameter.vendorExtensions); - } - - /* - * The openapi pattern spec follows the Perl convention and style of modifiers. Python - * does not support this in as natural a way so it needs to convert it. See - * https://docs.python.org/2/howto/regex.html#compilation-flags for details. - */ - public void postProcessPattern(String pattern, Map vendorExtensions) { - if (pattern != null) { - int i = pattern.lastIndexOf('/'); - - //Must follow Perl /pattern/modifiers convention - if (pattern.charAt(0) != '/' || i < 2) { - throw new IllegalArgumentException("Pattern must follow the Perl " - + "/pattern/modifiers convention. " + pattern + " is not valid."); - } - - String regex = pattern.substring(1, i).replace("'", "\\'"); - List modifiers = new ArrayList(); - - for (char c : pattern.substring(i).toCharArray()) { - if (regexModifiers.containsKey(c)) { - String modifier = regexModifiers.get(c); - modifiers.add(modifier); - } - } - - vendorExtensions.put("x-regex", regex); - vendorExtensions.put("x-modifiers", modifiers); - } - } - - @Override - public void postProcessFile(File file, String fileType) { - if (file == null) { - return; - } - String pythonPostProcessFile = System.getenv("PYTHON_POST_PROCESS_FILE"); - if (StringUtils.isEmpty(pythonPostProcessFile)) { - return; // skip if PYTHON_POST_PROCESS_FILE env variable is not defined - } - - // only process files with py extension - if ("py".equals(FilenameUtils.getExtension(file.toString()))) { - String command = pythonPostProcessFile + " " + file.toString(); - try { - Process p = Runtime.getRuntime().exec(command); - int exitValue = p.waitFor(); - if (exitValue != 0) { - LOGGER.error("Error running the command ({}). Exit value: {}", command, exitValue); - } else { - LOGGER.info("Successfully executed: " + command); - } - } catch (Exception e) { - LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage()); - } - } - } - } diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 910a76f04e2..28447fe92b2 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -74,6 +74,7 @@ org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen org.openapitools.codegen.languages.PowerShellClientCodegen org.openapitools.codegen.languages.PythonClientCodegen org.openapitools.codegen.languages.PythonFlaskConnexionServerCodegen +org.openapitools.codegen.languages.PythonAiohttpConnexionServerCodegen org.openapitools.codegen.languages.RClientCodegen org.openapitools.codegen.languages.RubyClientCodegen org.openapitools.codegen.languages.RubyOnRailsServerCodegen diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/README.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/README.mustache new file mode 100644 index 00000000000..3402bd835d9 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/README.mustache @@ -0,0 +1,46 @@ +# OpenAPI generated server + +## Overview +This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the +[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This +is an example of building a OpenAPI-enabled aiohtpp server. + +This example uses the [Connexion](https://github.com/zalando/connexion) library on top of aiohtpp. + +## Requirements +Python 3.5.2+ + +## Usage +To run the server, please execute the following from the root directory: + +``` +pip3 install -r requirements.txt +python3 -m {{packageName}} +``` + +and open your browser to here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/ui/ +``` + +Your OpenAPI definition lives here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/openapi.json +``` + +To launch the integration tests, use pytest: +``` +sudo pip install -r test-requirements.txt +pytest +``` + +## Prevent file overriding + +After first generation, add edited files to _.openapi-generator-ignore_ to prevent generator to overwrite them. Typically: +``` +server/controllers/* +test/* +*.txt +``` diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/__init__.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/__init__.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/__init__.mustache rename to modules/openapi-generator/src/main/resources/python-aiohttp/__init__.mustache diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/__init__main.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/__init__main.mustache new file mode 100644 index 00000000000..c164cd99e62 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/__init__main.mustache @@ -0,0 +1,12 @@ +import os +import connexion + + +def main(): + options = { + "swagger_ui": True + } + specification_dir = os.path.join(os.path.dirname(__file__), 'openapi') + app = connexion.AioHttpApp(__name__, specification_dir=specification_dir, options=options) + app.add_api('openapi.yaml', arguments={'title': '{{appName}}'}, pass_context_arg_name='request') + app.run(port={{serverPort}}) diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/__init__model.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/__init__model.mustache new file mode 100644 index 00000000000..654db8044f1 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/__init__model.mustache @@ -0,0 +1,5 @@ +# coding: utf-8 + +# import models into model package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}} +{{/models}} diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/__init__.py b/modules/openapi-generator/src/main/resources/python-aiohttp/__init__test.mustache similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/__init__.py rename to modules/openapi-generator/src/main/resources/python-aiohttp/__init__test.mustache diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/__main__.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/__main__.mustache new file mode 100644 index 00000000000..f20ae81db34 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/__main__.mustache @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from . import main + +if __name__ == '__main__': + main() diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/base_model_.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/base_model_.mustache new file mode 100644 index 00000000000..163d1b096e5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/base_model_.mustache @@ -0,0 +1,66 @@ +import pprint + +import typing + +from {{packageName}} import util + +T = typing.TypeVar('T') + + +class Model(object): + # openapiTypes: The key is attribute name and the + # value is attribute type. + openapi_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls: T, dikt: dict) -> T: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self) -> dict: + """Returns the model properties as a dict + """ + result = {} + + for attr_key, json_key in self.attribute_map.items(): + value = getattr(self, attr_key) + if value is None: + continue + if isinstance(value, list): + result[json_key] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[json_key] = value.to_dict() + elif isinstance(value, dict): + result[json_key] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[json_key] = value + + return result + + def to_str(self) -> str: + """Returns the string representation of the model + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/conftest.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/conftest.mustache new file mode 100644 index 00000000000..a90e6b0e22b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/conftest.mustache @@ -0,0 +1,17 @@ +import logging +import pytest +import os + +import connexion + + +@pytest.fixture +def client(loop, aiohttp_client): + logging.getLogger('connexion.operation').setLevel('ERROR') + options = { + "swagger_ui": True + } + specification_dir = os.path.join(os.path.dirname(__file__), '..', '{{packageName}}', 'openapi') + app = connexion.AioHttpApp(__name__, specification_dir=specification_dir, options=options) + app.add_api('openapi.yaml', pass_context_arg_name='request') + return loop.run_until_complete(aiohttp_client(app.app)) diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/controller.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/controller.mustache new file mode 100644 index 00000000000..c9adc6c12ce --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/controller.mustache @@ -0,0 +1,104 @@ +from typing import List, Dict +from aiohttp import web + +{{#imports}}{{import}} +{{/imports}} +from {{packageName}} import util +{{#operations}} +{{#operation}} + + +async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> web.Response: + """{{#summary}}{{.}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} + + {{#notes}}{{.}}{{/notes}} + + {{#allParams}} + :param {{paramName}}: {{description}} + {{^isContainer}} + {{#isPrimitiveType}} + :type {{paramName}}: {{>param_type}} + {{/isPrimitiveType}} + {{#isUuid}} + :type {{paramName}}: {{>param_type}} + {{/isUuid}} + {{^isPrimitiveType}} + {{#isFile}} + :type {{paramName}}: werkzeug.datastructures.FileStorage + {{/isFile}} + {{^isFile}} + {{^isUuid}} + :type {{paramName}}: dict | bytes + {{/isUuid}} + {{/isFile}} + {{/isPrimitiveType}} + {{/isContainer}} + {{#isListContainer}} + {{#items}} + {{#isPrimitiveType}} + :type {{paramName}}: List[{{>param_type}}] + {{/isPrimitiveType}} + {{^isPrimitiveType}} + :type {{paramName}}: list | bytes + {{/isPrimitiveType}} + {{/items}} + {{/isListContainer}} + {{#isMapContainer}} + {{#items}} + {{#isPrimitiveType}} + :type {{paramName}}: Dict[str, {{>param_type}}] + {{/isPrimitiveType}} + {{^isPrimitiveType}} + :type {{paramName}}: dict | bytes + {{/isPrimitiveType}} + {{/items}} + {{/isMapContainer}} + {{/allParams}} + + """ + {{#allParams}} + {{^isContainer}} + {{#isDate}} + {{paramName}} = util.deserialize_date({{paramName}}) + {{/isDate}} + {{#isDateTime}} + {{paramName}} = util.deserialize_datetime({{paramName}}) + {{/isDateTime}} + {{^isPrimitiveType}} + {{^isFile}} + {{^isUuid}} + {{paramName}} = {{baseType}}.from_dict({{paramName}}) + {{/isUuid}} + {{/isFile}} + {{/isPrimitiveType}} + {{/isContainer}} + {{#isListContainer}} + {{#items}} + {{#isDate}} + {{paramName}} = [util.deserialize_date(s) for s in {{paramName}}] + {{/isDate}} + {{#isDateTime}} + {{paramName}} = [util.deserialize_datetime(s) for s in {{paramName}}] + {{/isDateTime}} + {{#complexType}} + {{paramName}} = [{{complexType}}.from_dict(d) for d in {{paramName}}] + {{/complexType}} + {{/items}} + {{/isListContainer}} + {{#isMapContainer}} + {{#items}} + {{#isDate}} + {{paramName}} = {k: util.deserialize_date(v) for k, v in {{paramName}}} + {{/isDate}} + {{#isDateTime}} + {{paramName}} = {k: util.deserialize_datetime(v) for k, v in {{paramName}}} + {{/isDateTime}} + {{#complexType}} + {{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in {{paramName}}} + {{/complexType}} + {{/items}} + {{/isMapContainer}} + {{/allParams}} + return web.Response(status=200) +{{/operation}} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/controller_test.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/controller_test.mustache new file mode 100644 index 00000000000..a53f0288271 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/controller_test.mustache @@ -0,0 +1,68 @@ +# coding: utf-8 + +import pytest +import json +from aiohttp import web +{{#operations}} +{{#operation}} +{{#isMultipart}} +from aiohttp import FormData +{{/isMultipart}} +{{/operation}} +{{/operations}} + +{{#imports}}{{import}} +{{/imports}} + +{{#operations}} +{{#operation}} + +{{#vendorExtensions.x-skip-test}} +@pytest.mark.skip("{{reason}}") +{{/vendorExtensions.x-skip-test}} +async def test_{{operationId}}(client): + """Test case for {{{operationId}}} + + {{{summary}}} + """ + {{#bodyParam}} + {{paramName}} = {{{example}}} + {{/bodyParam}} + {{#queryParams}} + {{#-first}}params = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}} + {{/queryParams}} + headers = { {{#vendorExtensions.x-prefered-produce}} + 'Accept': '{{mediaType}}',{{/vendorExtensions.x-prefered-produce}}{{#vendorExtensions.x-prefered-consume}} + 'Content-Type': '{{mediaType}}',{{/vendorExtensions.x-prefered-consume}}{{#headerParams}} + '{{paramName}}': {{{example}}},{{/headerParams}}{{#authMethods}} + {{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}'{{name}}': 'special-key',{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'BasicZm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}} + } + {{#formParams}} + {{#isMultipart}} + {{#-first}} + data = FormData() + {{/-first}} + data.add_field('{{paramName}}', {{{example}}}) + {{/isMultipart}} + {{^isMultipart}} + {{#-first}} + data = { + {{/-first}} + '{{paramName}}': {{{example}}}{{#hasMore}},{{/hasMore}} + {{^hasMore}} + } + {{/hasMore}} + {{/isMultipart}} + {{/formParams}} + response = await client.request( + method='{{httpMethod}}', + path='{{#contextPath}}{{{.}}}{{/contextPath}}{{{path}}}'{{#pathParams}}{{#-first}}.format({{/-first}}{{paramName}}={{{example}}}{{#hasMore}}, {{/hasMore}}{{^hasMore}}){{/hasMore}}{{/pathParams}}, + headers=headers,{{#bodyParam}} + json={{paramName}},{{/bodyParam}}{{#formParams}}{{#-first}} + data=data,{{/-first}}{{/formParams}}{{#queryParams}}{{#-first}} + params=params,{{/-first}}{{/queryParams}} + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + +{{/operation}} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/model.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/model.mustache new file mode 100644 index 00000000000..9ab013ea9ef --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/model.mustache @@ -0,0 +1,162 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from {{modelPackage}}.base_model_ import Model +{{#models}} +{{#model}} +{{#pyImports}} +{{import}} +{{/pyImports}} +{{/model}} +{{/models}} +from {{packageName}} import util + + +{{#models}} +{{#model}} +class {{classname}}(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """{{#allowableValues}} + + """ + allowed enum values + """ +{{#enumVars}} + {{name}} = {{{value}}}{{^-last}} +{{/-last}} +{{/enumVars}}{{/allowableValues}} + + def __init__(self{{#vars}}, {{name}}: {{dataType}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): + """{{classname}} - a model defined in OpenAPI + + {{#vars}} + :param {{name}}: The {{name}} of this {{classname}}. + {{/vars}} + """ + self.openapi_types = { +{{#vars}} + '{{name}}': {{dataType}}{{#hasMore}},{{/hasMore}} +{{/vars}} + } + + self.attribute_map = { +{{#vars}} + '{{name}}': '{{baseName}}'{{#hasMore}},{{/hasMore}} +{{/vars}} + } +{{#vars}}{{#-first}} +{{/-first}} + self._{{name}} = {{name}} +{{/vars}} + + @classmethod + def from_dict(cls, dikt: dict) -> '{{classname}}': + """Returns the dict as a model + + :param dikt: A dict. + :return: The {{name}} of this {{classname}}. + """ + return util.deserialize_model(dikt, cls){{#vars}}{{#-first}} + +{{/-first}} + @property + def {{name}}(self): + """Gets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} + {{/description}} + + :return: The {{name}} of this {{classname}}. + :rtype: {{dataType}} + """ + return self._{{name}} + + @{{name}}.setter + def {{name}}(self, {{name}}): + """Sets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} + {{/description}} + + :param {{name}}: The {{name}} of this {{classname}}. + :type {{name}}: {{dataType}} + """ +{{#isEnum}} + allowed_values = [{{#allowableValues}}{{#values}}"{{{this}}}"{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] +{{#isContainer}} +{{#isListContainer}} + if not set({{{name}}}).issubset(set(allowed_values)): + raise ValueError( + "Invalid values for `{{{name}}}` [{0}], must be a subset of [{1}]" + .format(", ".join(map(str, set({{{name}}}) - set(allowed_values))), + ", ".join(map(str, allowed_values))) + ) +{{/isListContainer}} +{{#isMapContainer}} + if not set({{{name}}}.keys()).issubset(set(allowed_values)): + raise ValueError( + "Invalid keys in `{{{name}}}` [{0}], must be a subset of [{1}]" + .format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))), + ", ".join(map(str, allowed_values))) + ) +{{/isMapContainer}} +{{/isContainer}} +{{^isContainer}} + if {{{name}}} not in allowed_values: + raise ValueError( + "Invalid value for `{{{name}}}` ({0}), must be one of {1}" + .format({{{name}}}, allowed_values) + ) +{{/isContainer}} +{{/isEnum}} +{{^isEnum}} +{{#required}} + if {{name}} is None: + raise ValueError("Invalid value for `{{name}}`, must not be `None`") +{{/required}} +{{#hasValidation}} +{{#maxLength}} + if {{name}} is not None and len({{name}}) > {{maxLength}}: + raise ValueError("Invalid value for `{{name}}`, length must be less than or equal to `{{maxLength}}`") +{{/maxLength}} +{{#minLength}} + if {{name}} is not None and len({{name}}) < {{minLength}}: + raise ValueError("Invalid value for `{{name}}`, length must be greater than or equal to `{{minLength}}`") +{{/minLength}} +{{#maximum}} + if {{name}} is not None and {{name}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}: + raise ValueError("Invalid value for `{{name}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`") +{{/maximum}} +{{#minimum}} + if {{name}} is not None and {{name}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}: + raise ValueError("Invalid value for `{{name}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") +{{/minimum}} +{{#pattern}} + if {{name}} is not None and not re.search(r'{{{vendorExtensions.x-regex}}}', {{name}}{{#vendorExtensions.x-modifiers}}{{#-first}}, flags={{/-first}}re.{{.}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}): + raise ValueError("Invalid value for `{{name}}`, must be a follow pattern or equal to `{{{pattern}}}`") +{{/pattern}} +{{#maxItems}} + if {{name}} is not None and len({{name}}) > {{maxItems}}: + raise ValueError("Invalid value for `{{name}}`, number of items must be less than or equal to `{{maxItems}}`") +{{/maxItems}} +{{#minItems}} + if {{name}} is not None and len({{name}}) < {{minItems}}: + raise ValueError("Invalid value for `{{name}}`, number of items must be greater than or equal to `{{minItems}}`") +{{/minItems}} +{{/hasValidation}} +{{/isEnum}} + + self._{{name}} = {{name}}{{^-last}} + +{{/-last}} +{{/vars}} + +{{/model}} +{{/models}} diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/openapi.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/openapi.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/openapi.mustache rename to modules/openapi-generator/src/main/resources/python-aiohttp/openapi.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/param_type.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/param_type.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/param_type.mustache rename to modules/openapi-generator/src/main/resources/python-aiohttp/param_type.mustache diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/requirements.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/requirements.mustache new file mode 100644 index 00000000000..835c75de58a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/requirements.mustache @@ -0,0 +1,3 @@ +connexion[aiohttp,swagger-ui] == 2.0.2 +swagger-ui-bundle == 0.0.2 +aiohttp_jinja2 == 1.1.0 diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/security_controller_.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/security_controller_.mustache new file mode 100644 index 00000000000..bf7bf2b36d6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/security_controller_.mustache @@ -0,0 +1,59 @@ +from typing import List + +{{#authMethods}} +{{#isOAuth}} + +def info_from_{{name}}(token: str) -> dict: + """ + Validate and decode token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + 'scope' or 'scopes' will be passed to scope validation function. + Should return None if token is invalid or does not allow access to called API. + """ + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'} + + +def validate_scope_{{name}}(required_scopes: List[str], token_scopes: List[str]) -> bool: + """ Validate required scopes are included in token scope """ + return set(required_scopes).issubset(set(token_scopes)) + +{{/isOAuth}} +{{#isApiKey}} + +def info_from_{{name}}(api_key: str, required_scopes: None) -> dict: + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + Should return None if api_key is invalid or does not allow access to called API. + """ + return {'uid': 'user_id'} + +{{/isApiKey}} +{{#isBasicBasic}} + +def info_from_{{name}}(username: str, password: str, required_scopes: None) -> dict: + """ + Check and retrieve authentication information from basic auth. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + Should return None if auth is invalid or does not allow access to called API. + """ + return {'uid': 'user_id'} + +{{/isBasicBasic}} +{{#isBasicBearer}} + +def info_from_{{name}}(token: str) -> dict: + """ + Check and retrieve authentication information from custom bearer token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + Should return None if auth is invalid or does not allow access to called API. + """ + return {'uid': 'user_id'} + +{{/isBasicBearer}} +{{/authMethods}} + diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/test-requirements.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/test-requirements.mustache new file mode 100644 index 00000000000..1cb425f6551 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/test-requirements.mustache @@ -0,0 +1,6 @@ +coverage>=4.0.3 +pytest>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 +pytest-aiohttp>=0.3.0 diff --git a/modules/openapi-generator/src/main/resources/python-aiohttp/util.mustache b/modules/openapi-generator/src/main/resources/python-aiohttp/util.mustache new file mode 100644 index 00000000000..9263acb016e --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-aiohttp/util.mustache @@ -0,0 +1,130 @@ +import datetime + +import typing +from typing import Union + +T = typing.TypeVar('T') +Class = typing.Type[T] + + +def _deserialize(data: Union[dict, list, str], klass: Union[Class, str]) -> Union[dict, list, Class, int, float, str, bool, datetime.date, datetime.datetime]: + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in (int, float, str, bool): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif type(klass) == typing.GenericMeta: + if klass.__extra__ == list: + return _deserialize_list(data, klass.__args__[0]) + if klass.__extra__ == dict: + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass: Class) -> Union[Class, int, float, str, bool]: + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, float, str, bool. + """ + try: + value = klass(data) + except (UnicodeEncodeError, TypeError): + value = data + return value + + +def _deserialize_object(value: T) -> T: + """Return an original value. + + :return: object. + """ + return value + + +def deserialize_date(string: str) -> datetime.date: + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string: str) -> datetime.datetime: + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data: Union[dict, list], klass: T) -> T: + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.openapi_types: + return data + + if data is not None and isinstance(data, (list, dict)): + for attr, attr_type in instance.openapi_types.items(): + attr_key = instance.attribute_map[attr] + if attr_key in data: + value = data[attr_key] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data: list, boxed_type) -> list: + """Deserializes a list and its elements. + + :param data: list to deserialize. + :param boxed_type: class literal. + + :return: deserialized list. + """ + return [_deserialize(sub_data, boxed_type) for sub_data in data] + + +def _deserialize_dict(data: dict, boxed_type) -> dict: + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :param boxed_type: class literal. + + :return: deserialized dict. + """ + return {k: _deserialize(v, boxed_type) for k, v in data.items()} diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/Dockerfile.mustache b/modules/openapi-generator/src/main/resources/python-flask/Dockerfile.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/Dockerfile.mustache rename to modules/openapi-generator/src/main/resources/python-flask/Dockerfile.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/README.mustache b/modules/openapi-generator/src/main/resources/python-flask/README.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/README.mustache rename to modules/openapi-generator/src/main/resources/python-flask/README.mustache diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/__init__.py b/modules/openapi-generator/src/main/resources/python-flask/__init__.mustache similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/__init__.py rename to modules/openapi-generator/src/main/resources/python-flask/__init__.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/__init__model.mustache b/modules/openapi-generator/src/main/resources/python-flask/__init__model.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/__init__model.mustache rename to modules/openapi-generator/src/main/resources/python-flask/__init__model.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/__init__test.mustache b/modules/openapi-generator/src/main/resources/python-flask/__init__test.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/__init__test.mustache rename to modules/openapi-generator/src/main/resources/python-flask/__init__test.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/__main__.mustache b/modules/openapi-generator/src/main/resources/python-flask/__main__.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/__main__.mustache rename to modules/openapi-generator/src/main/resources/python-flask/__main__.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/base_model_.mustache b/modules/openapi-generator/src/main/resources/python-flask/base_model_.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/base_model_.mustache rename to modules/openapi-generator/src/main/resources/python-flask/base_model_.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/controller.mustache b/modules/openapi-generator/src/main/resources/python-flask/controller.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/controller.mustache rename to modules/openapi-generator/src/main/resources/python-flask/controller.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/controller_test.mustache b/modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache similarity index 65% rename from modules/openapi-generator/src/main/resources/flaskConnexion/controller_test.mustache rename to modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache index a41b12f2c21..66572861928 100644 --- a/modules/openapi-generator/src/main/resources/flaskConnexion/controller_test.mustache +++ b/modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -13,7 +14,10 @@ from {{packageName}}.test import BaseTestCase class {{#operations}}Test{{classname}}(BaseTestCase): """{{classname}} integration test stubs""" - {{#operation}} + {{#operation}} + {{#vendorExtensions.x-skip-test}} + @unittest.skip("{{reason}}") + {{/vendorExtensions.x-skip-test}} def test_{{operationId}}(self): """Test case for {{{operationId}}} @@ -25,18 +29,21 @@ class {{#operations}}Test{{classname}}(BaseTestCase): {{#queryParams}} {{#-first}}query_string = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}} {{/queryParams}} - {{#headerParams}} - {{#-first}}headers = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}} - {{/headerParams}} + headers = { {{#vendorExtensions.x-prefered-produce}} + 'Accept': '{{mediaType}}',{{/vendorExtensions.x-prefered-produce}}{{#vendorExtensions.x-prefered-consume}} + 'Content-Type': '{{mediaType}}',{{/vendorExtensions.x-prefered-consume}}{{#headerParams}} + '{{paramName}}': {{{example}}},{{/headerParams}}{{#authMethods}} + {{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}'{{name}}': 'special-key',{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'BasicZm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}} + } {{#formParams}} {{#-first}}data = dict({{/-first}}{{^-first}} {{/-first}}{{paramName}}={{{example}}}{{#hasMore}},{{/hasMore}}{{#-last}}){{/-last}} {{/formParams}} response = self.client.open( '{{#contextPath}}{{{.}}}{{/contextPath}}{{{path}}}'{{#pathParams}}{{#-first}}.format({{/-first}}{{paramName}}={{{example}}}{{#hasMore}}, {{/hasMore}}{{^hasMore}}){{/hasMore}}{{/pathParams}}, - method='{{httpMethod}}'{{#bodyParam}}, + method='{{httpMethod}}', + headers=headers{{#bodyParam}}, data=json.dumps({{paramName}}){{^consumes}}, - content_type='application/json'{{/consumes}}{{/bodyParam}}{{#headerParams}}{{#-first}}, - headers=headers{{/-first}}{{/headerParams}}{{#formParams}}{{#-first}}, + content_type='application/json'{{/consumes}}{{/bodyParam}}{{#formParams}}{{#-first}}, data=data{{/-first}}{{/formParams}}{{#consumes}}{{#-first}}, content_type='{{{mediaType}}}'{{/-first}}{{/consumes}}{{#queryParams}}{{#-first}}, query_string=query_string{{/-first}}{{/queryParams}}) @@ -47,5 +54,4 @@ class {{#operations}}Test{{classname}}(BaseTestCase): {{/operations}} if __name__ == '__main__': - import unittest unittest.main() diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/dockerignore.mustache b/modules/openapi-generator/src/main/resources/python-flask/dockerignore.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/dockerignore.mustache rename to modules/openapi-generator/src/main/resources/python-flask/dockerignore.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/encoder.mustache b/modules/openapi-generator/src/main/resources/python-flask/encoder.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/encoder.mustache rename to modules/openapi-generator/src/main/resources/python-flask/encoder.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/python-flask/git_push.sh.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/git_push.sh.mustache rename to modules/openapi-generator/src/main/resources/python-flask/git_push.sh.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/gitignore.mustache b/modules/openapi-generator/src/main/resources/python-flask/gitignore.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/gitignore.mustache rename to modules/openapi-generator/src/main/resources/python-flask/gitignore.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/model.mustache b/modules/openapi-generator/src/main/resources/python-flask/model.mustache similarity index 98% rename from modules/openapi-generator/src/main/resources/flaskConnexion/model.mustache rename to modules/openapi-generator/src/main/resources/python-flask/model.mustache index 66db2b5bd95..6b6743d5e66 100644 --- a/modules/openapi-generator/src/main/resources/flaskConnexion/model.mustache +++ b/modules/openapi-generator/src/main/resources/python-flask/model.mustache @@ -6,6 +6,13 @@ from datetime import date, datetime # noqa: F401 from typing import List, Dict # noqa: F401 from {{modelPackage}}.base_model_ import Model +{{#models}} +{{#model}} +{{#pyImports}} +{{import}} +{{/pyImports}} +{{/model}} +{{/models}} from {{packageName}} import util {{#imports}} diff --git a/modules/openapi-generator/src/main/resources/python-flask/openapi.mustache b/modules/openapi-generator/src/main/resources/python-flask/openapi.mustache new file mode 100644 index 00000000000..51ebafb0187 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-flask/openapi.mustache @@ -0,0 +1 @@ +{{{openapi-yaml}}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python-flask/param_type.mustache b/modules/openapi-generator/src/main/resources/python-flask/param_type.mustache new file mode 100644 index 00000000000..21e7e07ec53 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-flask/param_type.mustache @@ -0,0 +1 @@ +{{#isString}}str{{/isString}}{{#isInteger}}int{{/isInteger}}{{#isLong}}int{{/isLong}}{{#isFloat}}float{{/isFloat}}{{#isDouble}}float{{/isDouble}}{{#isByteArray}}str{{/isByteArray}}{{#isBinary}}str{{/isBinary}}{{#isBoolean}}bool{{/isBoolean}}{{#isDate}}str{{/isDate}}{{#isDateTime}}str{{/isDateTime}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/requirements.mustache b/modules/openapi-generator/src/main/resources/python-flask/requirements.mustache similarity index 87% rename from modules/openapi-generator/src/main/resources/flaskConnexion/requirements.mustache rename to modules/openapi-generator/src/main/resources/python-flask/requirements.mustache index 2a194f98782..8d4d653bfd7 100644 --- a/modules/openapi-generator/src/main/resources/flaskConnexion/requirements.mustache +++ b/modules/openapi-generator/src/main/resources/python-flask/requirements.mustache @@ -1,4 +1,4 @@ -connexion == 2.0.0 +connexion == 2.0.2 swagger-ui-bundle == 0.0.2 python_dateutil == 2.6.0 {{#supportPython2}} diff --git a/modules/openapi-generator/src/main/resources/python-flask/security_controller_.mustache b/modules/openapi-generator/src/main/resources/python-flask/security_controller_.mustache new file mode 100644 index 00000000000..f6293adda19 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/python-flask/security_controller_.mustache @@ -0,0 +1,90 @@ +from typing import List + +{{#authMethods}} +{{#isOAuth}} + +def info_from_{{name}}(token): + """ + Validate and decode token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + 'scope' or 'scopes' will be passed to scope validation function. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'} + + +def validate_scope_{{name}}(required_scopes, token_scopes): + """ + Validate required scopes are included in token scope + + :param required_scopes Required scope to access called API + :type required_scopes: List[str] + :param token_scopes Scope present in token + :type token_scopes: List[str] + :return: True if access to called API is allowed + :rtype: bool + """ + return set(required_scopes).issubset(set(token_scopes)) + +{{/isOAuth}} +{{#isApiKey}} + +def info_from_{{name}}(api_key, required_scopes): + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param api_key API key provided by Authorization header + :type api_key: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} + +{{/isApiKey}} +{{#isBasicBasic}} + +def info_from_{{name}}(username, password, required_scopes): + """ + Check and retrieve authentication information from basic auth. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param username login provided by Authorization header + :type username: str + :param password password provided by Authorization header + :type password: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to user or None if credentials are invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} + +{{/isBasicBasic}} +{{#isBasicBearer}} + +def info_from_{{name}}(token): + """ + Check and retrieve authentication information from custom bearer token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'uid': 'user_id'} + +{{/isBasicBearer}} +{{/authMethods}} + diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/setup.mustache b/modules/openapi-generator/src/main/resources/python-flask/setup.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/setup.mustache rename to modules/openapi-generator/src/main/resources/python-flask/setup.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/test-requirements.mustache b/modules/openapi-generator/src/main/resources/python-flask/test-requirements.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/test-requirements.mustache rename to modules/openapi-generator/src/main/resources/python-flask/test-requirements.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/tox.mustache b/modules/openapi-generator/src/main/resources/python-flask/tox.mustache similarity index 68% rename from modules/openapi-generator/src/main/resources/flaskConnexion/tox.mustache rename to modules/openapi-generator/src/main/resources/python-flask/tox.mustache index 3efa994317d..1195b3391b0 100644 --- a/modules/openapi-generator/src/main/resources/flaskConnexion/tox.mustache +++ b/modules/openapi-generator/src/main/resources/python-flask/tox.mustache @@ -1,5 +1,5 @@ [tox] -envlist = {{#supportPython2}}py27, {{/supportPython2}}py35 +envlist = {{#supportPython2}}py27, {{/supportPython2}}py3 [testenv] deps=-r{toxinidir}/requirements.txt diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/travis.mustache b/modules/openapi-generator/src/main/resources/python-flask/travis.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/travis.mustache rename to modules/openapi-generator/src/main/resources/python-flask/travis.mustache diff --git a/modules/openapi-generator/src/main/resources/flaskConnexion/util.mustache b/modules/openapi-generator/src/main/resources/python-flask/util.mustache similarity index 100% rename from modules/openapi-generator/src/main/resources/flaskConnexion/util.mustache rename to modules/openapi-generator/src/main/resources/python-flask/util.mustache diff --git a/pom.xml b/pom.xml index afad623c807..f2ea76bd8f0 100644 --- a/pom.xml +++ b/pom.xml @@ -1056,6 +1056,9 @@ samples/client/petstore/typescript-angular-v6-provided-in-root samples/server/petstore/rust-server + samples/server/petstore/python-aiohttp + samples/server/petstore/python-flask + samples/server/petstore/python-flask-python2 diff --git a/samples/server/petstore/flaskConnexion-python2/.openapi-generator-ignore b/samples/server/petstore/python-aiohttp/.openapi-generator-ignore similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/.openapi-generator-ignore rename to samples/server/petstore/python-aiohttp/.openapi-generator-ignore diff --git a/samples/server/petstore/flaskConnexion-python2/.openapi-generator/VERSION b/samples/server/petstore/python-aiohttp/.openapi-generator/VERSION similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/.openapi-generator/VERSION rename to samples/server/petstore/python-aiohttp/.openapi-generator/VERSION diff --git a/samples/server/petstore/python-aiohttp/Makefile b/samples/server/petstore/python-aiohttp/Makefile new file mode 100644 index 00000000000..763fe2bc4a4 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/Makefile @@ -0,0 +1,18 @@ + #!/bin/bash + +REQUIREMENTS_OUT=requirements.txt.log +VENV=.venv + +clean: + rm -rf $(REQUIREMENTS_OUT) + rm -rf $(VENV) + rm -rf .pytest_cache + rm -rf .coverage + find . -name "*.py[oc]" -delete + find . -name "__pycache__" -delete + +test: clean + bash ./test_python3.sh + +test-all: clean + bash ./test_python3.sh diff --git a/samples/server/petstore/python-aiohttp/README.md b/samples/server/petstore/python-aiohttp/README.md new file mode 100644 index 00000000000..5983d83f184 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/README.md @@ -0,0 +1,46 @@ +# OpenAPI generated server + +## Overview +This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the +[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This +is an example of building a OpenAPI-enabled aiohtpp server. + +This example uses the [Connexion](https://github.com/zalando/connexion) library on top of aiohtpp. + +## Requirements +Python 3.5.2+ + +## Usage +To run the server, please execute the following from the root directory: + +``` +pip3 install -r requirements.txt +python3 -m openapi_server +``` + +and open your browser to here: + +``` +http://localhost:8080/v2/ui/ +``` + +Your OpenAPI definition lives here: + +``` +http://localhost:8080/v2/openapi.json +``` + +To launch the integration tests, use pytest: +``` +sudo pip install -r test-requirements.txt +pytest +``` + +## Prevent file overriding + +After first generation, add edited files to _.openapi-generator-ignore_ to prevent generator to overwrite them. Typically: +``` +server/controllers/* +test/* +*.txt +``` diff --git a/samples/server/petstore/python-aiohttp/openapi_server/__main__.py b/samples/server/petstore/python-aiohttp/openapi_server/__main__.py new file mode 100644 index 00000000000..f20ae81db34 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/__main__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from . import main + +if __name__ == '__main__': + main() diff --git a/samples/server/petstore/flaskConnexion/openapi_server/__init__.py b/samples/server/petstore/python-aiohttp/openapi_server/controllers/__init__.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/__init__.py rename to samples/server/petstore/python-aiohttp/openapi_server/controllers/__init__.py diff --git a/samples/server/petstore/python-aiohttp/openapi_server/controllers/pet_controller.py b/samples/server/petstore/python-aiohttp/openapi_server/controllers/pet_controller.py new file mode 100644 index 00000000000..ad7557832ba --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/controllers/pet_controller.py @@ -0,0 +1,114 @@ +from typing import List, Dict +from aiohttp import web + +from openapi_server.models.api_response import ApiResponse +from openapi_server.models.pet import Pet +from openapi_server import util + + +async def add_pet(request: web.Request, body) -> web.Response: + """Add a new pet to the store + + + + :param body: Pet object that needs to be added to the store + :type body: dict | bytes + + """ + body = Pet.from_dict(body) + return web.Response(status=200) + + +async def delete_pet(request: web.Request, pet_id, api_key=None) -> web.Response: + """Deletes a pet + + + + :param pet_id: Pet id to delete + :type pet_id: int + :param api_key: + :type api_key: str + + """ + return web.Response(status=200) + + +async def find_pets_by_status(request: web.Request, status) -> web.Response: + """Finds Pets by status + + Multiple status values can be provided with comma separated strings + + :param status: Status values that need to be considered for filter + :type status: List[str] + + """ + return web.Response(status=200) + + +async def find_pets_by_tags(request: web.Request, tags) -> web.Response: + """Finds Pets by tags + + Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + + :param tags: Tags to filter by + :type tags: List[str] + + """ + return web.Response(status=200) + + +async def get_pet_by_id(request: web.Request, pet_id) -> web.Response: + """Find pet by ID + + Returns a single pet + + :param pet_id: ID of pet to return + :type pet_id: int + + """ + return web.Response(status=200) + + +async def update_pet(request: web.Request, body) -> web.Response: + """Update an existing pet + + + + :param body: Pet object that needs to be added to the store + :type body: dict | bytes + + """ + body = Pet.from_dict(body) + return web.Response(status=200) + + +async def update_pet_with_form(request: web.Request, pet_id, name=None, status=None) -> web.Response: + """Updates a pet in the store with form data + + + + :param pet_id: ID of pet that needs to be updated + :type pet_id: int + :param name: Updated name of the pet + :type name: str + :param status: Updated status of the pet + :type status: str + + """ + return web.Response(status=200) + + +async def upload_file(request: web.Request, pet_id, additional_metadata=None, file=None) -> web.Response: + """uploads an image + + + + :param pet_id: ID of pet to update + :type pet_id: int + :param additional_metadata: Additional data to pass to server + :type additional_metadata: str + :param file: file to upload + :type file: str + + """ + return web.Response(status=200) diff --git a/samples/server/petstore/python-aiohttp/openapi_server/controllers/security_controller_.py b/samples/server/petstore/python-aiohttp/openapi_server/controllers/security_controller_.py new file mode 100644 index 00000000000..90ce5c351a1 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/controllers/security_controller_.py @@ -0,0 +1,29 @@ +from typing import List + + +def info_from_api_key(api_key: str, required_scopes: None) -> dict: + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + Should return None if api_key is invalid or does not allow access to called API. + """ + return {'uid': 'user_id'} + + +def info_from_petstore_auth(token: str) -> dict: + """ + Validate and decode token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + 'scope' or 'scopes' will be passed to scope validation function. + Should return None if token is invalid or does not allow access to called API. + """ + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'} + + +def validate_scope_petstore_auth(required_scopes: List[str], token_scopes: List[str]) -> bool: + """ Validate required scopes are included in token scope """ + return set(required_scopes).issubset(set(token_scopes)) + + diff --git a/samples/server/petstore/python-aiohttp/openapi_server/controllers/store_controller.py b/samples/server/petstore/python-aiohttp/openapi_server/controllers/store_controller.py new file mode 100644 index 00000000000..80512d357f2 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/controllers/store_controller.py @@ -0,0 +1,52 @@ +from typing import List, Dict +from aiohttp import web + +from openapi_server.models.order import Order +from openapi_server import util + + +async def delete_order(request: web.Request, order_id) -> web.Response: + """Delete purchase order by ID + + For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + + :param order_id: ID of the order that needs to be deleted + :type order_id: str + + """ + return web.Response(status=200) + + +async def get_inventory(request: web.Request, ) -> web.Response: + """Returns pet inventories by status + + Returns a map of status codes to quantities + + + """ + return web.Response(status=200) + + +async def get_order_by_id(request: web.Request, order_id) -> web.Response: + """Find purchase order by ID + + For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions + + :param order_id: ID of pet that needs to be fetched + :type order_id: int + + """ + return web.Response(status=200) + + +async def place_order(request: web.Request, body) -> web.Response: + """Place an order for a pet + + + + :param body: order placed for purchasing the pet + :type body: dict | bytes + + """ + body = Order.from_dict(body) + return web.Response(status=200) diff --git a/samples/server/petstore/python-aiohttp/openapi_server/controllers/user_controller.py b/samples/server/petstore/python-aiohttp/openapi_server/controllers/user_controller.py new file mode 100644 index 00000000000..89dd0872413 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/controllers/user_controller.py @@ -0,0 +1,107 @@ +from typing import List, Dict +from aiohttp import web + +from openapi_server.models.user import User +from openapi_server import util + + +async def create_user(request: web.Request, body) -> web.Response: + """Create user + + This can only be done by the logged in user. + + :param body: Created user object + :type body: dict | bytes + + """ + body = User.from_dict(body) + return web.Response(status=200) + + +async def create_users_with_array_input(request: web.Request, body) -> web.Response: + """Creates list of users with given input array + + + + :param body: List of user object + :type body: list | bytes + + """ + body = [User.from_dict(d) for d in body] + return web.Response(status=200) + + +async def create_users_with_list_input(request: web.Request, body) -> web.Response: + """Creates list of users with given input array + + + + :param body: List of user object + :type body: list | bytes + + """ + body = [User.from_dict(d) for d in body] + return web.Response(status=200) + + +async def delete_user(request: web.Request, username) -> web.Response: + """Delete user + + This can only be done by the logged in user. + + :param username: The name that needs to be deleted + :type username: str + + """ + return web.Response(status=200) + + +async def get_user_by_name(request: web.Request, username) -> web.Response: + """Get user by user name + + + + :param username: The name that needs to be fetched. Use user1 for testing. + :type username: str + + """ + return web.Response(status=200) + + +async def login_user(request: web.Request, username, password) -> web.Response: + """Logs user into the system + + + + :param username: The user name for login + :type username: str + :param password: The password for login in clear text + :type password: str + + """ + return web.Response(status=200) + + +async def logout_user(request: web.Request, ) -> web.Response: + """Logs out current logged in user session + + + + + """ + return web.Response(status=200) + + +async def update_user(request: web.Request, username, body) -> web.Response: + """Updated user + + This can only be done by the logged in user. + + :param username: name that need to be deleted + :type username: str + :param body: Updated user object + :type body: dict | bytes + + """ + body = User.from_dict(body) + return web.Response(status=200) diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/__init__.py b/samples/server/petstore/python-aiohttp/openapi_server/models/__init__.py new file mode 100644 index 00000000000..8b108a5fe89 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/__init__.py @@ -0,0 +1,9 @@ +# coding: utf-8 + +# import models into model package +from openapi_server.models.api_response import ApiResponse +from openapi_server.models.category import Category +from openapi_server.models.order import Order +from openapi_server.models.pet import Pet +from openapi_server.models.tag import Tag +from openapi_server.models.user import User diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/api_response.py b/samples/server/petstore/python-aiohttp/openapi_server/models/api_response.py new file mode 100644 index 00000000000..2fc7342b8bc --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/api_response.py @@ -0,0 +1,110 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from openapi_server.models.base_model_ import Model +from openapi_server import util + + +class ApiResponse(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, code: int=None, type: str=None, message: str=None): + """ApiResponse - a model defined in OpenAPI + + :param code: The code of this ApiResponse. + :param type: The type of this ApiResponse. + :param message: The message of this ApiResponse. + """ + self.openapi_types = { + 'code': int, + 'type': str, + 'message': str + } + + self.attribute_map = { + 'code': 'code', + 'type': 'type', + 'message': 'message' + } + + self._code = code + self._type = type + self._message = message + + @classmethod + def from_dict(cls, dikt: dict) -> 'ApiResponse': + """Returns the dict as a model + + :param dikt: A dict. + :return: The ApiResponse of this ApiResponse. + """ + return util.deserialize_model(dikt, cls) + + @property + def code(self): + """Gets the code of this ApiResponse. + + + :return: The code of this ApiResponse. + :rtype: int + """ + return self._code + + @code.setter + def code(self, code): + """Sets the code of this ApiResponse. + + + :param code: The code of this ApiResponse. + :type code: int + """ + + self._code = code + + @property + def type(self): + """Gets the type of this ApiResponse. + + + :return: The type of this ApiResponse. + :rtype: str + """ + return self._type + + @type.setter + def type(self, type): + """Sets the type of this ApiResponse. + + + :param type: The type of this ApiResponse. + :type type: str + """ + + self._type = type + + @property + def message(self): + """Gets the message of this ApiResponse. + + + :return: The message of this ApiResponse. + :rtype: str + """ + return self._message + + @message.setter + def message(self, message): + """Sets the message of this ApiResponse. + + + :param message: The message of this ApiResponse. + :type message: str + """ + + self._message = message diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/base_model_.py b/samples/server/petstore/python-aiohttp/openapi_server/models/base_model_.py new file mode 100644 index 00000000000..54f342e5670 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/base_model_.py @@ -0,0 +1,66 @@ +import pprint + +import typing + +from openapi_server import util + +T = typing.TypeVar('T') + + +class Model(object): + # openapiTypes: The key is attribute name and the + # value is attribute type. + openapi_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls: T, dikt: dict) -> T: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self) -> dict: + """Returns the model properties as a dict + """ + result = {} + + for attr_key, json_key in self.attribute_map.items(): + value = getattr(self, attr_key) + if value is None: + continue + if isinstance(value, list): + result[json_key] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[json_key] = value.to_dict() + elif isinstance(value, dict): + result[json_key] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[json_key] = value + + return result + + def to_str(self) -> str: + """Returns the string representation of the model + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/category.py b/samples/server/petstore/python-aiohttp/openapi_server/models/category.py new file mode 100644 index 00000000000..930eef70829 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/category.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from openapi_server.models.base_model_ import Model +from openapi_server import util + + +class Category(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, id: int=None, name: str=None): + """Category - a model defined in OpenAPI + + :param id: The id of this Category. + :param name: The name of this Category. + """ + self.openapi_types = { + 'id': int, + 'name': str + } + + self.attribute_map = { + 'id': 'id', + 'name': 'name' + } + + self._id = id + self._name = name + + @classmethod + def from_dict(cls, dikt: dict) -> 'Category': + """Returns the dict as a model + + :param dikt: A dict. + :return: The Category of this Category. + """ + return util.deserialize_model(dikt, cls) + + @property + def id(self): + """Gets the id of this Category. + + + :return: The id of this Category. + :rtype: int + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Category. + + + :param id: The id of this Category. + :type id: int + """ + + self._id = id + + @property + def name(self): + """Gets the name of this Category. + + + :return: The name of this Category. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Category. + + + :param name: The name of this Category. + :type name: str + """ + + self._name = name diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/order.py b/samples/server/petstore/python-aiohttp/openapi_server/models/order.py new file mode 100644 index 00000000000..71504aba20c --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/order.py @@ -0,0 +1,193 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from openapi_server.models.base_model_ import Model +from openapi_server import util + + +class Order(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, id: int=None, pet_id: int=None, quantity: int=None, ship_date: datetime=None, status: str=None, complete: bool=False): + """Order - a model defined in OpenAPI + + :param id: The id of this Order. + :param pet_id: The pet_id of this Order. + :param quantity: The quantity of this Order. + :param ship_date: The ship_date of this Order. + :param status: The status of this Order. + :param complete: The complete of this Order. + """ + self.openapi_types = { + 'id': int, + 'pet_id': int, + 'quantity': int, + 'ship_date': datetime, + 'status': str, + 'complete': bool + } + + self.attribute_map = { + 'id': 'id', + 'pet_id': 'petId', + 'quantity': 'quantity', + 'ship_date': 'shipDate', + 'status': 'status', + 'complete': 'complete' + } + + self._id = id + self._pet_id = pet_id + self._quantity = quantity + self._ship_date = ship_date + self._status = status + self._complete = complete + + @classmethod + def from_dict(cls, dikt: dict) -> 'Order': + """Returns the dict as a model + + :param dikt: A dict. + :return: The Order of this Order. + """ + return util.deserialize_model(dikt, cls) + + @property + def id(self): + """Gets the id of this Order. + + + :return: The id of this Order. + :rtype: int + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Order. + + + :param id: The id of this Order. + :type id: int + """ + + self._id = id + + @property + def pet_id(self): + """Gets the pet_id of this Order. + + + :return: The pet_id of this Order. + :rtype: int + """ + return self._pet_id + + @pet_id.setter + def pet_id(self, pet_id): + """Sets the pet_id of this Order. + + + :param pet_id: The pet_id of this Order. + :type pet_id: int + """ + + self._pet_id = pet_id + + @property + def quantity(self): + """Gets the quantity of this Order. + + + :return: The quantity of this Order. + :rtype: int + """ + return self._quantity + + @quantity.setter + def quantity(self, quantity): + """Sets the quantity of this Order. + + + :param quantity: The quantity of this Order. + :type quantity: int + """ + + self._quantity = quantity + + @property + def ship_date(self): + """Gets the ship_date of this Order. + + + :return: The ship_date of this Order. + :rtype: datetime + """ + return self._ship_date + + @ship_date.setter + def ship_date(self, ship_date): + """Sets the ship_date of this Order. + + + :param ship_date: The ship_date of this Order. + :type ship_date: datetime + """ + + self._ship_date = ship_date + + @property + def status(self): + """Gets the status of this Order. + + Order Status + + :return: The status of this Order. + :rtype: str + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this Order. + + Order Status + + :param status: The status of this Order. + :type status: str + """ + allowed_values = ["placed", "approved", "delivered"] + if status not in allowed_values: + raise ValueError( + "Invalid value for `status` ({0}), must be one of {1}" + .format(status, allowed_values) + ) + + self._status = status + + @property + def complete(self): + """Gets the complete of this Order. + + + :return: The complete of this Order. + :rtype: bool + """ + return self._complete + + @complete.setter + def complete(self, complete): + """Sets the complete of this Order. + + + :param complete: The complete of this Order. + :type complete: bool + """ + + self._complete = complete diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/pet.py b/samples/server/petstore/python-aiohttp/openapi_server/models/pet.py new file mode 100644 index 00000000000..4931fe6fc97 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/pet.py @@ -0,0 +1,199 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from openapi_server.models.base_model_ import Model +from openapi_server.models.category import Category +from openapi_server.models.tag import Tag +from openapi_server import util + + +class Pet(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, id: int=None, category: Category=None, name: str=None, photo_urls: List[str]=None, tags: List[Tag]=None, status: str=None): + """Pet - a model defined in OpenAPI + + :param id: The id of this Pet. + :param category: The category of this Pet. + :param name: The name of this Pet. + :param photo_urls: The photo_urls of this Pet. + :param tags: The tags of this Pet. + :param status: The status of this Pet. + """ + self.openapi_types = { + 'id': int, + 'category': Category, + 'name': str, + 'photo_urls': List[str], + 'tags': List[Tag], + 'status': str + } + + self.attribute_map = { + 'id': 'id', + 'category': 'category', + 'name': 'name', + 'photo_urls': 'photoUrls', + 'tags': 'tags', + 'status': 'status' + } + + self._id = id + self._category = category + self._name = name + self._photo_urls = photo_urls + self._tags = tags + self._status = status + + @classmethod + def from_dict(cls, dikt: dict) -> 'Pet': + """Returns the dict as a model + + :param dikt: A dict. + :return: The Pet of this Pet. + """ + return util.deserialize_model(dikt, cls) + + @property + def id(self): + """Gets the id of this Pet. + + + :return: The id of this Pet. + :rtype: int + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Pet. + + + :param id: The id of this Pet. + :type id: int + """ + + self._id = id + + @property + def category(self): + """Gets the category of this Pet. + + + :return: The category of this Pet. + :rtype: Category + """ + return self._category + + @category.setter + def category(self, category): + """Sets the category of this Pet. + + + :param category: The category of this Pet. + :type category: Category + """ + + self._category = category + + @property + def name(self): + """Gets the name of this Pet. + + + :return: The name of this Pet. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Pet. + + + :param name: The name of this Pet. + :type name: str + """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") + + self._name = name + + @property + def photo_urls(self): + """Gets the photo_urls of this Pet. + + + :return: The photo_urls of this Pet. + :rtype: List[str] + """ + return self._photo_urls + + @photo_urls.setter + def photo_urls(self, photo_urls): + """Sets the photo_urls of this Pet. + + + :param photo_urls: The photo_urls of this Pet. + :type photo_urls: List[str] + """ + if photo_urls is None: + raise ValueError("Invalid value for `photo_urls`, must not be `None`") + + self._photo_urls = photo_urls + + @property + def tags(self): + """Gets the tags of this Pet. + + + :return: The tags of this Pet. + :rtype: List[Tag] + """ + return self._tags + + @tags.setter + def tags(self, tags): + """Sets the tags of this Pet. + + + :param tags: The tags of this Pet. + :type tags: List[Tag] + """ + + self._tags = tags + + @property + def status(self): + """Gets the status of this Pet. + + pet status in the store + + :return: The status of this Pet. + :rtype: str + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this Pet. + + pet status in the store + + :param status: The status of this Pet. + :type status: str + """ + allowed_values = ["available", "pending", "sold"] + if status not in allowed_values: + raise ValueError( + "Invalid value for `status` ({0}), must be one of {1}" + .format(status, allowed_values) + ) + + self._status = status diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/tag.py b/samples/server/petstore/python-aiohttp/openapi_server/models/tag.py new file mode 100644 index 00000000000..d494277441c --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/tag.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from openapi_server.models.base_model_ import Model +from openapi_server import util + + +class Tag(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, id: int=None, name: str=None): + """Tag - a model defined in OpenAPI + + :param id: The id of this Tag. + :param name: The name of this Tag. + """ + self.openapi_types = { + 'id': int, + 'name': str + } + + self.attribute_map = { + 'id': 'id', + 'name': 'name' + } + + self._id = id + self._name = name + + @classmethod + def from_dict(cls, dikt: dict) -> 'Tag': + """Returns the dict as a model + + :param dikt: A dict. + :return: The Tag of this Tag. + """ + return util.deserialize_model(dikt, cls) + + @property + def id(self): + """Gets the id of this Tag. + + + :return: The id of this Tag. + :rtype: int + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this Tag. + + + :param id: The id of this Tag. + :type id: int + """ + + self._id = id + + @property + def name(self): + """Gets the name of this Tag. + + + :return: The name of this Tag. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this Tag. + + + :param name: The name of this Tag. + :type name: str + """ + + self._name = name diff --git a/samples/server/petstore/python-aiohttp/openapi_server/models/user.py b/samples/server/petstore/python-aiohttp/openapi_server/models/user.py new file mode 100644 index 00000000000..24e024c54ab --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/models/user.py @@ -0,0 +1,237 @@ +# coding: utf-8 + +from datetime import date, datetime + +from typing import List, Dict, Type + +from openapi_server.models.base_model_ import Model +from openapi_server import util + + +class User(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, id: int=None, username: str=None, first_name: str=None, last_name: str=None, email: str=None, password: str=None, phone: str=None, user_status: int=None): + """User - a model defined in OpenAPI + + :param id: The id of this User. + :param username: The username of this User. + :param first_name: The first_name of this User. + :param last_name: The last_name of this User. + :param email: The email of this User. + :param password: The password of this User. + :param phone: The phone of this User. + :param user_status: The user_status of this User. + """ + self.openapi_types = { + 'id': int, + 'username': str, + 'first_name': str, + 'last_name': str, + 'email': str, + 'password': str, + 'phone': str, + 'user_status': int + } + + self.attribute_map = { + 'id': 'id', + 'username': 'username', + 'first_name': 'firstName', + 'last_name': 'lastName', + 'email': 'email', + 'password': 'password', + 'phone': 'phone', + 'user_status': 'userStatus' + } + + self._id = id + self._username = username + self._first_name = first_name + self._last_name = last_name + self._email = email + self._password = password + self._phone = phone + self._user_status = user_status + + @classmethod + def from_dict(cls, dikt: dict) -> 'User': + """Returns the dict as a model + + :param dikt: A dict. + :return: The User of this User. + """ + return util.deserialize_model(dikt, cls) + + @property + def id(self): + """Gets the id of this User. + + + :return: The id of this User. + :rtype: int + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this User. + + + :param id: The id of this User. + :type id: int + """ + + self._id = id + + @property + def username(self): + """Gets the username of this User. + + + :return: The username of this User. + :rtype: str + """ + return self._username + + @username.setter + def username(self, username): + """Sets the username of this User. + + + :param username: The username of this User. + :type username: str + """ + + self._username = username + + @property + def first_name(self): + """Gets the first_name of this User. + + + :return: The first_name of this User. + :rtype: str + """ + return self._first_name + + @first_name.setter + def first_name(self, first_name): + """Sets the first_name of this User. + + + :param first_name: The first_name of this User. + :type first_name: str + """ + + self._first_name = first_name + + @property + def last_name(self): + """Gets the last_name of this User. + + + :return: The last_name of this User. + :rtype: str + """ + return self._last_name + + @last_name.setter + def last_name(self, last_name): + """Sets the last_name of this User. + + + :param last_name: The last_name of this User. + :type last_name: str + """ + + self._last_name = last_name + + @property + def email(self): + """Gets the email of this User. + + + :return: The email of this User. + :rtype: str + """ + return self._email + + @email.setter + def email(self, email): + """Sets the email of this User. + + + :param email: The email of this User. + :type email: str + """ + + self._email = email + + @property + def password(self): + """Gets the password of this User. + + + :return: The password of this User. + :rtype: str + """ + return self._password + + @password.setter + def password(self, password): + """Sets the password of this User. + + + :param password: The password of this User. + :type password: str + """ + + self._password = password + + @property + def phone(self): + """Gets the phone of this User. + + + :return: The phone of this User. + :rtype: str + """ + return self._phone + + @phone.setter + def phone(self, phone): + """Sets the phone of this User. + + + :param phone: The phone of this User. + :type phone: str + """ + + self._phone = phone + + @property + def user_status(self): + """Gets the user_status of this User. + + User Status + + :return: The user_status of this User. + :rtype: int + """ + return self._user_status + + @user_status.setter + def user_status(self, user_status): + """Sets the user_status of this User. + + User Status + + :param user_status: The user_status of this User. + :type user_status: int + """ + + self._user_status = user_status diff --git a/samples/server/petstore/python-aiohttp/openapi_server/openapi/openapi.yaml b/samples/server/petstore/python-aiohttp/openapi_server/openapi/openapi.yaml new file mode 100644 index 00000000000..ac5d2ed72af --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/openapi/openapi.yaml @@ -0,0 +1,792 @@ +openapi: 3.0.1 +info: + description: This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + license: + name: Apache-2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + title: OpenAPI Petstore + version: 1.0.0 +servers: +- url: http://petstore.swagger.io/v2 +tags: +- description: Everything about your Pets + name: pet +- description: Access to Petstore orders + name: store +- description: Operations about user + name: user +paths: + /pet: + post: + operationId: add_pet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + x-body-name: body + responses: + 405: + content: {} + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + summary: Add a new pet to the store + tags: + - pet + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.pet_controller + put: + operationId: update_pet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + x-body-name: body + responses: + 400: + content: {} + description: Invalid ID supplied + 404: + content: {} + description: Pet not found + 405: + content: {} + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + summary: Update an existing pet + tags: + - pet + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.pet_controller + /pet/findByStatus: + get: + description: Multiple status values can be provided with comma separated strings + operationId: find_pets_by_status + parameters: + - description: Status values that need to be considered for filter + explode: false + in: query + name: status + required: true + schema: + items: + default: available + enum: + - available + - pending + - sold + type: string + type: array + style: form + responses: + 200: + content: + application/xml: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + application/json: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + description: successful operation + 400: + content: {} + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + summary: Finds Pets by status + tags: + - pet + x-openapi-router-controller: openapi_server.controllers.pet_controller + /pet/findByTags: + get: + deprecated: true + description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + operationId: find_pets_by_tags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + style: form + responses: + 200: + content: + application/xml: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + application/json: + schema: + items: + $ref: '#/components/schemas/Pet' + type: array + description: successful operation + 400: + content: {} + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + summary: Finds Pets by tags + tags: + - pet + x-openapi-router-controller: openapi_server.controllers.pet_controller + /pet/{pet_id}: + delete: + operationId: delete_pet + parameters: + - in: header + name: api_key + schema: + type: string + - description: Pet id to delete + in: path + name: pet_id + required: true + schema: + format: int64 + type: integer + responses: + 400: + content: {} + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + summary: Deletes a pet + tags: + - pet + x-openapi-router-controller: openapi_server.controllers.pet_controller + get: + description: Returns a single pet + operationId: get_pet_by_id + parameters: + - description: ID of pet to return + in: path + name: pet_id + required: true + schema: + format: int64 + type: integer + responses: + 200: + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: successful operation + 400: + content: {} + description: Invalid ID supplied + 404: + content: {} + description: Pet not found + security: + - api_key: [] + summary: Find pet by ID + tags: + - pet + x-openapi-router-controller: openapi_server.controllers.pet_controller + post: + operationId: update_pet_with_form + parameters: + - description: ID of pet that needs to be updated + in: path + name: pet_id + required: true + schema: + format: int64 + type: integer + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + x-body-name: body + responses: + 405: + content: {} + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + summary: Updates a pet in the store with form data + tags: + - pet + x-openapi-router-controller: openapi_server.controllers.pet_controller + x-codegen-request-body-name: body + /pet/{pet_id}/uploadImage: + post: + operationId: upload_file + parameters: + - description: ID of pet to update + in: path + name: pet_id + required: true + schema: + format: int64 + type: integer + requestBody: + content: + multipart/form-data: + schema: + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + format: binary + type: string + x-body-name: body + responses: + 200: + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + description: successful operation + security: + - petstore_auth: + - write:pets + - read:pets + summary: uploads an image + tags: + - pet + x-openapi-router-controller: openapi_server.controllers.pet_controller + x-codegen-request-body-name: body + /store/inventory: + get: + description: Returns a map of status codes to quantities + operationId: get_inventory + responses: + 200: + content: + application/json: + schema: + additionalProperties: + format: int32 + type: integer + type: object + description: successful operation + security: + - api_key: [] + summary: Returns pet inventories by status + tags: + - store + x-openapi-router-controller: openapi_server.controllers.store_controller + /store/order: + post: + operationId: place_order + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + x-body-name: body + responses: + 200: + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + description: successful operation + 400: + content: {} + description: Invalid Order + summary: Place an order for a pet + tags: + - store + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.store_controller + /store/order/{order_id}: + delete: + description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + operationId: delete_order + parameters: + - description: ID of the order that needs to be deleted + in: path + name: order_id + required: true + schema: + type: string + responses: + 400: + content: {} + description: Invalid ID supplied + 404: + content: {} + description: Order not found + summary: Delete purchase order by ID + tags: + - store + x-openapi-router-controller: openapi_server.controllers.store_controller + get: + description: For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions + operationId: get_order_by_id + parameters: + - description: ID of pet that needs to be fetched + in: path + name: order_id + required: true + schema: + format: int64 + maximum: 5 + minimum: 1 + type: integer + responses: + 200: + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + description: successful operation + 400: + content: {} + description: Invalid ID supplied + 404: + content: {} + description: Order not found + summary: Find purchase order by ID + tags: + - store + x-openapi-router-controller: openapi_server.controllers.store_controller + /user: + post: + description: This can only be done by the logged in user. + operationId: create_user + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + x-body-name: body + responses: + default: + content: {} + description: successful operation + summary: Create user + tags: + - user + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.user_controller + /user/createWithArray: + post: + operationId: create_users_with_array_input + requestBody: + content: + '*/*': + schema: + items: + $ref: '#/components/schemas/User' + type: array + description: List of user object + required: true + x-body-name: body + responses: + default: + content: {} + description: successful operation + summary: Creates list of users with given input array + tags: + - user + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.user_controller + /user/createWithList: + post: + operationId: create_users_with_list_input + requestBody: + content: + '*/*': + schema: + items: + $ref: '#/components/schemas/User' + type: array + description: List of user object + required: true + x-body-name: body + responses: + default: + content: {} + description: successful operation + summary: Creates list of users with given input array + tags: + - user + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.user_controller + /user/login: + get: + operationId: login_user + parameters: + - description: The user name for login + in: query + name: username + required: true + schema: + type: string + - description: The password for login in clear text + in: query + name: password + required: true + schema: + type: string + responses: + 200: + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + format: int32 + type: integer + X-Expires-After: + description: date in UTC when toekn expires + schema: + format: date-time + type: string + 400: + content: {} + description: Invalid username/password supplied + summary: Logs user into the system + tags: + - user + x-openapi-router-controller: openapi_server.controllers.user_controller + /user/logout: + get: + operationId: logout_user + responses: + default: + content: {} + description: successful operation + summary: Logs out current logged in user session + tags: + - user + x-openapi-router-controller: openapi_server.controllers.user_controller + /user/{username}: + delete: + description: This can only be done by the logged in user. + operationId: delete_user + parameters: + - description: The name that needs to be deleted + in: path + name: username + required: true + schema: + type: string + responses: + 400: + content: {} + description: Invalid username supplied + 404: + content: {} + description: User not found + summary: Delete user + tags: + - user + x-openapi-router-controller: openapi_server.controllers.user_controller + get: + operationId: get_user_by_name + parameters: + - description: The name that needs to be fetched. Use user1 for testing. + in: path + name: username + required: true + schema: + type: string + responses: + 200: + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + description: successful operation + 400: + content: {} + description: Invalid username supplied + 404: + content: {} + description: User not found + summary: Get user by user name + tags: + - user + x-openapi-router-controller: openapi_server.controllers.user_controller + put: + description: This can only be done by the logged in user. + operationId: update_user + parameters: + - description: name that need to be deleted + in: path + name: username + required: true + schema: + type: string + requestBody: + content: + '*/*': + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + x-body-name: body + responses: + 400: + content: {} + description: Invalid user supplied + 404: + content: {} + description: User not found + summary: Updated user + tags: + - user + x-codegen-request-body-name: body + x-openapi-router-controller: openapi_server.controllers.user_controller +components: + schemas: + Order: + description: An order for a pets from the pet store + example: + petId: 6 + quantity: 1 + id: 0 + shipDate: 2000-01-23T04:56:07.000+00:00 + complete: false + status: placed + properties: + id: + format: int64 + type: integer + petId: + format: int64 + type: integer + quantity: + format: int32 + type: integer + shipDate: + format: date-time + type: string + status: + description: Order Status + enum: + - placed + - approved + - delivered + type: string + complete: + default: false + type: boolean + title: Pet Order + type: object + xml: + name: Order + Category: + description: A category for a pet + example: + name: name + id: 6 + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet category + type: object + xml: + name: Category + User: + description: A User who is purchasing from the pet store + example: + firstName: firstName + lastName: lastName + password: password + userStatus: 6 + phone: phone + id: 0 + email: email + username: username + properties: + id: + format: int64 + type: integer + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + description: User Status + format: int32 + type: integer + title: a User + type: object + xml: + name: User + Tag: + description: A tag for a pet + example: + name: name + id: 1 + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + xml: + name: Tag + Pet: + description: A pet for sale in the pet store + example: + photoUrls: + - photoUrls + - photoUrls + name: doggie + id: 0 + category: + name: name + id: 6 + tags: + - name: name + id: 1 + - name: name + id: 1 + status: available + properties: + id: + format: int64 + type: integer + category: + $ref: '#/components/schemas/Category' + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + xml: + name: photoUrl + wrapped: true + tags: + items: + $ref: '#/components/schemas/Tag' + type: array + xml: + name: tag + wrapped: true + status: + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + title: a Pet + type: object + xml: + name: Pet + ApiResponse: + description: Describes the result of uploading an image resource + example: + code: 0 + type: type + message: message + properties: + code: + format: int32 + type: integer + type: + type: string + message: + type: string + title: An uploaded response + type: object + securitySchemes: + petstore_auth: + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + type: oauth2 + x-tokenInfoFunc: openapi_server.controllers.security_controller_.info_from_petstore_auth + x-scopeValidateFunc: openapi_server.controllers.security_controller_.validate_scope_petstore_auth + api_key: + in: header + name: api_key + type: apiKey + x-apikeyInfoFunc: openapi_server.controllers.security_controller_.info_from_api_key diff --git a/samples/server/petstore/python-aiohttp/openapi_server/util.py b/samples/server/petstore/python-aiohttp/openapi_server/util.py new file mode 100644 index 00000000000..9263acb016e --- /dev/null +++ b/samples/server/petstore/python-aiohttp/openapi_server/util.py @@ -0,0 +1,130 @@ +import datetime + +import typing +from typing import Union + +T = typing.TypeVar('T') +Class = typing.Type[T] + + +def _deserialize(data: Union[dict, list, str], klass: Union[Class, str]) -> Union[dict, list, Class, int, float, str, bool, datetime.date, datetime.datetime]: + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in (int, float, str, bool): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif type(klass) == typing.GenericMeta: + if klass.__extra__ == list: + return _deserialize_list(data, klass.__args__[0]) + if klass.__extra__ == dict: + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass: Class) -> Union[Class, int, float, str, bool]: + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, float, str, bool. + """ + try: + value = klass(data) + except (UnicodeEncodeError, TypeError): + value = data + return value + + +def _deserialize_object(value: T) -> T: + """Return an original value. + + :return: object. + """ + return value + + +def deserialize_date(string: str) -> datetime.date: + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string: str) -> datetime.datetime: + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data: Union[dict, list], klass: T) -> T: + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.openapi_types: + return data + + if data is not None and isinstance(data, (list, dict)): + for attr, attr_type in instance.openapi_types.items(): + attr_key = instance.attribute_map[attr] + if attr_key in data: + value = data[attr_key] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data: list, boxed_type) -> list: + """Deserializes a list and its elements. + + :param data: list to deserialize. + :param boxed_type: class literal. + + :return: deserialized list. + """ + return [_deserialize(sub_data, boxed_type) for sub_data in data] + + +def _deserialize_dict(data: dict, boxed_type) -> dict: + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :param boxed_type: class literal. + + :return: deserialized dict. + """ + return {k: _deserialize(v, boxed_type) for k, v in data.items()} diff --git a/samples/server/petstore/python-aiohttp/pom.xml b/samples/server/petstore/python-aiohttp/pom.xml new file mode 100644 index 00000000000..8e77233a458 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + org.openapitools + PythonAiohttpServer + pom + 1.0-SNAPSHOT + Python Aiohttp Server + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory} + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + pytest-test + integration-test + + exec + + + make + + test-all + + + + + + + + diff --git a/samples/server/petstore/python-aiohttp/requirements.txt b/samples/server/petstore/python-aiohttp/requirements.txt new file mode 100644 index 00000000000..835c75de58a --- /dev/null +++ b/samples/server/petstore/python-aiohttp/requirements.txt @@ -0,0 +1,3 @@ +connexion[aiohttp,swagger-ui] == 2.0.2 +swagger-ui-bundle == 0.0.2 +aiohttp_jinja2 == 1.1.0 diff --git a/samples/server/petstore/python-aiohttp/test-requirements.txt b/samples/server/petstore/python-aiohttp/test-requirements.txt new file mode 100644 index 00000000000..1cb425f6551 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/test-requirements.txt @@ -0,0 +1,6 @@ +coverage>=4.0.3 +pytest>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 +pytest-aiohttp>=0.3.0 diff --git a/samples/server/petstore/python-aiohttp/test_python3.sh b/samples/server/petstore/python-aiohttp/test_python3.sh new file mode 100755 index 00000000000..c6a92d00aed --- /dev/null +++ b/samples/server/petstore/python-aiohttp/test_python3.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +REQUIREMENTS_FILE=requirements.txt +TEST_REQUIREMENTS_FILE=test-requirements.txt +REQUIREMENTS_OUT=requirements.txt.log +SETUP_OUT=*.egg-info +VENV=.venv +DEACTIVE=false + +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 + +### set virtualenv +if [ -z "$VIRTUAL_ENV" ]; then + virtualenv $VENV --no-site-packages --always-copy --python python3 + source $VENV/bin/activate + DEACTIVE=true +fi + +### install dependencies +pip install -r $REQUIREMENTS_FILE -r $TEST_REQUIREMENTS_FILE | tee -a $REQUIREMENTS_OUT + +### run tests +pytest || exit 1 + +### static analysis of code +flake8 --show-source petstore_api/ + +### deactivate virtualenv +if [ $DEACTIVE == true ]; then + deactivate +fi diff --git a/samples/server/petstore/flaskConnexion/openapi_server/controllers/__init__.py b/samples/server/petstore/python-aiohttp/tests/__init__.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/controllers/__init__.py rename to samples/server/petstore/python-aiohttp/tests/__init__.py diff --git a/samples/server/petstore/python-aiohttp/tests/conftest.py b/samples/server/petstore/python-aiohttp/tests/conftest.py new file mode 100644 index 00000000000..4eba84b3c11 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/tests/conftest.py @@ -0,0 +1,17 @@ +import logging +import pytest +import os + +import connexion + + +@pytest.fixture +def client(loop, aiohttp_client): + logging.getLogger('connexion.operation').setLevel('ERROR') + options = { + "swagger_ui": True + } + specification_dir = os.path.join(os.path.dirname(__file__), '..', 'openapi_server', 'openapi') + app = connexion.AioHttpApp(__name__, specification_dir=specification_dir, options=options) + app.add_api('openapi.yaml', pass_context_arg_name='request') + return loop.run_until_complete(aiohttp_client(app.app)) diff --git a/samples/server/petstore/python-aiohttp/tests/test_pet_controller.py b/samples/server/petstore/python-aiohttp/tests/test_pet_controller.py new file mode 100644 index 00000000000..5f6384a34f6 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/tests/test_pet_controller.py @@ -0,0 +1,200 @@ +# coding: utf-8 + +import pytest +import json +from aiohttp import web +from aiohttp import FormData + +from openapi_server.models.api_response import ApiResponse +from openapi_server.models.pet import Pet + + +@pytest.mark.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760") +async def test_add_pet(client): + """Test case for add_pet + + Add a new pet to the store + """ + body = { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" +} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer special-key', + } + response = await client.request( + method='POST', + path='/v2/pet', + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_delete_pet(client): + """Test case for delete_pet + + Deletes a pet + """ + headers = { + 'api_key': 'api_key_example', + 'Authorization': 'Bearer special-key', + } + response = await client.request( + method='DELETE', + path='/v2/pet/{pet_id}'.format(pet_id=56), + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_find_pets_by_status(client): + """Test case for find_pets_by_status + + Finds Pets by status + """ + params = [('status', 'available')] + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer special-key', + } + response = await client.request( + method='GET', + path='/v2/pet/findByStatus', + headers=headers, + params=params, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_find_pets_by_tags(client): + """Test case for find_pets_by_tags + + Finds Pets by tags + """ + params = [('tags', 'tags_example')] + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer special-key', + } + response = await client.request( + method='GET', + path='/v2/pet/findByTags', + headers=headers, + params=params, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_get_pet_by_id(client): + """Test case for get_pet_by_id + + Find pet by ID + """ + headers = { + 'Accept': 'application/json', + 'api_key': 'special-key', + } + response = await client.request( + method='GET', + path='/v2/pet/{pet_id}'.format(pet_id=56), + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760") +async def test_update_pet(client): + """Test case for update_pet + + Update an existing pet + """ + body = { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" +} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer special-key', + } + response = await client.request( + method='PUT', + path='/v2/pet', + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("application/x-www-form-urlencoded not supported by Connexion") +async def test_update_pet_with_form(client): + """Test case for update_pet_with_form + + Updates a pet in the store with form data + """ + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Bearer special-key', + } + data = { + 'name': 'name_example', + 'status': 'status_example' + } + response = await client.request( + method='POST', + path='/v2/pet/{pet_id}'.format(pet_id=56), + headers=headers, + data=data, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("multipart/form-data not supported by Connexion") +async def test_upload_file(client): + """Test case for upload_file + + uploads an image + """ + headers = { + 'Accept': 'application/json', + 'Content-Type': 'multipart/form-data', + 'Authorization': 'Bearer special-key', + } + data = FormData() + data.add_field('additional_metadata', 'additional_metadata_example') + data.add_field('file', (BytesIO(b'some file data'), 'file.txt')) + response = await client.request( + method='POST', + path='/v2/pet/{pet_id}/uploadImage'.format(pet_id=56), + headers=headers, + data=data, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + diff --git a/samples/server/petstore/python-aiohttp/tests/test_store_controller.py b/samples/server/petstore/python-aiohttp/tests/test_store_controller.py new file mode 100644 index 00000000000..9d376a5f7a5 --- /dev/null +++ b/samples/server/petstore/python-aiohttp/tests/test_store_controller.py @@ -0,0 +1,76 @@ +# coding: utf-8 + +import pytest +import json +from aiohttp import web + +from openapi_server.models.order import Order + + +async def test_delete_order(client): + """Test case for delete_order + + Delete purchase order by ID + """ + headers = { + } + response = await client.request( + method='DELETE', + path='/v2/store/order/{order_id}'.format(order_id='order_id_example'), + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_get_inventory(client): + """Test case for get_inventory + + Returns pet inventories by status + """ + headers = { + 'Accept': 'application/json', + 'api_key': 'special-key', + } + response = await client.request( + method='GET', + path='/v2/store/inventory', + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_get_order_by_id(client): + """Test case for get_order_by_id + + Find purchase order by ID + """ + headers = { + 'Accept': 'application/json', + } + response = await client.request( + method='GET', + path='/v2/store/order/{order_id}'.format(order_id=5), + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") +async def test_place_order(client): + """Test case for place_order + + Place an order for a pet + """ + body = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + } + response = await client.request( + method='POST', + path='/v2/store/order', + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + diff --git a/samples/server/petstore/python-aiohttp/tests/test_user_controller.py b/samples/server/petstore/python-aiohttp/tests/test_user_controller.py new file mode 100644 index 00000000000..307cab0fc7a --- /dev/null +++ b/samples/server/petstore/python-aiohttp/tests/test_user_controller.py @@ -0,0 +1,149 @@ +# coding: utf-8 + +import pytest +import json +from aiohttp import web + +from openapi_server.models.user import User + + +@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") +async def test_create_user(client): + """Test case for create_user + + Create user + """ + body = {} + headers = { + 'Content-Type': 'application/json', + } + response = await client.request( + method='POST', + path='/v2/user', + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") +async def test_create_users_with_array_input(client): + """Test case for create_users_with_array_input + + Creates list of users with given input array + """ + body = [] + headers = { + 'Content-Type': 'application/json', + } + response = await client.request( + method='POST', + path='/v2/user/createWithArray', + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") +async def test_create_users_with_list_input(client): + """Test case for create_users_with_list_input + + Creates list of users with given input array + """ + body = [] + headers = { + 'Content-Type': 'application/json', + } + response = await client.request( + method='POST', + path='/v2/user/createWithList', + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_delete_user(client): + """Test case for delete_user + + Delete user + """ + headers = { + } + response = await client.request( + method='DELETE', + path='/v2/user/{username}'.format(username='username_example'), + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_get_user_by_name(client): + """Test case for get_user_by_name + + Get user by user name + """ + headers = { + 'Accept': 'application/json', + } + response = await client.request( + method='GET', + path='/v2/user/{username}'.format(username='username_example'), + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_login_user(client): + """Test case for login_user + + Logs user into the system + """ + params = [('username', 'username_example'), + ('password', 'password_example')] + headers = { + 'Accept': 'application/json', + } + response = await client.request( + method='GET', + path='/v2/user/login', + headers=headers, + params=params, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +async def test_logout_user(client): + """Test case for logout_user + + Logs out current logged in user session + """ + headers = { + } + response = await client.request( + method='GET', + path='/v2/user/logout', + headers=headers, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + + +@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") +async def test_update_user(client): + """Test case for update_user + + Updated user + """ + body = {} + headers = { + 'Content-Type': 'application/json', + } + response = await client.request( + method='PUT', + path='/v2/user/{username}'.format(username='username_example'), + headers=headers, + json=body, + ) + assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8') + diff --git a/samples/server/petstore/flaskConnexion-python2/.dockerignore b/samples/server/petstore/python-flask-python2/.dockerignore similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/.dockerignore rename to samples/server/petstore/python-flask-python2/.dockerignore diff --git a/samples/server/petstore/flaskConnexion-python2/.gitignore b/samples/server/petstore/python-flask-python2/.gitignore similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/.gitignore rename to samples/server/petstore/python-flask-python2/.gitignore diff --git a/samples/server/petstore/flaskConnexion/.openapi-generator-ignore b/samples/server/petstore/python-flask-python2/.openapi-generator-ignore similarity index 100% rename from samples/server/petstore/flaskConnexion/.openapi-generator-ignore rename to samples/server/petstore/python-flask-python2/.openapi-generator-ignore diff --git a/samples/server/petstore/flaskConnexion/.openapi-generator/VERSION b/samples/server/petstore/python-flask-python2/.openapi-generator/VERSION similarity index 100% rename from samples/server/petstore/flaskConnexion/.openapi-generator/VERSION rename to samples/server/petstore/python-flask-python2/.openapi-generator/VERSION diff --git a/samples/server/petstore/flaskConnexion-python2/.travis.yml b/samples/server/petstore/python-flask-python2/.travis.yml similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/.travis.yml rename to samples/server/petstore/python-flask-python2/.travis.yml diff --git a/samples/server/petstore/flaskConnexion-python2/Dockerfile b/samples/server/petstore/python-flask-python2/Dockerfile similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/Dockerfile rename to samples/server/petstore/python-flask-python2/Dockerfile diff --git a/samples/server/petstore/python-flask-python2/Makefile b/samples/server/petstore/python-flask-python2/Makefile new file mode 100644 index 00000000000..02f8acd53b4 --- /dev/null +++ b/samples/server/petstore/python-flask-python2/Makefile @@ -0,0 +1,20 @@ + #!/bin/bash + +REQUIREMENTS_OUT=test-requirements.txt.log +SETUP_OUT=*.egg-info +VENV=.venv + +clean: + rm -rf $(REQUIREMENTS_OUT) + rm -rf $(SETUP_OUT) + rm -rf $(VENV) + rm -rf .tox + rm -rf .coverage + find . -name "*.py[oc]" -delete + find . -name "__pycache__" -delete + +test: clean + bash ./test_python2.sh + +test-all: clean + bash ./test_python2.sh diff --git a/samples/server/petstore/flaskConnexion-python2/README.md b/samples/server/petstore/python-flask-python2/README.md similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/README.md rename to samples/server/petstore/python-flask-python2/README.md diff --git a/samples/server/petstore/flaskConnexion-python2/git_push.sh b/samples/server/petstore/python-flask-python2/git_push.sh similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/git_push.sh rename to samples/server/petstore/python-flask-python2/git_push.sh diff --git a/samples/server/petstore/python-flask-python2/openapi_server/__init__.py b/samples/server/petstore/python-flask-python2/openapi_server/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/__main__.py b/samples/server/petstore/python-flask-python2/openapi_server/__main__.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/__main__.py rename to samples/server/petstore/python-flask-python2/openapi_server/__main__.py diff --git a/samples/server/petstore/python-flask-python2/openapi_server/controllers/__init__.py b/samples/server/petstore/python-flask-python2/openapi_server/controllers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/pet_controller.py b/samples/server/petstore/python-flask-python2/openapi_server/controllers/pet_controller.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/pet_controller.py rename to samples/server/petstore/python-flask-python2/openapi_server/controllers/pet_controller.py diff --git a/samples/server/petstore/python-flask-python2/openapi_server/controllers/security_controller_.py b/samples/server/petstore/python-flask-python2/openapi_server/controllers/security_controller_.py new file mode 100644 index 00000000000..1db7a68f466 --- /dev/null +++ b/samples/server/petstore/python-flask-python2/openapi_server/controllers/security_controller_.py @@ -0,0 +1,48 @@ +from typing import List + + +def info_from_api_key(api_key, required_scopes): + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param api_key API key provided by Authorization header + :type api_key: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} + + +def info_from_petstore_auth(token): + """ + Validate and decode token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + 'scope' or 'scopes' will be passed to scope validation function. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'} + + +def validate_scope_petstore_auth(required_scopes, token_scopes): + """ + Validate required scopes are included in token scope + + :param required_scopes Required scope to access called API + :type required_scopes: List[str] + :param token_scopes Scope present in token + :type token_scopes: List[str] + :return: True if access to called API is allowed + :rtype: bool + """ + return set(required_scopes).issubset(set(token_scopes)) + + diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/store_controller.py b/samples/server/petstore/python-flask-python2/openapi_server/controllers/store_controller.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/store_controller.py rename to samples/server/petstore/python-flask-python2/openapi_server/controllers/store_controller.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/user_controller.py b/samples/server/petstore/python-flask-python2/openapi_server/controllers/user_controller.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/controllers/user_controller.py rename to samples/server/petstore/python-flask-python2/openapi_server/controllers/user_controller.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/encoder.py b/samples/server/petstore/python-flask-python2/openapi_server/encoder.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/encoder.py rename to samples/server/petstore/python-flask-python2/openapi_server/encoder.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/__init__.py b/samples/server/petstore/python-flask-python2/openapi_server/models/__init__.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/__init__.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/__init__.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/api_response.py b/samples/server/petstore/python-flask-python2/openapi_server/models/api_response.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/api_response.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/api_response.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/base_model_.py b/samples/server/petstore/python-flask-python2/openapi_server/models/base_model_.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/base_model_.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/base_model_.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/category.py b/samples/server/petstore/python-flask-python2/openapi_server/models/category.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/category.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/category.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/order.py b/samples/server/petstore/python-flask-python2/openapi_server/models/order.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/order.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/order.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/pet.py b/samples/server/petstore/python-flask-python2/openapi_server/models/pet.py similarity index 98% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/pet.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/pet.py index e3742dad220..a9c5e52330f 100644 --- a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/pet.py +++ b/samples/server/petstore/python-flask-python2/openapi_server/models/pet.py @@ -6,6 +6,8 @@ from datetime import date, datetime # noqa: F401 from typing import List, Dict # noqa: F401 from openapi_server.models.base_model_ import Model +from openapi_server.models.category import Category +from openapi_server.models.tag import Tag from openapi_server import util from openapi_server.models.category import Category # noqa: E501 diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/tag.py b/samples/server/petstore/python-flask-python2/openapi_server/models/tag.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/tag.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/tag.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/models/user.py b/samples/server/petstore/python-flask-python2/openapi_server/models/user.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/models/user.py rename to samples/server/petstore/python-flask-python2/openapi_server/models/user.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/openapi/openapi.yaml b/samples/server/petstore/python-flask-python2/openapi_server/openapi/openapi.yaml similarity index 97% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/openapi/openapi.yaml rename to samples/server/petstore/python-flask-python2/openapi_server/openapi/openapi.yaml index f0cc6c9fdd4..37c1cee4d71 100644 --- a/samples/server/petstore/flaskConnexion-python2/openapi_server/openapi/openapi.yaml +++ b/samples/server/petstore/python-flask-python2/openapi_server/openapi/openapi.yaml @@ -159,7 +159,7 @@ paths: tags: - pet x-openapi-router-controller: openapi_server.controllers.pet_controller - /pet/{petId}: + /pet/{pet_id}: delete: operationId: delete_pet parameters: @@ -169,7 +169,7 @@ paths: type: string - description: Pet id to delete in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -192,7 +192,7 @@ paths: parameters: - description: ID of pet to return in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -224,7 +224,7 @@ paths: parameters: - description: ID of pet that needs to be updated in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -252,13 +252,13 @@ paths: tags: - pet x-openapi-router-controller: openapi_server.controllers.pet_controller - /pet/{petId}/uploadImage: + /pet/{pet_id}/uploadImage: post: operationId: upload_file parameters: - description: ID of pet to update in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -338,14 +338,14 @@ paths: - store x-codegen-request-body-name: body x-openapi-router-controller: openapi_server.controllers.store_controller - /store/order/{orderId}: + /store/order/{order_id}: delete: description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors operationId: delete_order parameters: - description: ID of the order that needs to be deleted in: path - name: orderId + name: order_id required: true schema: type: string @@ -366,7 +366,7 @@ paths: parameters: - description: ID of pet that needs to be fetched in: path - name: orderId + name: order_id required: true schema: format: int64 @@ -772,7 +772,10 @@ components: write:pets: modify pets in your account read:pets: read your pets type: oauth2 + x-tokenInfoFunc: openapi_server.controllers.security_controller_.info_from_petstore_auth + x-scopeValidateFunc: openapi_server.controllers.security_controller_.validate_scope_petstore_auth api_key: in: header name: api_key type: apiKey + x-apikeyInfoFunc: openapi_server.controllers.security_controller_.info_from_api_key diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/__init__.py b/samples/server/petstore/python-flask-python2/openapi_server/test/__init__.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/test/__init__.py rename to samples/server/petstore/python-flask-python2/openapi_server/test/__init__.py diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_pet_controller.py b/samples/server/petstore/python-flask-python2/openapi_server/test/test_pet_controller.py similarity index 59% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_pet_controller.py rename to samples/server/petstore/python-flask-python2/openapi_server/test/test_pet_controller.py index 56d0dc2b57c..3100b498b35 100644 --- a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_pet_controller.py +++ b/samples/server/petstore/python-flask-python2/openapi_server/test/test_pet_controller.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -13,15 +14,37 @@ from openapi_server.test import BaseTestCase class TestPetController(BaseTestCase): """PetController integration test stubs""" + @unittest.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760") def test_add_pet(self): """Test case for add_pet Add a new pet to the store """ - body = Pet() + body = { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" +} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -32,9 +55,12 @@ class TestPetController(BaseTestCase): Deletes a pet """ - headers = [('api_key', 'api_key_example')] + headers = { + 'api_key': 'api_key_example', + 'Authorization': 'Bearer special-key', + } response = self.client.open( - '/v2/pet/{petId}'.format(pet_id=789), + '/v2/pet/{pet_id}'.format(pet_id=789), method='DELETE', headers=headers) self.assert200(response, @@ -46,9 +72,14 @@ class TestPetController(BaseTestCase): Finds Pets by status """ query_string = [('status', 'available')] + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet/findByStatus', method='GET', + headers=headers, query_string=query_string) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -59,9 +90,14 @@ class TestPetController(BaseTestCase): Finds Pets by tags """ query_string = [('tags', 'tags_example')] + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet/findByTags', method='GET', + headers=headers, query_string=query_string) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -71,51 +107,91 @@ class TestPetController(BaseTestCase): Find pet by ID """ + headers = { + 'Accept': 'application/json', + 'api_key': 'special-key', + } response = self.client.open( - '/v2/pet/{petId}'.format(pet_id=789), - method='GET') + '/v2/pet/{pet_id}'.format(pet_id=789), + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760") def test_update_pet(self): """Test case for update_pet Update an existing pet """ - body = Pet() + body = { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" +} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet', method='PUT', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("application/x-www-form-urlencoded not supported by Connexion") def test_update_pet_with_form(self): """Test case for update_pet_with_form Updates a pet in the store with form data """ + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Bearer special-key', + } data = dict(name='name_example', status='status_example') response = self.client.open( - '/v2/pet/{petId}'.format(pet_id=789), + '/v2/pet/{pet_id}'.format(pet_id=789), method='POST', + headers=headers, data=data, content_type='application/x-www-form-urlencoded') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("multipart/form-data not supported by Connexion") def test_upload_file(self): """Test case for upload_file uploads an image """ + headers = { + 'Accept': 'application/json', + 'Content-Type': 'multipart/form-data', + 'Authorization': 'Bearer special-key', + } data = dict(additional_metadata='additional_metadata_example', file=(BytesIO(b'some file data'), 'file.txt')) response = self.client.open( - '/v2/pet/{petId}/uploadImage'.format(pet_id=789), + '/v2/pet/{pet_id}/uploadImage'.format(pet_id=789), method='POST', + headers=headers, data=data, content_type='multipart/form-data') self.assert200(response, @@ -123,5 +199,4 @@ class TestPetController(BaseTestCase): if __name__ == '__main__': - import unittest unittest.main() diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_store_controller.py b/samples/server/petstore/python-flask-python2/openapi_server/test/test_store_controller.py similarity index 65% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_store_controller.py rename to samples/server/petstore/python-flask-python2/openapi_server/test/test_store_controller.py index 05749b7b507..e2d0c7ddd37 100644 --- a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_store_controller.py +++ b/samples/server/petstore/python-flask-python2/openapi_server/test/test_store_controller.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -17,9 +18,12 @@ class TestStoreController(BaseTestCase): Delete purchase order by ID """ + headers = { + } response = self.client.open( - '/v2/store/order/{orderId}'.format(order_id='order_id_example'), - method='DELETE') + '/v2/store/order/{order_id}'.format(order_id='order_id_example'), + method='DELETE', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -28,9 +32,14 @@ class TestStoreController(BaseTestCase): Returns pet inventories by status """ + headers = { + 'Accept': 'application/json', + 'api_key': 'special-key', + } response = self.client.open( '/v2/store/inventory', - method='GET') + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -39,21 +48,31 @@ class TestStoreController(BaseTestCase): Find purchase order by ID """ + headers = { + 'Accept': 'application/json', + } response = self.client.open( - '/v2/store/order/{orderId}'.format(order_id=5), - method='GET') + '/v2/store/order/{order_id}'.format(order_id=5), + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_place_order(self): """Test case for place_order Place an order for a pet """ - body = Order() + body = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/store/order', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -61,5 +80,4 @@ class TestStoreController(BaseTestCase): if __name__ == '__main__': - import unittest unittest.main() diff --git a/samples/server/petstore/flaskConnexion/openapi_server/test/test_user_controller.py b/samples/server/petstore/python-flask-python2/openapi_server/test/test_user_controller.py similarity index 70% rename from samples/server/petstore/flaskConnexion/openapi_server/test/test_user_controller.py rename to samples/server/petstore/python-flask-python2/openapi_server/test/test_user_controller.py index dac3b779b96..78cce39287e 100644 --- a/samples/server/petstore/flaskConnexion/openapi_server/test/test_user_controller.py +++ b/samples/server/petstore/python-flask-python2/openapi_server/test/test_user_controller.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -12,43 +13,58 @@ from openapi_server.test import BaseTestCase class TestUserController(BaseTestCase): """UserController integration test stubs""" + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_create_user(self): """Test case for create_user Create user """ - body = User() + body = {} + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_create_users_with_array_input(self): """Test case for create_users_with_array_input Creates list of users with given input array """ - body = None + body = [] + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user/createWithArray', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_create_users_with_list_input(self): """Test case for create_users_with_list_input Creates list of users with given input array """ - body = None + body = [] + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user/createWithList', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -59,9 +75,12 @@ class TestUserController(BaseTestCase): Delete user """ + headers = { + } response = self.client.open( '/v2/user/{username}'.format(username='username_example'), - method='DELETE') + method='DELETE', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -70,9 +89,13 @@ class TestUserController(BaseTestCase): Get user by user name """ + headers = { + 'Accept': 'application/json', + } response = self.client.open( '/v2/user/{username}'.format(username='username_example'), - method='GET') + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -83,9 +106,13 @@ class TestUserController(BaseTestCase): """ query_string = [('username', 'username_example'), ('password', 'password_example')] + headers = { + 'Accept': 'application/json', + } response = self.client.open( '/v2/user/login', method='GET', + headers=headers, query_string=query_string) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -95,21 +122,29 @@ class TestUserController(BaseTestCase): Logs out current logged in user session """ + headers = { + } response = self.client.open( '/v2/user/logout', - method='GET') + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_update_user(self): """Test case for update_user Updated user """ - body = User() + body = {} + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user/{username}'.format(username='username_example'), method='PUT', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -117,5 +152,4 @@ class TestUserController(BaseTestCase): if __name__ == '__main__': - import unittest unittest.main() diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/util.py b/samples/server/petstore/python-flask-python2/openapi_server/util.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/util.py rename to samples/server/petstore/python-flask-python2/openapi_server/util.py diff --git a/samples/server/petstore/python-flask-python2/pom.xml b/samples/server/petstore/python-flask-python2/pom.xml new file mode 100644 index 00000000000..113c387d5f2 --- /dev/null +++ b/samples/server/petstore/python-flask-python2/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + org.openapitools + PythonFlaskConnexionTestsPython2 + pom + 1.0-SNAPSHOT + Python Flask Server python 2 + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory} + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + nose-test + integration-test + + exec + + + make + + test-all + + + + + + + + diff --git a/samples/server/petstore/flaskConnexion-python2/requirements.txt b/samples/server/petstore/python-flask-python2/requirements.txt similarity index 82% rename from samples/server/petstore/flaskConnexion-python2/requirements.txt rename to samples/server/petstore/python-flask-python2/requirements.txt index c764c056b3b..de4b8821af6 100644 --- a/samples/server/petstore/flaskConnexion-python2/requirements.txt +++ b/samples/server/petstore/python-flask-python2/requirements.txt @@ -1,4 +1,4 @@ -connexion == 2.0.0 +connexion == 2.0.2 swagger-ui-bundle == 0.0.2 python_dateutil == 2.6.0 typing == 3.5.2.2 diff --git a/samples/server/petstore/flaskConnexion-python2/setup.py b/samples/server/petstore/python-flask-python2/setup.py similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/setup.py rename to samples/server/petstore/python-flask-python2/setup.py diff --git a/samples/server/petstore/flaskConnexion-python2/test-requirements.txt b/samples/server/petstore/python-flask-python2/test-requirements.txt similarity index 100% rename from samples/server/petstore/flaskConnexion-python2/test-requirements.txt rename to samples/server/petstore/python-flask-python2/test-requirements.txt diff --git a/samples/server/petstore/python-flask-python2/test_python2.sh b/samples/server/petstore/python-flask-python2/test_python2.sh new file mode 100755 index 00000000000..e579f4d7f55 --- /dev/null +++ b/samples/server/petstore/python-flask-python2/test_python2.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +REQUIREMENTS_FILE=test-requirements.txt +REQUIREMENTS_OUT=test-requirements.txt.log +SETUP_OUT=*.egg-info +VENV=.venv +DEACTIVE=false + +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 + +### set virtualenv +if [ -z "$VIRTUAL_ENV" ]; then + virtualenv $VENV --no-site-packages --always-copy --python python + source $VENV/bin/activate + DEACTIVE=true +fi + +### install dependencies +pip install -r $REQUIREMENTS_FILE | tee -a $REQUIREMENTS_OUT +python setup.py develop + +### run tests +tox || exit 1 + +### static analysis of code +flake8 --show-source petstore_api/ + +### deactivate virtualenv +#if [ $DEACTIVE == true ]; then +# deactivate +#fi diff --git a/samples/server/petstore/flaskConnexion-python2/tox.ini b/samples/server/petstore/python-flask-python2/tox.ini similarity index 85% rename from samples/server/petstore/flaskConnexion-python2/tox.ini rename to samples/server/petstore/python-flask-python2/tox.ini index b633a1f0598..26985414c88 100644 --- a/samples/server/petstore/flaskConnexion-python2/tox.ini +++ b/samples/server/petstore/python-flask-python2/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py35 +envlist = py27, py3 [testenv] deps=-r{toxinidir}/requirements.txt diff --git a/samples/server/petstore/flaskConnexion/.dockerignore b/samples/server/petstore/python-flask/.dockerignore similarity index 100% rename from samples/server/petstore/flaskConnexion/.dockerignore rename to samples/server/petstore/python-flask/.dockerignore diff --git a/samples/server/petstore/flaskConnexion/.gitignore b/samples/server/petstore/python-flask/.gitignore similarity index 100% rename from samples/server/petstore/flaskConnexion/.gitignore rename to samples/server/petstore/python-flask/.gitignore diff --git a/samples/server/petstore/python-flask/.openapi-generator-ignore b/samples/server/petstore/python-flask/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/server/petstore/python-flask/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/server/petstore/python-flask/.openapi-generator/VERSION b/samples/server/petstore/python-flask/.openapi-generator/VERSION new file mode 100644 index 00000000000..afa63656064 --- /dev/null +++ b/samples/server/petstore/python-flask/.openapi-generator/VERSION @@ -0,0 +1 @@ +4.0.0-SNAPSHOT \ No newline at end of file diff --git a/samples/server/petstore/flaskConnexion/.travis.yml b/samples/server/petstore/python-flask/.travis.yml similarity index 100% rename from samples/server/petstore/flaskConnexion/.travis.yml rename to samples/server/petstore/python-flask/.travis.yml diff --git a/samples/server/petstore/flaskConnexion/Dockerfile b/samples/server/petstore/python-flask/Dockerfile similarity index 100% rename from samples/server/petstore/flaskConnexion/Dockerfile rename to samples/server/petstore/python-flask/Dockerfile diff --git a/samples/server/petstore/python-flask/Makefile b/samples/server/petstore/python-flask/Makefile new file mode 100644 index 00000000000..b32712ef06c --- /dev/null +++ b/samples/server/petstore/python-flask/Makefile @@ -0,0 +1,20 @@ + #!/bin/bash + +REQUIREMENTS_OUT=test-requirements.txt.log +SETUP_OUT=*.egg-info +VENV=.venv + +clean: + rm -rf $(REQUIREMENTS_OUT) + rm -rf $(SETUP_OUT) + rm -rf $(VENV) + rm -rf .tox + rm -rf .coverage + find . -name "*.py[oc]" -delete + find . -name "__pycache__" -delete + +test: clean + bash ./test_python3.sh + +test-all: clean + bash ./test_python3.sh diff --git a/samples/server/petstore/flaskConnexion/README.md b/samples/server/petstore/python-flask/README.md similarity index 100% rename from samples/server/petstore/flaskConnexion/README.md rename to samples/server/petstore/python-flask/README.md diff --git a/samples/server/petstore/flaskConnexion/git_push.sh b/samples/server/petstore/python-flask/git_push.sh similarity index 100% rename from samples/server/petstore/flaskConnexion/git_push.sh rename to samples/server/petstore/python-flask/git_push.sh diff --git a/samples/server/petstore/python-flask/openapi_server/__init__.py b/samples/server/petstore/python-flask/openapi_server/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/server/petstore/flaskConnexion/openapi_server/__main__.py b/samples/server/petstore/python-flask/openapi_server/__main__.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/__main__.py rename to samples/server/petstore/python-flask/openapi_server/__main__.py diff --git a/samples/server/petstore/python-flask/openapi_server/controllers/__init__.py b/samples/server/petstore/python-flask/openapi_server/controllers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/server/petstore/flaskConnexion/openapi_server/controllers/pet_controller.py b/samples/server/petstore/python-flask/openapi_server/controllers/pet_controller.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/controllers/pet_controller.py rename to samples/server/petstore/python-flask/openapi_server/controllers/pet_controller.py diff --git a/samples/server/petstore/python-flask/openapi_server/controllers/security_controller_.py b/samples/server/petstore/python-flask/openapi_server/controllers/security_controller_.py new file mode 100644 index 00000000000..1db7a68f466 --- /dev/null +++ b/samples/server/petstore/python-flask/openapi_server/controllers/security_controller_.py @@ -0,0 +1,48 @@ +from typing import List + + +def info_from_api_key(api_key, required_scopes): + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param api_key API key provided by Authorization header + :type api_key: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} + + +def info_from_petstore_auth(token): + """ + Validate and decode token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + 'scope' or 'scopes' will be passed to scope validation function. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'} + + +def validate_scope_petstore_auth(required_scopes, token_scopes): + """ + Validate required scopes are included in token scope + + :param required_scopes Required scope to access called API + :type required_scopes: List[str] + :param token_scopes Scope present in token + :type token_scopes: List[str] + :return: True if access to called API is allowed + :rtype: bool + """ + return set(required_scopes).issubset(set(token_scopes)) + + diff --git a/samples/server/petstore/flaskConnexion/openapi_server/controllers/store_controller.py b/samples/server/petstore/python-flask/openapi_server/controllers/store_controller.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/controllers/store_controller.py rename to samples/server/petstore/python-flask/openapi_server/controllers/store_controller.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/controllers/user_controller.py b/samples/server/petstore/python-flask/openapi_server/controllers/user_controller.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/controllers/user_controller.py rename to samples/server/petstore/python-flask/openapi_server/controllers/user_controller.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/encoder.py b/samples/server/petstore/python-flask/openapi_server/encoder.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/encoder.py rename to samples/server/petstore/python-flask/openapi_server/encoder.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/__init__.py b/samples/server/petstore/python-flask/openapi_server/models/__init__.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/__init__.py rename to samples/server/petstore/python-flask/openapi_server/models/__init__.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/api_response.py b/samples/server/petstore/python-flask/openapi_server/models/api_response.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/api_response.py rename to samples/server/petstore/python-flask/openapi_server/models/api_response.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/base_model_.py b/samples/server/petstore/python-flask/openapi_server/models/base_model_.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/base_model_.py rename to samples/server/petstore/python-flask/openapi_server/models/base_model_.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/category.py b/samples/server/petstore/python-flask/openapi_server/models/category.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/category.py rename to samples/server/petstore/python-flask/openapi_server/models/category.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/order.py b/samples/server/petstore/python-flask/openapi_server/models/order.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/order.py rename to samples/server/petstore/python-flask/openapi_server/models/order.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/pet.py b/samples/server/petstore/python-flask/openapi_server/models/pet.py similarity index 98% rename from samples/server/petstore/flaskConnexion/openapi_server/models/pet.py rename to samples/server/petstore/python-flask/openapi_server/models/pet.py index 3489b25d5bc..e61674165e1 100644 --- a/samples/server/petstore/flaskConnexion/openapi_server/models/pet.py +++ b/samples/server/petstore/python-flask/openapi_server/models/pet.py @@ -6,6 +6,8 @@ from datetime import date, datetime # noqa: F401 from typing import List, Dict # noqa: F401 from openapi_server.models.base_model_ import Model +from openapi_server.models.category import Category +from openapi_server.models.tag import Tag from openapi_server import util from openapi_server.models.category import Category # noqa: E501 diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/tag.py b/samples/server/petstore/python-flask/openapi_server/models/tag.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/tag.py rename to samples/server/petstore/python-flask/openapi_server/models/tag.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/models/user.py b/samples/server/petstore/python-flask/openapi_server/models/user.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/models/user.py rename to samples/server/petstore/python-flask/openapi_server/models/user.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/openapi/openapi.yaml b/samples/server/petstore/python-flask/openapi_server/openapi/openapi.yaml similarity index 97% rename from samples/server/petstore/flaskConnexion/openapi_server/openapi/openapi.yaml rename to samples/server/petstore/python-flask/openapi_server/openapi/openapi.yaml index f0cc6c9fdd4..37c1cee4d71 100644 --- a/samples/server/petstore/flaskConnexion/openapi_server/openapi/openapi.yaml +++ b/samples/server/petstore/python-flask/openapi_server/openapi/openapi.yaml @@ -159,7 +159,7 @@ paths: tags: - pet x-openapi-router-controller: openapi_server.controllers.pet_controller - /pet/{petId}: + /pet/{pet_id}: delete: operationId: delete_pet parameters: @@ -169,7 +169,7 @@ paths: type: string - description: Pet id to delete in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -192,7 +192,7 @@ paths: parameters: - description: ID of pet to return in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -224,7 +224,7 @@ paths: parameters: - description: ID of pet that needs to be updated in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -252,13 +252,13 @@ paths: tags: - pet x-openapi-router-controller: openapi_server.controllers.pet_controller - /pet/{petId}/uploadImage: + /pet/{pet_id}/uploadImage: post: operationId: upload_file parameters: - description: ID of pet to update in: path - name: petId + name: pet_id required: true schema: format: int64 @@ -338,14 +338,14 @@ paths: - store x-codegen-request-body-name: body x-openapi-router-controller: openapi_server.controllers.store_controller - /store/order/{orderId}: + /store/order/{order_id}: delete: description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors operationId: delete_order parameters: - description: ID of the order that needs to be deleted in: path - name: orderId + name: order_id required: true schema: type: string @@ -366,7 +366,7 @@ paths: parameters: - description: ID of pet that needs to be fetched in: path - name: orderId + name: order_id required: true schema: format: int64 @@ -772,7 +772,10 @@ components: write:pets: modify pets in your account read:pets: read your pets type: oauth2 + x-tokenInfoFunc: openapi_server.controllers.security_controller_.info_from_petstore_auth + x-scopeValidateFunc: openapi_server.controllers.security_controller_.validate_scope_petstore_auth api_key: in: header name: api_key type: apiKey + x-apikeyInfoFunc: openapi_server.controllers.security_controller_.info_from_api_key diff --git a/samples/server/petstore/flaskConnexion/openapi_server/test/__init__.py b/samples/server/petstore/python-flask/openapi_server/test/__init__.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/test/__init__.py rename to samples/server/petstore/python-flask/openapi_server/test/__init__.py diff --git a/samples/server/petstore/flaskConnexion/openapi_server/test/test_pet_controller.py b/samples/server/petstore/python-flask/openapi_server/test/test_pet_controller.py similarity index 59% rename from samples/server/petstore/flaskConnexion/openapi_server/test/test_pet_controller.py rename to samples/server/petstore/python-flask/openapi_server/test/test_pet_controller.py index c3652a2df3c..f29ac97214b 100644 --- a/samples/server/petstore/flaskConnexion/openapi_server/test/test_pet_controller.py +++ b/samples/server/petstore/python-flask/openapi_server/test/test_pet_controller.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -13,15 +14,37 @@ from openapi_server.test import BaseTestCase class TestPetController(BaseTestCase): """PetController integration test stubs""" + @unittest.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760") def test_add_pet(self): """Test case for add_pet Add a new pet to the store """ - body = Pet() + body = { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" +} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -32,9 +55,12 @@ class TestPetController(BaseTestCase): Deletes a pet """ - headers = [('api_key', 'api_key_example')] + headers = { + 'api_key': 'api_key_example', + 'Authorization': 'Bearer special-key', + } response = self.client.open( - '/v2/pet/{petId}'.format(pet_id=56), + '/v2/pet/{pet_id}'.format(pet_id=56), method='DELETE', headers=headers) self.assert200(response, @@ -46,9 +72,14 @@ class TestPetController(BaseTestCase): Finds Pets by status """ query_string = [('status', 'available')] + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet/findByStatus', method='GET', + headers=headers, query_string=query_string) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -59,9 +90,14 @@ class TestPetController(BaseTestCase): Finds Pets by tags """ query_string = [('tags', 'tags_example')] + headers = { + 'Accept': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet/findByTags', method='GET', + headers=headers, query_string=query_string) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -71,51 +107,91 @@ class TestPetController(BaseTestCase): Find pet by ID """ + headers = { + 'Accept': 'application/json', + 'api_key': 'special-key', + } response = self.client.open( - '/v2/pet/{petId}'.format(pet_id=56), - method='GET') + '/v2/pet/{pet_id}'.format(pet_id=56), + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760") def test_update_pet(self): """Test case for update_pet Update an existing pet """ - body = Pet() + body = { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" +} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer special-key', + } response = self.client.open( '/v2/pet', method='PUT', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("application/x-www-form-urlencoded not supported by Connexion") def test_update_pet_with_form(self): """Test case for update_pet_with_form Updates a pet in the store with form data """ + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Bearer special-key', + } data = dict(name='name_example', status='status_example') response = self.client.open( - '/v2/pet/{petId}'.format(pet_id=56), + '/v2/pet/{pet_id}'.format(pet_id=56), method='POST', + headers=headers, data=data, content_type='application/x-www-form-urlencoded') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("multipart/form-data not supported by Connexion") def test_upload_file(self): """Test case for upload_file uploads an image """ + headers = { + 'Accept': 'application/json', + 'Content-Type': 'multipart/form-data', + 'Authorization': 'Bearer special-key', + } data = dict(additional_metadata='additional_metadata_example', file=(BytesIO(b'some file data'), 'file.txt')) response = self.client.open( - '/v2/pet/{petId}/uploadImage'.format(pet_id=56), + '/v2/pet/{pet_id}/uploadImage'.format(pet_id=56), method='POST', + headers=headers, data=data, content_type='multipart/form-data') self.assert200(response, @@ -123,5 +199,4 @@ class TestPetController(BaseTestCase): if __name__ == '__main__': - import unittest unittest.main() diff --git a/samples/server/petstore/flaskConnexion/openapi_server/test/test_store_controller.py b/samples/server/petstore/python-flask/openapi_server/test/test_store_controller.py similarity index 65% rename from samples/server/petstore/flaskConnexion/openapi_server/test/test_store_controller.py rename to samples/server/petstore/python-flask/openapi_server/test/test_store_controller.py index 05749b7b507..e2d0c7ddd37 100644 --- a/samples/server/petstore/flaskConnexion/openapi_server/test/test_store_controller.py +++ b/samples/server/petstore/python-flask/openapi_server/test/test_store_controller.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -17,9 +18,12 @@ class TestStoreController(BaseTestCase): Delete purchase order by ID """ + headers = { + } response = self.client.open( - '/v2/store/order/{orderId}'.format(order_id='order_id_example'), - method='DELETE') + '/v2/store/order/{order_id}'.format(order_id='order_id_example'), + method='DELETE', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -28,9 +32,14 @@ class TestStoreController(BaseTestCase): Returns pet inventories by status """ + headers = { + 'Accept': 'application/json', + 'api_key': 'special-key', + } response = self.client.open( '/v2/store/inventory', - method='GET') + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -39,21 +48,31 @@ class TestStoreController(BaseTestCase): Find purchase order by ID """ + headers = { + 'Accept': 'application/json', + } response = self.client.open( - '/v2/store/order/{orderId}'.format(order_id=5), - method='GET') + '/v2/store/order/{order_id}'.format(order_id=5), + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_place_order(self): """Test case for place_order Place an order for a pet """ - body = Order() + body = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/store/order', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -61,5 +80,4 @@ class TestStoreController(BaseTestCase): if __name__ == '__main__': - import unittest unittest.main() diff --git a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_user_controller.py b/samples/server/petstore/python-flask/openapi_server/test/test_user_controller.py similarity index 70% rename from samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_user_controller.py rename to samples/server/petstore/python-flask/openapi_server/test/test_user_controller.py index dac3b779b96..78cce39287e 100644 --- a/samples/server/petstore/flaskConnexion-python2/openapi_server/test/test_user_controller.py +++ b/samples/server/petstore/python-flask/openapi_server/test/test_user_controller.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import +import unittest from flask import json from six import BytesIO @@ -12,43 +13,58 @@ from openapi_server.test import BaseTestCase class TestUserController(BaseTestCase): """UserController integration test stubs""" + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_create_user(self): """Test case for create_user Create user """ - body = User() + body = {} + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_create_users_with_array_input(self): """Test case for create_users_with_array_input Creates list of users with given input array """ - body = None + body = [] + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user/createWithArray', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_create_users_with_list_input(self): """Test case for create_users_with_list_input Creates list of users with given input array """ - body = None + body = [] + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user/createWithList', method='POST', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -59,9 +75,12 @@ class TestUserController(BaseTestCase): Delete user """ + headers = { + } response = self.client.open( '/v2/user/{username}'.format(username='username_example'), - method='DELETE') + method='DELETE', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -70,9 +89,13 @@ class TestUserController(BaseTestCase): Get user by user name """ + headers = { + 'Accept': 'application/json', + } response = self.client.open( '/v2/user/{username}'.format(username='username_example'), - method='GET') + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -83,9 +106,13 @@ class TestUserController(BaseTestCase): """ query_string = [('username', 'username_example'), ('password', 'password_example')] + headers = { + 'Accept': 'application/json', + } response = self.client.open( '/v2/user/login', method='GET', + headers=headers, query_string=query_string) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) @@ -95,21 +122,29 @@ class TestUserController(BaseTestCase): Logs out current logged in user session """ + headers = { + } response = self.client.open( '/v2/user/logout', - method='GET') + method='GET', + headers=headers) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + @unittest.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760") def test_update_user(self): """Test case for update_user Updated user """ - body = User() + body = {} + headers = { + 'Content-Type': 'application/json', + } response = self.client.open( '/v2/user/{username}'.format(username='username_example'), method='PUT', + headers=headers, data=json.dumps(body), content_type='application/json') self.assert200(response, @@ -117,5 +152,4 @@ class TestUserController(BaseTestCase): if __name__ == '__main__': - import unittest unittest.main() diff --git a/samples/server/petstore/flaskConnexion/openapi_server/util.py b/samples/server/petstore/python-flask/openapi_server/util.py similarity index 100% rename from samples/server/petstore/flaskConnexion/openapi_server/util.py rename to samples/server/petstore/python-flask/openapi_server/util.py diff --git a/samples/server/petstore/python-flask/pom.xml b/samples/server/petstore/python-flask/pom.xml new file mode 100644 index 00000000000..2834d92dbc7 --- /dev/null +++ b/samples/server/petstore/python-flask/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + org.openapitools + PythonFlaskConnexionTests + pom + 1.0-SNAPSHOT + Python Flask Server + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory} + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + nose-test + integration-test + + exec + + + make + + test-all + + + + + + + + diff --git a/samples/server/petstore/flaskConnexion/requirements.txt b/samples/server/petstore/python-flask/requirements.txt similarity index 79% rename from samples/server/petstore/flaskConnexion/requirements.txt rename to samples/server/petstore/python-flask/requirements.txt index 52aca69de9a..66ecf7c2685 100644 --- a/samples/server/petstore/flaskConnexion/requirements.txt +++ b/samples/server/petstore/python-flask/requirements.txt @@ -1,4 +1,4 @@ -connexion == 2.0.0 +connexion == 2.0.2 swagger-ui-bundle == 0.0.2 python_dateutil == 2.6.0 setuptools >= 21.0.0 diff --git a/samples/server/petstore/flaskConnexion/setup.py b/samples/server/petstore/python-flask/setup.py similarity index 100% rename from samples/server/petstore/flaskConnexion/setup.py rename to samples/server/petstore/python-flask/setup.py diff --git a/samples/server/petstore/flaskConnexion/test-requirements.txt b/samples/server/petstore/python-flask/test-requirements.txt similarity index 100% rename from samples/server/petstore/flaskConnexion/test-requirements.txt rename to samples/server/petstore/python-flask/test-requirements.txt diff --git a/samples/server/petstore/python-flask/test_python3.sh b/samples/server/petstore/python-flask/test_python3.sh new file mode 100755 index 00000000000..9bd589401cd --- /dev/null +++ b/samples/server/petstore/python-flask/test_python3.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +REQUIREMENTS_FILE=test-requirements.txt +REQUIREMENTS_OUT=test-requirements.txt.log +SETUP_OUT=*.egg-info +VENV=.venv +DEACTIVE=false + +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 + +### set virtualenv +if [ -z "$VIRTUAL_ENV" ]; then + virtualenv $VENV --no-site-packages --always-copy --python python3 + source $VENV/bin/activate + DEACTIVE=true +fi + +### install dependencies +pip install -r $REQUIREMENTS_FILE | tee -a $REQUIREMENTS_OUT +python setup.py develop + +### run tests +tox || exit 1 + +### static analysis of code +flake8 --show-source petstore_api/ + +### deactivate virtualenv +#if [ $DEACTIVE == true ]; then +# deactivate +#fi diff --git a/samples/server/petstore/flaskConnexion/tox.ini b/samples/server/petstore/python-flask/tox.ini similarity index 89% rename from samples/server/petstore/flaskConnexion/tox.ini rename to samples/server/petstore/python-flask/tox.ini index 3e0b644eec4..ab4dfbb81b8 100644 --- a/samples/server/petstore/flaskConnexion/tox.ini +++ b/samples/server/petstore/python-flask/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35 +envlist = py3 [testenv] deps=-r{toxinidir}/requirements.txt