├── .circleci ├── config.yml └── extended │ ├── heroku-deploy.yml │ ├── pylint.yml │ └── test-with-postgresql.yml ├── .dockerignore ├── .env ├── .env.db ├── .env.dev ├── .github ├── CODEOWNERS ├── img │ └── preview.png └── workflows │ └── config-variants.yml ├── .gitignore ├── .openapi-generator-ignore ├── .openapi-generator ├── FILES └── VERSION ├── .pylintrc ├── Dockerfile ├── Dockerfile.test ├── LICENSE ├── Procfile ├── README.md ├── configure.py ├── docker-compose-test.yml ├── docker-compose.yml ├── entrypoint.sh ├── openapi_server ├── __init__.py ├── __main__.py ├── controllers │ ├── __init__.py │ ├── cart_controller.py │ ├── image_controller.py │ ├── menu_controller.py │ └── security_controller_.py ├── database │ ├── __init__.py │ ├── db_seed.py │ ├── images │ │ ├── pizza.jpg │ │ ├── readme.md │ │ ├── salad.jpg │ │ ├── soup.jpg │ │ ├── stew.jpg │ │ ├── water.jpg │ │ └── wrap.jpg │ └── models.py ├── encoder.py ├── models │ ├── __init__.py │ ├── base_model_.py │ ├── cart.py │ ├── error.py │ ├── inline_object.py │ ├── inline_response200.py │ └── menu_item.py ├── openapi │ └── openapi.yaml ├── test │ ├── __init__.py │ ├── test_cart_controller.py │ ├── test_database.py │ ├── test_image_controller.py │ └── test_menu_controller.py ├── typing_utils.py └── util.py ├── pylintrc ├── pytest.ini ├── requirements.txt ├── run-requirements.txt ├── runtime.txt └── setup.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | # The python orb contains a set of prepackaged circleci configuration you can use repeatedly in your configurations files 5 | # Orb commands and jobs help you with common scripting around a language/tool 6 | # so you dont have to copy and paste it everywhere. 7 | # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python 8 | python: circleci/python@2.1.1 9 | 10 | workflows: 11 | sample: # This is the name of the workflow, feel free to change it to better match your workflow. 12 | # Inside the workflow, you define the jobs you want to run. 13 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 14 | jobs: 15 | - build-and-test 16 | 17 | 18 | jobs: 19 | build-and-test: # This is the name of the job, feel free to change it to better match what you're trying to do! 20 | # These next lines defines a docker executors: https://circleci.com/docs/2.0/executor-types/ 21 | # You can specify an image from dockerhub or use one of the convenience images from CircleCI's Developer Hub 22 | # A list of available CircleCI docker convenience images are available here: https://circleci.com/developer/images/image/cimg/python 23 | # The executor is the environment in which the steps below will be executed - below will use a python 3.9 container 24 | # Change the version below to your required version of python 25 | docker: 26 | - image: cimg/python:3.10.5 27 | # Checkout the code as the first step. This is a dedicated CircleCI step. 28 | # The python orb's install-packages step will install the dependencies from a Pipfile via Pipenv by default. 29 | # Here we're making sure we use just use the system-wide pip. By default it uses the project root's requirements.txt. 30 | # Then run your tests! 31 | # CircleCI will report the results back to your VCS provider. 32 | steps: 33 | - checkout 34 | - python/install-packages: 35 | pkg-manager: pip 36 | # app-dir: ~/project/package-directory/ # If your requirements.txt isn't in the root directory. 37 | # pip-dependency-file: test-requirements.txt # if you have a different name for your requirements file, maybe one that combines your runtime and test requirements. 38 | - run: 39 | name: Run tests 40 | # This assumes pytest is installed via the install-package step above 41 | command: pytest 42 | -------------------------------------------------------------------------------- /.circleci/extended/heroku-deploy.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | # The python orb contains a set of prepackaged circleci configuration you can use repeatedly in your configurations files 5 | # Orb commands and jobs help you with common scripting around a language/tool 6 | # so you dont have to copy and paste it everywhere. 7 | # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python 8 | python: circleci/python@2.1.1 9 | # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/heroku 10 | heroku: circleci/heroku@1.2.6 11 | 12 | workflows: 13 | sample: # This is the name of the workflow, feel free to change it to better match your workflow. 14 | # Inside the workflow, you define the jobs you want to run. 15 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 16 | jobs: 17 | - build-and-test 18 | - heroku/deploy-via-git: 19 | force: true # this parameter instructs the push to use a force flag when pushing to the heroku remote, see: https://devcenter.heroku.com/articles/git 20 | requires: 21 | - build-and-test # only run deploy-via-git job if the build job has completed 22 | filters: 23 | branches: 24 | # This sample config runs this job on any branch matching the regex below, however, it's more likely you want to only run this job on master. 25 | only: /.*-heroku-deploy/ # Delete this line 26 | # only: master # Uncomment this line 27 | 28 | jobs: 29 | build-and-test: # This is the name of the job, feel free to change it to better match what you're trying to do! 30 | # These next lines defines a docker executors: https://circleci.com/docs/2.0/executor-types/ 31 | # You can specify an image from dockerhub or use one of the convenience images from CircleCI's Developer Hub 32 | # A list of available CircleCI docker convenience images are available here: https://circleci.com/developer/images/image/cimg/python 33 | # The executor is the environment in which the steps below will be executed - below will use a python 3.9 container 34 | # Change the version below to your required version of python 35 | docker: 36 | - image: cimg/python:3.10.5 37 | # Checkout the code as the first step. This is a dedicated CircleCI step. 38 | # The python orb's install-packages step will install the dependencies from a Pipfile via Pipenv by default. 39 | # Here we're making sure we use just use the system-wide pip. By default it uses the project root's requirements.txt. 40 | # Then run your tests! 41 | # CircleCI will report the results back to your VCS provider. 42 | steps: 43 | - checkout 44 | - python/install-packages: 45 | pkg-manager: pip 46 | # app-dir: ~/project/package-directory/ # If you're requirements.txt isn't in the root directory. 47 | # pip-dependency-file: test-requirements.txt # if you have a different name for your requirements file, maybe one that combines your runtime and test requirements. 48 | - run: 49 | name: Run tests 50 | # This assumes pytest is installed via the install-package step above 51 | command: pytest 52 | -------------------------------------------------------------------------------- /.circleci/extended/pylint.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | # The python orb contains a set of prepackaged circleci configuration you can use repeatedly in your configurations files 5 | # Orb commands and jobs help you with common scripting around a language/tool 6 | # so you dont have to copy and paste it everywhere. 7 | # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python 8 | python: circleci/python@2.1.1 9 | 10 | workflows: 11 | sample: # This is the name of the workflow, feel free to change it to better match your workflow. 12 | # Inside the workflow, you define the jobs you want to run. 13 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 14 | jobs: 15 | - build-and-test 16 | 17 | 18 | jobs: 19 | build-and-test: # This is the name of the job, feel free to change it to better match what you're trying to do! 20 | # These next lines defines a docker executors: https://circleci.com/docs/2.0/executor-types/ 21 | # You can specify an image from dockerhub or use one of the convenience images from CircleCI's Developer Hub 22 | # A list of available CircleCI docker convenience images are available here: https://circleci.com/developer/images/image/cimg/python 23 | # The executor is the environment in which the steps below will be executed - below will use a python 3.9 container 24 | # Change the version below to your required version of python 25 | docker: 26 | - image: cimg/python:3.10.5 27 | # Checkout the code as the first step. This is a dedicated CircleCI step. 28 | # The python orb's install-packages step will install the dependencies from a Pipfile via Pipenv by default. 29 | # Here we're making sure we use just use the system-wide pip. By default it uses the project root's requirements.txt. 30 | # Then run your tests! 31 | # CircleCI will report the results back to your VCS provider. 32 | steps: 33 | - checkout 34 | - python/install-packages: 35 | pkg-manager: pip 36 | # app-dir: ~/project/package-directory/ # If you're requirements.txt isn't in the root directory. 37 | # pip-dependency-file: test-requirements.txt # if you have a different name for your requirements file, maybe one that combines your runtime and test requirements. 38 | - run: 39 | name: Run pylint 40 | # This assumes pylint is installed via the install-package step above 41 | command: pylint openapi_server 42 | - run: 43 | name: Run tests 44 | # This assumes pytest is installed via the install-package step above 45 | command: pytest 46 | -------------------------------------------------------------------------------- /.circleci/extended/test-with-postgresql.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | # The python orb contains a set of prepackaged circleci configuration you can use repeatedly in your configurations files 5 | # Orb commands and jobs help you with common scripting around a language/tool 6 | # so you dont have to copy and paste it everywhere. 7 | # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python 8 | python: circleci/python@2.1.1 9 | 10 | workflows: 11 | sample: # This is the name of the workflow, feel free to change it to better match your workflow. 12 | # Inside the workflow, you define the jobs you want to run. 13 | # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows 14 | jobs: 15 | - build-and-test 16 | 17 | jobs: 18 | build-and-test: 19 | docker: 20 | - image: cimg/python:3.10.5 21 | environment: 22 | # This URL is configured from the environment variables set below 23 | DATABASE_URI: postgresql://root:password@localhost/circle_test?sslmode=disable 24 | - image: circleci/postgres:13.2 # an example of how to specify a service container 25 | environment: 26 | POSTGRES_USER: root 27 | POSTGRES_DB: circle_test 28 | POSTGRES_PASSWORD: password 29 | 30 | steps: 31 | - checkout 32 | - python/install-packages: 33 | pkg-manager: pip 34 | - run: 35 | name: Run tests 36 | command: pytest 37 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .travis.yaml 2 | .openapi-generator-ignore 3 | README.md 4 | tox.ini 5 | git_push.sh 6 | test-requirements.txt 7 | setup.py 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *,cover 54 | .hypothesis/ 55 | venv/ 56 | .python-version 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | #Ipython Notebook 72 | .ipynb_checkpoints 73 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | export DATABASE_URL="postgresql:///localhost/cfd" 2 | export PORT=80 -------------------------------------------------------------------------------- /.env.db: -------------------------------------------------------------------------------- 1 | POSTGRES_USER=test 2 | POSTGRES_PASSWORD=test 3 | POSTGRES_DB=test-cfd -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | DATABASE_URI=postgresql://test:test@db:5432/test-cfd 2 | PORT=8080 3 | POSTGRES_HOST=db 4 | POSTGRES_PORT=5432 5 | SKIP_DB_TESTS=False -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Automatically request PR reviews from the CPE Team 2 | * @CircleCI-Public/cpeng 3 | -------------------------------------------------------------------------------- /.github/img/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/.github/img/preview.png -------------------------------------------------------------------------------- /.github/workflows/config-variants.yml: -------------------------------------------------------------------------------- 1 | name: test-extended-configs 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | # TODO add an action workflow to ensure every file in .circleci/extended appears in this list 11 | configfile: ["heroku-deploy", "pylint", "test-with-postgresql"] 12 | steps: 13 | - name: checkout 14 | uses: actions/checkout@v2 15 | with: 16 | ref: ${{ github.event.pull_request.head.sha }} 17 | - name: Replace config 18 | run: cp .circleci/extended/${{ matrix.configfile }}.yml .circleci/config.yml 19 | - name: commit-push_branch-test 20 | uses: dsayling/commit-branch-check-action@v0.0.8 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | files: ".circleci/config.yml" 24 | commit-message: "Update circleci config for ${{ matrix.configfile }} from ${{github.event.pull_request.head.ref}}" 25 | dest-branch: ${{github.event.pull_request.head.ref}}-${{ matrix.configfile }} 26 | verify-checks: true 27 | delete-after-checks: true 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | venv/ 48 | .venv/ 49 | .python-version 50 | .pytest_cache 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ 64 | 65 | #Ipython Notebook 66 | .ipynb_checkpoints 67 | -------------------------------------------------------------------------------- /.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | README.md 25 | **requirements.txt 26 | openapi_server/__main__.py 27 | .gitignore 28 | .travis.yml -------------------------------------------------------------------------------- /.openapi-generator/FILES: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | git_push.sh 4 | openapi_server/__init__.py 5 | openapi_server/controllers/__init__.py 6 | openapi_server/controllers/cart_controller.py 7 | openapi_server/controllers/image_controller.py 8 | openapi_server/controllers/menu_controller.py 9 | openapi_server/controllers/security_controller_.py 10 | openapi_server/encoder.py 11 | openapi_server/models/__init__.py 12 | openapi_server/models/base_model_.py 13 | openapi_server/models/error.py 14 | openapi_server/models/inline_object.py 15 | openapi_server/models/inline_response200.py 16 | openapi_server/models/menu_item.py 17 | openapi_server/openapi/openapi.yaml 18 | openapi_server/test/__init__.py 19 | openapi_server/typing_utils.py 20 | openapi_server/util.py 21 | setup.py 22 | tox.ini 23 | -------------------------------------------------------------------------------- /.openapi-generator/VERSION: -------------------------------------------------------------------------------- 1 | 5.0.0-SNAPSHOT -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | errors-only= 3 | 4 | # A comma-separated list of package or module names from where C extensions may 5 | # be loaded. Extensions are loading into the active Python interpreter and may 6 | # run arbitrary code. 7 | extension-pkg-whitelist= 8 | 9 | # Specify a score threshold to be exceeded before program exits with error. 10 | fail-under=10.0 11 | 12 | # Add files or directories to the blacklist. They should be base names, not 13 | # paths. 14 | ignore=CVS 15 | 16 | # Add files or directories matching the regex patterns to the blacklist. The 17 | # regex matches against base names, not paths. 18 | ignore-patterns= 19 | 20 | # Python code to execute, usually for sys.path manipulation such as 21 | # pygtk.require(). 22 | #init-hook= 23 | 24 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 25 | # number of processors available to use. 26 | jobs=1 27 | 28 | # Control the amount of potential inferred values when inferring a single 29 | # object. This can help the performance when dealing with large functions or 30 | # complex, nested conditions. 31 | limit-inference-results=100 32 | 33 | # List of plugins (as comma separated values of python module names) to load, 34 | # usually to register additional checkers. 35 | load-plugins= 36 | 37 | # Pickle collected data for later comparisons. 38 | persistent=yes 39 | 40 | # When enabled, pylint would attempt to guess common misconfiguration and emit 41 | # user-friendly hints instead of false-positive error messages. 42 | suggestion-mode=yes 43 | 44 | # Allow loading of arbitrary C extensions. Extensions are imported into the 45 | # active Python interpreter and may run arbitrary code. 46 | unsafe-load-any-extension=no 47 | 48 | 49 | [MESSAGES CONTROL] 50 | 51 | # Only show warnings with the listed confidence levels. Leave empty to show 52 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 53 | confidence= 54 | 55 | # Disable the message, report, category or checker with the given id(s). You 56 | # can either give multiple identifiers separated by comma (,) or put this 57 | # option multiple times (only on the command line, not in the configuration 58 | # file where it should appear only once). You can also use "--disable=all" to 59 | # disable everything first and then reenable specific checks. For example, if 60 | # you want to run only the similarities checker, you can use "--disable=all 61 | # --enable=similarities". If you want to run only the classes checker, but have 62 | # no Warning level messages displayed, use "--disable=all --enable=classes 63 | # --disable=W". 64 | disable=print-statement, 65 | parameter-unpacking, 66 | unpacking-in-except, 67 | old-raise-syntax, 68 | backtick, 69 | long-suffix, 70 | old-ne-operator, 71 | old-octal-literal, 72 | import-star-module-level, 73 | non-ascii-bytes-literal, 74 | raw-checker-failed, 75 | bad-inline-option, 76 | locally-disabled, 77 | file-ignored, 78 | suppressed-message, 79 | useless-suppression, 80 | deprecated-pragma, 81 | use-symbolic-message-instead, 82 | apply-builtin, 83 | basestring-builtin, 84 | buffer-builtin, 85 | cmp-builtin, 86 | coerce-builtin, 87 | execfile-builtin, 88 | file-builtin, 89 | long-builtin, 90 | raw_input-builtin, 91 | reduce-builtin, 92 | standarderror-builtin, 93 | unicode-builtin, 94 | xrange-builtin, 95 | coerce-method, 96 | delslice-method, 97 | getslice-method, 98 | setslice-method, 99 | no-absolute-import, 100 | old-division, 101 | dict-iter-method, 102 | dict-view-method, 103 | next-method-called, 104 | metaclass-assignment, 105 | indexing-exception, 106 | raising-string, 107 | reload-builtin, 108 | oct-method, 109 | hex-method, 110 | nonzero-method, 111 | cmp-method, 112 | input-builtin, 113 | round-builtin, 114 | intern-builtin, 115 | unichr-builtin, 116 | map-builtin-not-iterating, 117 | zip-builtin-not-iterating, 118 | range-builtin-not-iterating, 119 | filter-builtin-not-iterating, 120 | using-cmp-argument, 121 | eq-without-hash, 122 | div-method, 123 | idiv-method, 124 | rdiv-method, 125 | exception-message-attribute, 126 | invalid-str-codec, 127 | sys-max-int, 128 | bad-python3-import, 129 | deprecated-string-function, 130 | deprecated-str-translate-call, 131 | deprecated-itertools-function, 132 | deprecated-types-field, 133 | next-method-defined, 134 | dict-items-not-iterating, 135 | dict-keys-not-iterating, 136 | dict-values-not-iterating, 137 | deprecated-operator-function, 138 | deprecated-urllib-function, 139 | xreadlines-attribute, 140 | deprecated-sys-function, 141 | exception-escape, 142 | comprehension-escape 143 | 144 | # Enable the message, report, category or checker with the given id(s). You can 145 | # either give multiple identifier separated by comma (,) or put this option 146 | # multiple time (only on the command line, not in the configuration file where 147 | # it should appear only once). See also the "--disable" option for examples. 148 | enable=c-extension-no-member 149 | 150 | 151 | [REPORTS] 152 | 153 | # Python expression which should return a score less than or equal to 10. You 154 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 155 | # which contain the number of messages in each category, as well as 'statement' 156 | # which is the total number of statements analyzed. This score is used by the 157 | # global evaluation report (RP0004). 158 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 159 | 160 | # Template used to display messages. This is a python new-style format string 161 | # used to format the message information. See doc for all details. 162 | #msg-template= 163 | 164 | # Set the output format. Available formats are text, parseable, colorized, json 165 | # and msvs (visual studio). You can also give a reporter class, e.g. 166 | # mypackage.mymodule.MyReporterClass. 167 | output-format=text 168 | 169 | # Tells whether to display a full report or only the messages. 170 | reports=no 171 | 172 | # Activate the evaluation score. 173 | score=yes 174 | 175 | 176 | [REFACTORING] 177 | 178 | # Maximum number of nested blocks for function / method body 179 | max-nested-blocks=5 180 | 181 | # Complete name of functions that never returns. When checking for 182 | # inconsistent-return-statements if a never returning function is called then 183 | # it will be considered as an explicit return statement and no message will be 184 | # printed. 185 | never-returning-functions=sys.exit 186 | 187 | 188 | [SIMILARITIES] 189 | 190 | # Ignore comments when computing similarities. 191 | ignore-comments=yes 192 | 193 | # Ignore docstrings when computing similarities. 194 | ignore-docstrings=yes 195 | 196 | # Ignore imports when computing similarities. 197 | ignore-imports=no 198 | 199 | # Minimum lines number of a similarity. 200 | min-similarity-lines=4 201 | 202 | 203 | [VARIABLES] 204 | 205 | # List of additional names supposed to be defined in builtins. Remember that 206 | # you should avoid defining new builtins when possible. 207 | additional-builtins= 208 | 209 | # Tells whether unused global variables should be treated as a violation. 210 | allow-global-unused-variables=yes 211 | 212 | # List of strings which can identify a callback function by name. A callback 213 | # name must start or end with one of those strings. 214 | callbacks=cb_, 215 | _cb 216 | 217 | # A regular expression matching the name of dummy variables (i.e. expected to 218 | # not be used). 219 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 220 | 221 | # Argument names that match this expression will be ignored. Default to name 222 | # with leading underscore. 223 | ignored-argument-names=_.*|^ignored_|^unused_ 224 | 225 | # Tells whether we should check for unused import in __init__ files. 226 | init-import=no 227 | 228 | # List of qualified module names which can have objects that can redefine 229 | # builtins. 230 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 231 | 232 | 233 | [SPELLING] 234 | 235 | # Limits count of emitted suggestions for spelling mistakes. 236 | max-spelling-suggestions=4 237 | 238 | # Spelling dictionary name. Available dictionaries: none. To make it work, 239 | # install the python-enchant package. 240 | spelling-dict= 241 | 242 | # List of comma separated words that should not be checked. 243 | spelling-ignore-words= 244 | 245 | # A path to a file that contains the private dictionary; one word per line. 246 | spelling-private-dict-file= 247 | 248 | # Tells whether to store unknown words to the private dictionary (see the 249 | # --spelling-private-dict-file option) instead of raising a message. 250 | spelling-store-unknown-words=no 251 | 252 | 253 | [MISCELLANEOUS] 254 | 255 | # List of note tags to take in consideration, separated by a comma. 256 | notes=FIXME, 257 | XXX, 258 | TODO 259 | 260 | # Regular expression of note tags to take in consideration. 261 | #notes-rgx= 262 | 263 | 264 | [TYPECHECK] 265 | 266 | # List of decorators that produce context managers, such as 267 | # contextlib.contextmanager. Add to this list to register other decorators that 268 | # produce valid context managers. 269 | contextmanager-decorators=contextlib.contextmanager 270 | 271 | # List of members which are set dynamically and missed by pylint inference 272 | # system, and so shouldn't trigger E1101 when accessed. Python regular 273 | # expressions are accepted. 274 | generated-members= 275 | 276 | # Tells whether missing members accessed in mixin class should be ignored. A 277 | # mixin class is detected if its name ends with "mixin" (case insensitive). 278 | ignore-mixin-members=yes 279 | 280 | # Tells whether to warn about missing members when the owner of the attribute 281 | # is inferred to be None. 282 | ignore-none=yes 283 | 284 | # This flag controls whether pylint should warn about no-member and similar 285 | # checks whenever an opaque object is returned when inferring. The inference 286 | # can return multiple potential results while evaluating a Python object, but 287 | # some branches might not be evaluated, which results in partial inference. In 288 | # that case, it might be useful to still emit no-member and other checks for 289 | # the rest of the inferred objects. 290 | ignore-on-opaque-inference=yes 291 | 292 | # List of class names for which member attributes should not be checked (useful 293 | # for classes with dynamically set attributes). This supports the use of 294 | # qualified names. 295 | ignored-classes=optparse.Values,thread._local,_thread._local 296 | 297 | # List of module names for which member attributes should not be checked 298 | # (useful for modules/projects where namespaces are manipulated during runtime 299 | # and thus existing member attributes cannot be deduced by static analysis). It 300 | # supports qualified module names, as well as Unix pattern matching. 301 | ignored-modules= 302 | 303 | # Show a hint with possible names when a member name was not found. The aspect 304 | # of finding the hint is based on edit distance. 305 | missing-member-hint=yes 306 | 307 | # The minimum edit distance a name should have in order to be considered a 308 | # similar match for a missing member name. 309 | missing-member-hint-distance=1 310 | 311 | # The total number of similar names that should be taken in consideration when 312 | # showing a hint for a missing member. 313 | missing-member-max-choices=1 314 | 315 | # List of decorators that change the signature of a decorated function. 316 | signature-mutators= 317 | 318 | 319 | [LOGGING] 320 | 321 | # The type of string formatting that logging methods do. `old` means using % 322 | # formatting, `new` is for `{}` formatting. 323 | logging-format-style=old 324 | 325 | # Logging modules to check that the string format arguments are in logging 326 | # function parameter format. 327 | logging-modules=logging 328 | 329 | 330 | [BASIC] 331 | 332 | # Naming style matching correct argument names. 333 | argument-naming-style=snake_case 334 | 335 | # Regular expression matching correct argument names. Overrides argument- 336 | # naming-style. 337 | #argument-rgx= 338 | 339 | # Naming style matching correct attribute names. 340 | attr-naming-style=snake_case 341 | 342 | # Regular expression matching correct attribute names. Overrides attr-naming- 343 | # style. 344 | #attr-rgx= 345 | 346 | # Bad variable names which should always be refused, separated by a comma. 347 | bad-names=foo, 348 | bar, 349 | baz, 350 | toto, 351 | tutu, 352 | tata 353 | 354 | # Bad variable names regexes, separated by a comma. If names match any regex, 355 | # they will always be refused 356 | bad-names-rgxs= 357 | 358 | # Naming style matching correct class attribute names. 359 | class-attribute-naming-style=any 360 | 361 | # Regular expression matching correct class attribute names. Overrides class- 362 | # attribute-naming-style. 363 | #class-attribute-rgx= 364 | 365 | # Naming style matching correct class names. 366 | class-naming-style=PascalCase 367 | 368 | # Regular expression matching correct class names. Overrides class-naming- 369 | # style. 370 | #class-rgx= 371 | 372 | # Naming style matching correct constant names. 373 | const-naming-style=UPPER_CASE 374 | 375 | # Regular expression matching correct constant names. Overrides const-naming- 376 | # style. 377 | #const-rgx= 378 | 379 | # Minimum line length for functions/classes that require docstrings, shorter 380 | # ones are exempt. 381 | docstring-min-length=-1 382 | 383 | # Naming style matching correct function names. 384 | function-naming-style=snake_case 385 | 386 | # Regular expression matching correct function names. Overrides function- 387 | # naming-style. 388 | #function-rgx= 389 | 390 | # Good variable names which should always be accepted, separated by a comma. 391 | good-names=i, 392 | j, 393 | k, 394 | ex, 395 | Run, 396 | _ 397 | 398 | # Good variable names regexes, separated by a comma. If names match any regex, 399 | # they will always be accepted 400 | good-names-rgxs= 401 | 402 | # Include a hint for the correct naming format with invalid-name. 403 | include-naming-hint=no 404 | 405 | # Naming style matching correct inline iteration names. 406 | inlinevar-naming-style=any 407 | 408 | # Regular expression matching correct inline iteration names. Overrides 409 | # inlinevar-naming-style. 410 | #inlinevar-rgx= 411 | 412 | # Naming style matching correct method names. 413 | method-naming-style=snake_case 414 | 415 | # Regular expression matching correct method names. Overrides method-naming- 416 | # style. 417 | #method-rgx= 418 | 419 | # Naming style matching correct module names. 420 | module-naming-style=snake_case 421 | 422 | # Regular expression matching correct module names. Overrides module-naming- 423 | # style. 424 | #module-rgx= 425 | 426 | # Colon-delimited sets of names that determine each other's naming style when 427 | # the name regexes allow several styles. 428 | name-group= 429 | 430 | # Regular expression which should only match function or class names that do 431 | # not require a docstring. 432 | no-docstring-rgx=^_ 433 | 434 | # List of decorators that produce properties, such as abc.abstractproperty. Add 435 | # to this list to register other decorators that produce valid properties. 436 | # These decorators are taken in consideration only for invalid-name. 437 | property-classes=abc.abstractproperty 438 | 439 | # Naming style matching correct variable names. 440 | variable-naming-style=snake_case 441 | 442 | # Regular expression matching correct variable names. Overrides variable- 443 | # naming-style. 444 | #variable-rgx= 445 | 446 | 447 | [STRING] 448 | 449 | # This flag controls whether inconsistent-quotes generates a warning when the 450 | # character used as a quote delimiter is used inconsistently within a module. 451 | check-quote-consistency=no 452 | 453 | # This flag controls whether the implicit-str-concat should generate a warning 454 | # on implicit string concatenation in sequences defined over several lines. 455 | check-str-concat-over-line-jumps=no 456 | 457 | 458 | [FORMAT] 459 | 460 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 461 | expected-line-ending-format= 462 | 463 | # Regexp for a line that is allowed to be longer than the limit. 464 | ignore-long-lines=^\s*(# )??$ 465 | 466 | # Number of spaces of indent required inside a hanging or continued line. 467 | indent-after-paren=4 468 | 469 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 470 | # tab). 471 | indent-string=' ' 472 | 473 | # Maximum number of characters on a single line. 474 | max-line-length=100 475 | 476 | # Maximum number of lines in a module. 477 | max-module-lines=1000 478 | 479 | # Allow the body of a class to be on the same line as the declaration if body 480 | # contains single statement. 481 | single-line-class-stmt=no 482 | 483 | # Allow the body of an if to be on the same line as the test if there is no 484 | # else. 485 | single-line-if-stmt=no 486 | 487 | 488 | [DESIGN] 489 | 490 | # Maximum number of arguments for function / method. 491 | max-args=5 492 | 493 | # Maximum number of attributes for a class (see R0902). 494 | max-attributes=7 495 | 496 | # Maximum number of boolean expressions in an if statement (see R0916). 497 | max-bool-expr=5 498 | 499 | # Maximum number of branch for function / method body. 500 | max-branches=12 501 | 502 | # Maximum number of locals for function / method body. 503 | max-locals=15 504 | 505 | # Maximum number of parents for a class (see R0901). 506 | max-parents=7 507 | 508 | # Maximum number of public methods for a class (see R0904). 509 | max-public-methods=20 510 | 511 | # Maximum number of return / yield for function / method body. 512 | max-returns=6 513 | 514 | # Maximum number of statements in function / method body. 515 | max-statements=50 516 | 517 | # Minimum number of public methods for a class (see R0903). 518 | min-public-methods=2 519 | 520 | 521 | [CLASSES] 522 | 523 | # List of method names used to declare (i.e. assign) instance attributes. 524 | defining-attr-methods=__init__, 525 | __new__, 526 | setUp, 527 | __post_init__ 528 | 529 | # List of member names, which should be excluded from the protected access 530 | # warning. 531 | exclude-protected=_asdict, 532 | _fields, 533 | _replace, 534 | _source, 535 | _make 536 | 537 | # List of valid names for the first argument in a class method. 538 | valid-classmethod-first-arg=cls 539 | 540 | # List of valid names for the first argument in a metaclass class method. 541 | valid-metaclass-classmethod-first-arg=cls 542 | 543 | 544 | [IMPORTS] 545 | 546 | # List of modules that can be imported at any level, not just the top level 547 | # one. 548 | allow-any-import-level= 549 | 550 | # Allow wildcard imports from modules that define __all__. 551 | allow-wildcard-with-all=no 552 | 553 | # Analyse import fallback blocks. This can be used to support both Python 2 and 554 | # 3 compatible code, which means that the block might have code that exists 555 | # only in one or another interpreter, leading to false positives when analysed. 556 | analyse-fallback-blocks=no 557 | 558 | # Deprecated modules which should not be used, separated by a comma. 559 | deprecated-modules=optparse,tkinter.tix 560 | 561 | # Create a graph of external dependencies in the given file (report RP0402 must 562 | # not be disabled). 563 | ext-import-graph= 564 | 565 | # Create a graph of every (i.e. internal and external) dependencies in the 566 | # given file (report RP0402 must not be disabled). 567 | import-graph= 568 | 569 | # Create a graph of internal dependencies in the given file (report RP0402 must 570 | # not be disabled). 571 | int-import-graph= 572 | 573 | # Force import order to recognize a module as part of the standard 574 | # compatibility libraries. 575 | known-standard-library= 576 | 577 | # Force import order to recognize a module as part of a third party library. 578 | known-third-party=enchant 579 | 580 | # Couples of modules and preferred modules, separated by a comma. 581 | preferred-modules= 582 | 583 | 584 | [EXCEPTIONS] 585 | 586 | # Exceptions that will emit a warning when being caught. Defaults to 587 | # "BaseException, Exception". 588 | overgeneral-exceptions=BaseException, 589 | Exception 590 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | RUN apt-get update && apt-get install -y netcat 7 | 8 | COPY run-requirements.txt /usr/src/app/requirements.txt 9 | 10 | RUN pip3 install --no-cache-dir -r requirements.txt 11 | 12 | COPY . /usr/src/app 13 | 14 | # run entrypoint.sh 15 | ENTRYPOINT ["/usr/src/app/entrypoint.sh"] -------------------------------------------------------------------------------- /Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | 6 | RUN apt-get update && apt-get install -y netcat 7 | 8 | COPY requirements.txt /usr/src/app/requirements.txt 9 | COPY run-requirements.txt /usr/src/app/run-requirements.txt 10 | # COPY run-requirements.txt /usr/src/app/requirements.txt 11 | 12 | RUN pip3 install --no-cache-dir -r requirements.txt 13 | 14 | COPY . /usr/src/app 15 | 16 | # run entrypoint.sh 17 | ENTRYPOINT ["/usr/src/app/entrypoint.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/LICENSE -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn openapi_server.__main__:app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Python App - Flask Server 2 | 3 | [![CircleCI Build Status](https://circleci.com/gh/CircleCI-Public/sample-python-cfd.svg?style=shield)](https://circleci.com/gh/CircleCI-Public/sample-python-cfd) [![Software License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/main/LICENSE) 4 | 5 | ## Description 6 | 7 | The sample python flask app here is designed to demonstrate what a typical python CI workflow may look on CircleCI. 8 | 9 | You can see the CI pipelines for this application running [live on CircleCI](https://app.circleci.com/pipelines/github/CircleCI-Public/sample-python-cfd?branch=main). 10 | 11 | In this sample config, we have a single workflow `build-and-test` which will install and cache our required python packages, and then run tests with `pytest`, a common python testing framework. This config makes use of the [Python orb](https://circleci.com/developer/orbs/orb/circleci/python), a package for CircleCI's config language, which makes writing our config shorter, and easier. 12 | 13 | ## Getting Started 14 | 15 | If you would like to copy the [config.yml](https://github.com/CircleCI-public/sample-python-cfd/blob/main/.circleci/config.yml) and adapt it to your project, be sure to read the comments in the config file to ensure it works for your project. For more details, see the [CircleCI configuration reference](https://circleci.com/docs/2.0/configuration-reference/). 16 | 17 | ## Addtional Sample Configuration Files 18 | 19 | Inside the `.circleci` directory, you will find an `extended` directory that extends the configuration beyond the default `.circleci/config.yml`. These configuration files are tested with every pull request to this sample app, so they stay up to date and verified working. 20 | 21 | ### Heroku Deploy 22 | 23 | The `.circleci/extended/heroku-deploy.yml` configuration file extends the default config by adding a job to deploy to heroku via a git push. For more information on how to configure this for your own project, visit the [CircleCI docs](https://circleci.com/docs/2.0/deployment-integrations/#a-simple-example-using-heroku) for more details 24 | 25 | ### Pylint 26 | 27 | The `.circleci/extended/pylint.yml` configuration file extends the default config by adding a step to sample job. The `.pylintrc` in the project directory is configured to fail the pipeline if any errors are present when linting. 28 | 29 | ## About This App 30 | 31 | This sample application is a flask REST server written in python, and utilizes the connexion framework which allows us build and run the service from an [OpenAPI/Swagger specification](https://swagger.io/specification/). 32 | 33 | ### Continuous Food Delivery 34 | 35 | When you start up the service, you can open [this page](http://localhost:8080/CFD/1.0.0/ui/) in your browser to view the available API endpoints. 36 | 37 | ![Swagger UI Screenshot](https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/main/.github/img/preview.png) 38 | 39 | ### Front-End 40 | 41 | CFD(Continuous Food Delivery) is a sample application that relies on a separate UI framework. If you would like to run this project locally with a complete UI, you can use a valid CFD front-end, such as one of the following sample projects: 42 | 43 | | Language | GitHub | Description | 44 | |---|---|---| 45 | | Javascript (Vue.js) | [Link](https://github.com/CircleCI-Public/sample-javascript-cfd) | A Javascript Front-End for CFD | 46 | 47 | ## Run and Test Locally 48 | 49 | If you would like to try this application out locally, you can find runtime instructions below. 50 | 51 | ### Requirements 52 | 53 | Python 3.5.2+ OR Docker 54 | 55 | ### Run Local Server 56 | 57 | To run the server on a Docker container, please execute the following from the root directory: 58 | 59 | ```bash 60 | docker-compose up --build 61 | ``` 62 | 63 | If not using docker, to run the server, please execute the following from the root directory: 64 | 65 | ``` 66 | pip3 install -r run-requirements.txt 67 | python3 -m openapi_server 68 | ``` 69 | 70 | ### Tests 71 | 72 | To launch the unit tests, use pytest: 73 | 74 | ``` 75 | pip3 install -r requirements.txt 76 | pytest 77 | ``` 78 | 79 | If you want to run tests using a live database, use the alternative compose file: 80 | 81 | ``` 82 | docker-compose -f docker-compose-test.yml up --build --exit-code-from web 83 | ``` 84 | 85 | ## Additional Resources 86 | 87 | * [CircleCI Docs](https://circleci.com/docs/) - The official CircleCI Documentation website. 88 | * [CircleCI Configuration Reference](https://circleci.com/docs/2.0/configuration-reference/#section=configuration) - From CircleCI Docs, the configuration reference page is one of the most useful pages we have. 89 | 90 | 91 | ## License 92 | 93 | This repository is licensed under the MIT license. 94 | The license can be found [here](./LICENSE). 95 | 96 | -------------------------------------------------------------------------------- /configure.py: -------------------------------------------------------------------------------- 1 | from openapi_server.__main__ import models, app 2 | 3 | 4 | def create_db(): 5 | app.app.app_context().push() 6 | models.db.drop_all() 7 | models.db.create_all() 8 | models.db.session.commit() 9 | 10 | 11 | def seed_db(): 12 | # db.session.add(item) 13 | # db.session.commit() 14 | pass 15 | 16 | 17 | if __name__ == "__main__": 18 | create_db() 19 | -------------------------------------------------------------------------------- /docker-compose-test.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | web: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile.test 8 | ports: 9 | - 8080:8080 10 | env_file: 11 | - ./.env.dev 12 | entrypoint: pytest 13 | db: 14 | image: postgres:12-alpine 15 | volumes: 16 | - postgres_data:/var/lib/postgresql/data/ 17 | env_file: 18 | - ./.env.db 19 | volumes: 20 | postgres_data: -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | web: 5 | build: . 6 | ports: 7 | - 8080:8080 8 | env_file: 9 | - ./.env.dev 10 | command: gunicorn openapi_server.__main__:app 11 | db: 12 | image: postgres:12-alpine 13 | volumes: 14 | - postgres_data:/var/lib/postgresql/data/ 15 | env_file: 16 | - ./.env.db 17 | volumes: 18 | postgres_data: -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Waiting for postgres..." 4 | 5 | while ! nc -z $POSTGRES_HOST $POSTGRES_PORT; do 6 | sleep 0.1 7 | done 8 | 9 | python configure.py 10 | 11 | exec "$@" -------------------------------------------------------------------------------- /openapi_server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/__init__.py -------------------------------------------------------------------------------- /openapi_server/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | import connexion 5 | 6 | from openapi_server import encoder 7 | from flask_cors import CORS 8 | from openapi_server.database import models 9 | from openapi_server.database import db_seed 10 | 11 | app = connexion.App(__name__, specification_dir="./openapi/") 12 | app.app.json_encoder = encoder.JSONEncoder 13 | # app.app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL", "postgresql:///test-cfd") 14 | app.app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv( 15 | "DATABASE_URI", "sqlite:///:memory:" 16 | ) # can this work if there's no db to connect to? 17 | app.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 18 | app.add_api("openapi.yaml", arguments={"title": "CFD"}, pythonic_params=True) 19 | CORS(app.app) 20 | models.db.init_app(app.app) 21 | 22 | # Seed the database if we're running with an in memory-db 23 | if "memory" in app.app.config.get("SQLALCHEMY_DATABASE_URI"): 24 | app.app.app_context().push() 25 | models.db.create_all() 26 | db_seed.seed() 27 | 28 | 29 | def main(): 30 | app.run(port=8080, debug=True) 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /openapi_server/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/controllers/__init__.py -------------------------------------------------------------------------------- /openapi_server/controllers/cart_controller.py: -------------------------------------------------------------------------------- 1 | import connexion 2 | import six 3 | from sqlalchemy import exc 4 | from sqlalchemy.sql.functions import mode 5 | 6 | from openapi_server.models.error import Error # noqa: E501 7 | from openapi_server.models.menu_item import MenuItem # noqa: E501 8 | from openapi_server import util 9 | 10 | from openapi_server.database import models 11 | from sqlalchemy.exc import SQLAlchemyError, IntegrityError 12 | 13 | 14 | def add_cart_item(): # noqa: E501 15 | """Add a menu item a cart 16 | 17 | Creates a new item in the cart. Duplicates are allowed # noqa: E501 18 | 19 | :param menu_item: Item to add to the cart 20 | :type menu_item: dict | bytes 21 | 22 | :rtype: None 23 | """ 24 | if connexion.request.is_json: 25 | menu_item = MenuItem.from_dict(connexion.request.get_json()) # noqa: E501 26 | try: 27 | models.Cart.add_item(connexion.request.remote_addr, menu_item) 28 | # models.Cart.add_item(connexion.request.host.split(':')[0], menu_item) 29 | except (SQLAlchemyError, TypeError, AttributeError): 30 | models.db.session.rollback() 31 | return Error(400), 400 32 | 33 | 34 | def delete_cart_item(item_id): # noqa: E501 35 | """Remove item from cart 36 | 37 | The item must be in the cart. If multiple of same item, call this twice # noqa: E501 38 | 39 | :param item_id: The menu item to delete from cart 40 | :type item_id: int 41 | 42 | :rtype: None 43 | """ 44 | # this is by no means efficient 45 | try: 46 | models.Cart.delete_item_by_id(connexion.request.remote_addr, item_id) 47 | except (SQLAlchemyError, TypeError): 48 | models.db.session.rollback() 49 | return Error(403), 403 # cart is already devoid of this item 50 | 51 | 52 | def list_cart(limit=None): # noqa: E501 53 | """List all cart items 54 | 55 | # noqa: E501 56 | 57 | :param limit: How many items to return at one time (max 100) 58 | :type limit: int 59 | 60 | :rtype: List[MenuItem] 61 | """ 62 | if cart := models.Cart.query_by_host(connexion.request.remote_addr): 63 | return [_.serialize() for _ in cart.items] 64 | else: 65 | return [] 66 | -------------------------------------------------------------------------------- /openapi_server/controllers/image_controller.py: -------------------------------------------------------------------------------- 1 | import connexion 2 | 3 | from openapi_server.models.error import Error # noqa: E501 4 | from openapi_server.models.inline_response200 import InlineResponse200 # noqa: E501 5 | from openapi_server.database import models 6 | from sqlalchemy.exc import SQLAlchemyError 7 | 8 | 9 | def add_image(): # noqa: E501 10 | """Add an image to the restaraunt 11 | 12 | Creates an image. Duplicates are allowed. Returns the image id # noqa: E501 13 | 14 | :param file_name: 15 | :type file_name: str 16 | 17 | :rtype: InlineResponse200 18 | """ 19 | uploaded_file = connexion.request.files["fileName"] 20 | # save the file to the path and then save the path to the 'db' 21 | 22 | try: 23 | image = models.Image.add(uploaded_file.read()) 24 | return InlineResponse200(image_id=image.id) 25 | except (SQLAlchemyError, TypeError): 26 | models.db.session.rollback() 27 | return Error(400), 400 28 | 29 | 30 | def delete_image(image_id): # noqa: E501 31 | """Remove image 32 | 33 | The imageId must exist # noqa: E501 34 | 35 | :param image_id: The imageId to delete 36 | :type image_id: int 37 | 38 | :rtype: None 39 | """ 40 | try: 41 | models.Image.delete_image(image_id) 42 | except (SQLAlchemyError, TypeError): 43 | models.db.session.rollback() 44 | return Error(400), 400 45 | 46 | 47 | def get_image(image_id): # noqa: E501 48 | """Get image 49 | 50 | Returns the image as image/png # noqa: E501 51 | 52 | :param image_id: The imageId of the image to retrieve 53 | :type image_id: int 54 | 55 | :rtype: file 56 | """ 57 | if not (image := models.Image.get_image(image_id)): 58 | return Error(404, "image not found"), 404 59 | return image.data 60 | -------------------------------------------------------------------------------- /openapi_server/controllers/menu_controller.py: -------------------------------------------------------------------------------- 1 | import connexion 2 | 3 | from openapi_server.models.error import Error # noqa: E501 4 | from openapi_server.models.menu_item import MenuItem # noqa: E501 5 | from openapi_server.database import models 6 | from sqlalchemy.exc import SQLAlchemyError 7 | 8 | 9 | def add_menu_item(): # noqa: E501 10 | """Create a menu item 11 | 12 | Creates a new item in the menu. Duplicates are allowed # noqa: E501 13 | 14 | :param menu_item: Item to add to the store 15 | :type menu_item: dict | bytes 16 | 17 | :rtype: None 18 | """ 19 | if connexion.request.is_json: 20 | try: 21 | menu_item = MenuItem.from_dict(connexion.request.get_json()) # noqa: E501 22 | return models.MenuItem.add(menu_item).serialize() 23 | except (SQLAlchemyError, TypeError): 24 | models.db.session.rollback() 25 | return Error(400), 400 26 | else: 27 | return Error(400), 400 28 | 29 | 30 | def list_menu(limit=None): # noqa: E501 31 | """List all menu items 32 | 33 | # noqa: E501 34 | 35 | :param limit: How many items to return at one time (max 100) 36 | :type limit: int 37 | 38 | :rtype: List[MenuItem] 39 | """ 40 | # todo setup limit by using paginate 41 | values = [_.serialize() for _ in models.MenuItem.query_all() if _] 42 | return values 43 | 44 | 45 | def show_menu_item_by_id(item_id): # noqa: E501 46 | """Info for a specific menu item 47 | 48 | # noqa: E501 49 | 50 | :param item_id: The id of the menu item to retrieve 51 | :type item_id: str 52 | 53 | :rtype: MenuItem 54 | """ 55 | if (item := models.MenuItem.query_by_id(int(item_id))) : 56 | return item.serialize() 57 | else: 58 | return Error(400), 400 59 | -------------------------------------------------------------------------------- /openapi_server/controllers/security_controller_.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | -------------------------------------------------------------------------------- /openapi_server/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/__init__.py -------------------------------------------------------------------------------- /openapi_server/database/db_seed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods and objects to seed the db initially. Assumes tables have been created. 3 | """ 4 | 5 | import os 6 | from openapi_server.models import MenuItem 7 | from openapi_server.database import models 8 | 9 | IMAGES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "images") 10 | 11 | menu_start_raw = [ 12 | { 13 | "description": "Fresh from the tap", 14 | "id": 0, 15 | "imageId": "water", 16 | "name": "Water", 17 | "price": 1.99, 18 | }, 19 | { 20 | "description": "Chicken Wrap - Sandwich", 21 | "id": 1, 22 | "imageId": "wrap", 23 | "name": "Chicken Wrap", 24 | "price": 14.99, 25 | }, 26 | { 27 | "description": "A slow cooked stew", 28 | "id": 2, 29 | "imageId": "stew", 30 | "name": "Stew", 31 | "price": 12.99, 32 | }, 33 | { 34 | "description": "It looks good in the menu picture", 35 | "id": 3, 36 | "imageId": "soup", 37 | "name": "Tomato Soup", 38 | "price": 4.99, 39 | }, 40 | { 41 | "description": "A green salad", 42 | "id": 4, 43 | "imageId": "salad", 44 | "name": "Salad", 45 | "price": 4.99, 46 | }, 47 | { 48 | "description": "A single slice of pizza", 49 | "id": 5, 50 | "imageId": "pizza", 51 | "name": "Slice of pizza", 52 | "price": 2.99, 53 | }, 54 | ] 55 | 56 | 57 | def _prepopulate_images(): 58 | for f in os.listdir(IMAGES_PATH): 59 | if f.endswith(".jpg"): 60 | name = f.split(".")[0] 61 | ipath = os.path.join(IMAGES_PATH, f) 62 | with open(ipath, "rb") as f2: 63 | raw = f2.read() 64 | image = models.Image.add(raw) 65 | # super inefficient 66 | for mi in menu_start_raw: 67 | if mi.get("imageId") == name: 68 | mi.update({"imageId": image.id}) 69 | 70 | 71 | def _prepopulate_menu(): 72 | for item in menu_start_raw: 73 | menu_item = MenuItem.from_dict(item) # noqa: E501 74 | models.MenuItem.add(menu_item) 75 | 76 | 77 | def seed(): 78 | _prepopulate_images() 79 | _prepopulate_menu() 80 | -------------------------------------------------------------------------------- /openapi_server/database/images/pizza.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/images/pizza.jpg -------------------------------------------------------------------------------- /openapi_server/database/images/readme.md: -------------------------------------------------------------------------------- 1 | # Image Credits 2 | 3 | All images are sourced from pixabay.com -------------------------------------------------------------------------------- /openapi_server/database/images/salad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/images/salad.jpg -------------------------------------------------------------------------------- /openapi_server/database/images/soup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/images/soup.jpg -------------------------------------------------------------------------------- /openapi_server/database/images/stew.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/images/stew.jpg -------------------------------------------------------------------------------- /openapi_server/database/images/water.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/images/water.jpg -------------------------------------------------------------------------------- /openapi_server/database/images/wrap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCI-Public/sample-python-cfd/e39680ba51b8f8e7822541bbfce34c0c6074da6b/openapi_server/database/images/wrap.jpg -------------------------------------------------------------------------------- /openapi_server/database/models.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | from sqlalchemy.exc import DataError 3 | 4 | 5 | db = SQLAlchemy() 6 | 7 | # helper method for easy mocking 8 | def _commit_item(item): 9 | db.session.add(item) 10 | db.session.commit() 11 | 12 | 13 | class MenuItem(db.Model): 14 | # definitely messes this up, the id should not be specified in the request 15 | id = db.Column(db.Integer, primary_key=True) 16 | description = db.Column(db.String()) 17 | name = db.Column(db.String()) 18 | price = db.Column(db.Float()) 19 | # image_id = db.Column(db.Integer) # this is probably broken 20 | image_id = db.Column(db.Integer, db.ForeignKey("image.id")) 21 | cart_id = db.Column(db.String, db.ForeignKey("cart.host"), nullable=True) 22 | 23 | def __init__( 24 | self, 25 | description, 26 | name, 27 | price, 28 | image_id, 29 | cart_id=None, 30 | ): 31 | self.description = description 32 | self.name = name 33 | self.price = price 34 | self.image_id = image_id # this should probably be anohter reference to a model 35 | self.cart_id = cart_id 36 | 37 | def __repr__(self): 38 | return "".format(self.id) 39 | 40 | # something like marshmallow would be a good addition if we were planning to scale this, but 41 | # there's just one model 42 | def serialize(self): 43 | return { 44 | "description": self.description, 45 | "id": self.id, 46 | "imageId": self.image_id, 47 | "name": self.name, 48 | "price": self.price, 49 | } 50 | 51 | @classmethod 52 | def add(cls, menu_item): 53 | item = cls( 54 | menu_item.description, menu_item.name, menu_item.price, menu_item.image_id 55 | ) 56 | _commit_item(item) 57 | return item 58 | 59 | @classmethod 60 | def query_all(cls): 61 | return cls.query.all() 62 | 63 | @classmethod 64 | def query_by_id(cls, item_id): 65 | return cls.query.filter(MenuItem.id == item_id).first() 66 | 67 | 68 | class Cart(db.Model): 69 | host = db.Column(db.String, primary_key=True) 70 | items = db.relationship("MenuItem") 71 | 72 | def __init__(self, host): 73 | self.host = host 74 | 75 | def __repr__(self): 76 | return "".format(self.host) 77 | 78 | @classmethod 79 | def add_item(cls, host, menu_item): 80 | if len(Cart.query.filter(Cart.host == host).all()) < 1: 81 | new_cart = cls(host) 82 | db.session.add(new_cart) 83 | db_item = MenuItem.query.filter(MenuItem.id == menu_item.id).first() 84 | db_item.cart_id = host 85 | db.session.commit() 86 | 87 | @classmethod 88 | def query_by_host(cls, host): 89 | return cls.query.filter(Cart.host == host).first() 90 | 91 | @classmethod 92 | def delete_item_by_id(cls, host, item_id): 93 | cart = cls.query_by_host(host) 94 | for item in cart.items: 95 | if item.id == item_id: 96 | cart.items.remove(item) 97 | break 98 | db.session.commit() 99 | 100 | 101 | class Image(db.Model): 102 | id = db.Column(db.Integer, primary_key=True) 103 | data = db.Column(db.LargeBinary) 104 | 105 | def __init__(self, data): 106 | self.data = data 107 | 108 | def __repr__(self): 109 | return "".format(self.id) 110 | 111 | @classmethod 112 | def add(cls, raw_data): 113 | image = cls(raw_data) 114 | _commit_item(image) 115 | return image 116 | 117 | @classmethod 118 | def delete_image(cls, image_id): 119 | if not (image := cls.get_image(image_id)): 120 | raise DataError 121 | db.session.delete(image) 122 | db.session.commit() 123 | 124 | @classmethod 125 | def get_image(cls, image_id): 126 | return cls.query.filter(Image.id == image_id).first() 127 | -------------------------------------------------------------------------------- /openapi_server/encoder.py: -------------------------------------------------------------------------------- 1 | from connexion.apps.flask_app import FlaskJSONEncoder 2 | import six 3 | 4 | from openapi_server.models.base_model_ import Model 5 | 6 | 7 | class JSONEncoder(FlaskJSONEncoder): 8 | include_nulls = False 9 | 10 | def default(self, o): 11 | if isinstance(o, Model): 12 | dikt = {} 13 | for attr, _ in six.iteritems(o.openapi_types): 14 | value = getattr(o, attr) 15 | if value is None and not self.include_nulls: 16 | continue 17 | attr = o.attribute_map[attr] 18 | dikt[attr] = value 19 | return dikt 20 | return FlaskJSONEncoder.default(self, o) 21 | -------------------------------------------------------------------------------- /openapi_server/models/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # flake8: noqa 4 | from __future__ import absolute_import 5 | 6 | # import models into model package 7 | from openapi_server.models.error import Error 8 | from openapi_server.models.inline_object import InlineObject 9 | from openapi_server.models.inline_response200 import InlineResponse200 10 | from openapi_server.models.menu_item import MenuItem 11 | -------------------------------------------------------------------------------- /openapi_server/models/base_model_.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | import six 4 | import typing 5 | 6 | from openapi_server import util 7 | 8 | T = typing.TypeVar("T") 9 | 10 | 11 | class Model(object): 12 | # openapiTypes: The key is attribute name and the 13 | # value is attribute type. 14 | openapi_types = {} 15 | 16 | # attributeMap: The key is attribute name and the 17 | # value is json key in definition. 18 | attribute_map = {} 19 | 20 | @classmethod 21 | def from_dict(cls: typing.Type[T], dikt) -> T: 22 | """Returns the dict as a model""" 23 | return util.deserialize_model(dikt, cls) 24 | 25 | def to_dict(self): 26 | """Returns the model properties as a dict 27 | 28 | :rtype: dict 29 | """ 30 | result = {} 31 | 32 | for attr, _ in six.iteritems(self.openapi_types): 33 | value = getattr(self, attr) 34 | if isinstance(value, list): 35 | result[attr] = list( 36 | map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value) 37 | ) 38 | elif hasattr(value, "to_dict"): 39 | result[attr] = value.to_dict() 40 | elif isinstance(value, dict): 41 | result[attr] = dict( 42 | map( 43 | lambda item: (item[0], item[1].to_dict()) 44 | if hasattr(item[1], "to_dict") 45 | else item, 46 | value.items(), 47 | ) 48 | ) 49 | else: 50 | result[attr] = value 51 | 52 | return result 53 | 54 | def to_str(self): 55 | """Returns the string representation of the model 56 | 57 | :rtype: str 58 | """ 59 | return pprint.pformat(self.to_dict()) 60 | 61 | def __repr__(self): 62 | """For `print` and `pprint`""" 63 | return self.to_str() 64 | 65 | def __eq__(self, other): 66 | """Returns true if both objects are equal""" 67 | return self.__dict__ == other.__dict__ 68 | 69 | def __ne__(self, other): 70 | """Returns true if both objects are not equal""" 71 | return not self == other 72 | -------------------------------------------------------------------------------- /openapi_server/models/cart.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | from datetime import date, datetime # noqa: F401 5 | 6 | from typing import List, Dict # noqa: F401 7 | 8 | from openapi_server.models.base_model_ import Model 9 | from openapi_server.models.menu_item import MenuItem 10 | from openapi_server import util 11 | 12 | from openapi_server.models.menu_item import MenuItem # noqa: E501 13 | 14 | 15 | class Cart(Model): 16 | """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 17 | 18 | Do not edit the class manually. 19 | """ 20 | 21 | def __init__(self, menu_items=None): # noqa: E501 22 | """Cart - a model defined in OpenAPI 23 | 24 | :param menu_items: The menu_items of this Cart. # noqa: E501 25 | :type menu_items: List[MenuItem] 26 | """ 27 | self.openapi_types = {"menu_items": List[MenuItem]} 28 | 29 | self.attribute_map = {"menu_items": "menuItems"} 30 | 31 | self._menu_items = menu_items 32 | 33 | @classmethod 34 | def from_dict(cls, dikt) -> "Cart": 35 | """Returns the dict as a model 36 | 37 | :param dikt: A dict. 38 | :type: dict 39 | :return: The Cart of this Cart. # noqa: E501 40 | :rtype: Cart 41 | """ 42 | return util.deserialize_model(dikt, cls) 43 | 44 | @property 45 | def menu_items(self): 46 | """Gets the menu_items of this Cart. 47 | 48 | 49 | :return: The menu_items of this Cart. 50 | :rtype: List[MenuItem] 51 | """ 52 | return self._menu_items 53 | 54 | @menu_items.setter 55 | def menu_items(self, menu_items): 56 | """Sets the menu_items of this Cart. 57 | 58 | 59 | :param menu_items: The menu_items of this Cart. 60 | :type menu_items: List[MenuItem] 61 | """ 62 | if menu_items is None: 63 | raise ValueError( 64 | "Invalid value for `menu_items`, must not be `None`" 65 | ) # noqa: E501 66 | 67 | self._menu_items = menu_items 68 | -------------------------------------------------------------------------------- /openapi_server/models/error.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | from datetime import date, datetime # noqa: F401 5 | 6 | from typing import List, Dict # noqa: F401 7 | 8 | from openapi_server.models.base_model_ import Model 9 | from openapi_server import util 10 | 11 | 12 | class Error(Model): 13 | """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 14 | 15 | Do not edit the class manually. 16 | """ 17 | 18 | def __init__(self, code=None, message=None): # noqa: E501 19 | """Error - a model defined in OpenAPI 20 | 21 | :param code: The code of this Error. # noqa: E501 22 | :type code: int 23 | :param message: The message of this Error. # noqa: E501 24 | :type message: str 25 | """ 26 | self.openapi_types = {"code": int, "message": str} 27 | 28 | self.attribute_map = {"code": "code", "message": "message"} 29 | 30 | self._code = code 31 | self._message = message 32 | 33 | @classmethod 34 | def from_dict(cls, dikt) -> "Error": 35 | """Returns the dict as a model 36 | 37 | :param dikt: A dict. 38 | :type: dict 39 | :return: The Error of this Error. # noqa: E501 40 | :rtype: Error 41 | """ 42 | return util.deserialize_model(dikt, cls) 43 | 44 | @property 45 | def code(self): 46 | """Gets the code of this Error. 47 | 48 | 49 | :return: The code of this Error. 50 | :rtype: int 51 | """ 52 | return self._code 53 | 54 | @code.setter 55 | def code(self, code): 56 | """Sets the code of this Error. 57 | 58 | 59 | :param code: The code of this Error. 60 | :type code: int 61 | """ 62 | if code is None: 63 | raise ValueError( 64 | "Invalid value for `code`, must not be `None`" 65 | ) # noqa: E501 66 | 67 | self._code = code 68 | 69 | @property 70 | def message(self): 71 | """Gets the message of this Error. 72 | 73 | 74 | :return: The message of this Error. 75 | :rtype: str 76 | """ 77 | return self._message 78 | 79 | @message.setter 80 | def message(self, message): 81 | """Sets the message of this Error. 82 | 83 | 84 | :param message: The message of this Error. 85 | :type message: str 86 | """ 87 | if message is None: 88 | raise ValueError( 89 | "Invalid value for `message`, must not be `None`" 90 | ) # noqa: E501 91 | 92 | self._message = message 93 | -------------------------------------------------------------------------------- /openapi_server/models/inline_object.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | from datetime import date, datetime # noqa: F401 5 | 6 | from typing import List, Dict # noqa: F401 7 | 8 | from openapi_server.models.base_model_ import Model 9 | from openapi_server import util 10 | 11 | 12 | class InlineObject(Model): 13 | """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 14 | 15 | Do not edit the class manually. 16 | """ 17 | 18 | def __init__(self, file_name=None): # noqa: E501 19 | """InlineObject - a model defined in OpenAPI 20 | 21 | :param file_name: The file_name of this InlineObject. # noqa: E501 22 | :type file_name: file 23 | """ 24 | self.openapi_types = {"file_name": file} # pylint: disable=undefined-variable 25 | 26 | self.attribute_map = {"file_name": "fileName"} 27 | 28 | self._file_name = file_name 29 | 30 | @classmethod 31 | def from_dict(cls, dikt) -> "InlineObject": 32 | """Returns the dict as a model 33 | 34 | :param dikt: A dict. 35 | :type: dict 36 | :return: The inline_object of this InlineObject. # noqa: E501 37 | :rtype: InlineObject 38 | """ 39 | return util.deserialize_model(dikt, cls) 40 | 41 | @property 42 | def file_name(self): 43 | """Gets the file_name of this InlineObject. 44 | 45 | 46 | :return: The file_name of this InlineObject. 47 | :rtype: file 48 | """ 49 | return self._file_name 50 | 51 | @file_name.setter 52 | def file_name(self, file_name): 53 | """Sets the file_name of this InlineObject. 54 | 55 | 56 | :param file_name: The file_name of this InlineObject. 57 | :type file_name: file 58 | """ 59 | 60 | self._file_name = file_name 61 | -------------------------------------------------------------------------------- /openapi_server/models/inline_response200.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | from datetime import date, datetime # noqa: F401 5 | 6 | from typing import List, Dict # noqa: F401 7 | 8 | from openapi_server.models.base_model_ import Model 9 | from openapi_server import util 10 | 11 | 12 | class InlineResponse200(Model): 13 | """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 14 | 15 | Do not edit the class manually. 16 | """ 17 | 18 | def __init__(self, image_id=None): # noqa: E501 19 | """InlineResponse200 - a model defined in OpenAPI 20 | 21 | :param image_id: The image_id of this InlineResponse200. # noqa: E501 22 | :type image_id: int 23 | """ 24 | self.openapi_types = {"image_id": int} 25 | 26 | self.attribute_map = {"image_id": "imageId"} 27 | 28 | self._image_id = image_id 29 | 30 | @classmethod 31 | def from_dict(cls, dikt) -> "InlineResponse200": 32 | """Returns the dict as a model 33 | 34 | :param dikt: A dict. 35 | :type: dict 36 | :return: The inline_response_200 of this InlineResponse200. # noqa: E501 37 | :rtype: InlineResponse200 38 | """ 39 | return util.deserialize_model(dikt, cls) 40 | 41 | @property 42 | def image_id(self): 43 | """Gets the image_id of this InlineResponse200. 44 | 45 | 46 | :return: The image_id of this InlineResponse200. 47 | :rtype: int 48 | """ 49 | return self._image_id 50 | 51 | @image_id.setter 52 | def image_id(self, image_id): 53 | """Sets the image_id of this InlineResponse200. 54 | 55 | 56 | :param image_id: The image_id of this InlineResponse200. 57 | :type image_id: int 58 | """ 59 | 60 | self._image_id = image_id 61 | -------------------------------------------------------------------------------- /openapi_server/models/menu_item.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | from datetime import date, datetime # noqa: F401 5 | 6 | from typing import List, Dict # noqa: F401 7 | 8 | from openapi_server.models.base_model_ import Model 9 | from openapi_server import util 10 | 11 | 12 | class MenuItem(Model): 13 | """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 14 | 15 | Do not edit the class manually. 16 | """ 17 | 18 | def __init__( 19 | self, id=None, description=None, name=None, price=None, image_id=None 20 | ): # noqa: E501 21 | """MenuItem - a model defined in OpenAPI 22 | 23 | :param id: The id of this MenuItem. # noqa: E501 24 | :type id: int 25 | :param description: The description of this MenuItem. # noqa: E501 26 | :type description: str 27 | :param name: The name of this MenuItem. # noqa: E501 28 | :type name: str 29 | :param price: The price of this MenuItem. # noqa: E501 30 | :type price: float 31 | :param image_id: The image_id of this MenuItem. # noqa: E501 32 | :type image_id: int 33 | """ 34 | self.openapi_types = { 35 | "id": int, 36 | "description": str, 37 | "name": str, 38 | "price": float, 39 | "image_id": int, 40 | } 41 | 42 | self.attribute_map = { 43 | "id": "id", 44 | "description": "description", 45 | "name": "name", 46 | "price": "price", 47 | "image_id": "imageId", 48 | } 49 | 50 | self._id = id 51 | self._description = description 52 | self._name = name 53 | self._price = price 54 | self._image_id = image_id 55 | 56 | @classmethod 57 | def from_dict(cls, dikt) -> "MenuItem": 58 | """Returns the dict as a model 59 | 60 | :param dikt: A dict. 61 | :type: dict 62 | :return: The MenuItem of this MenuItem. # noqa: E501 63 | :rtype: MenuItem 64 | """ 65 | return util.deserialize_model(dikt, cls) 66 | 67 | @property 68 | def id(self): 69 | """Gets the id of this MenuItem. 70 | 71 | 72 | :return: The id of this MenuItem. 73 | :rtype: int 74 | """ 75 | return self._id 76 | 77 | @id.setter 78 | def id(self, id): 79 | """Sets the id of this MenuItem. 80 | 81 | 82 | :param id: The id of this MenuItem. 83 | :type id: int 84 | """ 85 | if id is None: 86 | raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501 87 | 88 | self._id = id 89 | 90 | @property 91 | def description(self): 92 | """Gets the description of this MenuItem. 93 | 94 | 95 | :return: The description of this MenuItem. 96 | :rtype: str 97 | """ 98 | return self._description 99 | 100 | @description.setter 101 | def description(self, description): 102 | """Sets the description of this MenuItem. 103 | 104 | 105 | :param description: The description of this MenuItem. 106 | :type description: str 107 | """ 108 | if description is None: 109 | raise ValueError( 110 | "Invalid value for `description`, must not be `None`" 111 | ) # noqa: E501 112 | 113 | self._description = description 114 | 115 | @property 116 | def name(self): 117 | """Gets the name of this MenuItem. 118 | 119 | 120 | :return: The name of this MenuItem. 121 | :rtype: str 122 | """ 123 | return self._name 124 | 125 | @name.setter 126 | def name(self, name): 127 | """Sets the name of this MenuItem. 128 | 129 | 130 | :param name: The name of this MenuItem. 131 | :type name: str 132 | """ 133 | if name is None: 134 | raise ValueError( 135 | "Invalid value for `name`, must not be `None`" 136 | ) # noqa: E501 137 | 138 | self._name = name 139 | 140 | @property 141 | def price(self): 142 | """Gets the price of this MenuItem. 143 | 144 | 145 | :return: The price of this MenuItem. 146 | :rtype: float 147 | """ 148 | return self._price 149 | 150 | @price.setter 151 | def price(self, price): 152 | """Sets the price of this MenuItem. 153 | 154 | 155 | :param price: The price of this MenuItem. 156 | :type price: float 157 | """ 158 | if price is None: 159 | raise ValueError( 160 | "Invalid value for `price`, must not be `None`" 161 | ) # noqa: E501 162 | 163 | self._price = price 164 | 165 | @property 166 | def image_id(self): 167 | """Gets the image_id of this MenuItem. 168 | 169 | URL to an image of the menu item. This should be the image from the /image endpoint # noqa: E501 170 | 171 | :return: The image_id of this MenuItem. 172 | :rtype: int 173 | """ 174 | return self._image_id 175 | 176 | @image_id.setter 177 | def image_id(self, image_id): 178 | """Sets the image_id of this MenuItem. 179 | 180 | URL to an image of the menu item. This should be the image from the /image endpoint # noqa: E501 181 | 182 | :param image_id: The image_id of this MenuItem. 183 | :type image_id: int 184 | """ 185 | if image_id is None: 186 | raise ValueError( 187 | "Invalid value for `image_id`, must not be `None`" 188 | ) # noqa: E501 189 | 190 | self._image_id = image_id 191 | -------------------------------------------------------------------------------- /openapi_server/openapi/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | license: 4 | name: MIT 5 | title: Continous Food Delievery 6 | version: 1.0.0 7 | servers: 8 | - description: Local 9 | url: http://localhost:8080/CFD/1.0.0 10 | paths: 11 | /cart: 12 | get: 13 | operationId: list_cart 14 | parameters: 15 | - description: How many items to return at one time (max 100) 16 | explode: true 17 | in: query 18 | name: limit 19 | required: false 20 | schema: 21 | format: int32 22 | type: integer 23 | style: form 24 | responses: 25 | "200": 26 | content: 27 | application/json: 28 | schema: 29 | $ref: '#/components/schemas/Cart' 30 | description: A paged array of cart items 31 | headers: 32 | x-next: 33 | description: A link to the next page of responses 34 | explode: false 35 | schema: 36 | type: string 37 | style: simple 38 | default: 39 | content: 40 | application/json: 41 | schema: 42 | $ref: '#/components/schemas/Error' 43 | description: unexpected error 44 | summary: List all cart items 45 | tags: 46 | - cart 47 | x-openapi-router-controller: openapi_server.controllers.cart_controller 48 | post: 49 | description: Creates a new item in the cart. Duplicates are allowed 50 | operationId: add_cart_item 51 | requestBody: 52 | content: 53 | application/json: 54 | schema: 55 | $ref: '#/components/schemas/MenuItem' 56 | description: Item to add to the cart 57 | required: true 58 | responses: 59 | "201": 60 | description: Null response 61 | default: 62 | content: 63 | application/json: 64 | schema: 65 | $ref: '#/components/schemas/Error' 66 | description: unexpected error 67 | summary: Add a menu item a cart 68 | tags: 69 | - cart 70 | x-openapi-router-controller: openapi_server.controllers.cart_controller 71 | /cart/{itemId}: 72 | delete: 73 | description: | 74 | The item must be in the cart. If multiple of same item, call this twice 75 | operationId: delete_cart_item 76 | parameters: 77 | - description: The menu item to delete from cart 78 | explode: false 79 | in: path 80 | name: itemId 81 | required: true 82 | schema: 83 | format: int32 84 | type: integer 85 | style: simple 86 | responses: 87 | "201": 88 | description: Null response 89 | default: 90 | content: 91 | application/json: 92 | schema: 93 | $ref: '#/components/schemas/Error' 94 | description: unexpected error 95 | summary: Remove item from cart 96 | tags: 97 | - cart 98 | x-openapi-router-controller: openapi_server.controllers.cart_controller 99 | /image: 100 | post: 101 | description: Creates an image. Duplicates are allowed. Returns the image id 102 | operationId: add_image 103 | requestBody: 104 | $ref: '#/components/requestBodies/inline_object' 105 | content: 106 | multipart/form-data: 107 | schema: 108 | properties: 109 | fileName: 110 | format: binary 111 | type: string 112 | type: object 113 | responses: 114 | "200": 115 | content: 116 | application/json: 117 | schema: 118 | $ref: '#/components/schemas/inline_response_200' 119 | description: OK 120 | summary: Add an image to the restaraunt 121 | tags: 122 | - image 123 | x-openapi-router-controller: openapi_server.controllers.image_controller 124 | /image/{imageId}: 125 | delete: 126 | description: | 127 | The imageId must exist 128 | operationId: delete_image 129 | parameters: 130 | - description: The imageId to delete 131 | explode: false 132 | in: path 133 | name: imageId 134 | required: true 135 | schema: 136 | format: int32 137 | type: integer 138 | style: simple 139 | responses: 140 | "201": 141 | description: Null response 142 | default: 143 | content: 144 | application/json: 145 | schema: 146 | $ref: '#/components/schemas/Error' 147 | description: unexpected error 148 | summary: Remove image 149 | tags: 150 | - image 151 | x-openapi-router-controller: openapi_server.controllers.image_controller 152 | get: 153 | description: | 154 | Returns the image as image/png 155 | operationId: get_image 156 | parameters: 157 | - description: The imageId of the image to retrieve 158 | explode: false 159 | in: path 160 | name: imageId 161 | required: true 162 | schema: 163 | format: int32 164 | type: integer 165 | style: simple 166 | responses: 167 | "200": 168 | content: 169 | image/png: 170 | schema: 171 | format: binary 172 | type: string 173 | description: image 174 | summary: Get image 175 | tags: 176 | - image 177 | x-openapi-router-controller: openapi_server.controllers.image_controller 178 | /menu: 179 | get: 180 | operationId: list_menu 181 | parameters: 182 | - description: How many items to return at one time (max 100) 183 | explode: true 184 | in: query 185 | name: limit 186 | required: false 187 | schema: 188 | format: int32 189 | type: integer 190 | style: form 191 | responses: 192 | "200": 193 | content: 194 | application/json: 195 | schema: 196 | $ref: '#/components/schemas/Menu' 197 | description: A paged array of menu items 198 | headers: 199 | x-next: 200 | description: A link to the next page of responses 201 | explode: false 202 | schema: 203 | type: string 204 | style: simple 205 | default: 206 | content: 207 | application/json: 208 | schema: 209 | $ref: '#/components/schemas/Error' 210 | description: unexpected error 211 | summary: List all menu items 212 | tags: 213 | - menu 214 | x-openapi-router-controller: openapi_server.controllers.menu_controller 215 | post: 216 | description: Creates a new item in the menu. Duplicates are allowed 217 | operationId: add_menu_item 218 | requestBody: 219 | content: 220 | application/json: 221 | schema: 222 | $ref: '#/components/schemas/MenuItem' 223 | description: Item to add to the store 224 | required: true 225 | responses: 226 | "201": 227 | description: Null response 228 | default: 229 | content: 230 | application/json: 231 | schema: 232 | $ref: '#/components/schemas/Error' 233 | description: unexpected error 234 | summary: Create a menu item 235 | tags: 236 | - menu 237 | x-openapi-router-controller: openapi_server.controllers.menu_controller 238 | /menu/{itemId}: 239 | get: 240 | operationId: show_menu_item_by_id 241 | parameters: 242 | - description: The id of the menu item to retrieve 243 | explode: false 244 | in: path 245 | name: itemId 246 | required: true 247 | schema: 248 | type: string 249 | style: simple 250 | responses: 251 | "200": 252 | content: 253 | application/json: 254 | schema: 255 | $ref: '#/components/schemas/MenuItem' 256 | description: Expected response to a valid request 257 | default: 258 | content: 259 | application/json: 260 | schema: 261 | $ref: '#/components/schemas/Error' 262 | description: unexpected error 263 | summary: Info for a specific menu item 264 | tags: 265 | - menu 266 | x-openapi-router-controller: openapi_server.controllers.menu_controller 267 | components: 268 | requestBodies: 269 | inline_object: 270 | content: 271 | multipart/form-data: 272 | schema: 273 | $ref: '#/components/schemas/inline_object' 274 | schemas: 275 | MenuItem: 276 | example: 277 | imageId: 1 278 | price: 6.027456183070403 279 | name: name 280 | description: description 281 | id: 0 282 | properties: 283 | id: 284 | format: int32 285 | type: integer 286 | description: 287 | type: string 288 | name: 289 | type: string 290 | price: 291 | type: number 292 | imageId: 293 | description: "URL to an image of the menu item. \nThis should be the image\ 294 | \ from the /image endpoint\n" 295 | format: int32 296 | type: integer 297 | required: 298 | - description 299 | - id 300 | - imageId 301 | - name 302 | - price 303 | type: object 304 | Menu: 305 | items: 306 | $ref: '#/components/schemas/MenuItem' 307 | type: array 308 | Cart: 309 | items: 310 | $ref: '#/components/schemas/MenuItem' 311 | type: array 312 | Error: 313 | properties: 314 | code: 315 | format: int32 316 | type: integer 317 | message: 318 | type: string 319 | required: 320 | - code 321 | - message 322 | type: object 323 | inline_object: 324 | properties: 325 | fileName: 326 | format: binary 327 | type: string 328 | type: object 329 | inline_response_200: 330 | example: 331 | imageId: 1 332 | properties: 333 | imageId: 334 | type: integer 335 | type: object 336 | -------------------------------------------------------------------------------- /openapi_server/test/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | import connexion 5 | import pytest 6 | from flask_testing import TestCase 7 | from openapi_server import database 8 | 9 | from openapi_server.encoder import JSONEncoder 10 | 11 | from openapi_server.database import models 12 | 13 | DB_URI = os.getenv("DATABASE_URI", "") 14 | 15 | class BaseTestCase(TestCase): 16 | def create_app(self): 17 | # no database is setup, any tests requiring a DB will fail and should use the "BaseDBTestCase" 18 | logging.getLogger("connexion.operation").setLevel("ERROR") 19 | app = connexion.App(__name__, specification_dir="../openapi/") 20 | app.app.json_encoder = JSONEncoder 21 | app.add_api("openapi.yaml", pythonic_params=True) 22 | return app.app 23 | 24 | 25 | @pytest.mark.skipif("postgres" not in DB_URI, reason="No postgres database envvar, DB tests will skip") 26 | class BaseDBTestCase(BaseTestCase): 27 | def create_app(self): 28 | app = super().create_app() 29 | if "postgres" in DB_URI: 30 | app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI 31 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 32 | else: 33 | raise RuntimeError( 34 | "Request to run db tests, but postgresql db not connected" 35 | ) 36 | models.db.init_app(app) 37 | return app 38 | 39 | def setUp(self): 40 | models.db.create_all() 41 | 42 | def tearDown(self): 43 | models.db.session.remove() 44 | models.db.drop_all() 45 | -------------------------------------------------------------------------------- /openapi_server/test/test_cart_controller.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | import unittest 5 | from unittest import mock 6 | 7 | from flask import json 8 | from six import BytesIO 9 | 10 | from openapi_server.models.cart import Cart # noqa: E501 11 | from openapi_server.models.error import Error # noqa: E501 12 | from openapi_server.models.menu_item import MenuItem # noqa: E501 13 | from openapi_server.test import BaseTestCase 14 | from openapi_server.database import models 15 | 16 | 17 | class TestCartController(BaseTestCase): 18 | """CartController integration test stubs""" 19 | 20 | def setUp(self): 21 | self.sample_model = models.MenuItem( 22 | description="description", price=6.02, image_id=5, name="name" 23 | ) 24 | self.sample_cart = models.Cart("fake") 25 | self.sample_cart.items = [self.sample_model] 26 | self.sample_item = { 27 | "price": 6.02, 28 | "imageId": 5, 29 | "name": "name", 30 | "description": "description", 31 | "id": 13, 32 | } 33 | 34 | @mock.patch.object(models.Cart, "add_item") 35 | def test_add_cart_item(self, mock_add_item): 36 | """Test case for add_cart_item 37 | 38 | Add a menu item a cart 39 | """ 40 | menu_item = { 41 | "price": 6.027456183070403, 42 | "imageId": 5, 43 | "name": "name", 44 | "description": "description", 45 | "id": 0, 46 | } 47 | headers = { 48 | "Accept": "application/json", 49 | "Content-Type": "application/json", 50 | } 51 | response = self.client.open( 52 | "/CFD/1.0.0/cart", 53 | method="POST", 54 | headers=headers, 55 | data=json.dumps(menu_item), 56 | content_type="application/json", 57 | ) 58 | self.assertStatus( 59 | response, 204, "Response body is : " + response.data.decode("utf-8") 60 | ) 61 | 62 | @mock.patch.object(models.Cart, "delete_item_by_id") 63 | def test_delete_cart_item(self, mock_delete): 64 | """Test case for delete_cart_item 65 | 66 | Remove item from cart 67 | """ 68 | headers = { 69 | "Accept": "application/json", 70 | } 71 | response = self.client.open( 72 | "/CFD/1.0.0/cart/{item_id}".format(item_id=56), 73 | method="DELETE", 74 | headers=headers, 75 | ) 76 | self.assertStatus( 77 | response, 204, "Response body is : " + response.data.decode("utf-8") 78 | ) 79 | 80 | @mock.patch.object(models.Cart, "query_by_host") 81 | def test_list_cart(self, mock_get_cart): 82 | """Test case for list_cart 83 | 84 | List all cart items 85 | """ 86 | mock_get_cart.return_value = self.sample_cart 87 | query_string = [("limit", 56)] 88 | headers = { 89 | "Accept": "application/json", 90 | } 91 | response = self.client.open( 92 | "/CFD/1.0.0/cart", 93 | method="GET", 94 | headers=headers, 95 | query_string=query_string, 96 | ) 97 | self.assert200(response, "Response body is : " + response.data.decode("utf-8")) 98 | 99 | 100 | if __name__ == "__main__": 101 | unittest.main() 102 | -------------------------------------------------------------------------------- /openapi_server/test/test_database.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import os 3 | import unittest 4 | 5 | import pytest 6 | from flask import json 7 | import warnings 8 | from six import BytesIO 9 | from sqlalchemy import exc 10 | from openapi_server import test 11 | 12 | from openapi_server.models.error import Error # noqa: E501 13 | from openapi_server.models.menu_item import MenuItem # noqa: E501 14 | from openapi_server.test import BaseDBTestCase 15 | from openapi_server.database import models, db_seed 16 | from sqlalchemy.exc import SQLAlchemyError 17 | 18 | 19 | class TestDatabase(BaseDBTestCase): 20 | """MenuController integration test stubs""" 21 | 22 | def setUp(self): 23 | super().setUp() 24 | self.open_model = models.MenuItem( 25 | description="description", price=6.02, image_id=1, name="name" 26 | ) 27 | self.im_data = b"some file data" 28 | self.im_stream = BytesIO(self.im_data) 29 | self.im_model = models.Image(self.im_data) 30 | 31 | def test_add_menu_item_success(self): 32 | """Test case for add_menu_item 33 | Create a menu item 34 | """ 35 | db_seed._prepopulate_images() 36 | m = models.MenuItem.add(self.open_model) 37 | assert m.id == 1 38 | assert m.name == "name" 39 | assert m.price == 6.02 40 | assert m.description == "description" 41 | 42 | def test_add_menu_item_failure(self): 43 | self.open_model.price = "string" 44 | with pytest.raises(SQLAlchemyError): 45 | models.MenuItem.add(self.open_model) 46 | 47 | def test_menu_query(self): 48 | assert len([_.serialize() for _ in models.MenuItem.query_all() if _]) == 0 49 | db_seed.seed() 50 | assert len([_.serialize() for _ in models.MenuItem.query_all() if _]) >= len( 51 | db_seed.menu_start_raw 52 | ) 53 | 54 | def test_menu_query_by_id(self): 55 | assert models.MenuItem.query_by_id(1) is None 56 | db_seed.seed() 57 | assert models.MenuItem.query_by_id(1) is not None 58 | 59 | def test_add_cart_success(self): 60 | db_seed.seed() 61 | models.Cart.add_item("1.1.1.1", models.MenuItem.query_by_id(1)) 62 | 63 | def test_add_cart_failure(self): 64 | with pytest.raises(AttributeError): # terrible, should raise a better error 65 | models.Cart.add_item("1.1.1.1", models.MenuItem.query_by_id(100)) 66 | 67 | def test_list_cart(self): 68 | assert ( 69 | models.Cart.query_by_host("1.1.1.1") is None 70 | ) # this doesn't match the menu query... this is why we do TDD! 71 | db_seed.seed() 72 | test_item = models.MenuItem.query_by_id(1) 73 | assert test_item.cart_id is None 74 | models.Cart.add_item("1.1.1.1", test_item) 75 | assert len(models.Cart.query_by_host("1.1.1.1").items) == 1 76 | assert test_item.cart_id == "1.1.1.1" 77 | 78 | def test_image_add_failure(self): 79 | with pytest.raises(TypeError): 80 | models.Image.add({"bad"}) 81 | 82 | def test_get_image(self): 83 | assert models.Image.get_image(0) is None 84 | db_seed.seed() 85 | m = models.Image.get_image(1) 86 | assert all([m.id, m.data]) 87 | 88 | 89 | if __name__ == "__main__": 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /openapi_server/test/test_image_controller.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | import unittest 5 | from unittest import mock 6 | 7 | from flask import json 8 | from six import BytesIO 9 | 10 | from openapi_server.models.error import Error # noqa: E501 11 | from openapi_server.models.inline_response200 import InlineResponse200 # noqa: E501 12 | from openapi_server.test import BaseTestCase 13 | from openapi_server.database import models 14 | 15 | 16 | from openapi_server.controllers import image_controller 17 | 18 | 19 | class TestImageController(BaseTestCase): 20 | """ImageController integration test stubs""" 21 | 22 | def setUp(self): 23 | self.mock_data = b"some file data" 24 | self.mock_stream = BytesIO(self.mock_data) 25 | self.mock_model = models.Image(self.mock_data) 26 | 27 | # @unittest.skip('reason') 28 | @mock.patch.object(models.Image, "add") 29 | def test_add_image(self, mock_add): 30 | """Test case for add_image 31 | 32 | Add an image to the restaraunt 33 | """ 34 | mock_add.return_value = self.mock_model 35 | headers = { 36 | "Accept": "application/json", 37 | "Content-Type": "multipart/form-data", 38 | } 39 | data = dict(fileName=(self.mock_stream, "file.png")) 40 | response = self.client.open( 41 | "/CFD/1.0.0/image", 42 | method="POST", 43 | headers=headers, 44 | data=data, 45 | content_type="multipart/form-data", 46 | ) 47 | self.assert200(response, "Response body is : " + response.data.decode("utf-8")) 48 | 49 | @mock.patch.object(models.Image, "delete_image") 50 | def test_delete_image(self, mock_delete): 51 | """Test case for delete_image 52 | 53 | Remove image 54 | """ 55 | headers = { 56 | "Accept": "application/json", 57 | } 58 | response = self.client.open( 59 | "/CFD/1.0.0/image/{image_id}".format(image_id=0), 60 | method="DELETE", 61 | headers=headers, 62 | ) 63 | self.assertStatus( 64 | response, 204, "Response body is : " + response.data.decode("utf-8") 65 | ) 66 | assert mock_delete.call_count == 1 67 | 68 | @mock.patch.object(models.Image, "get_image") 69 | def test_get_image(self, mock_get): 70 | """Test case for get_image 71 | 72 | Get image 73 | """ 74 | mock_get.return_value = self.mock_model 75 | headers = { 76 | "Accept": "image/png application/json", 77 | } 78 | response = self.client.open( 79 | "/CFD/1.0.0/image/{image_id}".format(image_id=0), 80 | method="GET", 81 | headers=headers, 82 | ) 83 | self.assert200(response) 84 | 85 | 86 | if __name__ == "__main__": 87 | unittest.main() 88 | -------------------------------------------------------------------------------- /openapi_server/test/test_menu_controller.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | import unittest 5 | from unittest import mock 6 | 7 | from flask import json 8 | from six import BytesIO 9 | 10 | from openapi_server.models.error import Error # noqa: E501 11 | from openapi_server.models.menu_item import MenuItem # noqa: E501 12 | from openapi_server.test import BaseTestCase 13 | from openapi_server.database import models 14 | 15 | 16 | class TestMenuController(BaseTestCase): 17 | """MenuController integration test stubs""" 18 | 19 | def setUp(self): 20 | self.sample_model = models.MenuItem( 21 | description="description", price=6.02, image_id=5, name="name" 22 | ) 23 | self.sample_item = { 24 | "price": 6.02, 25 | "imageId": 5, 26 | "name": "name", 27 | "description": "description", 28 | "id": 13, 29 | } 30 | 31 | @mock.patch.object(models.MenuItem, "add") 32 | def test_add_menu_item(self, mock_item_add): 33 | """Test case for add_menu_item 34 | 35 | Create a menu item 36 | """ 37 | mock_item_add.return_value = self.sample_model 38 | headers = { 39 | "Accept": "application/json", 40 | "Content-Type": "application/json", 41 | } 42 | response = self.client.open( 43 | "/CFD/1.0.0/menu", 44 | method="POST", 45 | headers=headers, 46 | data=json.dumps(self.sample_item), 47 | content_type="application/json", 48 | ) 49 | self.assertStatus( 50 | response, 200, "Response body is : " + response.data.decode("utf-8") 51 | ) 52 | 53 | @mock.patch.object(models.MenuItem, "query_all") 54 | def test_list_menu(self, mock_query_menu): 55 | """Test case for list_menu 56 | 57 | List all menu items 58 | """ 59 | query_string = [("limit", 56)] 60 | headers = { 61 | "Accept": "application/json", 62 | } 63 | response = self.client.open( 64 | "/CFD/1.0.0/menu", 65 | method="GET", 66 | headers=headers, 67 | query_string=query_string, 68 | ) 69 | self.assert200(response, "Response body is : " + response.data.decode("utf-8")) 70 | 71 | @mock.patch.object(models.MenuItem, "query_by_id") 72 | def test_show_menu_item_by_id(self, mock_query): 73 | """Test case for show_menu_item_by_id 74 | 75 | Info for a specific menu item 76 | """ 77 | mock_query.return_value = self.sample_model 78 | headers = { 79 | "Accept": "application/json", 80 | } 81 | response = self.client.open( 82 | "/CFD/1.0.0/menu/{item_id}".format(item_id=0), method="GET", headers=headers 83 | ) 84 | self.assert200(response, "Response body is : " + response.data.decode("utf-8")) 85 | 86 | 87 | if __name__ == "__main__": 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /openapi_server/typing_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import sys 4 | 5 | if sys.version_info < (3, 7): 6 | import typing 7 | 8 | def is_generic(klass): 9 | """ Determine whether klass is a generic class """ 10 | return type(klass) == typing.GenericMeta # pylint: disable=no-member 11 | 12 | def is_dict(klass): 13 | """ Determine whether klass is a Dict """ 14 | return klass.__extra__ == dict 15 | 16 | def is_list(klass): 17 | """ Determine whether klass is a List """ 18 | return klass.__extra__ == list 19 | 20 | 21 | else: 22 | 23 | def is_generic(klass): 24 | """ Determine whether klass is a generic class """ 25 | return hasattr(klass, "__origin__") 26 | 27 | def is_dict(klass): 28 | """ Determine whether klass is a Dict """ 29 | return klass.__origin__ == dict 30 | 31 | def is_list(klass): 32 | """ Determine whether klass is a List """ 33 | return klass.__origin__ == list 34 | -------------------------------------------------------------------------------- /openapi_server/util.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import six 4 | import typing 5 | from openapi_server import typing_utils 6 | 7 | 8 | def _deserialize(data, klass): 9 | """Deserializes dict, list, str into an object. 10 | 11 | :param data: dict, list or str. 12 | :param klass: class literal, or string of class name. 13 | 14 | :return: object. 15 | """ 16 | if data is None: 17 | return None 18 | 19 | if klass in six.integer_types or klass in (float, str, bool, bytearray): 20 | return _deserialize_primitive(data, klass) 21 | elif klass == object: 22 | return _deserialize_object(data) 23 | elif klass == datetime.date: 24 | return deserialize_date(data) 25 | elif klass == datetime.datetime: 26 | return deserialize_datetime(data) 27 | elif typing_utils.is_generic(klass): 28 | if typing_utils.is_list(klass): 29 | return _deserialize_list(data, klass.__args__[0]) 30 | if typing_utils.is_dict(klass): 31 | return _deserialize_dict(data, klass.__args__[1]) 32 | else: 33 | return deserialize_model(data, klass) 34 | 35 | 36 | def _deserialize_primitive(data, klass): 37 | """Deserializes to primitive type. 38 | 39 | :param data: data to deserialize. 40 | :param klass: class literal. 41 | 42 | :return: int, long, float, str, bool. 43 | :rtype: int | long | float | str | bool 44 | """ 45 | try: 46 | value = klass(data) 47 | except UnicodeEncodeError: 48 | value = six.u(data) 49 | except TypeError: 50 | value = data 51 | return value 52 | 53 | 54 | def _deserialize_object(value): 55 | """Return an original value. 56 | 57 | :return: object. 58 | """ 59 | return value 60 | 61 | 62 | def deserialize_date(string): 63 | """Deserializes string to date. 64 | 65 | :param string: str. 66 | :type string: str 67 | :return: date. 68 | :rtype: date 69 | """ 70 | try: 71 | from dateutil.parser import parse 72 | 73 | return parse(string).date() 74 | except ImportError: 75 | return string 76 | 77 | 78 | def deserialize_datetime(string): 79 | """Deserializes string to datetime. 80 | 81 | The string should be in iso8601 datetime format. 82 | 83 | :param string: str. 84 | :type string: str 85 | :return: datetime. 86 | :rtype: datetime 87 | """ 88 | try: 89 | from dateutil.parser import parse 90 | 91 | return parse(string) 92 | except ImportError: 93 | return string 94 | 95 | 96 | def deserialize_model(data, klass): 97 | """Deserializes list or dict to model. 98 | 99 | :param data: dict, list. 100 | :type data: dict | list 101 | :param klass: class literal. 102 | :return: model object. 103 | """ 104 | instance = klass() 105 | 106 | if not instance.openapi_types: 107 | return data 108 | 109 | for attr, attr_type in six.iteritems(instance.openapi_types): 110 | if ( 111 | data is not None 112 | and instance.attribute_map[attr] in data 113 | and isinstance(data, (list, dict)) 114 | ): 115 | value = data[instance.attribute_map[attr]] 116 | setattr(instance, attr, _deserialize(value, attr_type)) 117 | 118 | return instance 119 | 120 | 121 | def _deserialize_list(data, boxed_type): 122 | """Deserializes a list and its elements. 123 | 124 | :param data: list to deserialize. 125 | :type data: list 126 | :param boxed_type: class literal. 127 | 128 | :return: deserialized list. 129 | :rtype: list 130 | """ 131 | return [_deserialize(sub_data, boxed_type) for sub_data in data] 132 | 133 | 134 | def _deserialize_dict(data, boxed_type): 135 | """Deserializes a dict and its elements. 136 | 137 | :param data: dict to deserialize. 138 | :type data: dict 139 | :param boxed_type: class literal. 140 | 141 | :return: deserialized dict. 142 | :rtype: dict 143 | """ 144 | return {k: _deserialize(v, boxed_type) for k, v in six.iteritems(data)} 145 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | errors-only= 3 | 4 | # A comma-separated list of package or module names from where C extensions may 5 | # be loaded. Extensions are loading into the active Python interpreter and may 6 | # run arbitrary code. 7 | extension-pkg-whitelist= 8 | 9 | # Specify a score threshold to be exceeded before program exits with error. 10 | fail-under=10.0 11 | 12 | # Add files or directories to the blacklist. They should be base names, not 13 | # paths. 14 | ignore=CVS 15 | 16 | # Add files or directories matching the regex patterns to the blacklist. The 17 | # regex matches against base names, not paths. 18 | ignore-patterns= 19 | 20 | # Python code to execute, usually for sys.path manipulation such as 21 | # pygtk.require(). 22 | #init-hook= 23 | 24 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 25 | # number of processors available to use. 26 | jobs=1 27 | 28 | # Control the amount of potential inferred values when inferring a single 29 | # object. This can help the performance when dealing with large functions or 30 | # complex, nested conditions. 31 | limit-inference-results=100 32 | 33 | # List of plugins (as comma separated values of python module names) to load, 34 | # usually to register additional checkers. 35 | load-plugins=pylint_flask_sqlalchemy, 36 | 37 | # Pickle collected data for later comparisons. 38 | persistent=yes 39 | 40 | # When enabled, pylint would attempt to guess common misconfiguration and emit 41 | # user-friendly hints instead of false-positive error messages. 42 | suggestion-mode=yes 43 | 44 | # Allow loading of arbitrary C extensions. Extensions are imported into the 45 | # active Python interpreter and may run arbitrary code. 46 | unsafe-load-any-extension=no 47 | 48 | 49 | [MESSAGES CONTROL] 50 | 51 | # Only show warnings with the listed confidence levels. Leave empty to show 52 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 53 | confidence= 54 | 55 | # Disable the message, report, category or checker with the given id(s). You 56 | # can either give multiple identifiers separated by comma (,) or put this 57 | # option multiple times (only on the command line, not in the configuration 58 | # file where it should appear only once). You can also use "--disable=all" to 59 | # disable everything first and then reenable specific checks. For example, if 60 | # you want to run only the similarities checker, you can use "--disable=all 61 | # --enable=similarities". If you want to run only the classes checker, but have 62 | # no Warning level messages displayed, use "--disable=all --enable=classes 63 | # --disable=W". 64 | disable=print-statement, 65 | parameter-unpacking, 66 | unpacking-in-except, 67 | old-raise-syntax, 68 | backtick, 69 | long-suffix, 70 | old-ne-operator, 71 | old-octal-literal, 72 | import-star-module-level, 73 | non-ascii-bytes-literal, 74 | raw-checker-failed, 75 | bad-inline-option, 76 | locally-disabled, 77 | file-ignored, 78 | suppressed-message, 79 | useless-suppression, 80 | deprecated-pragma, 81 | use-symbolic-message-instead, 82 | apply-builtin, 83 | basestring-builtin, 84 | buffer-builtin, 85 | cmp-builtin, 86 | coerce-builtin, 87 | execfile-builtin, 88 | file-builtin, 89 | long-builtin, 90 | raw_input-builtin, 91 | reduce-builtin, 92 | standarderror-builtin, 93 | unicode-builtin, 94 | xrange-builtin, 95 | coerce-method, 96 | delslice-method, 97 | getslice-method, 98 | setslice-method, 99 | no-absolute-import, 100 | old-division, 101 | dict-iter-method, 102 | dict-view-method, 103 | next-method-called, 104 | metaclass-assignment, 105 | indexing-exception, 106 | raising-string, 107 | reload-builtin, 108 | oct-method, 109 | hex-method, 110 | nonzero-method, 111 | cmp-method, 112 | input-builtin, 113 | round-builtin, 114 | intern-builtin, 115 | unichr-builtin, 116 | map-builtin-not-iterating, 117 | zip-builtin-not-iterating, 118 | range-builtin-not-iterating, 119 | filter-builtin-not-iterating, 120 | using-cmp-argument, 121 | eq-without-hash, 122 | div-method, 123 | idiv-method, 124 | rdiv-method, 125 | exception-message-attribute, 126 | invalid-str-codec, 127 | sys-max-int, 128 | bad-python3-import, 129 | deprecated-string-function, 130 | deprecated-str-translate-call, 131 | deprecated-itertools-function, 132 | deprecated-types-field, 133 | next-method-defined, 134 | dict-items-not-iterating, 135 | dict-keys-not-iterating, 136 | dict-values-not-iterating, 137 | deprecated-operator-function, 138 | deprecated-urllib-function, 139 | xreadlines-attribute, 140 | deprecated-sys-function, 141 | exception-escape, 142 | comprehension-escape 143 | 144 | # Enable the message, report, category or checker with the given id(s). You can 145 | # either give multiple identifier separated by comma (,) or put this option 146 | # multiple time (only on the command line, not in the configuration file where 147 | # it should appear only once). See also the "--disable" option for examples. 148 | enable=c-extension-no-member 149 | 150 | 151 | [REPORTS] 152 | 153 | # Python expression which should return a score less than or equal to 10. You 154 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 155 | # which contain the number of messages in each category, as well as 'statement' 156 | # which is the total number of statements analyzed. This score is used by the 157 | # global evaluation report (RP0004). 158 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 159 | 160 | # Template used to display messages. This is a python new-style format string 161 | # used to format the message information. See doc for all details. 162 | #msg-template= 163 | 164 | # Set the output format. Available formats are text, parseable, colorized, json 165 | # and msvs (visual studio). You can also give a reporter class, e.g. 166 | # mypackage.mymodule.MyReporterClass. 167 | output-format=text 168 | 169 | # Tells whether to display a full report or only the messages. 170 | reports=no 171 | 172 | # Activate the evaluation score. 173 | score=yes 174 | 175 | 176 | [REFACTORING] 177 | 178 | # Maximum number of nested blocks for function / method body 179 | max-nested-blocks=5 180 | 181 | # Complete name of functions that never returns. When checking for 182 | # inconsistent-return-statements if a never returning function is called then 183 | # it will be considered as an explicit return statement and no message will be 184 | # printed. 185 | never-returning-functions=sys.exit 186 | 187 | 188 | [SIMILARITIES] 189 | 190 | # Ignore comments when computing similarities. 191 | ignore-comments=yes 192 | 193 | # Ignore docstrings when computing similarities. 194 | ignore-docstrings=yes 195 | 196 | # Ignore imports when computing similarities. 197 | ignore-imports=no 198 | 199 | # Minimum lines number of a similarity. 200 | min-similarity-lines=4 201 | 202 | 203 | [VARIABLES] 204 | 205 | # List of additional names supposed to be defined in builtins. Remember that 206 | # you should avoid defining new builtins when possible. 207 | additional-builtins= 208 | 209 | # Tells whether unused global variables should be treated as a violation. 210 | allow-global-unused-variables=yes 211 | 212 | # List of strings which can identify a callback function by name. A callback 213 | # name must start or end with one of those strings. 214 | callbacks=cb_, 215 | _cb 216 | 217 | # A regular expression matching the name of dummy variables (i.e. expected to 218 | # not be used). 219 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 220 | 221 | # Argument names that match this expression will be ignored. Default to name 222 | # with leading underscore. 223 | ignored-argument-names=_.*|^ignored_|^unused_ 224 | 225 | # Tells whether we should check for unused import in __init__ files. 226 | init-import=no 227 | 228 | # List of qualified module names which can have objects that can redefine 229 | # builtins. 230 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 231 | 232 | 233 | [SPELLING] 234 | 235 | # Limits count of emitted suggestions for spelling mistakes. 236 | max-spelling-suggestions=4 237 | 238 | # Spelling dictionary name. Available dictionaries: none. To make it work, 239 | # install the python-enchant package. 240 | spelling-dict= 241 | 242 | # List of comma separated words that should not be checked. 243 | spelling-ignore-words= 244 | 245 | # A path to a file that contains the private dictionary; one word per line. 246 | spelling-private-dict-file= 247 | 248 | # Tells whether to store unknown words to the private dictionary (see the 249 | # --spelling-private-dict-file option) instead of raising a message. 250 | spelling-store-unknown-words=no 251 | 252 | 253 | [MISCELLANEOUS] 254 | 255 | # List of note tags to take in consideration, separated by a comma. 256 | notes=FIXME, 257 | XXX, 258 | TODO 259 | 260 | # Regular expression of note tags to take in consideration. 261 | #notes-rgx= 262 | 263 | 264 | [TYPECHECK] 265 | 266 | # List of decorators that produce context managers, such as 267 | # contextlib.contextmanager. Add to this list to register other decorators that 268 | # produce valid context managers. 269 | contextmanager-decorators=contextlib.contextmanager 270 | 271 | # List of members which are set dynamically and missed by pylint inference 272 | # system, and so shouldn't trigger E1101 when accessed. Python regular 273 | # expressions are accepted. 274 | generated-members= 275 | 276 | # Tells whether missing members accessed in mixin class should be ignored. A 277 | # mixin class is detected if its name ends with "mixin" (case insensitive). 278 | ignore-mixin-members=yes 279 | 280 | # Tells whether to warn about missing members when the owner of the attribute 281 | # is inferred to be None. 282 | ignore-none=yes 283 | 284 | # This flag controls whether pylint should warn about no-member and similar 285 | # checks whenever an opaque object is returned when inferring. The inference 286 | # can return multiple potential results while evaluating a Python object, but 287 | # some branches might not be evaluated, which results in partial inference. In 288 | # that case, it might be useful to still emit no-member and other checks for 289 | # the rest of the inferred objects. 290 | ignore-on-opaque-inference=yes 291 | 292 | # List of class names for which member attributes should not be checked (useful 293 | # for classes with dynamically set attributes). This supports the use of 294 | # qualified names. 295 | ignored-classes=optparse.Values,thread._local,_thread._local 296 | 297 | # List of module names for which member attributes should not be checked 298 | # (useful for modules/projects where namespaces are manipulated during runtime 299 | # and thus existing member attributes cannot be deduced by static analysis). It 300 | # supports qualified module names, as well as Unix pattern matching. 301 | ignored-modules= 302 | 303 | # Show a hint with possible names when a member name was not found. The aspect 304 | # of finding the hint is based on edit distance. 305 | missing-member-hint=yes 306 | 307 | # The minimum edit distance a name should have in order to be considered a 308 | # similar match for a missing member name. 309 | missing-member-hint-distance=1 310 | 311 | # The total number of similar names that should be taken in consideration when 312 | # showing a hint for a missing member. 313 | missing-member-max-choices=1 314 | 315 | # List of decorators that change the signature of a decorated function. 316 | signature-mutators= 317 | 318 | 319 | [LOGGING] 320 | 321 | # The type of string formatting that logging methods do. `old` means using % 322 | # formatting, `new` is for `{}` formatting. 323 | logging-format-style=old 324 | 325 | # Logging modules to check that the string format arguments are in logging 326 | # function parameter format. 327 | logging-modules=logging 328 | 329 | 330 | [BASIC] 331 | 332 | # Naming style matching correct argument names. 333 | argument-naming-style=snake_case 334 | 335 | # Regular expression matching correct argument names. Overrides argument- 336 | # naming-style. 337 | #argument-rgx= 338 | 339 | # Naming style matching correct attribute names. 340 | attr-naming-style=snake_case 341 | 342 | # Regular expression matching correct attribute names. Overrides attr-naming- 343 | # style. 344 | #attr-rgx= 345 | 346 | # Bad variable names which should always be refused, separated by a comma. 347 | bad-names=foo, 348 | bar, 349 | baz, 350 | toto, 351 | tutu, 352 | tata 353 | 354 | # Bad variable names regexes, separated by a comma. If names match any regex, 355 | # they will always be refused 356 | bad-names-rgxs= 357 | 358 | # Naming style matching correct class attribute names. 359 | class-attribute-naming-style=any 360 | 361 | # Regular expression matching correct class attribute names. Overrides class- 362 | # attribute-naming-style. 363 | #class-attribute-rgx= 364 | 365 | # Naming style matching correct class names. 366 | class-naming-style=PascalCase 367 | 368 | # Regular expression matching correct class names. Overrides class-naming- 369 | # style. 370 | #class-rgx= 371 | 372 | # Naming style matching correct constant names. 373 | const-naming-style=UPPER_CASE 374 | 375 | # Regular expression matching correct constant names. Overrides const-naming- 376 | # style. 377 | #const-rgx= 378 | 379 | # Minimum line length for functions/classes that require docstrings, shorter 380 | # ones are exempt. 381 | docstring-min-length=-1 382 | 383 | # Naming style matching correct function names. 384 | function-naming-style=snake_case 385 | 386 | # Regular expression matching correct function names. Overrides function- 387 | # naming-style. 388 | #function-rgx= 389 | 390 | # Good variable names which should always be accepted, separated by a comma. 391 | good-names=i, 392 | j, 393 | k, 394 | ex, 395 | Run, 396 | _ 397 | 398 | # Good variable names regexes, separated by a comma. If names match any regex, 399 | # they will always be accepted 400 | good-names-rgxs= 401 | 402 | # Include a hint for the correct naming format with invalid-name. 403 | include-naming-hint=no 404 | 405 | # Naming style matching correct inline iteration names. 406 | inlinevar-naming-style=any 407 | 408 | # Regular expression matching correct inline iteration names. Overrides 409 | # inlinevar-naming-style. 410 | #inlinevar-rgx= 411 | 412 | # Naming style matching correct method names. 413 | method-naming-style=snake_case 414 | 415 | # Regular expression matching correct method names. Overrides method-naming- 416 | # style. 417 | #method-rgx= 418 | 419 | # Naming style matching correct module names. 420 | module-naming-style=snake_case 421 | 422 | # Regular expression matching correct module names. Overrides module-naming- 423 | # style. 424 | #module-rgx= 425 | 426 | # Colon-delimited sets of names that determine each other's naming style when 427 | # the name regexes allow several styles. 428 | name-group= 429 | 430 | # Regular expression which should only match function or class names that do 431 | # not require a docstring. 432 | no-docstring-rgx=^_ 433 | 434 | # List of decorators that produce properties, such as abc.abstractproperty. Add 435 | # to this list to register other decorators that produce valid properties. 436 | # These decorators are taken in consideration only for invalid-name. 437 | property-classes=abc.abstractproperty 438 | 439 | # Naming style matching correct variable names. 440 | variable-naming-style=snake_case 441 | 442 | # Regular expression matching correct variable names. Overrides variable- 443 | # naming-style. 444 | #variable-rgx= 445 | 446 | 447 | [STRING] 448 | 449 | # This flag controls whether inconsistent-quotes generates a warning when the 450 | # character used as a quote delimiter is used inconsistently within a module. 451 | check-quote-consistency=no 452 | 453 | # This flag controls whether the implicit-str-concat should generate a warning 454 | # on implicit string concatenation in sequences defined over several lines. 455 | check-str-concat-over-line-jumps=no 456 | 457 | 458 | [FORMAT] 459 | 460 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 461 | expected-line-ending-format= 462 | 463 | # Regexp for a line that is allowed to be longer than the limit. 464 | ignore-long-lines=^\s*(# )??$ 465 | 466 | # Number of spaces of indent required inside a hanging or continued line. 467 | indent-after-paren=4 468 | 469 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 470 | # tab). 471 | indent-string=' ' 472 | 473 | # Maximum number of characters on a single line. 474 | max-line-length=100 475 | 476 | # Maximum number of lines in a module. 477 | max-module-lines=1000 478 | 479 | # Allow the body of a class to be on the same line as the declaration if body 480 | # contains single statement. 481 | single-line-class-stmt=no 482 | 483 | # Allow the body of an if to be on the same line as the test if there is no 484 | # else. 485 | single-line-if-stmt=no 486 | 487 | 488 | [DESIGN] 489 | 490 | # Maximum number of arguments for function / method. 491 | max-args=5 492 | 493 | # Maximum number of attributes for a class (see R0902). 494 | max-attributes=7 495 | 496 | # Maximum number of boolean expressions in an if statement (see R0916). 497 | max-bool-expr=5 498 | 499 | # Maximum number of branch for function / method body. 500 | max-branches=12 501 | 502 | # Maximum number of locals for function / method body. 503 | max-locals=15 504 | 505 | # Maximum number of parents for a class (see R0901). 506 | max-parents=7 507 | 508 | # Maximum number of public methods for a class (see R0904). 509 | max-public-methods=20 510 | 511 | # Maximum number of return / yield for function / method body. 512 | max-returns=6 513 | 514 | # Maximum number of statements in function / method body. 515 | max-statements=50 516 | 517 | # Minimum number of public methods for a class (see R0903). 518 | min-public-methods=2 519 | 520 | 521 | [CLASSES] 522 | 523 | # Warn about protected attribute access inside special methods 524 | check-protected-access-in-special-methods=no 525 | 526 | # List of method names used to declare (i.e. assign) instance attributes. 527 | defining-attr-methods=__init__, 528 | __new__, 529 | setUp, 530 | __post_init__ 531 | 532 | # List of member names, which should be excluded from the protected access 533 | # warning. 534 | exclude-protected=_asdict, 535 | _fields, 536 | _replace, 537 | _source, 538 | _make 539 | 540 | # List of valid names for the first argument in a class method. 541 | valid-classmethod-first-arg=cls 542 | 543 | # List of valid names for the first argument in a metaclass class method. 544 | valid-metaclass-classmethod-first-arg=cls 545 | 546 | 547 | [IMPORTS] 548 | 549 | # List of modules that can be imported at any level, not just the top level 550 | # one. 551 | allow-any-import-level= 552 | 553 | # Allow wildcard imports from modules that define __all__. 554 | allow-wildcard-with-all=no 555 | 556 | # Analyse import fallback blocks. This can be used to support both Python 2 and 557 | # 3 compatible code, which means that the block might have code that exists 558 | # only in one or another interpreter, leading to false positives when analysed. 559 | analyse-fallback-blocks=no 560 | 561 | # Deprecated modules which should not be used, separated by a comma. 562 | deprecated-modules=optparse,tkinter.tix 563 | 564 | # Create a graph of external dependencies in the given file (report RP0402 must 565 | # not be disabled). 566 | ext-import-graph= 567 | 568 | # Create a graph of every (i.e. internal and external) dependencies in the 569 | # given file (report RP0402 must not be disabled). 570 | import-graph= 571 | 572 | # Create a graph of internal dependencies in the given file (report RP0402 must 573 | # not be disabled). 574 | int-import-graph= 575 | 576 | # Force import order to recognize a module as part of the standard 577 | # compatibility libraries. 578 | known-standard-library= 579 | 580 | # Force import order to recognize a module as part of a third party library. 581 | known-third-party=enchant 582 | 583 | # Couples of modules and preferred modules, separated by a comma. 584 | preferred-modules= 585 | 586 | 587 | [EXCEPTIONS] 588 | 589 | # Exceptions that will emit a warning when being caught. Defaults to 590 | # "BaseException, Exception". 591 | overgeneral-exceptions=BaseException, 592 | Exception 593 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = 3 | ignore:.*json_available.* is deprecated:DeprecationWarning 4 | ignore:.*JSON serialization.*:FutureWarning -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r run-requirements.txt 2 | pytest~=7.1.3 # needed for python 2.7+3.4 3 | pytest-cov>=2.8.1 4 | pytest-randomly==1.2.3 # needed for python 2.7+3.4 5 | Flask-Testing==0.8.1 6 | pylint 7 | pylint-flask-sqlalchemy 8 | -------------------------------------------------------------------------------- /run-requirements.txt: -------------------------------------------------------------------------------- 1 | connexion[swagger-ui] >= 2.14.1; python_version>="3.6" 2 | flask-cors >= 3.0 3 | swagger-ui-bundle >= 0.0.2 4 | python_dateutil >= 2.6.0 5 | setuptools >= 21.0.0 6 | gunicorn == 20.0.4 7 | sqlalchemy == 1.4.18 8 | flask-sqlalchemy >= 3.0.2 9 | psycopg2-binary 10 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.5 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import sys 4 | from setuptools import setup, find_packages 5 | 6 | NAME = "openapi_server" 7 | VERSION = "1.0.0" 8 | 9 | # To install the library, run the following 10 | # 11 | # python setup.py install 12 | # 13 | # prerequisite: setuptools 14 | # http://pypi.python.org/pypi/setuptools 15 | 16 | REQUIRES = ["connexion>=2.0.2", "swagger-ui-bundle>=0.0.2", "python_dateutil>=2.6.0"] 17 | 18 | setup( 19 | name=NAME, 20 | version=VERSION, 21 | description="Continous Food Delivery", 22 | author_email="", 23 | url="", 24 | keywords=["OpenAPI", "Continuous Food Delivery"], 25 | install_requires=REQUIRES, 26 | packages=find_packages(), 27 | package_data={"": ["openapi/openapi.yaml"]}, 28 | include_package_data=True, 29 | entry_points={"console_scripts": ["openapi_server=openapi_server.__main__:main"]}, 30 | long_description="""\ 31 | No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 32 | """, 33 | ) 34 | --------------------------------------------------------------------------------