From 4e8c0e5ed7949c1eea23ca6199e21e1b1a3e9c31 Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Thu, 17 Apr 2025 13:09:10 +0100 Subject: [PATCH 1/9] refactor for ocm --- src/ansys/conceptev/core/app.py | 429 ++++++------------------ src/ansys/conceptev/core/ocm.py | 301 +++++++++++++++++ src/ansys/conceptev/core/projects.py | 154 --------- src/ansys/conceptev/core/responses.py | 49 +++ tests/test_app.py | 42 +++ tests/{test_projects.py => test_ocm.py} | 20 +- 6 files changed, 501 insertions(+), 494 deletions(-) create mode 100644 src/ansys/conceptev/core/ocm.py delete mode 100644 src/ansys/conceptev/core/projects.py create mode 100644 src/ansys/conceptev/core/responses.py rename tests/{test_projects.py => test_ocm.py} (86%) diff --git a/src/ansys/conceptev/core/app.py b/src/ansys/conceptev/core/app.py index 1b6751e3..1404b117 100644 --- a/src/ansys/conceptev/core/app.py +++ b/src/ansys/conceptev/core/app.py @@ -21,11 +21,7 @@ # SOFTWARE. """Simple API client for the Ansys ConceptEV service.""" -from collections import defaultdict import datetime -import json -from json import JSONDecodeError -import re from typing import Literal import httpx @@ -33,20 +29,52 @@ from ansys.conceptev.core import auth from ansys.conceptev.core.exceptions import ( - AccountsError, DeleteError, - DesignError, ProductAccessError, - ProductIdsError, - ProjectError, - ResponseError, ResultsError, TokenError, - UserDetailsError, +) +from ansys.conceptev.core.ocm import ( + create_design_instance, + create_new_project, + delete_project, + get_account_id, + get_account_ids, + get_default_hpc, + get_design_of_job, + get_design_title, + get_job_file, + get_job_info, + get_or_create_project, + get_product_id, + get_project_id, + get_project_ids, + get_status, + get_user_id, ) from ansys.conceptev.core.progress import check_status, monitor_job_progress +from ansys.conceptev.core.response import is_gateway_error, process_response from ansys.conceptev.core.settings import settings +__all__ = [ + get_or_create_project, + create_new_project, + create_design_instance, + get_product_id, + get_user_id, + get_account_id, + get_account_ids, + get_default_hpc, + get_job_file, + get_job_info, + get_design_of_job, + get_design_title, + get_status, + get_project_ids, + get_project_id, + delete_project, +] + Router = Literal[ "/architectures", "/components", @@ -85,13 +113,6 @@ app = auth.create_msal_app() -def is_gaetway_error(response) -> bool: - """Check if the response is a gateway error.""" - if isinstance(response, httpx.Response): - return response.status_code in (502, 504) - return False - - def get_http_client( token: str | None = None, design_instance_id: str | None = None, @@ -108,26 +129,13 @@ def get_http_client( client = httpx.Client(headers=header, auth=httpx_auth, params=params, base_url=BASE_URL) client.send = retry( - retry=retry_if_result(is_gaetway_error), + retry=retry_if_result(is_gateway_error), wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_delay(10), )(client.send) return client -def process_response(response) -> dict: - """Process a response. - - Check the value returned from the API and raise an error if the process is not successful. - """ - if response.status_code == 200 or response.status_code == 201: # Success - try: - return response.json() - except JSONDecodeError: - return response.content - raise ResponseError(f"Response Failed:{response.content}") - - def get( client: httpx.Client, router: Router, id: str | None = None, params: dict | None = None ) -> dict: @@ -191,48 +199,6 @@ def put(client: httpx.Client, router: Router, id: str, data: dict) -> dict: return process_response(response) -def create_new_project( - client: httpx.Client, - account_id: str, - hpc_id: str, - title: str, - project_goal: str = "Created from the CLI", -) -> dict: - """Create a project.""" - token = get_token(client) - project_data = { - "accountId": account_id, - "hpcId": hpc_id, - "projectTitle": title, - "projectGoal": project_goal, - } - created_project = httpx.post( - OCM_URL + "/project/create", headers={"Authorization": token}, json=project_data - ) - if created_project.status_code != 200 and created_project.status_code != 204: - raise ProjectError(f"Failed to create a project {created_project}.") - - return created_project.json() - - -def get_or_create_project(client: httpx.Client, account_id: str, hpc_id: str, title: str) -> dict: - """Get or create a project.""" - stored_errors = [] - options = [title, re.escape(title), title.split(maxsplit=1)[0]] - for search_string in options: - try: - projects = get_project_ids(search_string, account_id, client.headers["Authorization"]) - project_id = projects[title][0] - return project_id - except (ProjectError, KeyError, IndexError) as err: - stored_errors.append(err) - - project = create_new_project(client, account_id, hpc_id, title) - project_id = project["projectId"] - - return project_id - - def create_new_concept( client: httpx.Client, project_id: str, @@ -247,26 +213,17 @@ def create_new_concept( if product_id is None: product_id = get_product_id(token) - design_data = { - "projectId": project_id, - "productId": product_id, - "designTitle": title, - } - created_design = httpx.post( - OCM_URL + "/design/create", headers={"Authorization": token}, json=design_data + design_instance_id, design_id = create_design_instance( + project_id, title, token, product_id, return_design_id=True ) - if created_design.status_code not in (200, 204): - raise DesignError(f"Failed to create a design on OCM {created_design.content}.") - user_id = get_user_id(token) - design_instance_id = created_design.json()["designInstanceList"][0]["designInstanceId"] concept_data = { "capabilities_ids": [], "components_ids": [], "configurations_ids": [], - "design_id": created_design.json()["designId"], + "design_id": design_id, "design_instance_id": design_instance_id, "drive_cycles_ids": [], "jobs_ids": [], @@ -277,99 +234,19 @@ def create_new_concept( } query = { - "design_instance_id": created_design.json()["designInstanceList"][0]["designInstanceId"], + "design_instance_id": design_instance_id, } created_concept = post(client, "/concepts", data=concept_data, params=query) return created_concept -def get_product_id(token: str) -> str: - """Get the product ID.""" - products = httpx.get(OCM_URL + "/product/list", headers={"Authorization": token}) - if products.status_code != 200: - raise ProductIdsError(f"Failed to get product id.") - - product_id = [ - product["productId"] for product in products.json() if product["productName"] == "CONCEPTEV" - ][0] - return product_id - - -def get_user_id(token): - """Get the user ID.""" - user_details = httpx.post(OCM_URL + "/user/details", headers={"Authorization": token}) - if user_details.status_code not in (200, 204): - raise UserDetailsError(f"Failed to get a user details on OCM {user_details}.") - user_id = user_details.json()["userId"] - return user_id - - def get_concept_ids(client: httpx.Client) -> dict: """Get concept IDs.""" concepts = get(client, "/concepts") return {concept["name"]: concept["id"] for concept in concepts} -def get_account_ids(token: str) -> dict: - """Get account IDs.""" - response = httpx.post(url=OCM_URL + "/account/list", headers={"authorization": token}) - if response.status_code != 200: - raise AccountsError(f"Failed to get accounts {response}.") - accounts = { - account["account"]["accountName"]: account["account"]["accountId"] - for account in response.json() - } - return accounts - - -def get_account_id(token: str) -> str: - """Get the account ID from OCM using name from config file.""" - accounts = get_account_ids(token) - account_id = accounts[ACCOUNT_NAME] - return account_id - - -def get_default_hpc(token: str, account_id: str) -> dict: - """Get the default HPC ID.""" - response = httpx.post( - url=OCM_URL + "/account/hpc/default", - json={"accountId": account_id}, - headers={"authorization": token}, - ) - if response.status_code != 200: - raise AccountsError(f"Failed to get accounts {response}.") - return response.json()["hpcId"] - - -def create_submit_job( - client, - concept: dict, - account_id: str, - hpc_id: str, - job_name: str | None = None, -) -> dict: - """Create and then submit a job.""" - if job_name is None: - job_name = f"cli_job: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}" - job_input = { - "job_name": job_name, - "requirement_ids": concept["requirements_ids"], - "architecture_id": concept["architecture_id"], - "concept_id": concept["id"], - "design_instance_id": concept["design_instance_id"], - } - job, uploaded_file = post(client, "/jobs", data=job_input, account_id=account_id) - job_start = { - "job": job, - "uploaded_file": uploaded_file, - "account_id": account_id, - "hpc_id": hpc_id, - } - job_info = post(client, "/jobs:start", data=job_start, account_id=account_id) - return job_info - - def read_file(filename: str) -> str: """Read a given file.""" with open(filename, "r+b") as f: @@ -402,128 +279,6 @@ def read_results( return get_results(client, job_info, calculate_units, filtered) -def get_results( - client, - job_info: dict, - calculate_units: bool = True, - filtered: bool = False, -): - """Get the results.""" - version_number = get(client, "/utilities:data_format_version") - if filtered: - filename = f"filtered_output_v{version_number}.json" - else: - filename = f"output_file_v{version_number}.json" - response = client.post( - url="/jobs:result", - json=job_info, - params={ - "results_file_name": filename, - "calculate_units": calculate_units, - }, - ) - if response.status_code == 502 or response.status_code == 504: - raise ResultsError( - f"Request timed out {response}. " - f"Please try using either calculate_units=False or filtered=True." - ) - return process_response(response) - - -def get_job_file(token, job_id, filename, simulation_id=None, encrypted=False): - """Get the job file from the OnScale Cloud Manager.""" - encrypted_part = "decrypted/" if encrypted else "" - if simulation_id is not None: - path = f"{OCM_URL}/job/files/{encrypted_part}{job_id}/{simulation_id}/{filename}" - else: - path = f"{OCM_URL}/job/files/{encrypted_part}{job_id}/{filename}" - response = httpx.get( - url=path, headers={"authorization": token, "accept": "application/octet-stream"} - ) - if response.status_code != 200: - raise ResponseError(f"Failed to get file {response}.") - - return json.loads(response.content) - - -def get_job_info(token, job_id): - """Get the job info from the OnScale Cloud Manager.""" - response = httpx.post( - url=f"{OCM_URL}/job/load", headers={"authorization": token}, json={"jobId": job_id} - ) - response = process_response(response) - job_info = { - "job_id": job_id, - "simulation_id": response["simulations"][0]["simulationId"], - "job_name": response["jobName"], - "docker_tag": response["dockerTag"], - } - return job_info - - -def get_design_of_job(token, job_id): - """Get the job info from the OnScale Cloud Manager.""" - response = httpx.post( - url=f"{OCM_URL}/job/load", headers={"authorization": token}, json={"jobId": job_id} - ) - response = process_response(response) - return response["designInstanceId"] - - -def get_design_title(token, design_instance_id): - """Get the design Title from the OnScale Cloud Manager.""" - response = httpx.post( - url=f"{OCM_URL}/design/instance/load", - headers={"authorization": token}, - json={"designInstanceId": design_instance_id}, - ) - response = process_response(response) - design = httpx.post( - url=f"{OCM_URL}/design/load", - headers={"authorization": token}, - json={"designId": response["designId"]}, - ) - design = process_response(design) - return design["designTitle"] - - -def get_status(job_info: dict, token: str) -> str: - """Get the status of the job.""" - response = httpx.post( - url=OCM_URL + "/job/load", - json={"jobId": job_info["job_id"]}, - headers={"Authorization": token}, - ) - processed_response = process_response(response) - initial_status = processed_response["jobStatus"][-1]["jobStatus"] - return initial_status - - -def get_project_ids(name: str, account_id: str, token: str) -> dict: - """Get projects.""" - response = httpx.post( - url=OCM_URL + "/project/list/page", - json={"accountId": account_id, "filterByName": name, "pageNumber": 0, "pageSize": 1000}, - headers={"Authorization": token}, - ) - processed_response = process_response(response) - projects = processed_response["projects"] - project_dict = defaultdict(list) - for project in projects: - project_dict[project["projectTitle"]].append(project["projectId"]) - return project_dict - - -def get_project_id(name: str, account_id: str, token: str) -> str: - """Get project ID.""" - projects = get_project_ids(name, account_id, token) - if not projects: - raise ProjectError(f"Project with name {name} not found.") - if len(projects) > 1: - raise ProjectError(f"Multiple projects found with name {name}.") - return projects[name][0] - - def get_token(client: httpx.Client) -> str: """Get the token from the client.""" if client.auth is not None and client.auth.app is not None: @@ -533,27 +288,6 @@ def get_token(client: httpx.Client) -> str: raise TokenError("App not found in client.") -def delete_project(project_id, token): - """Delete a project.""" - ocm_delete_init = httpx.request( - method="DELETE", - url=OCM_URL + "/project/delete/init", - headers={"Authorization": token}, - json={"projectId": project_id}, - timeout=20, - ) - ocm_delete_init = process_response(ocm_delete_init) - ocm_delete = httpx.request( - method="DELETE", - url=OCM_URL + "/project/delete/execute", - headers={"Authorization": token}, - json={"projectId": project_id, "hash": ocm_delete_init["hash"]}, - timeout=20, - ) - ocm_delete = process_response(ocm_delete) - return ocm_delete - - def post_component_file(client: httpx.Client, filename: str, component_file_type: str) -> dict: """Send a POST request to the base client with a file. @@ -582,27 +316,6 @@ def get_concept(client: httpx.Client, design_instance_id: str) -> dict: return concept -def create_design_instance(project_id, title, token, product_id=None): - """Create a design instance on OCM.""" - if product_id is None: - product_id = get_product_id(token) - - design_data = { - "projectId": project_id, - "productId": product_id, - "designTitle": title, - } - created_design = httpx.post( - OCM_URL + "/design/create", headers={"Authorization": token}, json=design_data - ) - - if created_design.status_code not in (200, 204): - raise Exception(f"Failed to create a design on OCM {created_design.content}.") - - design_instance_id = created_design.json()["designInstanceList"][0]["designInstanceId"] - return design_instance_id - - def copy_concept(base_concept_id, design_instance_id, client): """Copy the reference concept to the new design instance.""" copy = { @@ -617,6 +330,62 @@ def copy_concept(base_concept_id, design_instance_id, client): return concept +def create_submit_job( + client, + concept: dict, + account_id: str, + hpc_id: str, + job_name: str | None = None, +) -> dict: + """Create and then submit a job.""" + if job_name is None: + job_name = f"cli_job: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}" + job_input = { + "job_name": job_name, + "requirement_ids": concept["requirements_ids"], + "architecture_id": concept["architecture_id"], + "concept_id": concept["id"], + "design_instance_id": concept["design_instance_id"], + } + job, uploaded_file = post(client, "/jobs", data=job_input, account_id=account_id) + job_start = { + "job": job, + "uploaded_file": uploaded_file, + "account_id": account_id, + "hpc_id": hpc_id, + } + job_info = post(client, "/jobs:start", data=job_start, account_id=account_id) + return job_info + + +def get_results( + client, + job_info: dict, + calculate_units: bool = True, + filtered: bool = False, +): + """Get the results.""" + version_number = get(client, "/utilities:data_format_version") + if filtered: + filename = f"filtered_output_v{version_number}.json" + else: + filename = f"output_file_v{version_number}.json" + response = client.post( + url="/jobs:result", + json=job_info, + params={ + "results_file_name": filename, + "calculate_units": calculate_units, + }, + ) + if response.status_code == 502 or response.status_code == 504: + raise ResultsError( + f"Request timed out {response}. " + f"Please try using either calculate_units=False or filtered=True." + ) + return process_response(response) + + def get_component_id_map(client, design_instance_id): """Get a map of component name to component id.""" ###TODO move to results file so its self contained. diff --git a/src/ansys/conceptev/core/ocm.py b/src/ansys/conceptev/core/ocm.py new file mode 100644 index 00000000..ea7eb406 --- /dev/null +++ b/src/ansys/conceptev/core/ocm.py @@ -0,0 +1,301 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Projects/OCM Specific functionality.""" + +from collections import defaultdict +import datetime +import json +import re + +import httpx + +from ansys.conceptev.core.exceptions import ( + AccountsError, + DesignError, + ProductIdsError, + ProjectError, + ResponseError, + UserDetailsError, +) +from ansys.conceptev.core.responses import process_response +from ansys.conceptev.core.settings import settings + +OCM_URL = settings.ocm_url +ACCOUNT_NAME = settings.account_name + + +def get_product_id(token: str) -> str: + """Get the product ID.""" + products = httpx.get(OCM_URL + "/product/list", headers={"Authorization": token}) + if products.status_code != 200: + raise ProductIdsError(f"Failed to get product id.") + + product_id = [ + product["productId"] for product in products.json() if product["productName"] == "CONCEPTEV" + ][0] + return product_id + + +def get_user_id(token): + """Get the user ID.""" + user_details = httpx.post(OCM_URL + "/user/details", headers={"Authorization": token}) + if user_details.status_code not in (200, 204): + raise UserDetailsError(f"Failed to get a user details on OCM {user_details}.") + user_id = user_details.json()["userId"] + return user_id + + +def get_account_ids(token: str) -> dict: + """Get account IDs.""" + response = httpx.post(url=OCM_URL + "/account/list", headers={"authorization": token}) + if response.status_code != 200: + raise AccountsError(f"Failed to get accounts {response}.") + accounts = { + account["account"]["accountName"]: account["account"]["accountId"] + for account in response.json() + } + return accounts + + +def get_account_id(token: str) -> str: + """Get the account ID from OCM using name from config file.""" + accounts = get_account_ids(token) + account_id = accounts[ACCOUNT_NAME] + return account_id + + +def get_default_hpc(token: str, account_id: str) -> dict: + """Get the default HPC ID.""" + response = httpx.post( + url=OCM_URL + "/account/hpc/default", + json={"accountId": account_id}, + headers={"authorization": token}, + ) + if response.status_code != 200: + raise AccountsError(f"Failed to get accounts {response}.") + return response.json()["hpcId"] + + +def create_new_project( + client: httpx.Client, + account_id: str, + hpc_id: str, + title: str, + project_goal: str = "Created from the CLI", +) -> dict: + """Create a project.""" + token = client.headers["Authorization"] + project_data = { + "accountId": account_id, + "hpcId": hpc_id, + "projectTitle": title, + "projectGoal": project_goal, + } + created_project = httpx.post( + OCM_URL + "/project/create", headers={"Authorization": token}, json=project_data + ) + if created_project.status_code != 200 and created_project.status_code != 204: + raise ProjectError(f"Failed to create a project {created_project}.") + + return created_project.json() + + +def create_new_design( + client: httpx.AsyncClient, project_id: str, product_id: str = None, title: str = None +) -> dict: + """Create a new design on OCM.""" + if title is None: + title = f"CLI concept {datetime.datetime.now()}" + + token = client.headers["Authorization"] + if product_id is None: + product_id = get_product_id(token) + + design_data = { + "projectId": project_id, + "productId": product_id, + "designTitle": title, + } + created_design = httpx.post( + OCM_URL + "/design/create", headers={"Authorization": token}, json=design_data + ) + + if created_design.status_code not in (200, 204): + raise DesignError(f"Failed to create a design on OCM {created_design.content}.") + return created_design.json() + + +def get_or_create_project(client: httpx.Client, account_id: str, hpc_id: str, title: str) -> dict: + """Get or create a project.""" + stored_errors = [] + options = [title, re.escape(title), title.split(maxsplit=1)[0]] + for search_string in options: + try: + projects = get_project_ids(search_string, account_id, client.headers["Authorization"]) + project_id = projects[title][0] + return project_id + except (ProjectError, KeyError, IndexError) as err: + stored_errors.append(err) + + project = create_new_project(client, account_id, hpc_id, title) + project_id = project["projectId"] + + return project_id + + +def create_design_instance(project_id, title, token, product_id=None, return_design_id=False): + """Create a design instance on OCM.""" + if product_id is None: + product_id = get_product_id(token) + + design_data = { + "projectId": project_id, + "productId": product_id, + "designTitle": title, + } + created_design = httpx.post( + OCM_URL + "/design/create", headers={"Authorization": token}, json=design_data + ) + + if created_design.status_code not in (200, 204): + raise Exception(f"Failed to create a design on OCM {created_design.content}.") + + design_instance_id = created_design.json()["designInstanceList"][0]["designInstanceId"] + if return_design_id: + return design_instance_id, created_design.json()["designId"] + return design_instance_id + + +def get_job_file(token, job_id, filename, simulation_id=None, encrypted=False): + """Get the job file from the OnScale Cloud Manager.""" + encrypted_part = "decrypted/" if encrypted else "" + if simulation_id is not None: + path = f"{OCM_URL}/job/files/{encrypted_part}{job_id}/{simulation_id}/{filename}" + else: + path = f"{OCM_URL}/job/files/{encrypted_part}{job_id}/{filename}" + response = httpx.get( + url=path, headers={"authorization": token, "accept": "application/octet-stream"} + ) + if response.status_code != 200: + raise ResponseError(f"Failed to get file {response}.") + + return json.loads(response.content) + + +def get_job_info(token, job_id): + """Get the job info from the OnScale Cloud Manager.""" + response = httpx.post( + url=f"{OCM_URL}/job/load", headers={"authorization": token}, json={"jobId": job_id} + ) + response = process_response(response) + job_info = { + "job_id": job_id, + "simulation_id": response["simulations"][0]["simulationId"], + "job_name": response["jobName"], + "docker_tag": response["dockerTag"], + } + return job_info + + +def get_design_of_job(token, job_id): + """Get the job info from the OnScale Cloud Manager.""" + response = httpx.post( + url=f"{OCM_URL}/job/load", headers={"authorization": token}, json={"jobId": job_id} + ) + response = process_response(response) + return response["designInstanceId"] + + +def get_design_title(token, design_instance_id): + """Get the design Title from the OnScale Cloud Manager.""" + response = httpx.post( + url=f"{OCM_URL}/design/instance/load", + headers={"authorization": token}, + json={"designInstanceId": design_instance_id}, + ) + response = process_response(response) + design = httpx.post( + url=f"{OCM_URL}/design/load", + headers={"authorization": token}, + json={"designId": response["designId"]}, + ) + design = process_response(design) + return design["designTitle"] + + +def get_status(job_info: dict, token: str) -> str: + """Get the status of the job.""" + response = httpx.post( + url=OCM_URL + "/job/load", + json={"jobId": job_info["job_id"]}, + headers={"Authorization": token}, + ) + processed_response = process_response(response) + initial_status = processed_response["jobStatus"][-1]["jobStatus"] + return initial_status + + +def get_project_ids(name: str, account_id: str, token: str) -> dict: + """Get projects.""" + response = httpx.post( + url=OCM_URL + "/project/list/page", + json={"accountId": account_id, "filterByName": name, "pageNumber": 0, "pageSize": 1000}, + headers={"Authorization": token}, + ) + processed_response = process_response(response) + projects = processed_response["projects"] + project_dict = defaultdict(list) + for project in projects: + project_dict[project["projectTitle"]].append(project["projectId"]) + return project_dict + + +def get_project_id(name: str, account_id: str, token: str) -> str: + """Get project ID.""" + projects = get_project_ids(name, account_id, token) + if not projects: + raise ProjectError(f"Project with name {name} not found.") + if len(projects) > 1: + raise ProjectError(f"Multiple projects found with name {name}.") + return projects[name][0] + + +def delete_project(project_id, token): + """Delete a project.""" + ocm_delete_init = httpx.request( + method="DELETE", + url=OCM_URL + "/project/delete/init", + headers={"Authorization": token}, + json={"projectId": project_id}, + timeout=20, + ) + ocm_delete_init = process_response(ocm_delete_init) + ocm_delete = httpx.request( + method="DELETE", + url=OCM_URL + "/project/delete/execute", + headers={"Authorization": token}, + json={"projectId": project_id, "hash": ocm_delete_init["hash"]}, + timeout=20, + ) + ocm_delete = process_response(ocm_delete) + return ocm_delete diff --git a/src/ansys/conceptev/core/projects.py b/src/ansys/conceptev/core/projects.py deleted file mode 100644 index ef7f9a0f..00000000 --- a/src/ansys/conceptev/core/projects.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -"""Projects/OCM Specific functionality.""" - -import datetime - -import httpx - -from ansys.conceptev.core.exceptions import ( - AccountsError, - DesignError, - ProductIdsError, - ProjectError, - UserDetailsError, -) -from ansys.conceptev.core.settings import settings - -OCM_URL = settings.ocm_url -ACCOUNT_NAME = settings.account_name - - -def get_product_id(token: str) -> str: - """Get the product ID.""" - products = httpx.get(OCM_URL + "/product/list", headers={"Authorization": token}) - if products.status_code != 200: - raise ProductIdsError(f"Failed to get product id.") - - product_id = [ - product["productId"] for product in products.json() if product["productName"] == "CONCEPTEV" - ][0] - return product_id - - -def get_user_id(token): - """Get the user ID.""" - user_details = httpx.post(OCM_URL + "/user/details", headers={"Authorization": token}) - if user_details.status_code not in (200, 204): - raise UserDetailsError(f"Failed to get a user details on OCM {user_details}.") - user_id = user_details.json()["userId"] - return user_id - - -def get_account_ids(token: str) -> dict: - """Get account IDs.""" - response = httpx.post(url=OCM_URL + "/account/list", headers={"authorization": token}) - if response.status_code != 200: - raise AccountsError(f"Failed to get accounts {response}.") - accounts = { - account["account"]["accountName"]: account["account"]["accountId"] - for account in response.json() - } - return accounts - - -def get_account_id(token: str) -> str: - """Get the account ID from OCM using name from config file.""" - accounts = get_account_ids(token) - account_id = accounts[ACCOUNT_NAME] - return account_id - - -def get_default_hpc(token: str, account_id: str) -> dict: - """Get the default HPC ID.""" - response = httpx.post( - url=OCM_URL + "/account/hpc/default", - json={"accountId": account_id}, - headers={"authorization": token}, - ) - if response.status_code != 200: - raise AccountsError(f"Failed to get accounts {response}.") - return response.json()["hpcId"] - - -def get_project_ids(name: str, account_id: str, token: str) -> dict: - """Get projects.""" - response = httpx.post( - url=OCM_URL + "/project/list/page", - json={"accountId": account_id, "filterByName": name, "pageNumber": 0, "pageSize": 1000}, - headers={"Authorization": token}, - ) - if response.status_code != 200: - raise ProjectError(f"Failed to get projects {response}.") - - projects = response.json()["projects"] - return {project["projectTitle"]: project["projectId"] for project in projects} - - -def create_new_project( - client: httpx.Client, - account_id: str, - hpc_id: str, - title: str, - project_goal: str = "Created from the CLI", -) -> dict: - """Create a project.""" - token = client.headers["Authorization"] - project_data = { - "accountId": account_id, - "hpcId": hpc_id, - "projectTitle": title, - "projectGoal": project_goal, - } - created_project = httpx.post( - OCM_URL + "/project/create", headers={"Authorization": token}, json=project_data - ) - if created_project.status_code != 200 and created_project.status_code != 204: - raise ProjectError(f"Failed to create a project {created_project}.") - - return created_project.json() - - -def create_new_design( - client: httpx.AsyncClient, project_id: str, product_id: str = None, title: str = None -) -> dict: - """Create a new design on OCM.""" - if title is None: - title = f"CLI concept {datetime.datetime.now()}" - - token = client.headers["Authorization"] - if product_id is None: - product_id = get_product_id(token) - - design_data = { - "projectId": project_id, - "productId": product_id, - "designTitle": title, - } - created_design = httpx.post( - OCM_URL + "/design/create", headers={"Authorization": token}, json=design_data - ) - - if created_design.status_code not in (200, 204): - raise DesignError(f"Failed to create a design on OCM {created_design.content}.") - return created_design.json() diff --git a/src/ansys/conceptev/core/responses.py b/src/ansys/conceptev/core/responses.py new file mode 100644 index 00000000..32981efc --- /dev/null +++ b/src/ansys/conceptev/core/responses.py @@ -0,0 +1,49 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for dealing with http responses.""" + +from json import JSONDecodeError + +import httpx + +from ansys.conceptev.core.exceptions import ResponseError + + +def is_gateway_error(response) -> bool: + """Check if the response is a gateway error.""" + if isinstance(response, httpx.Response): + return response.status_code in (502, 504) + return False + + +def process_response(response) -> dict: + """Process a response. + + Check the value returned from the API and raise an error if the process is not successful. + """ + if response.status_code == 200 or response.status_code == 201: # Success + try: + return response.json() + except JSONDecodeError: + return response.content + raise ResponseError(f"Response Failed:{response.content}") diff --git a/tests/test_app.py b/tests/test_app.py index 6c6b3106..a88f4876 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -451,3 +451,45 @@ def test_get_job_file(httpx_mock: HTTPXMock): ) results = app.get_job_file(token, job_id, file_name) assert results == {"json": "1"} + + +def test_get_concept(httpx_mock: HTTPXMock, client: httpx.Client): + design_instance_id = "123" + httpx_mock.add_response( + url=f"{conceptev_url}/concepts/{design_instance_id}" + f"?design_instance_id={design_instance_id}&populated=false", + method="get", + json={"name": "concept"}, + ) + httpx_mock.add_response( + url=f"{conceptev_url}/concepts/{design_instance_id}/configurations" + f"?design_instance_id={design_instance_id}", + method="get", + json=[{"name": "configurations"}], + ) + httpx_mock.add_response( + url=f"{conceptev_url}/concepts/{design_instance_id}/components" + f"?design_instance_id={design_instance_id}", + method="get", + json=[{"name": "components"}], + ) + httpx_mock.add_response( + url=f"{conceptev_url}/concepts/{design_instance_id}/requirements" + f"?design_instance_id={design_instance_id}", + method="get", + json=[{"name": "reequirements"}], + ) + httpx_mock.add_response( + url=f"{conceptev_url}/concepts/{design_instance_id}/architecture" + f"?design_instance_id={design_instance_id}", + method="get", + json={"name": "architecture"}, + ) + response = app.get_concept(client, design_instance_id) + assert response == { + "name": "concept", + "configurations": [{"name": "configurations"}], + "components": [{"name": "components"}], + "requirements": [{"name": "reequirements"}], + "architecture": {"name": "architecture"}, + } diff --git a/tests/test_projects.py b/tests/test_ocm.py similarity index 86% rename from tests/test_projects.py rename to tests/test_ocm.py index 26b126f8..464bfe6a 100644 --- a/tests/test_projects.py +++ b/tests/test_ocm.py @@ -28,7 +28,7 @@ import pytest from ansys.conceptev.core.auth import create_msal_app, get_ansyId_token -import ansys.conceptev.core.projects as projects +import ansys.conceptev.core.ocm as ocm from ansys.conceptev.core.settings import settings OCM_URL = settings.ocm_url @@ -44,21 +44,21 @@ def token(): @pytest.mark.integration def test_product_id(token): """Test product id from OCM.""" - product_id = projects.get_product_id(token) + product_id = ocm.get_product_id(token) assert product_id == "SAAS000040" @pytest.mark.integration def test_get_user_id(token): """Test user id from OCM.""" - user_id = projects.get_user_id(token) + user_id = ocm.get_user_id(token) assert user_id == "95bb6bf9-0afd-4426-b736-7e1c8abd5a78" @pytest.mark.integration def test_get_account_ids(token): """Test account ids from OCM.""" - account_ids = projects.get_account_ids(token) + account_ids = ocm.get_account_ids(token) assert account_ids == { "ConceptEv Test Account": "2a566ece-938d-4658-bae5-ffa387ac0547", "conceptev_testing@ansys.com": "108581c8-13e6-4b39-8051-5f8e61135aca", @@ -68,7 +68,7 @@ def test_get_account_ids(token): @pytest.mark.integration def test_get_account_id(token): """Test account ids from OCM.""" - account_id = projects.get_account_id(token) + account_id = ocm.get_account_id(token) assert account_id == "2a566ece-938d-4658-bae5-ffa387ac0547" @@ -76,7 +76,7 @@ def test_get_account_id(token): def test_get_default_hpc(token): """Test default HPC from OCM.""" account_id = "2a566ece-938d-4658-bae5-ffa387ac0547" - hpc_id = projects.get_default_hpc(token, account_id) + hpc_id = ocm.get_default_hpc(token, account_id) assert hpc_id == "3ded64e3-5a83-24a8-b6e4-9fc30f97a654" @@ -86,9 +86,9 @@ def test_get_project_ids(token): project_name = "New Project (with brackets)" account_id = "2a566ece-938d-4658-bae5-ffa387ac0547" - project_ids = projects.get_project_ids(re.escape(project_name), account_id, token) + project_ids = ocm.get_project_ids(re.escape(project_name), account_id, token) assert project_name in project_ids.keys() - assert "00932037-a633-464c-8d05-28353d9bfc49" in project_ids.values() + assert "00932037-a633-464c-8d05-28353d9bfc49" in project_ids[project_name] @pytest.mark.integration @@ -98,7 +98,7 @@ def test_create_new_project(token): hpc_id = "23c70728-b930-d1eb-a0b1-dbf9ea0f6278" project_name = f"hello {datetime.now()}" client = httpx.Client(headers={"Authorization": token}) - project = projects.create_new_project(client, account_id, hpc_id, project_name) + project = ocm.create_new_project(client, account_id, hpc_id, project_name) assert project["projectTitle"] == project_name now = datetime.now().timestamp() seconds_ago = now - (project["createDate"] / 1000) @@ -113,7 +113,7 @@ def test_create_new_design(token): project_id = "fcafedf2-9cb6-4035-9c6c-2bd7602537f2" client = httpx.Client(headers={"Authorization": token}) - design = projects.create_new_design(client, project_id, product_id) + design = ocm.create_new_design(client, project_id, product_id) now = datetime.now().timestamp() seconds_ago = now - (design["createDate"] / 1000) From 5621dceb058d44f40344f556987954a0e192d16e Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:10:41 +0000 Subject: [PATCH 2/9] chore: adding changelog file 220.test.md [dependabot-skip] --- doc/changelog.d/220.test.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/220.test.md diff --git a/doc/changelog.d/220.test.md b/doc/changelog.d/220.test.md new file mode 100644 index 00000000..7ab46727 --- /dev/null +++ b/doc/changelog.d/220.test.md @@ -0,0 +1 @@ +refactor for ocm \ No newline at end of file From 1a16414da9c3a31a79f38a066bde421c838e1153 Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Wed, 23 Apr 2025 09:57:50 +0100 Subject: [PATCH 3/9] adding missing responses file --- src/ansys/conceptev/core/response.py | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/ansys/conceptev/core/response.py diff --git a/src/ansys/conceptev/core/response.py b/src/ansys/conceptev/core/response.py new file mode 100644 index 00000000..7693328b --- /dev/null +++ b/src/ansys/conceptev/core/response.py @@ -0,0 +1,49 @@ +# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Module for processing API responses.""" + +from json import JSONDecodeError + +import httpx + +from ansys.conceptev.core.exceptions import ResponseError + + +def process_response(response) -> dict: + """Process a response. + + Check the value returned from the API and raise an error if the process is not successful. + """ + if response.status_code == 200 or response.status_code == 201: # Success + try: + return response.json() + except JSONDecodeError: + return response.content + raise ResponseError(f"Response Failed:{response.content}") + + +def is_gateway_error(response) -> bool: + """Check if the response is a gateway error.""" + if isinstance(response, httpx.Response): + return response.status_code in (502, 504) + return False From 2a449f270a21f2240d05e345c8d949bc0b40de31 Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Wed, 23 Apr 2025 11:59:18 +0100 Subject: [PATCH 4/9] updated get_token --- poetry.lock | 6 +++--- src/ansys/conceptev/core/app.py | 20 +++----------------- src/ansys/conceptev/core/auth.py | 10 ++++++++++ src/ansys/conceptev/core/ocm.py | 3 ++- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index cdcab156..04108c26 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -1342,7 +1342,7 @@ description = "Read resources from Python packages" optional = false python-versions = ">=3.9" groups = ["main", "doc"] -markers = "python_version < \"3.10\"" +markers = "python_version == \"3.9\"" files = [ {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, @@ -5267,7 +5267,7 @@ files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] -markers = {main = "python_version < \"3.10\"", build = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version <= \"3.11\" or python_full_version < \"3.10.2\""} +markers = {main = "python_version == \"3.9\"", build = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version <= \"3.11\" or python_full_version < \"3.10.2\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] diff --git a/src/ansys/conceptev/core/app.py b/src/ansys/conceptev/core/app.py index 1404b117..39df21d6 100644 --- a/src/ansys/conceptev/core/app.py +++ b/src/ansys/conceptev/core/app.py @@ -28,12 +28,7 @@ from tenacity import retry, retry_if_result, stop_after_delay, wait_random_exponential from ansys.conceptev.core import auth -from ansys.conceptev.core.exceptions import ( - DeleteError, - ProductAccessError, - ResultsError, - TokenError, -) +from ansys.conceptev.core.exceptions import DeleteError, ProductAccessError, ResultsError from ansys.conceptev.core.ocm import ( create_design_instance, create_new_project, @@ -209,7 +204,7 @@ def create_new_concept( if title is None: title = f"CLI concept {datetime.datetime.now()}" - token = get_token(client) + token = auth.get_token(client) if product_id is None: product_id = get_product_id(token) @@ -264,7 +259,7 @@ def read_results( ) -> dict: """Read job results.""" job_id = job_info["job_id"] - token = get_token(client) + token = auth.get_token(client) user_id = get_user_id(token) initial_status = get_status(job_info, token) if check_status(initial_status): # Job already completed @@ -279,15 +274,6 @@ def read_results( return get_results(client, job_info, calculate_units, filtered) -def get_token(client: httpx.Client) -> str: - """Get the token from the client.""" - if client.auth is not None and client.auth.app is not None: - return auth.get_ansyId_token(client.auth.app) - elif client.headers is not None and "Authorization" in client.headers: - return client.headers["Authorization"] - raise TokenError("App not found in client.") - - def post_component_file(client: httpx.Client, filename: str, component_file_type: str) -> dict: """Send a POST request to the base client with a file. diff --git a/src/ansys/conceptev/core/auth.py b/src/ansys/conceptev/core/auth.py index eadab205..5eb5ca2b 100644 --- a/src/ansys/conceptev/core/auth.py +++ b/src/ansys/conceptev/core/auth.py @@ -28,6 +28,7 @@ from msal import PublicClientApplication from msal_extensions import FilePersistence, build_encrypted_persistence, token_cache +from ansys.conceptev.core.exceptions import TokenError from ansys.conceptev.core.settings import settings logger = logging.getLogger(__name__) @@ -100,3 +101,12 @@ def auth_flow(self, request): token = get_ansyId_token(self.app) request.headers["Authorization"] = token yield request + + +def get_token(client: httpx.Client) -> str: + """Get the token from the client.""" + if client.auth is not None and client.auth.app is not None: + return get_ansyId_token(client.auth.app) + elif client.headers is not None and "Authorization" in client.headers: + return client.headers["Authorization"] + raise TokenError("App not found in client.") diff --git a/src/ansys/conceptev/core/ocm.py b/src/ansys/conceptev/core/ocm.py index ea7eb406..9b2e3492 100644 --- a/src/ansys/conceptev/core/ocm.py +++ b/src/ansys/conceptev/core/ocm.py @@ -29,6 +29,7 @@ import httpx +import ansys.conceptev.core.auth as auth from ansys.conceptev.core.exceptions import ( AccountsError, DesignError, @@ -104,7 +105,7 @@ def create_new_project( project_goal: str = "Created from the CLI", ) -> dict: """Create a project.""" - token = client.headers["Authorization"] + token = auth.get_token(client) project_data = { "accountId": account_id, "hpcId": hpc_id, From 05dc897eeb73cf855f9e8d02e9322d87c97d4019 Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Wed, 23 Apr 2025 13:32:19 +0100 Subject: [PATCH 5/9] missed import --- src/ansys/conceptev/core/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ansys/conceptev/core/app.py b/src/ansys/conceptev/core/app.py index 39df21d6..39830c37 100644 --- a/src/ansys/conceptev/core/app.py +++ b/src/ansys/conceptev/core/app.py @@ -28,6 +28,7 @@ from tenacity import retry, retry_if_result, stop_after_delay, wait_random_exponential from ansys.conceptev.core import auth +from ansys.conceptev.core.auth import get_token from ansys.conceptev.core.exceptions import DeleteError, ProductAccessError, ResultsError from ansys.conceptev.core.ocm import ( create_design_instance, @@ -68,6 +69,7 @@ get_project_ids, get_project_id, delete_project, + get_token, ] Router = Literal[ From 31fec53881f487d7877e71e1e39302a6881cd3cc Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Tue, 17 Jun 2025 09:17:20 +0100 Subject: [PATCH 6/9] removed duplicated module --- src/ansys/conceptev/core/app.py | 2 +- src/ansys/conceptev/core/response.py | 49 ---------------------------- 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 src/ansys/conceptev/core/response.py diff --git a/src/ansys/conceptev/core/app.py b/src/ansys/conceptev/core/app.py index 39830c37..68228dc1 100644 --- a/src/ansys/conceptev/core/app.py +++ b/src/ansys/conceptev/core/app.py @@ -49,7 +49,7 @@ get_user_id, ) from ansys.conceptev.core.progress import check_status, monitor_job_progress -from ansys.conceptev.core.response import is_gateway_error, process_response +from ansys.conceptev.core.responses import is_gateway_error, process_response from ansys.conceptev.core.settings import settings __all__ = [ diff --git a/src/ansys/conceptev/core/response.py b/src/ansys/conceptev/core/response.py deleted file mode 100644 index 7693328b..00000000 --- a/src/ansys/conceptev/core/response.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates. -# SPDX-License-Identifier: MIT -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -"""Module for processing API responses.""" - -from json import JSONDecodeError - -import httpx - -from ansys.conceptev.core.exceptions import ResponseError - - -def process_response(response) -> dict: - """Process a response. - - Check the value returned from the API and raise an error if the process is not successful. - """ - if response.status_code == 200 or response.status_code == 201: # Success - try: - return response.json() - except JSONDecodeError: - return response.content - raise ResponseError(f"Response Failed:{response.content}") - - -def is_gateway_error(response) -> bool: - """Check if the response is a gateway error.""" - if isinstance(response, httpx.Response): - return response.status_code in (502, 504) - return False From 0c7d1a509dd35bc9f87b8f27ee920cefbd55aa9c Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Tue, 17 Jun 2025 09:35:33 +0100 Subject: [PATCH 7/9] whitelist licences they have MIT just not being picked up for some reason --- .github/workflows/ci_cd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 7cf42456..c97fb6df 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -90,6 +90,7 @@ jobs: library-name: ${{ env.PACKAGE_NAME }} operating-system: ${{ matrix.os }} python-version: ${{ matrix.python-version }} + whitelist-license-check: "zipp,typing-inspection" smoke-tests-workaround: name: Build and Smoke tests From db6994919c2431ef0bf25124e9f0aed004bd24d9 Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Tue, 17 Jun 2025 09:56:54 +0100 Subject: [PATCH 8/9] updated on the workaround job --- .github/workflows/ci_cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index c97fb6df..aa4f9bf2 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -108,7 +108,7 @@ jobs: library-name: ${{ env.PACKAGE_NAME }} operating-system: ${{ matrix.os }} python-version: ${{ matrix.python-version }} - whitelist-license-check: "pillow,urllib3" + whitelist-license-check: "zipp,typing-inspection" tests: name: Tests and coverage From 7dec02121810881a606a73175c748c97eba0f724 Mon Sep 17 00:00:00 2001 From: Philip Usher Date: Tue, 17 Jun 2025 10:00:37 +0100 Subject: [PATCH 9/9] urrlib needed in 3.9 --- .github/workflows/ci_cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index aa4f9bf2..88fb8740 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -102,13 +102,13 @@ jobs: os: [ ubuntu-latest, windows-latest ] python-version: [ "3.9" ] steps: - - name: Build wheelhouse and perform smoke test + - name: Build wheelhouse and perform smoke test v3.9 uses: ansys/actions/build-wheelhouse@v8 with: library-name: ${{ env.PACKAGE_NAME }} operating-system: ${{ matrix.os }} python-version: ${{ matrix.python-version }} - whitelist-license-check: "zipp,typing-inspection" + whitelist-license-check: "zipp,typing-inspection,urllib3" tests: name: Tests and coverage