From 2b82333e6cecf45052a2d104bd3bb0ba867dd59b Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 22:40:14 +0800 Subject: [PATCH 01/11] Add in mustache templates to import functions from core module --- .codegen/server/Dockerfile.mustache | 31 ++++ .codegen/server/README.mustache | 60 ++++++ .codegen/server/__init__.mustache | 0 .codegen/server/__init__model.mustache | 7 + .codegen/server/__init__test.mustache | 16 ++ .codegen/server/__main__.mustache | 32 ++++ .codegen/server/base_model_.mustache | 73 ++++++++ .codegen/server/controller.mustache | 116 ++++++++++++ .codegen/server/controller_test.mustache | 57 ++++++ .codegen/server/dockerignore.mustache | 72 ++++++++ .codegen/server/encoder.mustache | 20 ++ .codegen/server/git_push.sh.mustache | 58 ++++++ .codegen/server/gitignore.mustache | 66 +++++++ .codegen/server/model.mustache | 172 ++++++++++++++++++ .codegen/server/openapi.mustache | 1 + .codegen/server/param_type.mustache | 1 + .codegen/server/requirements.mustache | 24 +++ .codegen/server/security_controller_.mustache | 90 +++++++++ .codegen/server/setup.mustache | 41 +++++ .codegen/server/test-requirements.mustache | 13 ++ .codegen/server/tox.mustache | 11 ++ .codegen/server/travis.mustache | 17 ++ .codegen/server/typing_utils.mustache | 32 ++++ .codegen/server/util.mustache | 142 +++++++++++++++ server/openapi_server/core/__init__.py | 0 .../core/health_check_controller.py | 19 ++ .../core/security_controller_.py | 1 + .../core/text_date_annotation_controller.py | 69 +++++++ server/openapi_server/core/tool_controller.py | 37 ++++ 29 files changed, 1278 insertions(+) create mode 100644 .codegen/server/Dockerfile.mustache create mode 100644 .codegen/server/README.mustache create mode 100644 .codegen/server/__init__.mustache create mode 100644 .codegen/server/__init__model.mustache create mode 100644 .codegen/server/__init__test.mustache create mode 100644 .codegen/server/__main__.mustache create mode 100644 .codegen/server/base_model_.mustache create mode 100644 .codegen/server/controller.mustache create mode 100644 .codegen/server/controller_test.mustache create mode 100644 .codegen/server/dockerignore.mustache create mode 100644 .codegen/server/encoder.mustache create mode 100644 .codegen/server/git_push.sh.mustache create mode 100644 .codegen/server/gitignore.mustache create mode 100644 .codegen/server/model.mustache create mode 100644 .codegen/server/openapi.mustache create mode 100644 .codegen/server/param_type.mustache create mode 100644 .codegen/server/requirements.mustache create mode 100644 .codegen/server/security_controller_.mustache create mode 100644 .codegen/server/setup.mustache create mode 100644 .codegen/server/test-requirements.mustache create mode 100644 .codegen/server/tox.mustache create mode 100644 .codegen/server/travis.mustache create mode 100644 .codegen/server/typing_utils.mustache create mode 100644 .codegen/server/util.mustache create mode 100644 server/openapi_server/core/__init__.py create mode 100644 server/openapi_server/core/health_check_controller.py create mode 100644 server/openapi_server/core/security_controller_.py create mode 100644 server/openapi_server/core/text_date_annotation_controller.py create mode 100644 server/openapi_server/core/tool_controller.py diff --git a/.codegen/server/Dockerfile.mustache b/.codegen/server/Dockerfile.mustache new file mode 100644 index 0000000..f040d41 --- /dev/null +++ b/.codegen/server/Dockerfile.mustache @@ -0,0 +1,31 @@ +{{#supportPython2}} +FROM python:2-alpine +{{/supportPython2}} +{{^supportPython2}} +FROM python:3-alpine +{{/supportPython2}} + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY requirements.txt /usr/src/app/ + +{{#supportPython2}} +RUN pip install --no-cache-dir -r requirements.txt +{{/supportPython2}} +{{^supportPython2}} +RUN pip3 install --no-cache-dir -r requirements.txt +{{/supportPython2}} + +COPY . /usr/src/app + +EXPOSE {{serverPort}} + +{{#supportPython2}} +ENTRYPOINT ["python"] +{{/supportPython2}} +{{^supportPython2}} +ENTRYPOINT ["python3"] +{{/supportPython2}} + +CMD ["-m", "{{packageName}}"] \ No newline at end of file diff --git a/.codegen/server/README.mustache b/.codegen/server/README.mustache new file mode 100644 index 0000000..3612c26 --- /dev/null +++ b/.codegen/server/README.mustache @@ -0,0 +1,60 @@ +# OpenAPI generated server + +## Overview +This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the +[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This +is an example of building a OpenAPI-enabled Flask server. + +This example uses the [Connexion](https://github.com/zalando/connexion) library on top of Flask. + +## Requirements +{{#supportPython2}} +Python 2.7+ +{{/supportPython2}} +{{^supportPython2}} +Python 3.5.2+ +{{/supportPython2}} + +## Usage +To run the server, please execute the following from the root directory: + +``` +{{#supportPython2}} +pip install -r requirements.txt +python -m {{packageName}} +{{/supportPython2}} +{{^supportPython2}} +pip3 install -r requirements.txt +python3 -m {{packageName}} +{{/supportPython2}} +``` + +and open your browser to here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/ui/ +``` + +Your OpenAPI definition lives here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/openapi.json +``` + +To launch the integration tests, use tox: +``` +sudo pip install tox +tox +``` + +## Running with Docker + +To run the server on a Docker container, please execute the following from the root directory: + +```bash +# building the image +docker build -t {{packageName}} . + +# starting up a container +docker run -p {{serverPort}}:{{serverPort}} {{packageName}} +``` \ No newline at end of file diff --git a/.codegen/server/__init__.mustache b/.codegen/server/__init__.mustache new file mode 100644 index 0000000..e69de29 diff --git a/.codegen/server/__init__model.mustache b/.codegen/server/__init__model.mustache new file mode 100644 index 0000000..98b56e3 --- /dev/null +++ b/.codegen/server/__init__model.mustache @@ -0,0 +1,7 @@ +# coding: utf-8 + +# flake8: noqa +from __future__ import absolute_import +# import models into model package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}} +{{/models}} \ No newline at end of file diff --git a/.codegen/server/__init__test.mustache b/.codegen/server/__init__test.mustache new file mode 100644 index 0000000..cecbffd --- /dev/null +++ b/.codegen/server/__init__test.mustache @@ -0,0 +1,16 @@ +import logging + +import connexion +from flask_testing import TestCase + +from {{packageName}}.encoder import JSONEncoder + + +class BaseTestCase(TestCase): + + def create_app(self): + logging.getLogger('connexion.operation').setLevel('ERROR') + app = connexion.App(__name__, specification_dir='../openapi/') + app.app.json_encoder = JSONEncoder + app.add_api('openapi.yaml', pythonic_params=True) + return app.app diff --git a/.codegen/server/__main__.mustache b/.codegen/server/__main__.mustache new file mode 100644 index 0000000..b6018a3 --- /dev/null +++ b/.codegen/server/__main__.mustache @@ -0,0 +1,32 @@ +{{#supportPython2}} +#!/usr/bin/env python +{{/supportPython2}} +{{^supportPython2}} +#!/usr/bin/env python3 +{{/supportPython2}} + +import connexion +{{#featureCORS}} +from flask_cors import CORS +{{/featureCORS}} + +from {{packageName}} import encoder + + +def main(): + app = connexion.App(__name__, specification_dir='./openapi/') + app.app.json_encoder = encoder.JSONEncoder + app.add_api('openapi.yaml', + arguments={'title': '{{appName}}'}, + pythonic_params=True) + +{{#featureCORS}} + # add CORS support + CORS(app.app) + +{{/featureCORS}} + app.run(port={{serverPort}}) + + +if __name__ == '__main__': + main() diff --git a/.codegen/server/base_model_.mustache b/.codegen/server/base_model_.mustache new file mode 100644 index 0000000..e41e51e --- /dev/null +++ b/.codegen/server/base_model_.mustache @@ -0,0 +1,73 @@ +import pprint + +import six +{{^supportPython2}} +import typing +{{/supportPython2}} + +from {{packageName}} import util +{{^supportPython2}} + +T = typing.TypeVar('T') +{{/supportPython2}} + + +class Model(object): + # openapiTypes: The key is attribute name and the + # value is attribute type. + openapi_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls{{^supportPython2}}: typing.Type[T]{{/supportPython2}}, dikt){{^supportPython2}} -> T{{/supportPython2}}: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/.codegen/server/controller.mustache b/.codegen/server/controller.mustache new file mode 100644 index 0000000..4fde589 --- /dev/null +++ b/.codegen/server/controller.mustache @@ -0,0 +1,116 @@ +import connexion +import six + +{{#imports}}{{import}} # noqa: E501 +{{/imports}} +from {{packageName}} import util +from {{packageName}}.core.controllers import {{classFilename}} as controller +{{#operations}} +{{#operation}} + + +def {{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{^-last}}, {{/-last}}{{/allParams}}): # noqa: E501 + """{{#summary}}{{.}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} + + {{#notes}}{{.}}{{/notes}} # noqa: E501 + + {{#allParams}} + :param {{paramName}}: {{description}} + {{^isContainer}} + {{#isPrimitiveType}} + :type {{paramName}}: {{>param_type}} + {{/isPrimitiveType}} + {{#isUuid}} + :type {{paramName}}: {{>param_type}} + {{/isUuid}} + {{^isPrimitiveType}} + {{#isFile}} + :type {{paramName}}: werkzeug.datastructures.FileStorage + {{/isFile}} + {{^isFile}} + {{^isUuid}} + :type {{paramName}}: dict | bytes + {{/isUuid}} + {{/isFile}} + {{/isPrimitiveType}} + {{/isContainer}} + {{#isArray}} + {{#items}} + {{#isPrimitiveType}} + :type {{paramName}}: List[{{>param_type}}] + {{/isPrimitiveType}} + {{^isPrimitiveType}} + :type {{paramName}}: list | bytes + {{/isPrimitiveType}} + {{/items}} + {{/isArray}} + {{#isMap}} + {{#items}} + {{#isPrimitiveType}} + :type {{paramName}}: Dict[str, {{>param_type}}] + {{/isPrimitiveType}} + {{^isPrimitiveType}} + :type {{paramName}}: dict | bytes + {{/isPrimitiveType}} + {{/items}} + {{/isMap}} + {{/allParams}} + + :rtype: {{#returnType}}{{.}}{{/returnType}}{{^returnType}}None{{/returnType}} + """ + {{#allParams}} + {{^isContainer}} + {{#isDate}} + {{paramName}} = util.deserialize_date({{paramName}}) + {{/isDate}} + {{#isDateTime}} + {{paramName}} = util.deserialize_datetime({{paramName}}) + {{/isDateTime}} + {{^isPrimitiveType}} + {{^isFile}} + {{^isUuid}} + if connexion.request.is_json: + {{paramName}} = {{#baseType}}{{baseType}}{{/baseType}}{{^baseType}}{{#dataType}} {{dataType}}{{/dataType}}{{/baseType}}.from_dict(connexion.request.get_json()) # noqa: E501 + {{/isUuid}} + {{/isFile}} + {{/isPrimitiveType}} + {{/isContainer}} + {{#isArray}} + {{#items}} + {{#isDate}} + if connexion.request.is_json: + {{paramName}} = [util.deserialize_date(s) for s in connexion.request.get_json()] # noqa: E501 + {{/isDate}} + {{#isDateTime}} + if connexion.request.is_json: + {{paramName}} = [util.deserialize_datetime(s) for s in connexion.request.get_json()] # noqa: E501 + {{/isDateTime}} + {{#complexType}} + if connexion.request.is_json: + {{paramName}} = [{{complexType}}.from_dict(d) for d in connexion.request.get_json()] # noqa: E501 + {{/complexType}} + {{/items}} + {{/isArray}} + {{#isMap}} + {{#items}} + {{#isDate}} + if connexion.request.is_json: + {{paramName}} = {k: util.deserialize_date(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/isDate}} + {{#isDateTime}} + if connexion.request.is_json: + {{paramName}} = {k: util.deserialize_datetime(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/isDateTime}} + {{#complexType}} + if connexion.request.is_json: + {{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/complexType}} + {{/items}} + {{/isMap}} + {{/allParams}} + return controller.{{operationId}}( + {{#allParams}}{{paramName}}={{paramName}}{{^-last}}, + {{/-last}}{{/allParams}} + ) +{{/operation}} +{{/operations}} diff --git a/.codegen/server/controller_test.mustache b/.codegen/server/controller_test.mustache new file mode 100644 index 0000000..ab901b2 --- /dev/null +++ b/.codegen/server/controller_test.mustache @@ -0,0 +1,57 @@ +# coding: utf-8 + +from __future__ import absolute_import +import unittest + +from flask import json +from six import BytesIO + +{{#imports}}{{import}} # noqa: E501 +{{/imports}} +from {{packageName}}.test import BaseTestCase + + +class {{#operations}}Test{{classname}}(BaseTestCase): + """{{classname}} integration test stubs""" + + {{#operation}} + {{#vendorExtensions.x-skip-test}} + @unittest.skip("{{reason}}") + {{/vendorExtensions.x-skip-test}} + def test_{{operationId}}(self): + """Test case for {{{operationId}}} + + {{{summary}}} + """ + {{#bodyParam}} + {{paramName}} = {{{example}}} + {{/bodyParam}} + {{#queryParams}} + {{#-first}}query_string = [{{/-first}}{{^-first}} {{/-first}}('{{^vendorExtensions.x-python-connexion-openapi-name}}{{paramName}}{{/vendorExtensions.x-python-connexion-openapi-name}}{{#vendorExtensions.x-python-connexion-openapi-name}}{{vendorExtensions.x-python-connexion-openapi-name}}{{/vendorExtensions.x-python-connexion-openapi-name}}', {{{example}}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}} + {{/queryParams}} + headers = { {{#vendorExtensions.x-prefered-produce}} + 'Accept': '{{mediaType}}',{{/vendorExtensions.x-prefered-produce}}{{#vendorExtensions.x-prefered-consume}} + 'Content-Type': '{{mediaType}}',{{/vendorExtensions.x-prefered-consume}}{{#headerParams}} + '{{paramName}}': {{{example}}},{{/headerParams}}{{#authMethods}} + {{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}'{{name}}': 'special-key',{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'Basic Zm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}} + } + {{#formParams}} + {{#-first}}data = dict({{/-first}}{{^-first}} {{/-first}}{{paramName}}={{{example}}}{{^-last}},{{/-last}}{{#-last}}){{/-last}} + {{/formParams}} + response = self.client.open( + '{{#contextPath}}{{{.}}}{{/contextPath}}{{{path}}}'{{#pathParams}}{{#-first}}.format({{/-first}}{{paramName}}={{{example}}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/pathParams}}, + method='{{httpMethod}}', + headers=headers{{#bodyParam}}, + data=json.dumps({{paramName}}){{^consumes}}, + content_type='application/json'{{/consumes}}{{/bodyParam}}{{#formParams}}{{#-first}}, + data=data{{/-first}}{{/formParams}}{{#consumes}}{{#-first}}, + content_type='{{{mediaType}}}'{{/-first}}{{/consumes}}{{#queryParams}}{{#-first}}, + query_string=query_string{{/-first}}{{/queryParams}}) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + {{/operation}} +{{/operations}} + +if __name__ == '__main__': + unittest.main() diff --git a/.codegen/server/dockerignore.mustache b/.codegen/server/dockerignore.mustache new file mode 100644 index 0000000..f961960 --- /dev/null +++ b/.codegen/server/dockerignore.mustache @@ -0,0 +1,72 @@ +.travis.yaml +.openapi-generator-ignore +README.md +tox.ini +git_push.sh +test-requirements.txt +setup.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/.codegen/server/encoder.mustache b/.codegen/server/encoder.mustache new file mode 100644 index 0000000..c2c402b --- /dev/null +++ b/.codegen/server/encoder.mustache @@ -0,0 +1,20 @@ +from connexion.apps.flask_app import FlaskJSONEncoder +import six + +from {{modelPackage}}.base_model_ import Model + + +class JSONEncoder(FlaskJSONEncoder): + include_nulls = False + + def default(self, o): + if isinstance(o, Model): + dikt = {} + for attr, _ in six.iteritems(o.openapi_types): + value = getattr(o, attr) + if value is None and not self.include_nulls: + continue + attr = o.attribute_map[attr] + dikt[attr] = value + return dikt + return FlaskJSONEncoder.default(self, o) diff --git a/.codegen/server/git_push.sh.mustache b/.codegen/server/git_push.sh.mustache new file mode 100644 index 0000000..8b3f689 --- /dev/null +++ b/.codegen/server/git_push.sh.mustache @@ -0,0 +1,58 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="{{{gitHost}}}" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/.codegen/server/gitignore.mustache b/.codegen/server/gitignore.mustache new file mode 100644 index 0000000..43995bd --- /dev/null +++ b/.codegen/server/gitignore.mustache @@ -0,0 +1,66 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.venv/ +.python-version +.pytest_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/.codegen/server/model.mustache b/.codegen/server/model.mustache new file mode 100644 index 0000000..0c4309a --- /dev/null +++ b/.codegen/server/model.mustache @@ -0,0 +1,172 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from {{modelPackage}}.base_model_ import Model +{{#models}} +{{#model}} +{{#pyImports}} +{{import}} +{{/pyImports}} +{{/model}} +{{/models}} +from {{packageName}} import util + +{{#imports}} +{{{import}}} # noqa: E501 +{{/imports}} + +{{#models}} +{{#model}} +class {{classname}}(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ +{{#allowableValues}} + + """ + allowed enum values + """ +{{#enumVars}} + {{name}} = {{{value}}}{{^-last}} +{{/-last}} +{{/enumVars}} +{{/allowableValues}} + + def __init__(self{{#vars}}, {{name}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): # noqa: E501 + """{{classname}} - a model defined in OpenAPI + + {{#vars}} + :param {{name}}: The {{name}} of this {{classname}}. # noqa: E501 + :type {{name}}: {{dataType}} + {{/vars}} + """ + self.openapi_types = { +{{#vars}} + '{{name}}': {{{dataType}}}{{^-last}},{{/-last}} +{{/vars}} + } + + self.attribute_map = { +{{#vars}} + '{{name}}': '{{baseName}}'{{^-last}},{{/-last}} +{{/vars}} + } +{{#vars}}{{#-first}} +{{/-first}} + self._{{name}} = {{name}} +{{/vars}} + + @classmethod + def from_dict(cls, dikt){{^supportPython2}} -> '{{classname}}'{{/supportPython2}}: + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The {{name}} of this {{classname}}. # noqa: E501 + :rtype: {{classname}} + """ + return util.deserialize_model(dikt, cls){{#vars}}{{#-first}} + +{{/-first}} + @property + def {{name}}(self): + """Gets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} # noqa: E501 + {{/description}} + + :return: The {{name}} of this {{classname}}. + :rtype: {{dataType}} + """ + return self._{{name}} + + @{{name}}.setter + def {{name}}(self, {{name}}): + """Sets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} # noqa: E501 + {{/description}} + + :param {{name}}: The {{name}} of this {{classname}}. + :type {{name}}: {{dataType}} + """ +{{#isEnum}} +{{#isContainer}} + allowed_values = [{{#isNullable}}None,{{/isNullable}}{{#allowableValues}}{{#values}}{{#items.isString}}"{{/items.isString}}{{{this}}}{{#items.isString}}"{{/items.isString}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 +{{#isArray}} + if not set({{{name}}}).issubset(set(allowed_values)): + raise ValueError( + "Invalid values for `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}) - set(allowed_values))), # noqa: E501 + ", ".join(map(str, allowed_values))) + ) +{{/isArray}} +{{#isMap}} + if not set({{{name}}}.keys()).issubset(set(allowed_values)): + raise ValueError( + "Invalid keys in `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))), # noqa: E501 + ", ".join(map(str, allowed_values))) + ) +{{/isMap}} +{{/isContainer}} +{{^isContainer}} + allowed_values = [{{#isNullable}}None,{{/isNullable}}{{#allowableValues}}{{#values}}{{#isString}}"{{/isString}}{{{this}}}{{#isString}}"{{/isString}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 + if {{{name}}} not in allowed_values: + raise ValueError( + "Invalid value for `{{{name}}}` ({0}), must be one of {1}" + .format({{{name}}}, allowed_values) + ) +{{/isContainer}} +{{/isEnum}} +{{^isEnum}} +{{#required}} + if {{name}} is None: + raise ValueError("Invalid value for `{{name}}`, must not be `None`") # noqa: E501 +{{/required}} +{{#hasValidation}} +{{#maxLength}} + if {{name}} is not None and len({{name}}) > {{maxLength}}: + raise ValueError("Invalid value for `{{name}}`, length must be less than or equal to `{{maxLength}}`") # noqa: E501 +{{/maxLength}} +{{#minLength}} + if {{name}} is not None and len({{name}}) < {{minLength}}: + raise ValueError("Invalid value for `{{name}}`, length must be greater than or equal to `{{minLength}}`") # noqa: E501 +{{/minLength}} +{{#maximum}} + if {{name}} is not None and {{name}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}: # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`") # noqa: E501 +{{/maximum}} +{{#minimum}} + if {{name}} is not None and {{name}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}: # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") # noqa: E501 +{{/minimum}} +{{#pattern}} + if {{name}} is not None and not re.search(r'{{{vendorExtensions.x-regex}}}', {{name}}{{#vendorExtensions.x-modifiers}}{{#-first}}, flags={{/-first}}re.{{.}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}): # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a follow pattern or equal to `{{{pattern}}}`") # noqa: E501 +{{/pattern}} +{{#maxItems}} + if {{name}} is not None and len({{name}}) > {{maxItems}}: + raise ValueError("Invalid value for `{{name}}`, number of items must be less than or equal to `{{maxItems}}`") # noqa: E501 +{{/maxItems}} +{{#minItems}} + if {{name}} is not None and len({{name}}) < {{minItems}}: + raise ValueError("Invalid value for `{{name}}`, number of items must be greater than or equal to `{{minItems}}`") # noqa: E501 +{{/minItems}} +{{/hasValidation}} +{{/isEnum}} + + self._{{name}} = {{name}}{{^-last}} + +{{/-last}} +{{/vars}} + +{{/model}} +{{/models}} diff --git a/.codegen/server/openapi.mustache b/.codegen/server/openapi.mustache new file mode 100644 index 0000000..51ebafb --- /dev/null +++ b/.codegen/server/openapi.mustache @@ -0,0 +1 @@ +{{{openapi-yaml}}} \ No newline at end of file diff --git a/.codegen/server/param_type.mustache b/.codegen/server/param_type.mustache new file mode 100644 index 0000000..21e7e07 --- /dev/null +++ b/.codegen/server/param_type.mustache @@ -0,0 +1 @@ +{{#isString}}str{{/isString}}{{#isInteger}}int{{/isInteger}}{{#isLong}}int{{/isLong}}{{#isFloat}}float{{/isFloat}}{{#isDouble}}float{{/isDouble}}{{#isByteArray}}str{{/isByteArray}}{{#isBinary}}str{{/isBinary}}{{#isBoolean}}bool{{/isBoolean}}{{#isDate}}str{{/isDate}}{{#isDateTime}}str{{/isDateTime}} \ No newline at end of file diff --git a/.codegen/server/requirements.mustache b/.codegen/server/requirements.mustache new file mode 100644 index 0000000..6e2cf51 --- /dev/null +++ b/.codegen/server/requirements.mustache @@ -0,0 +1,24 @@ +connexion[swagger-ui] >= 2.6.0; python_version>="3.6" +# 2.3 is the last version that supports python 3.4-3.5 +connexion[swagger-ui] <= 2.3.0; python_version=="3.5" or python_version=="3.4" +{{#supportPython2}} +connexion[swagger-ui] == 2.4.0; python_version<="2.7" +{{/supportPython2}} +# connexion requires werkzeug but connexion < 2.4.0 does not install werkzeug +# we must peg werkzeug versions below to fix connexion +# https://github.com/zalando/connexion/pull/1044 +werkzeug == 0.16.1; python_version=="3.5" or python_version=="3.4" +swagger-ui-bundle >= 0.0.2 +python_dateutil >= 2.6.0 +{{#featureCORS}} +# should support both Python 2 and Python 3 +flask-cors >= 3.0.10 +{{/featureCORS}} +{{#supportPython2}} +typing >= 3.5.2.2 +# For specs with timestamps, pyyaml 5.3 broke connexion's spec parsing in python 2. +# Connexion uses copy.deepcopy() on the spec, thus hitting this bug: +# https://github.com/yaml/pyyaml/issues/387 +pyyaml < 5.3; python_version<="2.7" +{{/supportPython2}} +setuptools >= 21.0.0 diff --git a/.codegen/server/security_controller_.mustache b/.codegen/server/security_controller_.mustache new file mode 100644 index 0000000..f6293ad --- /dev/null +++ b/.codegen/server/security_controller_.mustache @@ -0,0 +1,90 @@ +from typing import List + +{{#authMethods}} +{{#isOAuth}} + +def info_from_{{name}}(token): + """ + Validate and decode token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + 'scope' or 'scopes' will be passed to scope validation function. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'} + + +def validate_scope_{{name}}(required_scopes, token_scopes): + """ + Validate required scopes are included in token scope + + :param required_scopes Required scope to access called API + :type required_scopes: List[str] + :param token_scopes Scope present in token + :type token_scopes: List[str] + :return: True if access to called API is allowed + :rtype: bool + """ + return set(required_scopes).issubset(set(token_scopes)) + +{{/isOAuth}} +{{#isApiKey}} + +def info_from_{{name}}(api_key, required_scopes): + """ + Check and retrieve authentication information from api_key. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param api_key API key provided by Authorization header + :type api_key: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} + +{{/isApiKey}} +{{#isBasicBasic}} + +def info_from_{{name}}(username, password, required_scopes): + """ + Check and retrieve authentication information from basic auth. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param username login provided by Authorization header + :type username: str + :param password password provided by Authorization header + :type password: str + :param required_scopes Always None. Used for other authentication method + :type required_scopes: None + :return: Information attached to user or None if credentials are invalid or does not allow access to called API + :rtype: dict | None + """ + return {'uid': 'user_id'} + +{{/isBasicBasic}} +{{#isBasicBearer}} + +def info_from_{{name}}(token): + """ + Check and retrieve authentication information from custom bearer token. + Returned value will be passed in 'token_info' parameter of your operation function, if there is one. + 'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one. + + :param token Token provided by Authorization header + :type token: str + :return: Decoded token information or None if token is invalid + :rtype: dict | None + """ + return {'uid': 'user_id'} + +{{/isBasicBearer}} +{{/authMethods}} + diff --git a/.codegen/server/setup.mustache b/.codegen/server/setup.mustache new file mode 100644 index 0000000..722f439 --- /dev/null +++ b/.codegen/server/setup.mustache @@ -0,0 +1,41 @@ +# coding: utf-8 + +import sys +from setuptools import setup, find_packages + +NAME = "{{packageName}}" +VERSION = "{{packageVersion}}" +{{#apiInfo}}{{#apis}}{{#-last}} +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = [ + "connexion>=2.0.2", + "swagger-ui-bundle>=0.0.2", + "python_dateutil>=2.6.0"{{#supportPython2}}, + "typing>=3.5.2.2"{{/supportPython2}} +] + +setup( + name=NAME, + version=VERSION, + description="{{appName}}", + author_email="{{infoEmail}}", + url="{{packageUrl}}", + keywords=["OpenAPI", "{{appName}}"], + install_requires=REQUIRES, + packages=find_packages({{#pythonSrcRoot}}"{{{.}}}"{{/pythonSrcRoot}}),{{#pythonSrcRoot}} + package_dir={"": "{{{.}}}"},{{/pythonSrcRoot}} + package_data={'': ['{{#pythonSrcRoot}}{{{.}}}/{{/pythonSrcRoot}}openapi/openapi.yaml']}, + include_package_data=True, + entry_points={ + 'console_scripts': ['{{packageName}}={{packageName}}.__main__:main']}, + long_description="""\ + {{appDescription}} + """ +) +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/.codegen/server/test-requirements.mustache b/.codegen/server/test-requirements.mustache new file mode 100644 index 0000000..7157c73 --- /dev/null +++ b/.codegen/server/test-requirements.mustache @@ -0,0 +1,13 @@ +{{#useNose}} +coverage>=4.0.3 +nose>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 +{{/useNose}} +{{^useNose}} +pytest~=4.6.7 # needed for python 2.7+3.4 +pytest-cov>=2.8.1 +pytest-randomly==1.2.3 # needed for python 2.7+3.4 +{{/useNose}} +Flask-Testing==0.8.0 diff --git a/.codegen/server/tox.mustache b/.codegen/server/tox.mustache new file mode 100644 index 0000000..d342cd5 --- /dev/null +++ b/.codegen/server/tox.mustache @@ -0,0 +1,11 @@ +[tox] +envlist = {{#supportPython2}}py27, {{/supportPython2}}py3 +skipsdist=True + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + {toxinidir} + +commands= + {{^useNose}}pytest --cov={{{pythonSrcRoot}}}{{{packageName}}}{{/useNose}}{{#useNose}}nosetests{{/useNose}} diff --git a/.codegen/server/travis.mustache b/.codegen/server/travis.mustache new file mode 100644 index 0000000..e03b816 --- /dev/null +++ b/.codegen/server/travis.mustache @@ -0,0 +1,17 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: +{{#supportPython2}} + - "2.7" +{{/supportPython2}} + - "3.2" + - "3.3" + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" +# command to install dependencies +install: "pip install -r requirements.txt" +# command to run tests +script: nosetests diff --git a/.codegen/server/typing_utils.mustache b/.codegen/server/typing_utils.mustache new file mode 100644 index 0000000..0563f81 --- /dev/null +++ b/.codegen/server/typing_utils.mustache @@ -0,0 +1,32 @@ +# coding: utf-8 + +import sys + +if sys.version_info < (3, 7): + import typing + + def is_generic(klass): + """ Determine whether klass is a generic class """ + return type(klass) == typing.GenericMeta + + def is_dict(klass): + """ Determine whether klass is a Dict """ + return klass.__extra__ == dict + + def is_list(klass): + """ Determine whether klass is a List """ + return klass.__extra__ == list + +else: + + def is_generic(klass): + """ Determine whether klass is a generic class """ + return hasattr(klass, '__origin__') + + def is_dict(klass): + """ Determine whether klass is a Dict """ + return klass.__origin__ == dict + + def is_list(klass): + """ Determine whether klass is a List """ + return klass.__origin__ == list diff --git a/.codegen/server/util.mustache b/.codegen/server/util.mustache new file mode 100644 index 0000000..f2b9000 --- /dev/null +++ b/.codegen/server/util.mustache @@ -0,0 +1,142 @@ +import datetime + +import six +import typing +from {{packageName}} import typing_utils + + +def _deserialize(data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in six.integer_types or klass in (float, str, bool, bytearray): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif typing_utils.is_generic(klass): + if typing_utils.is_list(klass): + return _deserialize_list(data, klass.__args__[0]) + if typing_utils.is_dict(klass): + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = six.u(data) + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return an original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.openapi_types: + return data + + for attr, attr_type in six.iteritems(instance.openapi_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) + for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) + for k, v in six.iteritems(data)} diff --git a/server/openapi_server/core/__init__.py b/server/openapi_server/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/openapi_server/core/health_check_controller.py b/server/openapi_server/core/health_check_controller.py new file mode 100644 index 0000000..0d32730 --- /dev/null +++ b/server/openapi_server/core/health_check_controller.py @@ -0,0 +1,19 @@ +from openapi_server.models.error import Error # noqa: E501 +from openapi_server.models.health_check import HealthCheck # noqa: E501 + + +def get_health_check(): # noqa: E501 + """Get health check information + + Get information about the health of the service # noqa: E501 + + + :rtype: HealthCheck + """ + try: + res = HealthCheck(status="pass") + status = 200 + except Exception as error: + status = 500 + res = Error("Internal error", status, str(error)) + return res, status diff --git a/server/openapi_server/core/security_controller_.py b/server/openapi_server/core/security_controller_.py new file mode 100644 index 0000000..bc1adc3 --- /dev/null +++ b/server/openapi_server/core/security_controller_.py @@ -0,0 +1 @@ +# from typing import List diff --git a/server/openapi_server/core/text_date_annotation_controller.py b/server/openapi_server/core/text_date_annotation_controller.py new file mode 100644 index 0000000..bdf7269 --- /dev/null +++ b/server/openapi_server/core/text_date_annotation_controller.py @@ -0,0 +1,69 @@ +import connexion +import re + +from openapi_server.models.error import Error # noqa: E501 +from openapi_server.models.text_date_annotation_request import \ + TextDateAnnotationRequest # noqa: E501 +from openapi_server.models.text_date_annotation import TextDateAnnotation +from openapi_server.models.text_date_annotation_response import \ + TextDateAnnotationResponse # noqa: E501 + + +def create_text_date_annotations(): # noqa: E501 + """Annotate dates in a clinical note + + Return the date annotations found in a clinical note # noqa: E501 + + :rtype: TextDateAnnotations + """ + res = None + status = None + if connexion.request.is_json: + try: + annotation_request = TextDateAnnotationRequest.from_dict( + connexion.request.get_json()) # noqa: E501 + note = annotation_request._note + + annotations = [] + # Adapted from https://stackoverflow.com/a/61234139 + matches = re.finditer( + "([1-9]|0[1-9]|1[0-2])(/)([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])" + + "(/)(19[0-9][0-9]|20[0-9][0-9])", note._text) + add_date_annotation(annotations, matches, "MM/DD/YYYY") + + matches = re.finditer( + "([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])(\\.)([1-9]|0[1-9]|" + + "1[0-2])(\\.)(19[0-9][0-9]|20[0-9][0-9])", note._text) + add_date_annotation(annotations, matches, "DD.MM.YYYY") + + matches = re.finditer( + "([1-9][1-9][0-9][0-9]|2[0-9][0-9][0-9])", note._text) + add_date_annotation(annotations, matches, "YYYY") + + matches = re.finditer( + "(January|February|March|April|May|June|July|August|" + + "September|October|November|December)", + note._text, re.IGNORECASE) + add_date_annotation(annotations, matches, "MMMM") + + res = TextDateAnnotationResponse(annotations) + status = 200 + except Exception as error: + status = 500 + res = Error("Internal error", status, str(error)) + return res, status + + +def add_date_annotation(annotations, matches, date_format): + """ + Converts matches to TextDateAnnotation objects and adds them to the + annotations array specified. + """ + for match in matches: + annotations.append(TextDateAnnotation( + start=match.start(), + length=len(match[0]), + text=match[0], + date_format=date_format, + confidence=95.5 + )) diff --git a/server/openapi_server/core/tool_controller.py b/server/openapi_server/core/tool_controller.py new file mode 100644 index 0000000..cb66937 --- /dev/null +++ b/server/openapi_server/core/tool_controller.py @@ -0,0 +1,37 @@ +from openapi_server.models.tool import Tool # noqa: E501 +from openapi_server.models.tool_dependencies import ToolDependencies # noqa: E501 +from openapi_server.models.license import License + + +def get_tool(): # noqa: E501 + """Get tool information + + Get information about the tool # noqa: E501 + + + :rtype: Tool + """ + tool = Tool( + name="date-annotator-example", + version="1.0.1", + license=License.APACHE_2_0, + repository="github:nlpsandbox/date-annotator-example", + description="Example implementation of the NLP Sandbox Date Annotator", + author="The NLP Sandbox Team", + author_email="thomas.schaffter@sagebionetworks.org", + url="https://github.com/nlpsandbox/date-annotator-example", + tool_type="nlpsandbox:date-annotator", + tool_api_version="1.0.1" + ) + return tool, 200 + + +def get_tool_dependencies(): # noqa: E501 + """Get tool dependencies + + Get the dependencies of this tool # noqa: E501 + + + :rtype: ToolDependencies + """ + return ToolDependencies(tool_dependencies=[]), 200 From c2d2630e8a01a360a1db9e01f45dd651100cefc9 Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 22:47:31 +0800 Subject: [PATCH 02/11] Auto generate code from templates --- package.json | 4 +- server/.openapi-generator-ignore | 1 + server/.openapi-generator/VERSION | 2 +- .../controllers/health_check_controller.py | 15 ++-- .../controllers/security_controller_.py | 4 +- .../text_date_annotation_controller.py | 72 ++++--------------- .../controllers/tool_controller.py | 25 +++---- server/openapi_server/test/__init__.py | 16 +++++ server/openapi_server/util.py | 1 + 9 files changed, 57 insertions(+), 83 deletions(-) diff --git a/package.json b/package.json index e29bc2b..df8edb9 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "@openapitools/openapi-generator-cli": "2.1.26" }, "scripts": { - "generate:server": "openapi-generator-cli generate -g python-flask -o server -i $npm_config_specification", - "generate:server:latest": "openapi-generator-cli generate -g python-flask -o server -i https://nlpsandbox.github.io/nlpsandbox-schemas/date-annotator/latest/openapi.json", + "generate:server": "openapi-generator-cli generate -g python-flask -o server -i $npm_config_specification -t .codegen/server", + "generate:server:latest": "openapi-generator-cli generate -g python-flask -o server -i https://nlpsandbox.github.io/nlpsandbox-schemas/date-annotator/latest/openapi.json -t .codegen/server", "lint": "cd server && flake8", "test": "cd server && tox" } diff --git a/server/.openapi-generator-ignore b/server/.openapi-generator-ignore index c18c1fc..2241a11 100644 --- a/server/.openapi-generator-ignore +++ b/server/.openapi-generator-ignore @@ -34,3 +34,4 @@ Dockerfile requirements.txt test-requirements.txt tox.ini +openapi_server/__main__.py diff --git a/server/.openapi-generator/VERSION b/server/.openapi-generator/VERSION index 28cbf7c..32f3eaa 100644 --- a/server/.openapi-generator/VERSION +++ b/server/.openapi-generator/VERSION @@ -1 +1 @@ -5.0.0 \ No newline at end of file +5.0.1 \ No newline at end of file diff --git a/server/openapi_server/controllers/health_check_controller.py b/server/openapi_server/controllers/health_check_controller.py index 0d32730..ac36b8b 100644 --- a/server/openapi_server/controllers/health_check_controller.py +++ b/server/openapi_server/controllers/health_check_controller.py @@ -1,5 +1,10 @@ +import connexion +import six + from openapi_server.models.error import Error # noqa: E501 from openapi_server.models.health_check import HealthCheck # noqa: E501 +from openapi_server import util +from openapi_server.core.controllers import health_check_controller as controller def get_health_check(): # noqa: E501 @@ -10,10 +15,6 @@ def get_health_check(): # noqa: E501 :rtype: HealthCheck """ - try: - res = HealthCheck(status="pass") - status = 200 - except Exception as error: - status = 500 - res = Error("Internal error", status, str(error)) - return res, status + return controller.get_health_check( + + ) diff --git a/server/openapi_server/controllers/security_controller_.py b/server/openapi_server/controllers/security_controller_.py index bc1adc3..ecac405 100644 --- a/server/openapi_server/controllers/security_controller_.py +++ b/server/openapi_server/controllers/security_controller_.py @@ -1 +1,3 @@ -# from typing import List +from typing import List + + diff --git a/server/openapi_server/controllers/text_date_annotation_controller.py b/server/openapi_server/controllers/text_date_annotation_controller.py index bdf7269..744cef1 100644 --- a/server/openapi_server/controllers/text_date_annotation_controller.py +++ b/server/openapi_server/controllers/text_date_annotation_controller.py @@ -1,69 +1,25 @@ import connexion -import re +import six from openapi_server.models.error import Error # noqa: E501 -from openapi_server.models.text_date_annotation_request import \ - TextDateAnnotationRequest # noqa: E501 -from openapi_server.models.text_date_annotation import TextDateAnnotation -from openapi_server.models.text_date_annotation_response import \ - TextDateAnnotationResponse # noqa: E501 +from openapi_server.models.text_date_annotation_request import TextDateAnnotationRequest # noqa: E501 +from openapi_server.models.text_date_annotation_response import TextDateAnnotationResponse # noqa: E501 +from openapi_server import util +from openapi_server.core.controllers import text_date_annotation_controller as controller -def create_text_date_annotations(): # noqa: E501 +def create_text_date_annotations(text_date_annotation_request=None): # noqa: E501 """Annotate dates in a clinical note Return the date annotations found in a clinical note # noqa: E501 - :rtype: TextDateAnnotations - """ - res = None - status = None - if connexion.request.is_json: - try: - annotation_request = TextDateAnnotationRequest.from_dict( - connexion.request.get_json()) # noqa: E501 - note = annotation_request._note - - annotations = [] - # Adapted from https://stackoverflow.com/a/61234139 - matches = re.finditer( - "([1-9]|0[1-9]|1[0-2])(/)([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])" + - "(/)(19[0-9][0-9]|20[0-9][0-9])", note._text) - add_date_annotation(annotations, matches, "MM/DD/YYYY") - - matches = re.finditer( - "([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])(\\.)([1-9]|0[1-9]|" + - "1[0-2])(\\.)(19[0-9][0-9]|20[0-9][0-9])", note._text) - add_date_annotation(annotations, matches, "DD.MM.YYYY") - - matches = re.finditer( - "([1-9][1-9][0-9][0-9]|2[0-9][0-9][0-9])", note._text) - add_date_annotation(annotations, matches, "YYYY") + :param text_date_annotation_request: + :type text_date_annotation_request: dict | bytes - matches = re.finditer( - "(January|February|March|April|May|June|July|August|" + - "September|October|November|December)", - note._text, re.IGNORECASE) - add_date_annotation(annotations, matches, "MMMM") - - res = TextDateAnnotationResponse(annotations) - status = 200 - except Exception as error: - status = 500 - res = Error("Internal error", status, str(error)) - return res, status - - -def add_date_annotation(annotations, matches, date_format): + :rtype: TextDateAnnotationResponse """ - Converts matches to TextDateAnnotation objects and adds them to the - annotations array specified. - """ - for match in matches: - annotations.append(TextDateAnnotation( - start=match.start(), - length=len(match[0]), - text=match[0], - date_format=date_format, - confidence=95.5 - )) + if connexion.request.is_json: + text_date_annotation_request = TextDateAnnotationRequest.from_dict(connexion.request.get_json()) # noqa: E501 + return controller.create_text_date_annotations( + text_date_annotation_request=text_date_annotation_request + ) diff --git a/server/openapi_server/controllers/tool_controller.py b/server/openapi_server/controllers/tool_controller.py index cb66937..8ac01de 100644 --- a/server/openapi_server/controllers/tool_controller.py +++ b/server/openapi_server/controllers/tool_controller.py @@ -1,6 +1,11 @@ +import connexion +import six + +from openapi_server.models.error import Error # noqa: E501 from openapi_server.models.tool import Tool # noqa: E501 from openapi_server.models.tool_dependencies import ToolDependencies # noqa: E501 -from openapi_server.models.license import License +from openapi_server import util +from openapi_server.core.controllers import tool_controller as controller def get_tool(): # noqa: E501 @@ -11,19 +16,9 @@ def get_tool(): # noqa: E501 :rtype: Tool """ - tool = Tool( - name="date-annotator-example", - version="1.0.1", - license=License.APACHE_2_0, - repository="github:nlpsandbox/date-annotator-example", - description="Example implementation of the NLP Sandbox Date Annotator", - author="The NLP Sandbox Team", - author_email="thomas.schaffter@sagebionetworks.org", - url="https://github.com/nlpsandbox/date-annotator-example", - tool_type="nlpsandbox:date-annotator", - tool_api_version="1.0.1" + return controller.get_tool( + ) - return tool, 200 def get_tool_dependencies(): # noqa: E501 @@ -34,4 +29,6 @@ def get_tool_dependencies(): # noqa: E501 :rtype: ToolDependencies """ - return ToolDependencies(tool_dependencies=[]), 200 + return controller.get_tool_dependencies( + + ) diff --git a/server/openapi_server/test/__init__.py b/server/openapi_server/test/__init__.py index e69de29..364aba9 100644 --- a/server/openapi_server/test/__init__.py +++ b/server/openapi_server/test/__init__.py @@ -0,0 +1,16 @@ +import logging + +import connexion +from flask_testing import TestCase + +from openapi_server.encoder import JSONEncoder + + +class BaseTestCase(TestCase): + + def create_app(self): + logging.getLogger('connexion.operation').setLevel('ERROR') + app = connexion.App(__name__, specification_dir='../openapi/') + app.app.json_encoder = JSONEncoder + app.add_api('openapi.yaml', pythonic_params=True) + return app.app diff --git a/server/openapi_server/util.py b/server/openapi_server/util.py index 773d513..e1185a7 100644 --- a/server/openapi_server/util.py +++ b/server/openapi_server/util.py @@ -1,6 +1,7 @@ import datetime import six +import typing from openapi_server import typing_utils From 86f0a2960f4ac6a49beeeb507c19e5bc78d75714 Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 22:54:55 +0800 Subject: [PATCH 03/11] Add --- server/openapi_server/core/{ => controllers}/__init__.py | 0 .../core/{ => controllers}/health_check_controller.py | 0 .../openapi_server/core/{ => controllers}/security_controller_.py | 0 .../core/{ => controllers}/text_date_annotation_controller.py | 0 server/openapi_server/core/{ => controllers}/tool_controller.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename server/openapi_server/core/{ => controllers}/__init__.py (100%) rename server/openapi_server/core/{ => controllers}/health_check_controller.py (100%) rename server/openapi_server/core/{ => controllers}/security_controller_.py (100%) rename server/openapi_server/core/{ => controllers}/text_date_annotation_controller.py (100%) rename server/openapi_server/core/{ => controllers}/tool_controller.py (100%) diff --git a/server/openapi_server/core/__init__.py b/server/openapi_server/core/controllers/__init__.py similarity index 100% rename from server/openapi_server/core/__init__.py rename to server/openapi_server/core/controllers/__init__.py diff --git a/server/openapi_server/core/health_check_controller.py b/server/openapi_server/core/controllers/health_check_controller.py similarity index 100% rename from server/openapi_server/core/health_check_controller.py rename to server/openapi_server/core/controllers/health_check_controller.py diff --git a/server/openapi_server/core/security_controller_.py b/server/openapi_server/core/controllers/security_controller_.py similarity index 100% rename from server/openapi_server/core/security_controller_.py rename to server/openapi_server/core/controllers/security_controller_.py diff --git a/server/openapi_server/core/text_date_annotation_controller.py b/server/openapi_server/core/controllers/text_date_annotation_controller.py similarity index 100% rename from server/openapi_server/core/text_date_annotation_controller.py rename to server/openapi_server/core/controllers/text_date_annotation_controller.py diff --git a/server/openapi_server/core/tool_controller.py b/server/openapi_server/core/controllers/tool_controller.py similarity index 100% rename from server/openapi_server/core/tool_controller.py rename to server/openapi_server/core/controllers/tool_controller.py From b7d167e12eb5fa96771a5d30f04ab1ae0b66f635 Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 22:55:08 +0800 Subject: [PATCH 04/11] Add --- server/openapi_server/core/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 server/openapi_server/core/__init__.py diff --git a/server/openapi_server/core/__init__.py b/server/openapi_server/core/__init__.py new file mode 100644 index 0000000..e69de29 From dd51ea678e1cea9834e9fabc8ab0443414a2a357 Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 23:01:11 +0800 Subject: [PATCH 05/11] Fix --- .../text_date_annotation_controller.py | 67 +++++++++---------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/server/openapi_server/core/controllers/text_date_annotation_controller.py b/server/openapi_server/core/controllers/text_date_annotation_controller.py index bdf7269..c716d3d 100644 --- a/server/openapi_server/core/controllers/text_date_annotation_controller.py +++ b/server/openapi_server/core/controllers/text_date_annotation_controller.py @@ -1,15 +1,12 @@ -import connexion import re from openapi_server.models.error import Error # noqa: E501 -from openapi_server.models.text_date_annotation_request import \ - TextDateAnnotationRequest # noqa: E501 from openapi_server.models.text_date_annotation import TextDateAnnotation from openapi_server.models.text_date_annotation_response import \ TextDateAnnotationResponse # noqa: E501 -def create_text_date_annotations(): # noqa: E501 +def create_text_date_annotations(text_date_annotation_request): # noqa: E501 """Annotate dates in a clinical note Return the date annotations found in a clinical note # noqa: E501 @@ -18,39 +15,35 @@ def create_text_date_annotations(): # noqa: E501 """ res = None status = None - if connexion.request.is_json: - try: - annotation_request = TextDateAnnotationRequest.from_dict( - connexion.request.get_json()) # noqa: E501 - note = annotation_request._note - - annotations = [] - # Adapted from https://stackoverflow.com/a/61234139 - matches = re.finditer( - "([1-9]|0[1-9]|1[0-2])(/)([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])" + - "(/)(19[0-9][0-9]|20[0-9][0-9])", note._text) - add_date_annotation(annotations, matches, "MM/DD/YYYY") - - matches = re.finditer( - "([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])(\\.)([1-9]|0[1-9]|" + - "1[0-2])(\\.)(19[0-9][0-9]|20[0-9][0-9])", note._text) - add_date_annotation(annotations, matches, "DD.MM.YYYY") - - matches = re.finditer( - "([1-9][1-9][0-9][0-9]|2[0-9][0-9][0-9])", note._text) - add_date_annotation(annotations, matches, "YYYY") - - matches = re.finditer( - "(January|February|March|April|May|June|July|August|" + - "September|October|November|December)", - note._text, re.IGNORECASE) - add_date_annotation(annotations, matches, "MMMM") - - res = TextDateAnnotationResponse(annotations) - status = 200 - except Exception as error: - status = 500 - res = Error("Internal error", status, str(error)) + try: + note = text_date_annotation_request._note + annotations = [] + # Adapted from https://stackoverflow.com/a/61234139 + matches = re.finditer( + "([1-9]|0[1-9]|1[0-2])(/)([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])" + + "(/)(19[0-9][0-9]|20[0-9][0-9])", note._text) + add_date_annotation(annotations, matches, "MM/DD/YYYY") + + matches = re.finditer( + "([1-9]|0[1-9]|1[0-9]|2[0-9]|3[0-1])(\\.)([1-9]|0[1-9]|" + + "1[0-2])(\\.)(19[0-9][0-9]|20[0-9][0-9])", note._text) + add_date_annotation(annotations, matches, "DD.MM.YYYY") + + matches = re.finditer( + "([1-9][1-9][0-9][0-9]|2[0-9][0-9][0-9])", note._text) + add_date_annotation(annotations, matches, "YYYY") + + matches = re.finditer( + "(January|February|March|April|May|June|July|August|" + + "September|October|November|December)", + note._text, re.IGNORECASE) + add_date_annotation(annotations, matches, "MMMM") + + res = TextDateAnnotationResponse(annotations) + status = 200 + except Exception as error: + status = 500 + res = Error("Internal error", status, str(error)) return res, status From f4c49be652146665eb52f193e0dcd338a6fe8a02 Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 23:06:12 +0800 Subject: [PATCH 06/11] Remove noqa comments --- .../core/controllers/text_date_annotation_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/openapi_server/core/controllers/text_date_annotation_controller.py b/server/openapi_server/core/controllers/text_date_annotation_controller.py index c716d3d..ce0dd1f 100644 --- a/server/openapi_server/core/controllers/text_date_annotation_controller.py +++ b/server/openapi_server/core/controllers/text_date_annotation_controller.py @@ -1,12 +1,12 @@ import re -from openapi_server.models.error import Error # noqa: E501 +from openapi_server.models.error import Error from openapi_server.models.text_date_annotation import TextDateAnnotation from openapi_server.models.text_date_annotation_response import \ - TextDateAnnotationResponse # noqa: E501 + TextDateAnnotationResponse -def create_text_date_annotations(text_date_annotation_request): # noqa: E501 +def create_text_date_annotations(text_date_annotation_request): """Annotate dates in a clinical note Return the date annotations found in a clinical note # noqa: E501 From 541864abfa6ee2df9814af9963aec589bab16014 Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 23:07:02 +0800 Subject: [PATCH 07/11] Lint --- .../core/controllers/health_check_controller.py | 9 ++++----- .../core/controllers/tool_controller.py | 14 ++++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/server/openapi_server/core/controllers/health_check_controller.py b/server/openapi_server/core/controllers/health_check_controller.py index 0d32730..89d65e7 100644 --- a/server/openapi_server/core/controllers/health_check_controller.py +++ b/server/openapi_server/core/controllers/health_check_controller.py @@ -1,12 +1,11 @@ -from openapi_server.models.error import Error # noqa: E501 -from openapi_server.models.health_check import HealthCheck # noqa: E501 +from openapi_server.models.error import Error +from openapi_server.models.health_check import HealthCheck -def get_health_check(): # noqa: E501 +def get_health_check(): """Get health check information - Get information about the health of the service # noqa: E501 - + Get information about the health of the service :rtype: HealthCheck """ diff --git a/server/openapi_server/core/controllers/tool_controller.py b/server/openapi_server/core/controllers/tool_controller.py index cb66937..827a67b 100644 --- a/server/openapi_server/core/controllers/tool_controller.py +++ b/server/openapi_server/core/controllers/tool_controller.py @@ -1,13 +1,12 @@ -from openapi_server.models.tool import Tool # noqa: E501 -from openapi_server.models.tool_dependencies import ToolDependencies # noqa: E501 +from openapi_server.models.tool import Tool +from openapi_server.models.tool_dependencies import ToolDependencies from openapi_server.models.license import License -def get_tool(): # noqa: E501 +def get_tool(): """Get tool information - Get information about the tool # noqa: E501 - + Get information about the tool :rtype: Tool """ @@ -26,11 +25,10 @@ def get_tool(): # noqa: E501 return tool, 200 -def get_tool_dependencies(): # noqa: E501 +def get_tool_dependencies(): """Get tool dependencies - Get the dependencies of this tool # noqa: E501 - + Get the dependencies of this tool :rtype: ToolDependencies """ From 0018785ad1568f8a691e45e8bce824af603a1a0e Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 23:15:09 +0800 Subject: [PATCH 08/11] Add to omission --- server/setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/server/setup.cfg b/server/setup.cfg index 2434351..a59cafa 100644 --- a/server/setup.cfg +++ b/server/setup.cfg @@ -5,6 +5,7 @@ max-line-length: 80 exclude = .* openapi_server/models/*.py + openapi_server/controllers/*.py [coverage:run] omit = From a09f4c5fdf2cf9a1c17f1a2ef8d10c55b3bc8fef Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Tue, 9 Mar 2021 23:32:59 +0800 Subject: [PATCH 09/11] Comment out typing --- .codegen/server/util.mustache | 2 +- server/openapi_server/util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.codegen/server/util.mustache b/.codegen/server/util.mustache index f2b9000..707c83d 100644 --- a/.codegen/server/util.mustache +++ b/.codegen/server/util.mustache @@ -1,7 +1,7 @@ import datetime import six -import typing +# import typing from {{packageName}} import typing_utils diff --git a/server/openapi_server/util.py b/server/openapi_server/util.py index e1185a7..a8e57b5 100644 --- a/server/openapi_server/util.py +++ b/server/openapi_server/util.py @@ -1,7 +1,7 @@ import datetime import six -import typing +# import typing from openapi_server import typing_utils From 0cc564765263177e146fba821914dd65eda0568e Mon Sep 17 00:00:00 2001 From: Thomas Yu Date: Wed, 10 Mar 2021 11:29:05 +0800 Subject: [PATCH 10/11] Update server/openapi_server/core/controllers/text_date_annotation_controller.py --- .../core/controllers/text_date_annotation_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/openapi_server/core/controllers/text_date_annotation_controller.py b/server/openapi_server/core/controllers/text_date_annotation_controller.py index ce0dd1f..4d7614c 100644 --- a/server/openapi_server/core/controllers/text_date_annotation_controller.py +++ b/server/openapi_server/core/controllers/text_date_annotation_controller.py @@ -9,7 +9,7 @@ def create_text_date_annotations(text_date_annotation_request): """Annotate dates in a clinical note - Return the date annotations found in a clinical note # noqa: E501 + Return the date annotations found in a clinical note :rtype: TextDateAnnotations """ From 1cb3563c3435ffa42ca5c9e33e1c10c4a649bb0b Mon Sep 17 00:00:00 2001 From: thomasyu888 Date: Wed, 17 Mar 2021 20:25:54 +0800 Subject: [PATCH 11/11] Add --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 0dd8794..7ac292e 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,14 @@ The command below will run the unit and integration tests. npm run test +### Creating Openapi Generator Template + +This command will create the openapi-generator templates, for more information about [using templates](https://openapi-generator.tech/docs/templating/) + +``` +openapi-generator author template -g python -o .codegen/server +``` + ### Versioning This package uses [semantic versioning] for releasing new versions. Creating a