From f739d4976d9e9889f3e831450c0404a1dd96ecf3 Mon Sep 17 00:00:00 2001 From: Alex Kanitz Date: Sun, 5 Nov 2023 16:06:16 +0100 Subject: [PATCH] ci: check code format with Black (#74) --- .github/workflows/main.yml | 4 +- pro_wes/client_wes.py | 48 ++---- pro_wes/config_models.py | 29 ++-- pro_wes/exceptions.py | 34 ++-- pro_wes/ga4gh/wes/controllers.py | 2 +- pro_wes/ga4gh/wes/models.py | 78 +++++---- pro_wes/ga4gh/wes/service_info.py | 22 ++- pro_wes/ga4gh/wes/states.py | 23 ++- pro_wes/ga4gh/wes/workflow_runs.py | 12 +- pro_wes/tasks/utils.py | 12 +- pro_wes/utils/db.py | 7 +- pro_wes/version.py | 2 +- pyproject.toml | 4 + requirements_dev.txt | 1 + setup.cfg | 6 + setup.py | 4 +- .../ga4gh/wes/endpoints/test_service_info.py | 158 +++++++++--------- 17 files changed, 219 insertions(+), 227 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 30d49fd..12412db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ on: jobs: lint: - name: Run linting + name: Run static code analysis runs-on: ubuntu-latest steps: - name: Check out repository @@ -24,6 +24,8 @@ jobs: pip install . - name: Lint with Flake8 run: flake8 + - name: Check code format with Black + run: black --check setup.py pro_wes/ tests/ test: name: Run tests runs-on: ubuntu-latest diff --git a/pro_wes/client_wes.py b/pro_wes/client_wes.py index b514d69..e1cd43a 100644 --- a/pro_wes/client_wes.py +++ b/pro_wes/client_wes.py @@ -69,18 +69,14 @@ def get_service_info( try: response_unvalidated = self.session.get(url, **kwargs).json() except (RequestException, ValueError) as exc: - raise EngineUnavailable( - "external workflow engine unavailable" - ) from exc + raise EngineUnavailable("external workflow engine unavailable") from exc try: response = ServiceInfo(**response_unvalidated) except Exception: try: response = ErrorResponse(**response_unvalidated) except Exception as exc: - raise ValueError( - f"invalid response: {response_unvalidated}" - ) from exc + raise ValueError(f"invalid response: {response_unvalidated}") from exc return response def post_run( @@ -133,18 +129,14 @@ def post_run( **kwargs, ).json() except (RequestException, ValueError) as exc: - raise EngineUnavailable( - "external workflow engine unavailable" - ) from exc + raise EngineUnavailable("external workflow engine unavailable") from exc try: response = RunId(**response_unvalidated) except Exception: try: response = ErrorResponse(**response_unvalidated) except Exception as exc: - raise ValueError( - f"invalid response: {response_unvalidated}" - ) from exc + raise ValueError(f"invalid response: {response_unvalidated}") from exc return response def get_runs( @@ -171,18 +163,14 @@ def get_runs( try: response_unvalidated = self.session.get(url, **kwargs).json() except (RequestException, ValueError) as exc: - raise EngineUnavailable( - "external workflow engine unavailable" - ) from exc + raise EngineUnavailable("external workflow engine unavailable") from exc try: response = RunListResponse(**response_unvalidated) except Exception: try: response = ErrorResponse(**response_unvalidated) except Exception as exc: - raise ValueError( - f"invalid response: {response_unvalidated}" - ) from exc + raise ValueError(f"invalid response: {response_unvalidated}") from exc return response def get_run( @@ -211,9 +199,7 @@ def get_run( try: response_unvalidated = self.session.get(url, **kwargs).json() except (RequestException, ValueError) as exc: - raise EngineUnavailable( - "external workflow engine unavailable" - ) from exc + raise EngineUnavailable("external workflow engine unavailable") from exc # skip validation; workaround for cwl-WES return response_unvalidated try: @@ -222,9 +208,7 @@ def get_run( try: response = ErrorResponse(**response_unvalidated) except Exception as exc: - raise ValueError( - f"invalid response: {response_unvalidated}" - ) from exc + raise ValueError(f"invalid response: {response_unvalidated}") from exc return response def get_run_status( @@ -252,18 +236,14 @@ def get_run_status( try: response_unvalidated = self.session.get(url, **kwargs).json() except (RequestException, ValueError) as exc: - raise EngineUnavailable( - "external workflow engine unavailable" - ) from exc + raise EngineUnavailable("external workflow engine unavailable") from exc try: response = RunStatus(**response_unvalidated) except Exception: try: response = ErrorResponse(**response_unvalidated) except Exception as exc: - raise ValueError( - f"invalid response: {response_unvalidated}" - ) from exc + raise ValueError(f"invalid response: {response_unvalidated}") from exc return response def cancel_run( @@ -291,18 +271,14 @@ def cancel_run( try: response_unvalidated = self.session.post(url, **kwargs).json() except (RequestException, ValueError) as exc: - raise EngineUnavailable( - "external workflow engine unavailable" - ) from exc + raise EngineUnavailable("external workflow engine unavailable") from exc try: response = RunId(**response_unvalidated) except Exception: try: response = ErrorResponse(**response_unvalidated) except Exception as exc: - raise ValueError( - f"invalid response: {response_unvalidated}" - ) from exc + raise ValueError(f"invalid response: {response_unvalidated}") from exc return response def set_token( diff --git a/pro_wes/config_models.py b/pro_wes/config_models.py index 61c15b4..6617786 100644 --- a/pro_wes/config_models.py +++ b/pro_wes/config_models.py @@ -23,6 +23,7 @@ class Defaults(BaseModel): timeout: Timeout for outgoing requests. May be overridden by more specific parameters for each endpoint. """ + timeout: Optional[int] = 3 @@ -46,9 +47,10 @@ class PostRuns(BaseModel): raised and the job is set to state `SYSTEM_ERROR`. Once a valid response is obtained, the counter is reset to 1. """ - storage_path: Path = Path('/data') + + storage_path: Path = Path("/data") db_insert_attempts: int = 10 - id_charset: str = 'string.ascii_uppercase + string.digits' + id_charset: str = "string.ascii_uppercase + string.digits" id_length: int = 6 timeout_post: Optional[int] = None timeout_job: Optional[int] = None @@ -65,6 +67,7 @@ class ListRuns(BaseModel): Attributes: default_page_size: Default page size for response pagination. """ + default_page_size: int = 5 @@ -75,6 +78,7 @@ class WorkflowTypeVersion(BaseModel): workflow_type_version: List of one or more acceptable versions for the workflow type. """ + workflow_type_version: Optional[List[str]] = [] @@ -91,6 +95,7 @@ class DefaultWorkflowEngineParameter(BaseModel): type: Parameter type. default_value: Stringified version of default parameter. """ + name: Optional[str] type: Optional[str] default_value: Optional[str] @@ -129,25 +134,20 @@ class ServiceInfo(BaseModel): on how to get an authorization token for use with this service. tags: Additional information about this service as key-value pairs. """ + workflow_type_versions: Dict[str, WorkflowTypeVersion] = { - 'CWL': WorkflowTypeVersion(workflow_type_version=['v1.0']), + "CWL": WorkflowTypeVersion(workflow_type_version=["v1.0"]), } supported_wes_versions: List[str] = [ - '1.0.0', + "1.0.0", ] supported_filesystem_protocols: List[str] = [ - 'http', + "http", ] workflow_engine_versions: Dict[str, str] = {} - default_workflow_engine_parameters: List[ - DefaultWorkflowEngineParameter - ] = [] - auth_instructions_url: AnyUrl = ( - 'https://lifescience-ri.eu/ls-login/' - ) - tags: Dict[str, str] = { - 'service_repo': 'https://github.com/elixir-europe/proWES' - } + default_workflow_engine_parameters: List[DefaultWorkflowEngineParameter] = [] + auth_instructions_url: AnyUrl = "https://lifescience-ri.eu/ls-login/" + tags: Dict[str, str] = {"service_repo": "https://github.com/elixir-europe/proWES"} class CustomConfig(BaseModel): @@ -167,6 +167,7 @@ class CustomConfig(BaseModel): service_info: Configuration parameters for initiliazing the service info. """ + defaults: Defaults = Defaults() post_runs: PostRuns = PostRuns() list_runs: ListRuns = ListRuns() diff --git a/pro_wes/exceptions.py b/pro_wes/exceptions.py index 91454f4..d7e03c3 100644 --- a/pro_wes/exceptions.py +++ b/pro_wes/exceptions.py @@ -15,11 +15,13 @@ class EngineProblem(InternalServerError): """The external workflow engine appears to experience problems.""" + pass class EngineUnavailable(EngineProblem): """The external workflow engine is not available.""" + pass @@ -27,11 +29,13 @@ class NoSuitableEngine(BadRequest): """Raised when the service does not know of a suitable engine to process the requested workflow run. """ + pass class RunNotFound(NotFound): """Raised when workflow run with given run identifier was not found.""" + pass @@ -39,69 +43,71 @@ class IdsUnavailableProblem(PyMongoError): """Raised when no unique run identifier could be found for insertion into the database collection. """ + pass class StorageUnavailableProblem(OSError): """Raised when storage is not available for OS operations.""" + pass exceptions = { Exception: { "message": "An unexpected error occurred.", - "code": '500', + "code": "500", }, BadRequest: { "message": "The request is malformed.", - "code": '400', + "code": "400", }, BadRequestProblem: { "message": "The request is malformed.", - "code": '400', + "code": "400", }, ExtraParameterProblem: { "message": "The request is malformed.", - "code": '400', + "code": "400", }, NoSuitableEngine: { "message": "No suitable workflow engine known.", - "code": '400', + "code": "400", }, ValidationError: { "message": "The request is malformed.", - "code": '400', + "code": "400", }, Unauthorized: { "message": " The request is unauthorized.", - "code": '401', + "code": "401", }, Forbidden: { "message": "The requester is not authorized to perform this action.", - "code": '403', + "code": "403", }, NotFound: { "message": "The requested resource wasn't found.", - "code": '404', + "code": "404", }, RunNotFound: { "message": "The requested run wasn't found.", - "code": '404', + "code": "404", }, EngineUnavailable: { "message": "Could not reach remote WES service.", - "code": '500', + "code": "500", }, InternalServerError: { "message": "An unexpected error occurred.", - "code": '500', + "code": "500", }, IdsUnavailableProblem: { "message": "No/few unique run identifiers available.", - "code": '500', + "code": "500", }, StorageUnavailableProblem: { "message": "Storage is not accessible.", - "code": '500', + "code": "500", }, } diff --git a/pro_wes/ga4gh/wes/controllers.py b/pro_wes/ga4gh/wes/controllers.py index 625d271..282e571 100644 --- a/pro_wes/ga4gh/wes/controllers.py +++ b/pro_wes/ga4gh/wes/controllers.py @@ -41,7 +41,7 @@ def postServiceInfo(**kwargs) -> Tuple[None, str, Dict]: """ service_info = ServiceInfo() headers = service_info.set_service_info(data=request.json) - return (None, '201', headers) + return (None, "201", headers) # controller for `POST /runs` diff --git a/pro_wes/ga4gh/wes/models.py b/pro_wes/ga4gh/wes/models.py index 9ba8ce6..c9cf1c7 100644 --- a/pro_wes/ga4gh/wes/models.py +++ b/pro_wes/ga4gh/wes/models.py @@ -45,6 +45,7 @@ class Attachment(BaseModel): filename: Name of the file as indicated in the run request. path: Path to the file on the app's storage system. """ + filename: str path: Path @@ -84,17 +85,18 @@ class RunRequest(BaseModel): pro_wes.exceptions.ValidationError: The class was instantianted with an illegal data type. """ + workflow_params: str workflow_type: str workflow_type_version: str - tags: Optional[str] = '{}' - workflow_engine_parameters: Optional[str] = '{}' + tags: Optional[str] = "{}" + workflow_engine_parameters: Optional[str] = "{}" workflow_url: str @validator( - 'workflow_type', - 'workflow_type_version', - 'workflow_url', + "workflow_type", + "workflow_type_version", + "workflow_url", always=True, ) def required_str_field_not_empty( @@ -113,14 +115,14 @@ def required_str_field_not_empty( ValueError: The value is either missing or constitutes an empty string. """ - if value == '' or value is None: + if value == "" or value is None: raise ValueError("field required") return value @validator( - 'workflow_params', - 'tags', - 'workflow_engine_parameters', + "workflow_params", + "tags", + "workflow_engine_parameters", always=True, ) def json_serialized_object_field_valid( @@ -158,10 +160,10 @@ def json_serialized_object_field_valid( ValueError: The value could be JSON decoded, but the deserialized value does not represent an object/dictionary. """ - if value == '' or value == 'null' or value is None: - if field.name == 'workflow_params': + if value == "" or value == "null" or value is None: + if field.name == "workflow_params": raise ValueError("field required") - return '{}' + return "{}" try: decoded = loads(value) except JSONDecodeError: @@ -188,15 +190,13 @@ def workflow_type_and_version_supported( NoSuitableEngine: The service does not know of a suitable workflow engine service to process this request. """ - service_info = ServiceInfoController().get_service_info( - get_counts=False - ) - type_versions = service_info['workflow_type_versions'] - type = values.get('workflow_type') - version = values.get('workflow_type_version') + service_info = ServiceInfoController().get_service_info(get_counts=False) + type_versions = service_info["workflow_type_versions"] + type = values.get("workflow_type") + version = values.get("workflow_type_version") if ( - type not in type_versions or - version not in type_versions[type]['workflow_type_version'] + type not in type_versions + or version not in type_versions[type]["workflow_type_version"] ): raise NoSuitableEngine return values @@ -218,6 +218,7 @@ class Log(BaseModel): stderr: A URL to retrieve standard error logs of the task. stdout: A URL to retrieve standard output logs of the task. """ + cmd: Optional[List[str]] = [] end_time: Optional[str] = None exit_code: Optional[int] = None @@ -247,16 +248,17 @@ class State(BaseEnum): CANCELING: The task was canceled by the user, and is in the process of stopping. """ - UNKNOWN = 'UNKNOWN' - QUEUED = 'QUEUED' - INITIALIZING = 'INITIALIZING' - RUNNING = 'RUNNING' - PAUSED = 'PAUSED' - COMPLETE = 'COMPLETE' - EXECUTOR_ERROR = 'EXECUTOR_ERROR' - SYSTEM_ERROR = 'SYSTEM_ERROR' - CANCELED = 'CANCELED' - CANCELING = 'CANCELING' + + UNKNOWN = "UNKNOWN" + QUEUED = "QUEUED" + INITIALIZING = "INITIALIZING" + RUNNING = "RUNNING" + PAUSED = "PAUSED" + COMPLETE = "COMPLETE" + EXECUTOR_ERROR = "EXECUTOR_ERROR" + SYSTEM_ERROR = "SYSTEM_ERROR" + CANCELED = "CANCELED" + CANCELING = "CANCELING" @property def is_finished(self): @@ -298,6 +300,7 @@ class RunLog(BaseModel): state: State of workflow run. task_logs: List of workflow task/job logs. """ + outputs: Optional[Dict] = None request: Optional[RunRequest] = None run_id: Optional[str] = None @@ -333,8 +336,9 @@ class WesEndpoint(BaseModel): specification, i.e., `/ga4gh/wes/v1`. run_id: Identifier for workflow run on external WES endpoint. """ + host: str - base_path: Optional[str] = '/ga4gh/wes/v1' + base_path: Optional[str] = "/ga4gh/wes/v1" run_id: Optional[str] @@ -359,6 +363,7 @@ class DbDocument(BaseModel): was forwarded. work_dir: Working directory for workflow run. """ + attachments: List[Attachment] = [] run_log: RunLog = RunLog() task_id: Optional[str] = None @@ -376,6 +381,7 @@ class RunId(BaseModel): Attributes: run_id: Workflow run identifier. """ + run_id: str @@ -390,6 +396,7 @@ class RunStatus(BaseModel): run_id: Workflow run identifier. state: Workflow run state. """ + run_id: str state: State @@ -405,6 +412,7 @@ class ErrorResponse(BaseModel): msg: Detailed error message. status_code: HTTP status code. """ + msg: Optional[str] status_code: int @@ -416,6 +424,7 @@ class RunListResponse(BaseModel): runs: List of runs, indicating the run identifier and status for each. next_page_token: Token to receive the next page of results. """ + runs: Optional[List[RunStatus]] = [] next_page_token: Optional[str] @@ -427,6 +436,7 @@ class WorkflowTypeVersion(BaseModel): workflow_type_version: List of one or more acceptable versions for the workflow type. """ + workflow_type_version: Optional[List[str]] = [] @@ -443,6 +453,7 @@ class DefaultWorkflowEngineParameter(BaseModel): type: Parameter type. default_value: Stringified version of default parameter. """ + name: Optional[str] type: Optional[str] default_value: Optional[str] @@ -485,13 +496,12 @@ class ServiceInfo(BaseModel): on how to get an authorization token for use with this service. tags: Additional information about this service as key-value pairs. """ + workflow_type_versions: Dict[str, WorkflowTypeVersion] supported_wes_versions: List[str] = [] supported_filesystem_protocols: List[str] = [] workflow_engine_versions: Dict[str, str] - default_workflow_engine_parameters: List[ - DefaultWorkflowEngineParameter - ] = [] + default_workflow_engine_parameters: List[DefaultWorkflowEngineParameter] = [] system_state_counts: Dict[str, int] auth_instructions_url: str tags: Dict[str, str] diff --git a/pro_wes/ga4gh/wes/service_info.py b/pro_wes/ga4gh/wes/service_info.py index f935a32..4f01611 100644 --- a/pro_wes/ga4gh/wes/service_info.py +++ b/pro_wes/ga4gh/wes/service_info.py @@ -17,7 +17,6 @@ class ServiceInfo: - def __init__(self) -> None: """Class for WES API service info server-side controller methods. @@ -34,11 +33,10 @@ def __init__(self) -> None: self.config: Dict = current_app.config self.foca_config: Config = self.config.foca self.db_client_service_info: Collection = ( - self.foca_config.db.dbs['runStore'] - .collections['service_info'].client + self.foca_config.db.dbs["runStore"].collections["service_info"].client ) self.db_client_runs: Collection = ( - self.foca_config.db.dbs['runStore'].collections['runs'].client + self.foca_config.db.dbs["runStore"].collections["runs"].client ) self.object_id: str = "000000000000000000000000" @@ -55,13 +53,13 @@ def get_service_info(self, get_counts: bool = True) -> Dict: NotFound: Service info was not found. """ service_info = self.db_client_service_info.find_one( - {'_id': ObjectId(self.object_id)}, - {'_id': False}, + {"_id": ObjectId(self.object_id)}, + {"_id": False}, ) if service_info is None: raise NotFound if get_counts: - service_info['system_state_counts'] = self._get_state_counts() + service_info["system_state_counts"] = self._get_state_counts() return service_info def set_service_info( @@ -74,7 +72,7 @@ def set_service_info( data: Dictionary of service info values. Cf. """ self.db_client_service_info.replace_one( - filter={'_id': ObjectId(self.object_id)}, + filter={"_id": ObjectId(self.object_id)}, replacement=data, upsert=True, ) @@ -86,10 +84,10 @@ def _get_state_counts(self) -> Dict[str, int]: cursor = self.db_client_runs.find( filter={}, projection={ - 'run_log.state': True, - '_id': False, - } + "run_log.state": True, + "_id": False, + }, ) for record in cursor: - current_counts[record['run_log']['state']] += 1 + current_counts[record["run_log"]["state"]] += 1 return current_counts diff --git a/pro_wes/ga4gh/wes/states.py b/pro_wes/ga4gh/wes/states.py index b8a612c..7437126 100644 --- a/pro_wes/ga4gh/wes/states.py +++ b/pro_wes/ga4gh/wes/states.py @@ -1,25 +1,24 @@ -class States(): - +class States: UNDEFINED = [ - 'UNKNOWN', + "UNKNOWN", ] CANCELABLE = [ - 'INITIALIZING', - 'PAUSED', - 'QUEUED', - 'RUNNING', + "INITIALIZING", + "PAUSED", + "QUEUED", + "RUNNING", ] FINISHED = [ - 'COMPLETE', - 'EXECUTOR_ERROR', - 'SYSTEM_ERROR', - 'CANCELED', + "COMPLETE", + "EXECUTOR_ERROR", + "SYSTEM_ERROR", + "CANCELED", ] UNFINISHED = CANCELABLE + [ - 'CANCELING', + "CANCELING", ] ALL = FINISHED + UNFINISHED + UNDEFINED diff --git a/pro_wes/ga4gh/wes/workflow_runs.py b/pro_wes/ga4gh/wes/workflow_runs.py index aa3f59c..91da67f 100644 --- a/pro_wes/ga4gh/wes/workflow_runs.py +++ b/pro_wes/ga4gh/wes/workflow_runs.py @@ -141,11 +141,9 @@ def run_workflow( raise Forbidden else: raise InternalServerError - document_stored: DbDocument = ( - db_connector.upsert_fields_in_root_object( - root="wes_endpoint", - run_id=response.run_id, - ) + document_stored: DbDocument = db_connector.upsert_fields_in_root_object( + root="wes_endpoint", + run_id=response.run_id, ) # track workflow progress in background @@ -487,9 +485,7 @@ def _validate_run_request( """ dict_of_lists = form_data.to_dict(flat=False) # flatten single item lists - dict_atomic = { - k: v[0] if len(v) == 1 else v for k, v in dict_of_lists.items() - } + dict_atomic = {k: v[0] if len(v) == 1 else v for k, v in dict_of_lists.items()} # remove 'workflow_attachment' field dict_atomic.pop("workflow_attachment", None) model_instance = RunRequest(**dict_atomic) diff --git a/pro_wes/tasks/utils.py b/pro_wes/tasks/utils.py index affe2cb..738d3db 100644 --- a/pro_wes/tasks/utils.py +++ b/pro_wes/tasks/utils.py @@ -16,18 +16,18 @@ def set_run_state( collection: Collection, run_id: str, task_id: Optional[str] = None, - state: str = 'UNKNOWN', + state: str = "UNKNOWN", ): """Set/update state of run associated with Celery task.""" if not task_id: document = collection.find_one( - filter={'run_id': run_id}, + filter={"run_id": run_id}, projection={ - 'task_id': True, - '_id': False, - } + "task_id": True, + "_id": False, + }, ) - _task_id = document['task_id'] + _task_id = document["task_id"] else: _task_id = task_id try: diff --git a/pro_wes/utils/db.py b/pro_wes/utils/db.py index b6cd2f8..1e50fd2 100644 --- a/pro_wes/utils/db.py +++ b/pro_wes/utils/db.py @@ -97,12 +97,7 @@ def upsert_fields_in_root_object( """ document_unvalidated = self.collection.find_one_and_update( {"task_id": self.task_id}, - { - "$set": { - ".".join([root, key]): value - for (key, value) in kwargs.items() - } - }, + {"$set": {".".join([root, key]): value for (key, value) in kwargs.items()}}, return_document=ReturnDocument.AFTER, ) try: diff --git a/pro_wes/version.py b/pro_wes/version.py index 471bc6b..7e0737b 100644 --- a/pro_wes/version.py +++ b/pro_wes/version.py @@ -1,3 +1,3 @@ """Single source of truth for package version.""" -__version__ = '0.18.0' +__version__ = "0.18.0" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f55796b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[tool.black] +max-line-length = 88 +target-version = ["py310"] +workers = 1 diff --git a/requirements_dev.txt b/requirements_dev.txt index 7b09ced..965b86d 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,3 +1,4 @@ +black>=23.9.1,<24 coverage>=6.5,<7 flake8>=5.0.4,<6 mongomock>=4.1.2,<5 diff --git a/setup.cfg b/setup.cfg index d519294..de06669 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,11 @@ +[flake8] +exclude = .git,.eggs,build,venv,env +max-line-length = 88 +extend-ignore = E203 + [coverage:run] source = pro_wes omit = pro_wes/app.py + diff --git a/setup.py b/setup.py index 163ac7e..071492e 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ INSTALL_REQUIRES = _file.read().splitlines() setup( - name='pro-wes', + name="pro-wes", version=__version__, # noqa: F821 # pylint: disable=undefined-variable license="Apache License 2.0", description="Proxy/gateway GA4GH WES service", @@ -23,7 +23,7 @@ long_description_content_type="text/markdown", author="ELIXIR Cloud & AAI", author_email="cloud-service@elixir-europe.org", - url='https://github.com/elixir-cloud-aai/proWES.git', + url="https://github.com/elixir-cloud-aai/proWES.git", project_urls={ "Repository": "https://github.com/elixir-cloud-aai/proWES", "ELIXIR Cloud & AAI": "https://elixir-cloud.dcc.sib.swiss/", diff --git a/tests/ga4gh/wes/endpoints/test_service_info.py b/tests/ga4gh/wes/endpoints/test_service_info.py index 00b6b67..db68dc9 100644 --- a/tests/ga4gh/wes/endpoints/test_service_info.py +++ b/tests/ga4gh/wes/endpoints/test_service_info.py @@ -11,31 +11,27 @@ from unittest.mock import MagicMock -from pro_wes.ga4gh.wes.service_info import ( - RegisterServiceInfo -) +from pro_wes.ga4gh.wes.service_info import RegisterServiceInfo from pro_wes.exceptions import ( NotFound, ValidationError, ) -INDEX_CONFIG = { - 'keys': [('id', 1)] -} +INDEX_CONFIG = {"keys": [("id", 1)]} COLLECTION_CONFIG = { - 'indexes': [INDEX_CONFIG], + "indexes": [INDEX_CONFIG], } DB_CONFIG = { - 'collections': { - 'objects': COLLECTION_CONFIG, - 'service_info': COLLECTION_CONFIG, + "collections": { + "objects": COLLECTION_CONFIG, + "service_info": COLLECTION_CONFIG, }, } MONGO_CONFIG = { - 'host': 'mongodb', - 'port': 27017, - 'dbs': { - 'drsStore': DB_CONFIG, + "host": "mongodb", + "port": 27017, + "dbs": { + "drsStore": DB_CONFIG, }, } SERVICE_INFO_CONFIG = { @@ -46,32 +42,19 @@ "environment": "test", "id": "org.ga4gh.myservice", "name": "My project", - "organization": { - "name": "My organization", - "url": "https://example.com" - }, - "type": { - "artifact": "beacon", - "group": "org.ga4gh", - "version": "1.0.0" - }, + "organization": {"name": "My organization", "url": "https://example.com"}, + "type": {"artifact": "beacon", "group": "org.ga4gh", "version": "1.0.0"}, "updatedAt": "2019-06-04T12:58:19Z", - "version": "1.0.0" + "version": "1.0.0", } ENDPOINT_CONFIG = { - "objects": { - "id_charset": 'string.digits', - "id_length": 6 - }, - "access_methods": { - "id_charset": 'string.digits', - "id_length": 6 - }, + "objects": {"id_charset": "string.digits", "id_length": 6}, + "access_methods": {"id_charset": "string.digits", "id_length": 6}, "service_info": deepcopy(SERVICE_INFO_CONFIG), "url_prefix": "http", "external_host": "1.2.3.4", "external_port": 8080, - "api_path": "ga4gh/drs/v1" + "api_path": "ga4gh/drs/v1", } SERVICE_CONFIG = { "url_prefix": "http", @@ -80,12 +63,12 @@ "api_path": "ga4gh/drs/v1", } HEADERS_SERVICE_INFO = { - 'Content-type': 'application/json', - 'Location': ( + "Content-type": "application/json", + "Location": ( f"{SERVICE_CONFIG['url_prefix']}://{SERVICE_CONFIG['external_host']}:" f"{SERVICE_CONFIG['external_port']}/{SERVICE_CONFIG['api_path']}/" "service-info" - ) + ), } @@ -94,17 +77,19 @@ def test_get_service_info(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) mock_resp = deepcopy(SERVICE_INFO_CONFIG) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client.insert_one(mock_resp) + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections["service_info"].client.insert_one( + mock_resp + ) with app.app_context(): assert RegisterServiceInfo().get_service_info() == SERVICE_INFO_CONFIG @@ -114,14 +99,15 @@ def test_get_service_info_na(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection with app.app_context(): with pytest.raises(NotFound): RegisterServiceInfo().get_service_info() @@ -132,14 +118,15 @@ def test_set_service_info_from_config(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection with app.app_context(): service_info = RegisterServiceInfo() service_info.set_service_info_from_config() @@ -150,17 +137,18 @@ def test_set_service_info_from_config_corrupt(): """Test for setting service info from corrupt config.""" app = Flask(__name__) mock_resp = deepcopy(ENDPOINT_CONFIG) - del mock_resp['service_info']['id'] + del mock_resp["service_info"]["id"] setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=mock_resp, ), ) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection with app.app_context(): with pytest.raises(ValidationError): service_info = RegisterServiceInfo() @@ -174,17 +162,19 @@ def test_set_service_info_from_config_skip(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) mock_resp = deepcopy(SERVICE_INFO_CONFIG) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client.insert_one(mock_resp) + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections["service_info"].client.insert_one( + mock_resp + ) with app.app_context(): service_info = RegisterServiceInfo() service_info.set_service_info_from_config() @@ -196,22 +186,26 @@ def test_get_service_info_duplicatekey(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection - mock = MagicMock(side_effect=[DuplicateKeyError(''), None]) - app.config.foca.db.dbs['drsStore'] \ - .collections['service_info'].client.insert_one = mock + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection + mock = MagicMock(side_effect=[DuplicateKeyError(""), None]) + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client.insert_one = mock mock_db_call = MagicMock(name="Find_Obj") - mock_db_call.return_value.sort.return_value \ - .limit.return_value.next.return_value = deepcopy(SERVICE_INFO_CONFIG) - app.config.foca.db.dbs['drsStore'] \ - .collections['service_info'].client.find = mock_db_call + mock_db_call.return_value.sort.return_value.limit.return_value.next.return_value = ( + deepcopy(SERVICE_INFO_CONFIG) + ) + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client.find = mock_db_call with app.app_context(): get_service_info = RegisterServiceInfo().get_service_info() assert get_service_info == SERVICE_INFO_CONFIG @@ -222,14 +216,15 @@ def test_set_service_info_from_app_context(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection with app.app_context(): service_info = RegisterServiceInfo() service_info.set_service_info_from_app_context( @@ -243,16 +238,17 @@ def test__upsert_service_info_insert(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection data = deepcopy(SERVICE_INFO_CONFIG) - del data['contactUrl'] + del data["contactUrl"] with app.app_context(): service_info = RegisterServiceInfo() service_info._upsert_service_info(data=data) @@ -265,19 +261,21 @@ def test__upsert_service_info_update(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG, ), ) mock_resp = deepcopy(SERVICE_INFO_CONFIG) - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client = mongomock.MongoClient().db.collection - app.config.foca.db.dbs['drsStore'].collections['service_info'] \ - .client.insert_one(mock_resp) + app.config.foca.db.dbs["drsStore"].collections[ + "service_info" + ].client = mongomock.MongoClient().db.collection + app.config.foca.db.dbs["drsStore"].collections["service_info"].client.insert_one( + mock_resp + ) data = deepcopy(SERVICE_INFO_CONFIG) - del data['contactUrl'] + del data["contactUrl"] with app.app_context(): service_info = RegisterServiceInfo() service_info._upsert_service_info(data=data) @@ -290,7 +288,7 @@ def test__get_headers(): app = Flask(__name__) setattr( app.config, - 'foca', + "foca", Config( db=MongoConfig(**MONGO_CONFIG), endpoints=ENDPOINT_CONFIG,