def post(self): args = parser.parse_args() datamanager = api.get_datamanager() (id, path) = datamanager.new() # Writing images for image in args.images: image.save(os.path.join(path, image.filename)) try: options_str = args['options'] options = json.loads(options_str) options = [optionDAO(**o) for o in options] except KeyError: # Key is not present pass # Writting metadata metadata = Metadata(uuid=id, name=args['name'], images=[image.filename for image in args.images], options=options, creation_date=datetime.datetime.now(), status=StatusDAO(StatusCode.QUEUED), processingTime=-1) datamanager.setstatus(id, json.dumps(metadata.toDict())) api.get_executor().process(path, os.path.join(path, datamanager.STATUS_FILE)) current_app.logger.info("task/new request with meta {}".format( json.dumps(metadata.toDict(), separators=(',', ':')))) return Response200DAO(id).toDict()
def setUp(self): self.app = create_app(environment="Testing") self.app.app_context().push() self.app.testing = True self.url_prefix = self.app.config['URL_PREFIX'] self.datamanager = api.get_datamanager() self.executor = api.get_executor()
def gen_board_async(): """Starts the generation of a Board with a given difficulty level. --- post: tags: - Boards summary: Starts the generation of a Board with a given difficulty level. parameters: - name: difficultyLevel in: query description: The difficulty level. Possible values are 1 (easy), 2 (medium) or 3 (difficult). required: true schema: type: integer enum: [1, 2, 3] responses: 202: description: The generation has been started. The url for querying the search progress is returned in the "location" header of the response. 400: description: Error detail in "message". """ try: dif_level = int(request.args.get("difficultyLevel")) except: raise (InvalidUsage( "Bad request: 'difficultyLevel' parameter required with value in [1, 2, 3]." )) if dif_level < 1 or dif_level > 3: raise InvalidUsage( "Bad request: invalid difficulty level, '{}'".format(dif_level), 400) board_level = lsdk.PuzzleDifficulty.HARD if dif_level == 1: board_level = lsdk.PuzzleDifficulty.EASY elif dif_level == 2: board_level = lsdk.PuzzleDifficulty.MEDIUM executor = get_executor() # Invokes the generation worker giving a unique id to index the # corresponding future. This unique id will then be passed to the # generation status endpoint so it knows which future to query. location_value = "" current_job_id = str(uuid.uuid4()) executor.submit_stored(current_job_id, gen_board_worker, board_level) location_value = url_for('api.get_gen_status', job_id=current_job_id) response = current_app.response_class(status=202) response.headers["Location"] = location_value return response
def create_all_solved_boards_async(): """Starts a search for all the solutions for a given board. --- post: tags: - Solved Boards summary: Starts a search for all the solutions for a given board (may take a while). requestBody: description: The board to be solved and the max. number of solutions to return. required: true content: application/json: schema: SolveAllSchema responses: 202: description: The search has been started. The url for querying the search progress is returned in the "location" header of the response. 400: description: Error detail in "message". """ global job_info global job_info_lock board = lsdk.Board() try: body = request.get_json() board = lsdk.Board(body["board"]) # Board to solve max_solutions = body["maxSolutions"] except Exception as e: raise (InvalidUsage("Bad request: {}".format(str(e)), 400)) executor = get_executor() # Invokes the solving worker giving a unique id to index the # corresponding future. This unique id will then be passed to the # solving status endpoint so it knows which future to query. location_value = "" current_job_id = str(uuid.uuid4()) with job_info_lock: info = {"progress_percent": 0.0, "num_solutions": 0, "cancel": False} job_info[current_job_id] = info executor.submit_stored(current_job_id, solve_board_worker, board, max_solutions, current_job_id) location_value = url_for('api.get_solve_status', job_id=current_job_id) response = current_app.response_class(status=202) response.headers["Location"] = location_value return response
def on_solver_finished(result, solutions): global job_info global job_info_lock nonlocal job_id nonlocal asyncSolveCompleted asyncSolveCompleted = True nonlocal solver_result solver_result = result nonlocal board_solutions board_solutions = solutions if result == lsdk.SolverResult.ASYNC_SOLVED_CANCELLED: # Job has been cancelled externally - cleans-up. executor = get_executor() with job_info_lock: job_info.pop(job_id) executor.futures.pop(job_id)
def cancel_async_solve(job_id): """Cancels an ongoing search for all the solutions of a board. --- delete: tags: - Solved Boards summary: Cancels an ongoing search for all the solutions of a board. parameters: - name: job_id in: path description: The id of the search job to be cancelled. required: true schema: type: string responses: 204: description: The search has been cancelled. 404: description: The given job_id does not correspond to a known ongoing search job. """ global job_info global job_info_lock executor = get_executor() fut = None try: fut = executor.futures._futures[job_id] except KeyError: return make_response( jsonify({ "message": "no board solving to cancel for job-id '{}'".format(job_id) }), 404) if not fut.done(): # Marks the job to be stopped as soon as possible by the worker. with job_info_lock: job_info[job_id]["cancel"] = True else: # The job has been done but a cancel has been invoked, there's a # chance that no request for its result will come - do the clean- # up from here. executor.futures.pop(job_id) response = current_app.response_class(status=204) return response
def get_gen_status(job_id): """Retrieves the status of a generate board request given its job id. --- get: tags: - Boards summary: Retrieves the status of a generate board request given its job id. parameters: - name: job_id in: path required: true description: the id of the generate board job. schema: type: string responses: 200: description: If the generation is not finished returns a json with the fields "current_step" and "total_steps". If the board has been generated, returns the content described below. content: application/json: schema: GeneratedBoardSchema 404: description: The job_id doesn't refer to a known job. """ executor = get_executor() fut = None try: fut = executor.futures._futures[job_id] except KeyError: return make_response( jsonify( { "message": "no board generation for job-id '{}'".format(job_id) }, ), 404) if not fut.done(): return jsonify({"status": "generating"}) else: fut = executor.futures.pop(job_id) return jsonify(fut.result())
def get_solve_status(job_id): """Returns the status of a search for all the solutions of a given board. --- get: tags: - Solved Boards summary: Returns the status of a search for all the solutions of a given board. parameters: - name: job_id in: path required: true description: the id of the search solutions job schema: type: string responses: 200: description: If the search is not finished returns a json with the fields "cancel_url", "progress_percent", "num_unsolvables" and "num_solutions". "cancel_url" is the url the client should send a "DELETE" request to in order to cancel the search for all solutions. "progress_percent" is the progress percentage. "num_unsolvables" is the number of intermediary unsolvable boards found during the search. "num_solutions" is the number of solutions found so far. If the search is finished, a json with the structure below is returned. content: application/json: schema: BoardSolutionsSchema 404: description: The job_id doesn't refer to a known job. """ global job_info global job_info_lock executor = get_executor() fut = None try: fut = executor.futures._futures[job_id] except KeyError: return make_response( jsonify( {"message": "no board solving for job-id '{}'".format(job_id) }, ), 404) if not fut.done(): resp = {"status": "solving"} resp["cancel_url"] = url_for('api.cancel_async_solve', job_id=job_id, _external=True) with job_info_lock: resp["progress_percent"] = job_info[job_id]["progress_percent"] resp["num_unsolvables"] = job_info[job_id]["num_unsolvables"] resp["num_solutions"] = job_info[job_id]["num_solutions"] return jsonify(resp) else: fut = executor.futures.pop(job_id) with job_info_lock: job_info.pop(job_id) return jsonify(fut.result())