diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9dbae1..6eb03fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,7 +105,7 @@ jobs: uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push id: docker_build uses: docker/build-push-action@v2 diff --git a/README.md b/README.md index 29ac9c7..880aa3d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # NLP Sandbox Person Name Annotator Example [![GitHub Release](https://img.shields.io/github/release/nlpsandbox/person-name-annotator-example.svg?include_prereleases&color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/nlpsandbox/person-name-annotator-example/releases) -[![GitHub CI](https://img.shields.io/github/workflow/status/nlpsandbox/person-name-annotator-example/ci.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/nlpsandbox/person-name-annotator-example/actions) +[![GitHub CI](https://img.shields.io/github/workflow/status/nlpsandbox/person-name-annotator-example/CI.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/nlpsandbox/person-name-annotator-example/actions) [![GitHub License](https://img.shields.io/github/license/nlpsandbox/person-name-annotator-example.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&logo=github)](https://github.com/nlpsandbox/person-name-annotator-example/blob/develop/LICENSE) [![Docker Pulls](https://img.shields.io/docker/pulls/nlpsandbox/person-name-annotator-example.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=pulls&logo=docker)](https://hub.docker.com/r/nlpsandbox/person-name-annotator-example) [![Discord](https://img.shields.io/discord/770484164393828373.svg?color=94398d&labelColor=555555&logoColor=ffffff&style=for-the-badge&label=Discord&logo=discord)](https://nlpsandbox.io/discord "Realtime support / chat with the community and the team") @@ -19,8 +19,8 @@ found in the clinical note. ### Specification -- Person Name Annotator API version: 1.0.2 -- Tool version: 1.0.2 +- Person Name Annotator API version: 1.1.0 +- Tool version: 1.1.0 - Docker image: [nlpsandbox/person-name-annotator-example] ## Model @@ -52,13 +52,13 @@ You can stop the container run with `Ctrl+C`, followed by `docker-compose down`. We recommend using a Conda environment to install and run the Person Name Annotator. - conda create --name person-name-annotator python=3.9.1 + conda create --name person-name-annotator python=3.9.4 conda activate person-name-annotator Install and start the Person Name Annotator. cd server/ - pip install -e . + pip install -r requirements.txt python -m openapi_server ### Accessing the UI diff --git a/docker-compose.yml b/docker-compose.yml index 3516aa3..68781a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: person-name-annotator: - image: nlpsandbox/person-name-annotator-example:1.0.2 + image: nlpsandbox/person-name-annotator-example:1.1.0 build: context: server dockerfile: Dockerfile diff --git a/package.json b/package.json index de464b8..4d54c26 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,11 @@ "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/person-name-annotator/latest/openapi.json", + "generate:server:edge": "openapi-generator-cli generate -g python-flask -o server -i https://nlpsandbox.github.io/nlpsandbox-schemas/person-name-annotator/edge/openapi.json", "lint": "cd server && flake8", - "test": "cd server && tox" + "test": "cd server && tox", + "install:dependencies": "npm ci && cd server && pip install -r requirements.txt", + "start:dev": "cd server && python -m openapi_server", + "start:prod": "docker-compose up --build" } } diff --git a/server/openapi_server/__main__.py b/server/openapi_server/__main__.py index 6d59966..9a9c67b 100644 --- a/server/openapi_server/__main__.py +++ b/server/openapi_server/__main__.py @@ -15,7 +15,7 @@ def main(): - app.run(port=8080) + app.run(port=8080, debug=False) if __name__ == '__main__': diff --git a/server/openapi_server/controllers/tool_controller.py b/server/openapi_server/controllers/tool_controller.py index 2e7c8fd..aaf82fb 100644 --- a/server/openapi_server/controllers/tool_controller.py +++ b/server/openapi_server/controllers/tool_controller.py @@ -13,7 +13,7 @@ def get_tool(): # noqa: E501 """ tool = Tool( name="person-name-annotator-example", - version="1.0.2", + version="1.1.0", license=License.APACHE_2_0, repository="github:nlpsandbox/person-name-annotator-example", description="Example implementation of the NLP Sandbox Person " + @@ -22,7 +22,7 @@ def get_tool(): # noqa: E501 author_email="thomas.schaffter@sagebionetworks.org", url="https://github.com/nlpsandbox/person-name-annotator-example", type="nlpsandbox:person-name-annotator", - api_version="1.0.2" + api_version="1.1.0" ) return tool, 200 @@ -35,4 +35,4 @@ def get_tool_dependencies(): # noqa: E501 :rtype: ToolDependencies """ - return ToolDependencies(tool_dependencies=[]), 200 + return ToolDependencies(tools=[]), 200 diff --git a/server/openapi_server/models/tool_dependencies.py b/server/openapi_server/models/tool_dependencies.py index 2a18105..47ba1b0 100644 --- a/server/openapi_server/models/tool_dependencies.py +++ b/server/openapi_server/models/tool_dependencies.py @@ -17,21 +17,21 @@ class ToolDependencies(Model): Do not edit the class manually. """ - def __init__(self, tool_dependencies=None): # noqa: E501 + def __init__(self, tools=None): # noqa: E501 """ToolDependencies - a model defined in OpenAPI - :param tool_dependencies: The tool_dependencies of this ToolDependencies. # noqa: E501 - :type tool_dependencies: List[Tool] + :param tools: The tools of this ToolDependencies. # noqa: E501 + :type tools: List[Tool] """ self.openapi_types = { - 'tool_dependencies': List[Tool] + 'tools': List[Tool] } self.attribute_map = { - 'tool_dependencies': 'toolDependencies' + 'tools': 'tools' } - self._tool_dependencies = tool_dependencies + self._tools = tools @classmethod def from_dict(cls, dikt) -> 'ToolDependencies': @@ -45,26 +45,26 @@ def from_dict(cls, dikt) -> 'ToolDependencies': return util.deserialize_model(dikt, cls) @property - def tool_dependencies(self): - """Gets the tool_dependencies of this ToolDependencies. + def tools(self): + """Gets the tools of this ToolDependencies. A list of tools # noqa: E501 - :return: The tool_dependencies of this ToolDependencies. + :return: The tools of this ToolDependencies. :rtype: List[Tool] """ - return self._tool_dependencies + return self._tools - @tool_dependencies.setter - def tool_dependencies(self, tool_dependencies): - """Sets the tool_dependencies of this ToolDependencies. + @tools.setter + def tools(self, tools): + """Sets the tools of this ToolDependencies. A list of tools # noqa: E501 - :param tool_dependencies: The tool_dependencies of this ToolDependencies. - :type tool_dependencies: List[Tool] + :param tools: The tools of this ToolDependencies. + :type tools: List[Tool] """ - if tool_dependencies is None: - raise ValueError("Invalid value for `tool_dependencies`, must not be `None`") # noqa: E501 + if tools is None: + raise ValueError("Invalid value for `tools`, must not be `None`") # noqa: E501 - self._tool_dependencies = tool_dependencies + self._tools = tools diff --git a/server/openapi_server/openapi/openapi.yaml b/server/openapi_server/openapi/openapi.yaml index 0ffc517..075e6d7 100644 --- a/server/openapi_server/openapi/openapi.yaml +++ b/server/openapi_server/openapi/openapi.yaml @@ -1,24 +1,25 @@ openapi: 3.0.3 info: contact: - email: thomas.schaffter@sagebionetworks.org - name: The NLP Sandbox Team + email: team@nlpsandbox.io + name: NLP Sandbox Team url: https://nlpsandbox.io description: | # Introduction - The Person Name Annotator is one of the first type of NLP Tools that can be benchmarked on [nlpsandbox.io](https://nlpsandbox.io). A Person Name Annotator takes as input a clinical note and outputs a list of predicted person name annotations found in the clinical note. This OpenAPI document describes the specification of a Person Name Annotator. This specification includes the schemas of the input and output data, and the conditions that this annotator must meet if you want to benchmark its performance on [nlpsandbox.io](https://nlpsandbox.io). + A Person Name Annotator takes as input a clinical note and outputs a list of predicted person name annotations found in the clinical note. This OpenAPI document describes the specification of a Person Name Annotator. This specification includes the schemas of the input and output data, and the conditions that this annotator must meet if you want to benchmark its performance on [nlpsandbox.io](https://nlpsandbox.io). # Getting Started - The GitHub repository [nlpsandbox/person-name-annotator-example](https://github.com/nlpsandbox/person-name-annotator-example) provides a simple example implementation of a Python-Flask Person Name Annotator. By the end of the tutorial available in this repository, you will have built a Docker image for a simple Person Name Annotator. You will then be able to submit this image to [nlpsandbox.io](https://nlpsandbox.io) to benchmark its performance. + The GitHub repository [nlpsandbox/person-name-annotator-example](https://github.com/nlpsandbox/person-name-annotator-example) provides a simple example implementation of a Python-Flask Person Name Annotator. By the end of the tutorial available in the README, you will have built a Docker image for a simple Person Name Annotator. You will then be able to submit this image to [nlpsandbox.io](https://nlpsandbox.io) to benchmark its performance. # Benchmarking Requirements - The following conditions must be met by your Person Name Annotator if you want to benchmark its performance on [nlpsandbox.io](https://nlpsandbox.io). + Your NLP Sandbox tool must meet the following conditions before evaluating its performance on [nlpsandbox.io](https://nlpsandbox.io). - The endpoint `/` must redirect to `/api/v1/tool`. - The endpoint `/ui` must redirect to the web interface (UI). - The output of this tool must be reproducible: a given input should always generate the same output. - - This tool must not attempt to connect to remote server for reproducibility, - robustness, and security reasons. When benchmarked on [nlpsandbox.io](https://nlpsandbox.io), - this tool will not be able to connect to remote servers. + - To ensure the results are reproducible and robust, and the data are + secured, this tool must not connect to any remote server. When benchmarked + on [nlpsandbox.io](https://nlpsandbox.io), this tool will not be able to + connect to remote servers. # Examples - [Person Name Annotator Example (Python)](https://github.com/nlpsandbox/person-name-annotator-example) @@ -26,7 +27,7 @@ info: name: Apache 2.0 url: https://github.com/nlpsandbox/nlpsandbox-schemas/blob/develop/LICENSE title: NLP Sandbox Person Name Annotator API - version: 1.0.2 + version: 1.1.0 x-logo: url: https://nlpsandbox.github.io/nlpsandbox-schemas/logo.png servers: @@ -394,7 +395,7 @@ components: authorEmail: author@example.com url: https://example.com type: nlpsandbox:date-annotator - apiVersion: 1.0.2 + apiVersion: 1.1.0 properties: name: description: The tool name @@ -454,7 +455,7 @@ components: ToolDependencies: description: A list of tool dependencies example: - toolDependencies: + tools: - name: awesome-nlp-tool version: 1.0.6 license: apache-2.0 @@ -464,7 +465,7 @@ components: authorEmail: author@example.com url: https://example.com type: nlpsandbox:date-annotator - apiVersion: 1.0.2 + apiVersion: 1.1.0 - name: awesome-nlp-tool version: 1.0.6 license: apache-2.0 @@ -474,13 +475,13 @@ components: authorEmail: author@example.com url: https://example.com type: nlpsandbox:date-annotator - apiVersion: 1.0.2 + apiVersion: 1.1.0 properties: - toolDependencies: + tools: description: A list of tools items: $ref: '#/components/schemas/Tool' type: array required: - - toolDependencies + - tools type: object 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/test/integration/__init__.py b/server/openapi_server/test/integration/__init__.py deleted file mode 100644 index 58f698a..0000000 --- a/server/openapi_server/test/integration/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -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/test/integration/test_health_check_controller.py b/server/openapi_server/test/test_health_check_controller.py similarity index 72% rename from server/openapi_server/test/integration/test_health_check_controller.py rename to server/openapi_server/test/test_health_check_controller.py index da298b0..a82e52e 100644 --- a/server/openapi_server/test/integration/test_health_check_controller.py +++ b/server/openapi_server/test/test_health_check_controller.py @@ -3,7 +3,12 @@ from __future__ import absolute_import import unittest -from openapi_server.test.integration import BaseTestCase +from flask import json +from six import BytesIO + +from openapi_server.models.error import Error # noqa: E501 +from openapi_server.models.health_check import HealthCheck # noqa: E501 +from openapi_server.test import BaseTestCase class TestHealthCheckController(BaseTestCase): @@ -14,7 +19,7 @@ def test_get_health_check(self): Get health check information """ - headers = { + headers = { 'Accept': 'application/json', } response = self.client.open( diff --git a/server/openapi_server/test/integration/test_text_person_name_annotation_controller.py b/server/openapi_server/test/test_text_person_name_annotation_controller.py similarity index 62% rename from server/openapi_server/test/integration/test_text_person_name_annotation_controller.py rename to server/openapi_server/test/test_text_person_name_annotation_controller.py index 583e5f6..a61844f 100644 --- a/server/openapi_server/test/integration/test_text_person_name_annotation_controller.py +++ b/server/openapi_server/test/test_text_person_name_annotation_controller.py @@ -4,8 +4,12 @@ import unittest from flask import json +from six import BytesIO -from openapi_server.test.integration import BaseTestCase +from openapi_server.models.error import Error # noqa: E501 +from openapi_server.models.text_person_name_annotation_request import TextPersonNameAnnotationRequest # noqa: E501 +from openapi_server.models.text_person_name_annotation_response import TextPersonNameAnnotationResponse # noqa: E501 +from openapi_server.test import BaseTestCase class TestTextPersonNameAnnotationController(BaseTestCase): @@ -17,14 +21,14 @@ def test_create_text_person_name_annotations(self): Annotate person names in a clinical note """ text_person_name_annotation_request = { - "note": { - "identifier": "awesome-note", - "type": "loinc:LP29684-5", - "patientId": "awesome-patient", - "text": "On 12/26/2020, Ms. Chloe Price met with Dr. Prescott." - } - } - headers = { + "note" : { + "identifier" : "awesome-note", + "text" : "On 12/26/2020, Ms. Chloe Price met with Dr. Prescott in Seattle.", + "type" : "loinc:LP29684-5", + "patientId" : "awesome-patient" + } +} + headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', } diff --git a/server/openapi_server/test/integration/test_tool_controller.py b/server/openapi_server/test/test_tool_controller.py similarity index 75% rename from server/openapi_server/test/integration/test_tool_controller.py rename to server/openapi_server/test/test_tool_controller.py index 78b7dbe..7bfa6f3 100644 --- a/server/openapi_server/test/integration/test_tool_controller.py +++ b/server/openapi_server/test/test_tool_controller.py @@ -3,7 +3,13 @@ from __future__ import absolute_import import unittest -from openapi_server.test.integration import BaseTestCase +from flask import json +from six import BytesIO + +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.test import BaseTestCase class TestToolController(BaseTestCase): @@ -14,7 +20,7 @@ def test_get_tool(self): Get tool information """ - headers = { + headers = { 'Accept': 'application/json', } response = self.client.open( @@ -29,7 +35,7 @@ def test_get_tool_dependencies(self): Get tool dependencies """ - headers = { + headers = { 'Accept': 'application/json', } response = self.client.open( diff --git a/server/setup.cfg b/server/setup.cfg index 2434351..8ef068e 100644 --- a/server/setup.cfg +++ b/server/setup.cfg @@ -5,6 +5,7 @@ max-line-length: 80 exclude = .* openapi_server/models/*.py + openapi_server/test/*.py [coverage:run] omit =