diff --git a/.env b/.env index 706ccab..00c65cc 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -FLASK_DEBUG=True \ No newline at end of file +FLASK_DEBUG=True diff --git a/.github/workflows/prepare_pr.yml b/.github/workflows/prepare_pr.yml new file mode 100644 index 0000000..d0fbe7b --- /dev/null +++ b/.github/workflows/prepare_pr.yml @@ -0,0 +1,11 @@ +name: Prepare PR + +on: + pull_request: + branches: + - next + +jobs: + prepare: + uses: Geode-solutions/actions/.github/workflows/py-prepare-pr.yml@master + secrets: inherit \ No newline at end of file diff --git a/app.py b/app.py index efb9199..8d09cfd 100644 --- a/app.py +++ b/app.py @@ -87,6 +87,7 @@ def kill_task(): @app.errorhandler(HTTPException) def handle_exception(e): response = e.get_response() + print(e.description) response.data = flask.json.dumps( { "code": e.code, diff --git a/blueprint_crs_converter.py b/blueprint_crs_converter.py deleted file mode 100644 index 8045071..0000000 --- a/blueprint_crs_converter.py +++ /dev/null @@ -1,143 +0,0 @@ -import shutil -import flask -import flask_cors -import os -import werkzeug -import functions -import zipfile -import geode_objects - - -crs_converter_routes = flask.Blueprint('crs_converter_routes', __name__) -flask_cors.CORS(crs_converter_routes) - - -@crs_converter_routes.before_request -def before_request(): - functions.create_lock_file() - -@crs_converter_routes.teardown_request -def teardown_request(exception): - functions.remove_lock_file() - functions.create_time_file() - -@crs_converter_routes.route('/versions', methods=['GET']) -def crs_converter_versions(): - list_packages = ['OpenGeode-core', 'OpenGeode-IO', 'OpenGeode-Geosciences', 'OpenGeode-GeosciencesIO'] - - return flask.make_response({'versions': functions.get_versions(list_packages)}, 200) - -@crs_converter_routes.route('/allowed_files', methods=['GET']) -def crs_converter_allowed_files(): - extensions = functions.list_all_input_extensions() - - return {'status': 200, 'extensions': extensions} - -@crs_converter_routes.route('/allowed_objects', methods=['POST']) -def crs_converter_allowed_objects(): - filename = flask.request.form.get('filename') - if filename is None: - return flask.make_response({'error_message': 'No file sent'}, 400) - file_extension = os.path.splitext(filename)[1][1:] - allowed_objects = functions.list_objects(file_extension) - - return flask.make_response({'allowed_objects': allowed_objects}, 200) - -@crs_converter_routes.route('/geographic_coordinate_systems', methods=['POST']) -def crs_converter_geographic_coordinate_systems(): - geode_object = flask.request.form.get('geode_object') - if geode_object is None: - return flask.make_response({'error_message': 'No geode_object sent'}, 400) - - infos = functions.get_geographic_coordinate_systems(geode_object) - crs_list = [] - - for info in infos: - crs = {} - crs['name'] = info.name - crs['code'] = info.code - crs['authority'] = info.authority - crs_list.append(crs) - - return flask.make_response({'crs_list': crs_list}, 200) - -@crs_converter_routes.route('/output_file_extensions', methods=['POST']) -def crs_converter_output_file_extensions(): - geode_object = flask.request.form.get('geode_object') - if geode_object is None: - return flask.make_response({'error_message': 'No geode_object sent'}, 400) - output_file_extensions = functions.list_output_file_extensions(geode_object) - - return flask.make_response({'output_file_extensions': output_file_extensions}, 200) - -@crs_converter_routes.route('/convert_file', methods=['POST']) -async def crs_converter_convert_file(): - UPLOAD_FOLDER = flask.current_app.config['UPLOAD_FOLDER'] - geode_object= flask.request.form.get('geode_object') - file = flask.request.form.get('file') - filename = flask.request.form.get('filename') - filesize = flask.request.form.get('filesize') - input_crs_authority = flask.request.form.get('input_crs_authority') - input_crs_code = flask.request.form.get('input_crs_code') - input_crs_name = flask.request.form.get('input_crs_name') - output_crs_authority = flask.request.form.get('output_crs_authority') - output_crs_code = flask.request.form.get('output_crs_code') - output_crs_name = flask.request.form.get('output_crs_name') - extension = flask.request.form.get('extension') - - if geode_object is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No geode_object sent' }, 400) - if file is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No file sent' }, 400) - if filename is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No filename sent' }, 400) - if filesize is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No filesize sent' }, 400) - if input_crs_authority is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No input_crs_authority sent' }, 400) - if input_crs_code is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No input_crs_code sent' }, 400) - if input_crs_name is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No input_crs_name sent' }, 400) - if output_crs_authority is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No output_crs_authority sent' }, 400) - if output_crs_code is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No output_crs_code sent' }, 400) - if output_crs_name is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No output_crs_name sent' }, 400) - if extension is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No extension sent' }, 400) - - input_crs = { - 'authority': input_crs_authority, - 'code': input_crs_code, - 'name': input_crs_name - } - - output_crs = { - 'authority': output_crs_authority, - 'code': output_crs_code, - 'name': output_crs_name - } - - uploaded_file = functions.upload_file(file, filename, UPLOAD_FOLDER, filesize) - if not uploaded_file: - flask.make_response({ 'name': 'Internal Server Error','description': 'File not uploaded' }, 500) - - secure_filename = werkzeug.utils.secure_filename(filename) - file_path = os.path.join(UPLOAD_FOLDER, secure_filename).replace('\\','/') - data = geode_objects.objects_list()[geode_object]['load'](file_path) - strict_file_name = os.path.splitext(secure_filename)[0] - new_file_name = strict_file_name + '.' + extension - - functions.asign_geographic_coordinate_system_info(geode_object, data, input_crs) - functions.convert_geographic_coordinate_system_info(geode_object, data, output_crs) - - geode_objects.objects_list()[geode_object]['save'](data, os.path.join(UPLOAD_FOLDER, new_file_name)) - - response = flask.send_from_directory(directory = UPLOAD_FOLDER, path = new_file_name, as_attachment = True, mimetype = 'application/octet-binary') - response.headers['new-file-name'] = new_file_name - response.headers['Access-Control-Expose-Headers'] = 'new-file-name' - - return response - diff --git a/blueprint_file_converter.py b/blueprint_file_converter.py deleted file mode 100644 index 05fce4d..0000000 --- a/blueprint_file_converter.py +++ /dev/null @@ -1,117 +0,0 @@ -import shutil -import flask -import flask_cors -import os -import werkzeug -import functions -import zipfile - -file_converter_routes = flask.Blueprint('file_converter_routes', __name__) -flask_cors.CORS(file_converter_routes) - - -@file_converter_routes.before_request -def before_request(): - functions.create_lock_file() - -@file_converter_routes.teardown_request -def teardown_request(exception): - functions.remove_lock_file() - functions.create_time_file() - -@file_converter_routes.route('/versions', methods=['GET']) -def file_converter_versions(): - list_packages = ['OpenGeode-core', 'OpenGeode-IO', 'OpenGeode-Geosciences', 'OpenGeode-GeosciencesIO'] - - return flask.make_response({'versions': functions.get_versions(list_packages)}, 200) - -@file_converter_routes.route('/allowed_files', methods=['GET']) -def file_converter_allowed_files(): - extensions = functions.list_all_input_extensions() - - return {'status': 200, 'extensions': extensions} - -@file_converter_routes.route('/allowed_objects', methods=['POST']) -def file_converter_allowed_objects(): - filename = flask.request.form.get('filename') - if filename is None: - return flask.make_response({'error_message': 'No file sent'}, 400) - file_extension = os.path.splitext(filename)[1][1:] - allowed_objects = functions.list_objects(file_extension) - - return flask.make_response({'allowed_objects': allowed_objects}, 200) - -@file_converter_routes.route('/output_file_extensions', methods=['POST']) -def file_converter_output_file_extensions(): - geode_object = flask.request.form.get('geode_object') - if geode_object is None: - return flask.make_response({'error_message': 'No geode_object sent'}, 400) - list = functions.list_output_file_extensions(geode_object) - - return flask.make_response({'output_file_extensions': list}, 200) - -@file_converter_routes.route('/convert_file', methods=['POST']) -async def file_converter_convert_file(): - UPLOAD_FOLDER = flask.current_app.config['UPLOAD_FOLDER'] - geode_object = flask.request.form.get('geode_object') - file = flask.request.form.get('file') - filename = flask.request.form.get('filename') - filesize = flask.request.form.get('filesize') - extension = flask.request.form.get('extension') - - if geode_object is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No geode_object sent' }, 400) - if file is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No file sent' }, 400) - if filename is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No filename sent' }, 400) - if filesize is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No filesize sent' }, 400) - if extension is None: - return flask.make_response({ 'name': 'Bad Request','description': 'No extension sent' }, 400) - - uploaded_file = functions.upload_file(file, filename, UPLOAD_FOLDER, filesize) - if not uploaded_file: - flask.make_response({ 'name': 'Internal Server Error','description': 'File not uploaded' }, 500) - - secure_filename = werkzeug.utils.secure_filename(filename) - file_path = os.path.join(UPLOAD_FOLDER, secure_filename) - data = functions.geode_objects.objects_list()[geode_object]['load'](file_path) - strict_file_name = os.path.splitext(secure_filename)[0] - new_file_name = strict_file_name + '.' + extension - - sub_folder = f'{UPLOAD_FOLDER}/{strict_file_name}/' - if os.path.exists(sub_folder): - shutil.rmtree(sub_folder) - - functions.geode_objects.objects_list()[geode_object]['save'](data, os.path.join(UPLOAD_FOLDER, new_file_name)) - mimetype = 'application/octet-binary' - - list_exceptions = ['triangle', 'vtm'] - if extension in list_exceptions: - if extension == 'triangle': - os.mkdir(sub_folder) - os.chdir(sub_folder) - generated_files = f'{UPLOAD_FOLDER}/{strict_file_name}' - shutil.move(generated_files + '.ele', sub_folder) - shutil.move(generated_files + '.neigh', sub_folder) - shutil.move(generated_files + '.node', sub_folder) - os.chdir('..') - elif extension == 'vtm': - generated_files = f'{UPLOAD_FOLDER}/{strict_file_name}' - shutil.move(generated_files + '.vtm', sub_folder) - shutil.move(strict_file_name, subFolder) - new_file_name = strict_file_name + '.zip' - mimetype = 'application/zip' - with zipfile.ZipFile(f'{UPLOAD_FOLDER}/{new_file_name}', 'w') as zipObj: - for folder_name, sub_folders, file_names in os.walk(sub_folder): - for filename in file_names: - file_path = os.path.join(folder_name, filename) - zipObj.write(file_path, os.path.basename(file_path)) - - response = flask.send_from_directory(directory=UPLOAD_FOLDER, path=new_file_name, as_attachment=True, mimetype = mimetype) - response.headers['new-file-name'] = new_file_name - response.headers['Access-Control-Expose-Headers'] = 'new-file-name' - - return response - diff --git a/blueprint_validity_checker.py b/blueprint_validity_checker.py deleted file mode 100644 index b8232f2..0000000 --- a/blueprint_validity_checker.py +++ /dev/null @@ -1,107 +0,0 @@ -import flask -import flask_cors -import os -import functions -import geode_objects -import inspector_functions -import werkzeug - -validity_checker_routes = flask.Blueprint('validity_checker_routes', __name__) -flask_cors.CORS(validity_checker_routes) - -@validity_checker_routes.before_request -def before_request(): - functions.create_lock_file() - -@validity_checker_routes.teardown_request -def teardown_request(exception): - functions.remove_lock_file() - functions.create_time_file() - -@validity_checker_routes.route('/versions', methods=['GET']) -def validity_checker_versions(): - list_packages = ['OpenGeode-core', 'OpenGeode-IO', 'OpenGeode-Geosciences', 'OpenGeode-GeosciencesIO', 'OpenGeode-Inspector'] - - return flask.make_response({'versions': functions.get_versions(list_packages)}, 200) - -@validity_checker_routes.route('/allowed_files', methods=['GET']) -def validity_checker_allowed_files(): - extensions = functions.list_all_input_extensions() - - return flask.make_response({'extensions': extensions}, 200) - -@validity_checker_routes.route('/allowed_objects', methods=['POST']) -def validity_checker_allowed_objects(): - filename = flask.request.form.get('filename') - if filename is None: - return flask.make_response({'description': 'No file sent'}, 400) - file_extension = os.path.splitext(filename)[1][1:] - allowed_objects = functions.list_objects(file_extension) - - return flask.make_response({'allowed_objects': allowed_objects}, 200) - -@validity_checker_routes.route('/upload_file', methods=['POST']) -def validity_checker_upload_file(): - UPLOAD_FOLDER = flask.current_app.config['UPLOAD_FOLDER'] - file = flask.request.form.get('file') - filename = flask.request.form.get('filename') - filesize = flask.request.form.get('filesize') - if file is None: - return flask.make_response({'description': 'No file sent'}, 400) - if filename is None: - return flask.make_response({'description': 'No filename sent'}, 400) - if filesize is None: - return flask.make_response({'description': 'No filesize sent'}, 400) - - uploadedFile = functions.upload_file(file, filename, UPLOAD_FOLDER, filesize) - if not uploadedFile: - flask.make_response({'description': 'File not uploaded'}, 500) - - return flask.make_response({'message': 'File uploaded'}, 200) - -@validity_checker_routes.route('/tests_names', methods=['POST']) -def validity_checker_test_names(): - geode_object = flask.request.form.get('geode_object') - if geode_object is None: - return flask.make_response({'description': 'No geode_object sent'}, 400) - model_checks = inspector_functions.json_return(inspector_functions.inspectors()[geode_object]['tests_names']) - - return flask.make_response({'model_checks': model_checks}, 200) - -@validity_checker_routes.route('/inspect_file', methods=['POST']) -def validity_checker_inspect_file(): - UPLOAD_FOLDER = flask.current_app.config['UPLOAD_FOLDER'] - geode_object = flask.request.form.get('geode_object') - filename = flask.request.form.get('filename') - test = flask.request.form.get('test') - - if geode_object is None: - return flask.make_response({'error_message': 'No geode_object sent'}, 400) - if filename is None: - return flask.make_response({'error_message': 'No filename sent'}, 400) - if test is None: - return flask.make_response({'error_message': 'No test sent'}, 400) - - secure_filename = werkzeug.utils.secure_filename(filename) - file_path = os.path.join(UPLOAD_FOLDER, secure_filename) - data = geode_objects.objects_list()[geode_object]['load'](file_path) - inspector = inspector_functions.inspectors()[geode_object]['inspector'](data) - test_result = getattr(inspector, test)() - expected_value = inspector_functions.expected_results()[test] - - if test_result != expected_value or type(test_result) != type(expected_value): - if type(test_result) is list: - if type(test_result[0]) is tuple: - temp_test_result = [] - for tuple_item in test_result: - temp_list = [] - for index in range(len(tuple_item)): - temp_list.append(tuple_item[index].string()) - temp_test_result.append(temp_list) - test_result = temp_test_result - result = test_result == expected_value and type(test_result) == type(expected_value) - - if (result == False): - print(f'{test=}', flush=True) - - return flask.make_response({'result': result, 'list_invalidities': str(test_result)}, 200) diff --git a/blueprints/blueprint_tools.py b/blueprints/blueprint_tools.py index 0f1ebdd..b2874bb 100644 --- a/blueprints/blueprint_tools.py +++ b/blueprints/blueprint_tools.py @@ -2,26 +2,61 @@ import flask import flask_cors from opengeodeweb_back import geode_functions, geode_objects +import werkzeug import blueprints.tools.blueprint_file_converter as bp_file_converter import blueprints.tools.blueprint_validity_checker as bp_validity_checker import blueprints.tools.blueprint_crs_converter as bp_crs_converter -tools_routes = flask.Blueprint('crs_converter_routes', __name__) +tools_routes = flask.Blueprint("crs_converter_routes", __name__) flask_cors.CORS(tools_routes) @tools_routes.before_request def before_request(): - geode_functions.create_lock_file(os.path.abspath(flask.current_app.config["LOCK_FOLDER"])) + geode_functions.create_lock_file( + os.path.abspath(flask.current_app.config["LOCK_FOLDER"]) + ) @tools_routes.teardown_request def teardown_request(exception): - geode_functions.remove_lock_file(os.path.abspath(flask.current_app.config["LOCK_FOLDER"])) - geode_functions.create_time_file(os.path.abspath(flask.current_app.config["TIME_FOLDER"])) - - -tools_routes.register_blueprint(bp_file_converter.file_converter_routes, url_prefix='/file_converter', name='file_converter_blueprint') -tools_routes.register_blueprint(bp_validity_checker.validity_checker_routes, url_prefix='/validity_checker', name='validity_checker_blueprint') -tools_routes.register_blueprint(bp_crs_converter.crs_converter_routes, url_prefix='/crs_converter', name='crs_converter_blueprint') + geode_functions.remove_lock_file( + os.path.abspath(flask.current_app.config["LOCK_FOLDER"]) + ) + geode_functions.create_time_file( + os.path.abspath(flask.current_app.config["TIME_FOLDER"]) + ) + + +tools_routes.register_blueprint( + bp_file_converter.file_converter_routes, + url_prefix="/file_converter", + name="file_converter_blueprint", +) +tools_routes.register_blueprint( + bp_validity_checker.validity_checker_routes, + url_prefix="/validity_checker", + name="validity_checker_blueprint", +) +tools_routes.register_blueprint( + bp_crs_converter.crs_converter_routes, + url_prefix="/crs_converter", + name="crs_converter_blueprint", +) + + +@tools_routes.route("/upload_file", methods=["OPTIONS", "PUT"]) +def upload_file(): + if flask.request.method == "OPTIONS": + return flask.make_response({}, 200) + + UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] + if not os.path.exists(UPLOAD_FOLDER): + os.mkdir(UPLOAD_FOLDER) + files = flask.request.files.getlist("content") + + for file in files: + filename = werkzeug.utils.secure_filename(os.path.basename(file.filename)) + file.save(os.path.join(UPLOAD_FOLDER, filename)) + return flask.make_response({"message": "File uploaded"}, 201) diff --git a/blueprints/tools/blueprint_crs_converter.py b/blueprints/tools/blueprint_crs_converter.py index 239193a..e9ca26c 100644 --- a/blueprints/tools/blueprint_crs_converter.py +++ b/blueprints/tools/blueprint_crs_converter.py @@ -24,37 +24,30 @@ def crs_converter_versions(): "OpenGeode-GeosciencesIO", ] return flask.make_response( - {"versions": geode_functions.get_versions(list_packages)}, 200 + {"versions": geode_functions.versions(list_packages)}, 200 ) @crs_converter_routes.route("/allowed_files", methods=["GET"]) def crs_converter_allowed_files(): - extensions = geode_functions.list_all_input_extensions() + extensions = geode_functions.list_input_extensions("crs") return {"status": 200, "extensions": extensions} @crs_converter_routes.route("/allowed_objects", methods=["POST"]) def crs_converter_allowed_objects(): - array_variables = ["filename"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - print(variables_dict["filename"]) - file_extension = os.path.splitext(variables_dict["filename"])[1][1:] - allowed_objects = geode_functions.list_objects(file_extension) + geode_functions.validate_request(flask.request, ["filename"]) + file_extension = os.path.splitext(flask.request.json["filename"])[1][1:] + allowed_objects = geode_functions.list_geode_objects(file_extension, "crs") return flask.make_response({"allowed_objects": allowed_objects}, 200) @crs_converter_routes.route("/geographic_coordinate_systems", methods=["POST"]) def crs_converter_geographic_coordinate_systems(): - array_variables = ["geode_object"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - infos = geode_functions.get_geographic_coordinate_systems( - variables_dict["geode_object"] + geode_functions.validate_request(flask.request, ["geode_object"]) + infos = geode_functions.geographic_coordinate_systems( + flask.request.json["geode_object"] ) crs_list = [] @@ -69,13 +62,10 @@ def crs_converter_geographic_coordinate_systems(): @crs_converter_routes.route("/output_file_extensions", methods=["POST"]) -def crs_converter_output_file_extensions(): - array_variables = ["geode_object"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - output_file_extensions = geode_functions.list_output_file_extensions( - variables_dict["geode_object"] +def crs_converter_output_file_extensiongeode_functionss(): + geode_functions.validate_request(flask.request, ["geode_object"]) + output_file_extensions = geode_functions.geode_object_output_extensions( + flask.request.json["geode_object"] ) return flask.make_response({"output_file_extensions": output_file_extensions}, 200) @@ -84,59 +74,49 @@ def crs_converter_output_file_extensions(): @crs_converter_routes.route("/convert_file", methods=["POST"]) async def crs_converter_convert_file(): UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] - - array_variables = [ - "geode_object", - "file", - "filename", - "filesize", - "input_crs_authority", - "input_crs_code", - "input_crs_name", - "output_crs_authority", - "output_crs_code", - "output_crs_name", - "extension", - ] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables + geode_functions.validate_request( + flask.request, + [ + "geode_object", + "filename", + "input_crs_authority", + "input_crs_code", + "input_crs_name", + "output_crs_authority", + "output_crs_code", + "output_crs_name", + "extension", + ], ) input_crs = { - "authority": variables_dict["input_crs_authority"], - "code": variables_dict["input_crs_code"], - "name": variables_dict["input_crs_name"], + "authority": flask.request.json["input_crs_authority"], + "code": flask.request.json["input_crs_code"], + "name": flask.request.json["input_crs_name"], } output_crs = { - "authority": variables_dict["output_crs_authority"], - "code": variables_dict["output_crs_code"], - "name": variables_dict["output_crs_name"], + "authority": flask.request.json["output_crs_authority"], + "code": flask.request.json["output_crs_code"], + "name": flask.request.json["output_crs_name"], } - geode_functions.upload_file( - variables_dict["file"], - variables_dict["filename"], - UPLOAD_FOLDER, - variables_dict["filesize"], - ) - - secure_filename = werkzeug.utils.secure_filename(variables_dict["filename"]) + secure_filename = werkzeug.utils.secure_filename(flask.request.json["filename"]) file_path = os.path.abspath(os.path.join(UPLOAD_FOLDER, secure_filename)) - data = geode_functions.load(variables_dict["geode_object"], file_path) + data = geode_functions.load(flask.request.json["geode_object"], file_path) strict_file_name = os.path.splitext(secure_filename)[0] - new_file_name = strict_file_name + "." + variables_dict["extension"] + new_file_name = strict_file_name + "." + flask.request.json["extension"] geode_functions.assign_geographic_coordinate_system_info( - variables_dict["geode_object"], data, input_crs + flask.request.json["geode_object"], data, input_crs ) geode_functions.convert_geographic_coordinate_system_info( - variables_dict["geode_object"], data, output_crs + flask.request.json["geode_object"], data, output_crs ) geode_functions.save( + flask.request.json["geode_object"], data, - variables_dict["geode_object"], os.path.abspath(UPLOAD_FOLDER), new_file_name, ) @@ -149,4 +129,4 @@ async def crs_converter_convert_file(): response.headers["new-file-name"] = new_file_name response.headers["Access-Control-Expose-Headers"] = "new-file-name" - return response \ No newline at end of file + return response diff --git a/blueprints/tools/blueprint_file_converter.py b/blueprints/tools/blueprint_file_converter.py index 4b3ed12..c0462fd 100644 --- a/blueprints/tools/blueprint_file_converter.py +++ b/blueprints/tools/blueprint_file_converter.py @@ -24,36 +24,30 @@ def file_converter_versions(): ] return flask.make_response( - {"versions": geode_functions.get_versions(list_packages)}, 200 + {"versions": geode_functions.versions(list_packages)}, 200 ) @file_converter_routes.route("/allowed_files", methods=["GET"]) def file_converter_allowed_files(): - extensions = geode_functions.list_all_input_extensions() + extensions = geode_functions.list_input_extensions() return {"status": 200, "extensions": extensions} @file_converter_routes.route("/allowed_objects", methods=["POST"]) def file_converter_allowed_objects(): - array_variables = ["filename"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - file_extension = os.path.splitext(variables_dict["filename"])[1][1:] - allowed_objects = geode_functions.list_objects(file_extension) + geode_functions.validate_request(flask.request, ["filename"]) + file_extension = os.path.splitext(flask.request.json["filename"])[1][1:] + allowed_objects = geode_functions.list_geode_objects(file_extension) return flask.make_response({"allowed_objects": allowed_objects}, 200) @file_converter_routes.route("/output_file_extensions", methods=["POST"]) def file_converter_output_file_extensions(): - array_variables = ["geode_object"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - output_file_extensions = geode_functions.list_output_file_extensions( - variables_dict["geode_object"] + geode_functions.validate_request(flask.request, ["geode_object"]) + output_file_extensions = geode_functions.geode_object_output_extensions( + flask.request.json["geode_object"] ) return flask.make_response({"output_file_extensions": output_file_extensions}, 200) @@ -62,39 +56,31 @@ def file_converter_output_file_extensions(): async def file_converter_convert_file(): UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] - array_variables = ["geode_object", "file", "filename", "filesize", "extension"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - - geode_functions.upload_file( - variables_dict["file"], - variables_dict["filename"], - UPLOAD_FOLDER, - variables_dict["filesize"], + geode_functions.validate_request( + flask.request, ["geode_object", "filename", "extension"] ) - secure_filename = werkzeug.utils.secure_filename(variables_dict["filename"]) + secure_filename = werkzeug.utils.secure_filename(flask.request.json["filename"]) file_path = os.path.abspath(os.path.join(UPLOAD_FOLDER, secure_filename)) - data = geode_functions.load(variables_dict["geode_object"], file_path) + data = geode_functions.load(flask.request.json["geode_object"], file_path) strict_file_name = os.path.splitext(secure_filename)[0] - new_file_name = strict_file_name + "." + variables_dict["extension"] + new_file_name = strict_file_name + "." + flask.request.json["extension"] sub_folder = f"{UPLOAD_FOLDER}/{strict_file_name}/" if os.path.exists(sub_folder): shutil.rmtree(sub_folder) geode_functions.save( + flask.request.json["geode_object"], data, - variables_dict["geode_object"], os.path.abspath(UPLOAD_FOLDER), new_file_name, ) mimetype = "application/octet-binary" list_exceptions = ["triangle", "vtm"] - if variables_dict["extension"] in list_exceptions: - if variables_dict["extension"] == "triangle": + if flask.request.json["extension"] in list_exceptions: + if flask.request.json["extension"] == "triangle": os.mkdir(sub_folder) os.chdir(sub_folder) generated_files = f"{UPLOAD_FOLDER}/{strict_file_name}" @@ -102,18 +88,17 @@ async def file_converter_convert_file(): shutil.move(generated_files + ".neigh", sub_folder) shutil.move(generated_files + ".node", sub_folder) os.chdir("..") - elif variables_dict["extension"] == "vtm": + elif flask.request.json["extension"] == "vtm": generated_files = f"{UPLOAD_FOLDER}/{strict_file_name}" - print(f"{generated_files=}") shutil.move(generated_files + ".vtm", sub_folder) - # shutil.move(strict_file_name, sub_folder) new_file_name = strict_file_name + ".zip" - print(f"{new_file_name=}") mimetype = "application/zip" with zipfile.ZipFile(f"{UPLOAD_FOLDER}/{new_file_name}", "w") as zipObj: for folder_name, sub_folders, file_names in os.walk(sub_folder): - for variables_dict["filename"] in file_names: - file_path = os.path.join(folder_name, variables_dict["filename"]) + for flask.request.json["filename"] in file_names: + file_path = os.path.join( + folder_name, flask.request.json["filename"] + ) zipObj.write(file_path, os.path.basename(file_path)) response = flask.send_from_directory( @@ -125,4 +110,4 @@ async def file_converter_convert_file(): response.headers["new-file-name"] = new_file_name response.headers["Access-Control-Expose-Headers"] = "new-file-name" - return response \ No newline at end of file + return response diff --git a/blueprints/tools/blueprint_validity_checker.py b/blueprints/tools/blueprint_validity_checker.py index 2b1a3a8..479bf30 100644 --- a/blueprints/tools/blueprint_validity_checker.py +++ b/blueprints/tools/blueprint_validity_checker.py @@ -21,56 +21,34 @@ def validity_checker_versions(): "OpenGeode-GeosciencesIO", "OpenGeode-Inspector", ] - return flask.make_response( - {"versions": geode_functions.get_versions(list_packages)}, 200 + {"versions": geode_functions.versions(list_packages)}, 200 ) @validity_checker_routes.route("/allowed_files", methods=["GET"]) def validity_checker_allowed_files(): - extensions = geode_functions.list_all_input_extensions() + extensions = geode_functions.list_input_extensions("inspector") return flask.make_response({"extensions": extensions}, 200) @validity_checker_routes.route("/allowed_objects", methods=["POST"]) def validity_checker_allowed_objects(): - array_variables = ["filename"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - file_extension = os.path.splitext(variables_dict["filename"])[1][1:] - allowed_objects = geode_functions.list_objects(file_extension) + geode_functions.validate_request(flask.request, ["filename"]) + file_extension = os.path.splitext(flask.request.json["filename"])[1][1:] + allowed_objects = geode_functions.list_geode_objects(file_extension, "inspector") return flask.make_response({"allowed_objects": allowed_objects}, 200) -@validity_checker_routes.route("/upload_file", methods=["POST"]) -def validity_checker_upload_file(): - UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] - array_variables = ["file", "filename", "filesize"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) - geode_functions.upload_file( - variables_dict["file"], - variables_dict["filename"], - UPLOAD_FOLDER, - variables_dict["filesize"], - ) - - return flask.make_response({"message": "File uploaded"}, 200) - - @validity_checker_routes.route("/tests_names", methods=["POST"]) def validity_checker_test_names(): - array_variables = ["geode_object"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) + geode_functions.validate_request(flask.request, ["geode_object"]) model_checks = inspector_functions.json_return( - inspector_functions.inspectors()[variables_dict["geode_object"]]["tests_names"] + inspector_functions.inspectors()[flask.request.json["geode_object"]][ + "tests_names" + ] ) return flask.make_response({"model_checks": model_checks}, 200) @@ -80,15 +58,13 @@ def validity_checker_test_names(): def validity_checker_inspect_file(): UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] array_variables = ["geode_object", "filename", "test"] - variables_dict = geode_functions.get_form_variables( - flask.request.form, array_variables - ) + geode_functions.validate_request(flask.request, array_variables) - secure_filename = werkzeug.utils.secure_filename(variables_dict["filename"]) + secure_filename = werkzeug.utils.secure_filename(flask.request.json["filename"]) file_path = os.path.abspath(os.path.join(UPLOAD_FOLDER, secure_filename)) - data = geode_functions.load(variables_dict["geode_object"], file_path) - inspector = geode_functions.get_inspector(variables_dict["geode_object"], data) - test_result = getattr(inspector, variables_dict["test"])() + data = geode_functions.load(flask.request.json["geode_object"], file_path) + inspector = geode_functions.inspector(flask.request.json["geode_object"], data) + test_result = getattr(inspector, flask.request.json["test"])() if type(test_result) == int: expected_result = 0 @@ -112,9 +88,9 @@ def validity_checker_inspect_file(): ) if result == False: - test_name = variables_dict["test"] + test_name = flask.request.json["test"] print(f"Wrong test result: {test_name}", flush=True) return flask.make_response( {"result": result, "list_invalidities": str(test_result)}, 200 - ) \ No newline at end of file + ) diff --git a/blueprints/workflows/blueprint_explicit.py b/blueprints/workflows/blueprint_explicit.py index 963a343..7191d22 100644 --- a/blueprints/workflows/blueprint_explicit.py +++ b/blueprints/workflows/blueprint_explicit.py @@ -26,10 +26,10 @@ def sendBaseData(): ) viewable_1 = geode_functions.save_viewable( - model_A1, "BRep", os.path.abspath(DATA_FOLDER), "model_A1" + "BRep", model_A1, os.path.abspath(DATA_FOLDER), "model_A1" ) viewable_2 = geode_functions.save_viewable( - topo, "TriangulatedSurface3D", os.path.abspath(DATA_FOLDER), "topo" + "TriangulatedSurface3D", topo, os.path.abspath(DATA_FOLDER), "topo" ) return flask.make_response( { @@ -66,10 +66,10 @@ def sendBRepStats(): nb_surfaces = brep_explicit.nb_surfaces() nb_blocks = brep_explicit.nb_blocks() geode_functions.save( - brep_explicit, "BRep", os.path.abspath(DATA_FOLDER), "explicit_brep.og_brep" + "BRep", brep_explicit, os.path.abspath(DATA_FOLDER), "explicit_brep.og_brep" ) viewable_file_name = geode_functions.save_viewable( - brep_explicit, "BRep", os.path.abspath(DATA_FOLDER), "explicit_brep" + "BRep", brep_explicit, os.path.abspath(DATA_FOLDER), "explicit_brep" ) return flask.make_response( { @@ -87,20 +87,17 @@ def sendBRepStats(): @explicit_routes.route("/remesh", methods=["POST"]) def remesh(): DATA_FOLDER = flask.current_app.config["DATA_FOLDER"] - variables = geode_functions.get_form_variables(flask.request.form, ["metric"]) + geode_functions.validate_request(flask.request, ["metric"]) min_metric = 50 max_metric = 500 brep = geode_functions.load( "BRep", os.path.abspath(DATA_FOLDER + "explicit_brep.og_brep") ) - try: - metric = float(variables["metric"]) - except ValueError: - flask.abort(400, "Invalid data format for the metric variable") + metric = float(flask.request.json["metric"]) brep_metric = geode_common.ConstantMetric3D(metric) brep_remeshed, _ = geode_simplex.simplex_remesh_brep(brep, brep_metric) viewable_file_name = geode_functions.save_viewable( - brep_remeshed, "BRep", os.path.abspath(DATA_FOLDER), "remeshed_simplex_brep" + "BRep", brep_remeshed, os.path.abspath(DATA_FOLDER), "remeshed_simplex_brep" ) return flask.make_response( { diff --git a/blueprints/workflows/blueprint_implicit.py b/blueprints/workflows/blueprint_implicit.py index 066dc59..796269d 100644 --- a/blueprints/workflows/blueprint_implicit.py +++ b/blueprints/workflows/blueprint_implicit.py @@ -79,20 +79,12 @@ def step0(): @implicit_routes.route("/update_value", methods=["POST"]) def update_value(): - print(f"{flask.request.form=}", flush=True) - variables = geode_functions.get_form_variables( - flask.request.form, + geode_functions.validate_request( + flask.request, ["point", "value"], ) - try: - point = int(variables["point"]) - except ValueError: - flask.abort(400, "Invalid data format for the point") - - try: - value = float(variables["value"]) - except ValueError: - flask.abort(400, "Invalid data format for the value") + point = int(flask.request.json["point"]) + value = float(flask.request.json["value"]) DATA_FOLDER = flask.current_app.config["DATA_FOLDER"] data_constraints = geode_numerics.DataPointsManager3D() @@ -118,9 +110,8 @@ def update_value(): @implicit_routes.route("/step1", methods=["POST"]) def step1(): - print(f"{flask.request.form=}", flush=True) - variables = geode_functions.get_form_variables( - flask.request.form, + geode_functions.validate_request( + flask.request, ["isovalues"], ) DATA_FOLDER = flask.current_app.config["DATA_FOLDER"] @@ -132,19 +123,16 @@ def step1(): bbox = og.BoundingBox3D() bbox.add_point(og.Point3D([0, 0, 0])) bbox.add_point(og.Point3D([40, 40, 40])) - function_computer = geode_implicit.RegularGridScalarFunctionComputer3D( - data_constraints, - bbox, - 10, - geode_numerics.GridScalarFunctionComputerType.FDM_boundaryfree_curvature_minimization, + function_computer = geode_implicit.ScalarFunctionComputer3D( + data_constraints, bbox, 10 ) scalar_function_name = "Boundary free - Curvature" function_computer.compute_scalar_function(scalar_function_name) - expliciter = geode_implicit.RegularGridScalarFunctionExpliciter3D( - function_computer.grid_with_functions(), scalar_function_name + expliciter = geode_implicit.GridScalarFunctionExpliciter3D( + function_computer.grid_with_results(), scalar_function_name ) - expliciter.add_scalar_isovalues(json.loads(variables["isovalues"])) + expliciter.add_scalar_isovalues(json.loads(flask.request.json["isovalues"])) brep = expliciter.build_brep() implicit_model = og_geosciences.implicit_model_from_structural_model_scalar_field( og_geosciences.StructuralModel(brep), scalar_function_name @@ -153,14 +141,14 @@ def step1(): for surface in implicit_model.surfaces(): builder.remove_surface(surface) geode_functions.save( - implicit_model, "StructuralModel", + implicit_model, os.path.abspath(DATA_FOLDER), "implicit.og_strm", ) viewable_file_name = geode_functions.save_viewable( - implicit_model, "StructuralModel", + implicit_model, os.path.abspath(DATA_FOLDER), "implicit_structural_model", ) @@ -175,38 +163,28 @@ def step1(): @implicit_routes.route("/step2", methods=["POST"]) def step2(): - print(f"{flask.request.form=}", flush=True) - variables = geode_functions.get_form_variables( - flask.request.form, ["axis", "coordinate"] - ) + geode_functions.validate_request(flask.request, ["axis", "coordinate"]) DATA_FOLDER = flask.current_app.config["DATA_FOLDER"] implicit_model = og_geosciences.ImplicitStructuralModel( geode_functions.load( "StructuralModel", os.path.abspath(DATA_FOLDER + "implicit.og_strm") ) ) - try: - axis = int(variables["axis"]) - except ValueError: - flask.abort(400, "Invalid data format for the axis") - - try: - coordinate = int(variables["coordinate"]) - except ValueError: - flask.abort(400, "Invalid data format for the coordinate") + axis = int(flask.request.json["axis"]) + coordinate = int(flask.request.json["coordinate"]) extracted_cross_section = geode_implicit.extract_implicit_cross_section_from_axis( implicit_model, axis, coordinate ) geode_functions.save( - extracted_cross_section, "CrossSection", + extracted_cross_section, os.path.abspath(DATA_FOLDER), "cross_section.og_xsctn", ) viewable_file_name = geode_functions.save_viewable( - extracted_cross_section, "CrossSection", + extracted_cross_section, os.path.abspath(DATA_FOLDER), "implicit_cross_section", ) @@ -221,27 +199,22 @@ def step2(): @implicit_routes.route("/step3", methods=["POST"]) def step3(): - print(f"{flask.request.form=}", flush=True) - variables = geode_functions.get_form_variables(flask.request.form, ["metric"]) + geode_functions.validate_request(flask.request, ["metric"]) DATA_FOLDER = flask.current_app.config["DATA_FOLDER"] extracted_cross_section = geode_functions.load( "CrossSection", os.path.abspath(DATA_FOLDER + "cross_section.og_xsctn") ) - try: - metric = float(variables["metric"]) - except ValueError: - flask.abort(400, "Invalid data format for the metric") + metric = float(flask.request.json["metric"]) sharp_section = geode_conversion.add_section_sharp_features( extracted_cross_section, 120 ) - print(dir(geode_conversion), flush=True) constant_metric = geode_common.ConstantMetric2D(metric) remeshed_section, _ = geode_simplex.simplex_remesh_section( sharp_section, constant_metric ) viewable_file_name = geode_functions.save_viewable( - remeshed_section, "Section", + remeshed_section, os.path.abspath(DATA_FOLDER), "implicit_remeshed_section", ) diff --git a/blueprints/workflows/blueprint_simplex.py b/blueprints/workflows/blueprint_simplex.py index 6faf664..0a0d17a 100644 --- a/blueprints/workflows/blueprint_simplex.py +++ b/blueprints/workflows/blueprint_simplex.py @@ -19,7 +19,7 @@ def initialize(): "StructuralModel", os.path.abspath(WORKFLOWS_DATA_FOLDER + "corbi.og_strm") ) viewable_file_name = geode_functions.save_viewable( - brep, "BRep", os.path.abspath(DATA_FOLDER), "simplex_brep" + "BRep", brep, os.path.abspath(DATA_FOLDER), "simplex_brep" ) return flask.make_response( { @@ -34,8 +34,8 @@ def initialize(): def remesh(): WORKFLOWS_DATA_FOLDER = flask.current_app.config["WORKFLOWS_DATA_FOLDER"] DATA_FOLDER = flask.current_app.config["DATA_FOLDER"] - variables = geode_functions.get_form_variables( - flask.request.form, ["metric", "faults_metric"] + variables = geode_functions.validate_request( + flask.request, ["metric", "faults_metric"] ) min_metric = 10 max_metric = 300 @@ -43,42 +43,36 @@ def remesh(): "StructuralModel", os.path.abspath(WORKFLOWS_DATA_FOLDER + "corbi.og_strm") ) brep_metric = geode_simplex.BRepMetricConstraints(brep) - try: - metric = float(variables["metric"]) - if min_metric <= metric <= max_metric: - brep_metric.set_default_metric(metric) - else: - return flask.make_response( - { - "name": "Bad Request", - "description": "Wrong metric value, should be between {min_metric} and {max_metric}", - }, - 400, - ) - except ValueError: - flask.abort(400, "Invalid data format for the metric variable") + metric = float(flask.request.json["metric"]) + if min_metric <= metric <= max_metric: + brep_metric.set_default_metric(metric) + else: + return flask.make_response( + { + "name": "Bad Request", + "description": "Wrong metric value, should be between {min_metric} and {max_metric}", + }, + 400, + ) - try: - faults_metric = float(variables["faults_metric"]) - if min_metric <= faults_metric <= max_metric: - for fault in brep.faults(): - for surface in brep.fault_items(fault): - brep_metric.set_surface_metric(surface, faults_metric) - else: - return flask.make_response( - { - "name": "Bad Request", - "description": "Wrong faults_metric value, should be between {min_metric} and {max_metric}", - }, - 400, - ) - except ValueError: - flask.abort(400, "Invalid data format for the faults_metric variable") + faults_metric = float(flask.request.json["faults_metric"]) + if min_metric <= faults_metric <= max_metric: + for fault in brep.faults(): + for surface in brep.fault_items(fault): + brep_metric.set_surface_metric(surface, faults_metric) + else: + return flask.make_response( + { + "name": "Bad Request", + "description": "Wrong faults_metric value, should be between {min_metric} and {max_metric}", + }, + 400, + ) metric = brep_metric.build_metric() brep_remeshed, _ = geode_simplex.simplex_remesh_brep(brep, metric) viewable_file_name = geode_functions.save_viewable( - brep_remeshed, "BRep", os.path.abspath(DATA_FOLDER), "remeshed_simplex_brep" + "BRep", brep_remeshed, os.path.abspath(DATA_FOLDER), "remeshed_simplex_brep" ) return flask.make_response( { diff --git a/config.py b/config.py index a2191b8..623a3ca 100644 --- a/config.py +++ b/config.py @@ -9,8 +9,8 @@ class Config(object): CORS_HEADERS = "Content-Type" UPLOAD_FOLDER = "./uploads" WORKFLOWS_DATA_FOLDER = "./data_workflows/" - LOCK_FOLDER = "./lock" - TIME_FOLDER = "./time" + LOCK_FOLDER = "./lock/" + TIME_FOLDER = "./time/" class ProdConfig(Config): diff --git a/requirements.txt b/requirements.txt index 578abf2..77d9ecd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,28 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile requirements.in +# pip-compile --resolver=backtracking requirements.in # asgiref==3.7.2 # via flask -blinker==1.6.2 +blinker==1.7.0 # via flask click==8.1.7 # via flask colorama==0.4.6 # via click -flask[async]==2.3.3 +flask[async]==3.0.0 # via # -r requirements.in # flask-cors flask-cors==4.0.0 # via -r requirements.in -geode-background==7.4.0 +geode-background==7.4.4 # via # geode-explicit # geode-implicit # geode-simplex -geode-common==26.3.0 +geode-common==29.0.1 # via # -r requirements.in # geode-background @@ -31,24 +31,25 @@ geode-common==26.3.0 # geode-implicit # geode-simplex # geode-viewables -geode-conversion==5.1.0 + # opengeodeweb-back +geode-conversion==5.1.3 # via # geode-explicit # geode-implicit -geode-explicit==4.3.0 +geode-explicit==4.3.5 # via # -r requirements.in # geode-implicit -geode-implicit==2.3.0 +geode-implicit==2.4.2 # via -r requirements.in -geode-numerics==3.1.0 +geode-numerics==4.1.0 # via # -r requirements.in # geode-implicit # geode-simplex -geode-simplex==6.2.0 +geode-simplex==6.2.4 # via -r requirements.in -geode-viewables==2.1.0 +geode-viewables==2.1.3 # via opengeodeweb-back importlib-metadata==6.8.0 # via flask @@ -60,7 +61,7 @@ markupsafe==2.1.3 # via # jinja2 # werkzeug -opengeode-core==14.8.0 +opengeode-core==14.9.3 # via # -r requirements.in # geode-background @@ -76,7 +77,7 @@ opengeode-core==14.8.0 # opengeode-inspector # opengeode-io # opengeodeweb-back -opengeode-geosciences==7.2.0 +opengeode-geosciences==7.2.1 # via # -r requirements.in # geode-explicit @@ -85,20 +86,20 @@ opengeode-geosciences==7.2.0 # opengeode-geosciencesio # opengeode-inspector # opengeodeweb-back -opengeode-geosciencesio==4.5.0 +opengeode-geosciencesio==4.5.1 # via # -r requirements.in # geode-explicit # geode-implicit # opengeode-inspector # opengeodeweb-back -opengeode-inspector==3.1.0 +opengeode-inspector==4.0.0 # via # -r requirements.in # geode-explicit # geode-implicit # opengeodeweb-back -opengeode-io==6.2.0 +opengeode-io==6.2.1 # via # -r requirements.in # geode-explicit @@ -106,13 +107,13 @@ opengeode-io==6.2.0 # geode-viewables # opengeode-inspector # opengeodeweb-back -opengeodeweb-back==0.0.16 +opengeodeweb-back==1.4.0 # via -r requirements.in python-dotenv==1.0.0 # via -r requirements.in typing-extensions==4.8.0 # via asgiref -werkzeug==2.3.7 +werkzeug==3.0.1 # via # -r requirements.in # flask diff --git a/tests/conftest.py b/tests/conftest.py index edce09a..6fde69f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,12 @@ import pytest from app import app + @pytest.fixture def client(): - app.config['TESTING'] = True - app.config['SERVER_NAME'] = 'TEST' - app.config['DATA_FOLDER'] = './data/' + app.config["TESTING"] = True + app.config["SERVER_NAME"] = "TEST" + app.config["DATA_FOLDER"] = "./data/" client = app.test_client() + client.headers = {"Content-type": "application/json", "Accept": "application/json"} yield client diff --git a/tests/test_crs_converter.py b/tests/test_crs_converter.py index fdd2ccc..df50198 100644 --- a/tests/test_crs_converter.py +++ b/tests/test_crs_converter.py @@ -1,5 +1,6 @@ import os import base64 +from werkzeug.datastructures import FileStorage base_route = "/tools/crs_converter" @@ -18,59 +19,22 @@ def test_allowed_files(client): assert response.status_code == 200 extensions = response.json["extensions"] assert type(extensions) is list - list_extensions = [ - "dat", - "dev", - "dxf", - "lso", - "ml", - "msh", - "obj", - "og_brep", - "og_edc2d", - "og_edc3d", - "og_grp", - "og_hso3d", - "og_psf2d", - "og_psf3d", - "og_pso3d", - "og_pts2d", - "og_pts3d", - "og_rgd2d", - "og_rgd3d", - "og_sctn", - "og_strm", - "og_tsf2d", - "og_tsf3d", - "og_tso3d", - "og_vts", - "og_xsctn", - "ply", - "smesh", - "stl", - "svg", - "ts", - "txt", - "vtp", - "vtu", - "wl", - ] - for extension in list_extensions: - assert extension in extensions + for extension in extensions: + assert type(extension) is str def test_allowed_objects(client): route = f"{base_route}/allowed_objects" # Normal test with filename 'corbi.og_brep' - response = client.post(route, data={"filename": "corbi.og_brep"}) + response = client.post(route, json={"filename": "corbi.og_brep"}) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] assert type(allowed_objects) is list assert "BRep" in allowed_objects # Normal test with filename .vtu - response = client.post(route, data={"filename": "toto.vtu"}) + response = client.post(route, json={"filename": "toto.vtu"}) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] list_objects = ["HybridSolid3D", "PolyhedralSolid3D", "TetrahedralSolid3D"] @@ -78,14 +42,14 @@ def test_allowed_objects(client): assert geode_object in allowed_objects # Test with stupid filename - response = client.post(route, data={"filename": "toto.tutu"}) + response = client.post(route, json={"filename": "toto.tutu"}) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] assert type(allowed_objects) is list assert not allowed_objects # Test without filename - response = client.post(route) + response = client.post(route, json={}) assert response.status_code == 400 error_message = response.json["description"] assert error_message == "No filename sent" @@ -95,7 +59,7 @@ def test_geographic_coordinate_systems(client): route = f"{base_route}/geographic_coordinate_systems" # Normal test with geode_object 'BRep' - response = client.post(route, data={"geode_object": "BRep"}) + response = client.post(route, json={"geode_object": "BRep"}) assert response.status_code == 200 crs_list = response.json["crs_list"] assert type(crs_list) is list @@ -103,7 +67,7 @@ def test_geographic_coordinate_systems(client): assert type(crs) is dict # Test without geode_object - response = client.post(route) + response = client.post(route, json={}) assert response.status_code == 400 error_message = response.json["description"] assert error_message == "No geode_object sent" @@ -113,34 +77,15 @@ def test_output_file_extensions(client): route = f"{base_route}/output_file_extensions" # Normal test with geode_object - response = client.post(route, data={"geode_object": "BRep"}) + response = client.post(route, json={"geode_object": "BRep"}) assert response.status_code == 200 output_file_extensions = response.json["output_file_extensions"] assert type(output_file_extensions) is list - list_output_file_extensions = ["msh", "og_brep"] - for output_file_extension in list_output_file_extensions: - assert output_file_extension in output_file_extensions - - # Normal test with geode_object - response = client.post(route, data={"geode_object": "TriangulatedSurface3D"}) - assert response.status_code == 200 - output_file_extensions = response.json["output_file_extensions"] - assert type(output_file_extensions) is list - list_output_file_extensions = ["obj", "og_tsf3d", "stl", "vtp"] - for output_file_extension in list_output_file_extensions: - assert output_file_extension in output_file_extensions - - # Normal test with geode_object - response = client.post(route, data={"geode_object": "StructuralModel"}) - assert response.status_code == 200 - output_file_extensions = response.json["output_file_extensions"] - assert type(output_file_extensions) is list - list_output_file_extensions = ["lso", "ml", "msh", "og_brep", "og_strm"] - for output_file_extension in list_output_file_extensions: - assert output_file_extension in output_file_extensions + for output_file_extension in output_file_extensions: + assert type(output_file_extension) is str # Test without object - response = client.post(route) + response = client.post(route, json={}) assert response.status_code == 400 error_message = response.json["description"] assert error_message == "No geode_object sent" @@ -159,16 +104,17 @@ def test_convert_file(client): extension = ["msh", "vtu"] for index, filename in enumerate(filenames): - file = base64.b64encode(open(f"./tests/{filename}", "rb").read()) - filesize = int(os.path.getsize(f"./tests/{filename}")) + response = client.put( + "tools/upload_file", + data={"content": FileStorage(open(f"./tests/{filename}", "rb"))}, + ) + assert response.status_code == 201 response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, @@ -179,17 +125,14 @@ def test_convert_file(client): }, ) + print(response) assert response.status_code == 200 - assert type((response.data)) is bytes - assert len((response.data)) > 0 # Test without geode_object response = client.post( f"{base_route}/convert_file", - data={ - "file": file, + json={ "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, @@ -204,34 +147,11 @@ def test_convert_file(client): error_description = response.json["description"] assert error_description == "No geode_object sent" - # Test without file - response = client.post( - f"{base_route}/convert_file", - data={ - "geode_object": geode_object[index], - "filename": filename, - "filesize": filesize, - "input_crs_authority": input_crs_authority, - "input_crs_code": input_crs_code, - "input_crs_name": input_crs_name, - "output_crs_authority": output_crs_authority, - "output_crs_code": output_crs_code, - "output_crs_name": output_crs_name, - "extension": extension[index], - }, - ) - - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "No file sent" - # Test without filename response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, @@ -246,35 +166,12 @@ def test_convert_file(client): error_description = response.json["description"] assert error_description == "No filename sent" - # Test without filesize - response = client.post( - f"{base_route}/convert_file", - data={ - "geode_object": geode_object[index], - "file": file, - "filename": filename, - "input_crs_authority": input_crs_authority, - "input_crs_code": input_crs_code, - "input_crs_name": input_crs_name, - "output_crs_authority": output_crs_authority, - "output_crs_code": output_crs_code, - "output_crs_name": output_crs_name, - "extension": extension[index], - }, - ) - - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "No filesize sent" - # Test without input_crs_authority response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, "output_crs_authority": output_crs_authority, @@ -291,11 +188,9 @@ def test_convert_file(client): # Test without input_crs_code response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_name": input_crs_name, "output_crs_authority": output_crs_authority, @@ -312,11 +207,9 @@ def test_convert_file(client): # Test without input_crs_name response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "output_crs_authority": output_crs_authority, @@ -333,11 +226,9 @@ def test_convert_file(client): # Test without output_crs_authority response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, @@ -354,11 +245,9 @@ def test_convert_file(client): # Test without output_crs_code response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, @@ -375,11 +264,9 @@ def test_convert_file(client): # Test without output_crs_name response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, @@ -396,11 +283,9 @@ def test_convert_file(client): # Test without extension response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object[index], - "file": file, "filename": filename, - "filesize": filesize, "input_crs_authority": input_crs_authority, "input_crs_code": input_crs_code, "input_crs_name": input_crs_name, diff --git a/tests/test_file_converter.py b/tests/test_file_converter.py index a671d26..09b3d1f 100644 --- a/tests/test_file_converter.py +++ b/tests/test_file_converter.py @@ -1,5 +1,6 @@ import os import base64 +from werkzeug.datastructures import FileStorage base_route = "/tools/file_converter" @@ -18,51 +19,14 @@ def test_allowed_files(client): assert response.status_code == 200 extensions = response.json["extensions"] assert type(extensions) is list - list_extensions = [ - "dat", - "dev", - "dxf", - "lso", - "ml", - "msh", - "obj", - "og_brep", - "og_edc2d", - "og_edc3d", - "og_grp", - "og_hso3d", - "og_psf2d", - "og_psf3d", - "og_pso3d", - "og_pts2d", - "og_pts3d", - "og_rgd2d", - "og_rgd3d", - "og_sctn", - "og_strm", - "og_tsf2d", - "og_tsf3d", - "og_tso3d", - "og_vts", - "og_xsctn", - "ply", - "smesh", - "stl", - "svg", - "ts", - "txt", - "vtp", - "vtu", - "wl", - ] - for extension in list_extensions: - assert extension in extensions + for extension in extensions: + assert type(extension) is str def test_allowed_objects(client): # Normal test with filename 'corbi.og_brep' response = client.post( - f"{base_route}/allowed_objects", data={"filename": "corbi.og_brep"} + f"{base_route}/allowed_objects", json={"filename": "corbi.og_brep"} ) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] @@ -71,7 +35,7 @@ def test_allowed_objects(client): # Normal test with filename .vtu response = client.post( - f"{base_route}/allowed_objects", data={"filename": "toto.vtu"} + f"{base_route}/allowed_objects", json={"filename": "toto.vtu"} ) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] @@ -81,7 +45,7 @@ def test_allowed_objects(client): # Test with stupid filename response = client.post( - f"{base_route}/allowed_objects", data={"filename": "toto.tutu"} + f"{base_route}/allowed_objects", json={"filename": "toto.tutu"} ) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] @@ -89,7 +53,7 @@ def test_allowed_objects(client): assert not allowed_objects # Test without filename - response = client.post(f"{base_route}/allowed_objects") + response = client.post(f"{base_route}/allowed_objects", json={}) assert response.status_code == 400 error_message = response.json["description"] assert error_message == "No filename sent" @@ -98,40 +62,16 @@ def test_allowed_objects(client): def test_output_file_extensions(client): # Normal test with object response = client.post( - f"{base_route}/output_file_extensions", data={"geode_object": "BRep"} + f"{base_route}/output_file_extensions", json={"geode_object": "BRep"} ) assert response.status_code == 200 output_file_extensions = response.json["output_file_extensions"] assert type(output_file_extensions) is list - list_output_file_extensions = ["msh", "og_brep"] - for output_file_extension in list_output_file_extensions: - assert output_file_extension in output_file_extensions - - # Normal test with object - response = client.post( - f"{base_route}/output_file_extensions", - data={"geode_object": "TriangulatedSurface3D"}, - ) - assert response.status_code == 200 - output_file_extensions = response.json["output_file_extensions"] - assert type(output_file_extensions) is list - list_output_file_extensions = ["obj", "og_tsf3d", "stl", "vtp"] - for output_file_extension in list_output_file_extensions: - assert output_file_extension in output_file_extensions - - # Normal test with object - response = client.post( - f"{base_route}/output_file_extensions", data={"geode_object": "StructuralModel"} - ) - assert response.status_code == 200 - output_file_extensions = response.json["output_file_extensions"] - assert type(output_file_extensions) is list - list_output_file_extensions = ["lso", "ml", "msh", "og_brep", "og_strm"] - for output_file_extension in list_output_file_extensions: - assert output_file_extension in output_file_extensions + for output_file_extension in output_file_extensions: + assert type(output_file_extension) is str # Test without object - response = client.post(f"{base_route}/output_file_extensions") + response = client.post(f"{base_route}/output_file_extensions", json={}) assert response.status_code == 400 error_message = response.json["description"] assert error_message == "No geode_object sent" @@ -141,32 +81,32 @@ def test_convert_file(client): # Normal test with object/file/filename/extension geode_object = "BRep" filename = "corbi.og_brep" - file = base64.b64encode(open("./tests/corbi.og_brep", "rb").read()) - filesize = int(os.path.getsize("./tests/corbi.og_brep")) extension = "msh" + response = client.put( + "tools/upload_file", + data={"content": FileStorage(open(f"./tests/{filename}", "rb"))}, + ) + assert response.status_code == 201 + response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object, - "file": file, "filename": filename, - "filesize": filesize, "extension": extension, }, ) assert response.status_code == 200 - assert type((response.data)) is bytes - assert len((response.data)) > 0 + # assert type((response.data)) is bytes + # assert len((response.data)) > 0 # Test without object response = client.post( f"{base_route}/convert_file", - data={ - "file": file, + json={ "filename": filename, - "filesize": filesize, "extension": extension, }, ) @@ -175,28 +115,11 @@ def test_convert_file(client): error_description = response.json["description"] assert error_description == "No geode_object sent" - # Test without file - response = client.post( - f"{base_route}/convert_file", - data={ - "geode_object": geode_object, - "filename": filename, - "filesize": filesize, - "extension": extension, - }, - ) - - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "No file sent" - # Test without filename response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object, - "file": file, - "filesize": filesize, "extension": extension, }, ) @@ -205,29 +128,12 @@ def test_convert_file(client): error_description = response.json["description"] assert error_description == "No filename sent" - # Test without filesize - response = client.post( - f"{base_route}/convert_file", - data={ - "geode_object": geode_object, - "file": file, - "filename": filename, - "extension": extension, - }, - ) - - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "No filesize sent" - # Test without extension response = client.post( f"{base_route}/convert_file", - data={ + json={ "geode_object": geode_object, - "file": file, "filename": filename, - "filesize": filesize, }, ) diff --git a/tests/test_implicit.py b/tests/test_implicit.py index 0f68258..3c4946e 100644 --- a/tests/test_implicit.py +++ b/tests/test_implicit.py @@ -20,7 +20,7 @@ def test_step1(client): # Normal test with isovalues response = client.post( f"{base_route}/step1", - data={ + json={ "isovalues": isovalues, }, ) @@ -33,7 +33,7 @@ def test_step1(client): # Test without isovalues response = client.post( f"{base_route}/step1", - data={}, + json={}, ) assert response.status_code == 400 error_description = response.json["description"] @@ -41,12 +41,12 @@ def test_step1(client): def test_step2(client): - axis = "0" - coordinate = "2" + axis = 0 + coordinate = 2 # Normal test with axis/diretcion response = client.post( - f"{base_route}/step2", data={"axis": axis, "coordinate": coordinate} + f"{base_route}/step2", json={"axis": axis, "coordinate": coordinate} ) assert response.status_code == 200 viewable_file_name = response.json["viewable_file_name"] @@ -55,37 +55,19 @@ def test_step2(client): assert type(id) is str # Test without axis - response = client.post(f"{base_route}/step2", data={"coordinate": coordinate}) + response = client.post(f"{base_route}/step2", json={"coordinate": coordinate}) assert response.status_code == 400 error_description = response.json["description"] assert error_description == "No axis sent" - # Test with stupid axis value - axis_stupid = "Toto" - response = client.post( - f"{base_route}/step2", data={"axis": axis_stupid, "coordinate": coordinate} - ) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "Invalid data format for the axis" - - # Test with stupid coordinate value - coordinate_stupid = "Toto" - response = client.post( - f"{base_route}/step2", data={"axis": axis, "coordinate": coordinate_stupid} - ) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "Invalid data format for the coordinate" - def test_step3(client): - metric = "1" + metric = 1 # Normal test with metric response = client.post( f"{base_route}/step3", - data={ + json={ "metric": metric, }, ) @@ -96,19 +78,7 @@ def test_step3(client): assert type(id) is str # Test without metric - response = client.post(f"{base_route}/step3", data={}) + response = client.post(f"{base_route}/step3", json={}) assert response.status_code == 400 error_description = response.json["description"] assert error_description == "No metric sent" - - # Test with stupid metric value - metric_stupid = "Toto" - response = client.post( - f"{base_route}/step3", - data={ - "metric": metric_stupid, - }, - ) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "Invalid data format for the metric" diff --git a/tests/test_simplex.py b/tests/test_simplex.py index 01c93e7..2ef0bdb 100644 --- a/tests/test_simplex.py +++ b/tests/test_simplex.py @@ -14,13 +14,13 @@ def test_initialize(client): def test_remesh(client): - metric = "150" - faults_metric = "50" + metric = 150 + faults_metric = 50 # Normal test response = client.post( f"{base_route}/remesh", - data={ + json={ "metric": metric, "faults_metric": faults_metric, }, @@ -34,7 +34,7 @@ def test_remesh(client): # Test without faults_metric response = client.post( f"{base_route}/remesh", - data={ + json={ "metric": metric, }, ) @@ -45,23 +45,10 @@ def test_remesh(client): # Test without metric response = client.post( f"{base_route}/remesh", - data={ + json={ "faults_metric": faults_metric, }, ) assert response.status_code == 400 error_description = response.json["description"] assert error_description == "No metric sent" - - # Test with stupid surface metric - globalMetric_stupid = "Toto" - response = client.post( - f"{base_route}/remesh", - data={ - "faults_metric": faults_metric, - "metric": globalMetric_stupid, - }, - ) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "Invalid data format for the metric variable" diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 0000000..84156e8 --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,14 @@ +import os +import base64 +from werkzeug.datastructures import FileStorage + +base_route = "/tools" + + +def test_upload_file(client): + response = client.put( + f"{base_route}/upload_file", + data={"content": FileStorage(open("./tests/corbi.og_brep", "rb"))}, + ) + + assert response.status_code == 201 diff --git a/tests/test_validity_checker.py b/tests/test_validity_checker.py index 3a1f977..daca855 100644 --- a/tests/test_validity_checker.py +++ b/tests/test_validity_checker.py @@ -1,5 +1,6 @@ import os import base64 +from werkzeug.datastructures import FileStorage base_route = "/tools/validity_checker" @@ -18,71 +19,24 @@ def test_allowed_files(client): assert response.status_code == 200 extensions = response.json["extensions"] assert type(extensions) is list - list_extensions = [ - "dat", - "dev", - "dxf", - "lso", - "ml", - "msh", - "obj", - "og_brep", - "og_edc2d", - "og_edc3d", - "og_grp", - "og_hso3d", - "og_psf2d", - "og_psf3d", - "og_pso3d", - "og_pts2d", - "og_pts3d", - "og_rgd2d", - "og_rgd3d", - "og_sctn", - "og_strm", - "og_tsf2d", - "og_tsf3d", - "og_tso3d", - "og_vts", - "og_xsctn", - "ply", - "smesh", - "stl", - "svg", - "ts", - "txt", - "vtp", - "vtu", - "wl", - ] - - for extension in list_extensions: - assert extension in extensions + for extension in extensions: + assert type(extension) is str def test_allowed_objects(client): # Normal test with filename 'corbi.og_brep' response = client.post( - f"{base_route}/allowed_objects", data={"filename": "corbi.og_brep"} + f"{base_route}/allowed_objects", json={"filename": "corbi.og_brep"} ) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] assert type(allowed_objects) is list - assert "BRep" in allowed_objects - - # Normal test with filename .vtu - response = client.post( - f"{base_route}/allowed_objects", data={"filename": "toto.vtu"} - ) - assert response.status_code == 200 - allowed_objects = response.json["allowed_objects"] - list_objects = ["HybridSolid3D", "PolyhedralSolid3D", "TetrahedralSolid3D"] - for geode_object in list_objects: - assert geode_object in allowed_objects + for geode_object in allowed_objects: + assert type(geode_object) is str # Test with stupid filename response = client.post( - f"{base_route}/allowed_objects", data={"filename": "toto.tutu"} + f"{base_route}/allowed_objects", json={"filename": "toto.tutu"} ) assert response.status_code == 200 allowed_objects = response.json["allowed_objects"] @@ -90,47 +44,7 @@ def test_allowed_objects(client): assert not allowed_objects # Test without filename - response = client.post(f"{base_route}/allowed_objects") - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "No filename sent" - - -def test_upload_file(client): - # Test with file - response = client.post( - f"{base_route}/upload_file", - data={ - "file": base64.b64encode(open("./tests/corbi.og_brep", "rb").read()), - "filename": "corbi.og_brep", - "filesize": os.path.getsize("./tests/corbi.og_brep"), - }, - ) - - assert response.status_code == 200 - message = response.json["message"] - assert message == "File uploaded" - - # Test without file - response = client.post( - f"{base_route}/upload_file", - data={ - "filename": "corbi.og_brep", - }, - ) - - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == "No file sent" - - # Test without filename - response = client.post( - f"{base_route}/upload_file", - data={ - "file": base64.b64encode(open("./tests/corbi.og_brep", "rb").read()), - }, - ) - + response = client.post(f"{base_route}/allowed_objects", json={}) assert response.status_code == 400 error_description = response.json["description"] assert error_description == "No filename sent" @@ -162,7 +76,8 @@ def test_test_names(client): for geode_object in ObjectArray: # Normal test with all objects response = client.post( - f"{base_route}/tests_names", data={"geode_object": geode_object} + f"{base_route}/tests_names", + json={"geode_object": geode_object}, ) assert response.status_code == 200 model_checks = response.json["model_checks"] @@ -182,7 +97,7 @@ def test_test_names(client): print("is_leaf") response_test = client.post( f"{base_route}/inspect_file", - data={ + json={ "object": "BRep", "filename": "corbi.og_brep", "test": check["route"], @@ -196,11 +111,18 @@ def test_test_names(client): def test_inspect_file(client): # Test with file + + filename = "corbi.og_brep" + + response = client.put( + "tools/upload_file", + data={"content": FileStorage(open(f"./tests/{filename}", "rb"))}, + ) + assert response.status_code == 201 + response = client.post( f"{base_route}/inspect_file", - data={ - "file": base64.b64encode(open("./tests/corbi.og_brep", "rb").read()), - "filename": "corbi.og_brep", - "filesize": os.path.getsize("./tests/corbi.og_brep"), + json={ + "filename": filename, }, )