diff options
| author | bozo.kopic <bozo@kopic.xyz> | 2022-01-03 00:58:17 +0100 |
|---|---|---|
| committer | bozo.kopic <bozo@kopic.xyz> | 2022-01-03 00:58:17 +0100 |
| commit | ae33b2c75ec0e03fd1d80241a4c4b29acadb517c (patch) | |
| tree | fa86a159bafb6ea5c1605cd66a67f8be5a24ba3e /src_py | |
| parent | 1a73bd946b8636dbcbb2970c8f8e919b888074ab (diff) | |
calculate native implementation
Diffstat (limited to 'src_py')
| -rw-r--r-- | src_py/opcut/main.py | 90 | ||||
| -rw-r--r-- | src_py/opcut/server.py | 64 |
2 files changed, 110 insertions, 44 deletions
diff --git a/src_py/opcut/main.py b/src_py/opcut/main.py index df9987f..475288e 100644 --- a/src_py/opcut/main.py +++ b/src_py/opcut/main.py @@ -15,25 +15,13 @@ import opcut.output import opcut.server -log_schema_id: str = 'hat-json://logging.yaml#' params_schema_id: str = 'opcut://opcut.yaml#/definitions/params' result_schema_id: str = 'opcut://opcut.yaml#/definitions/result' @click.group() -@click.option('--log', - default=None, - metavar='PATH', - type=Path, - help=f"logging configuration file path {log_schema_id}") -def main(log: typing.Optional[Path]): +def main(): """Application main entry point""" - if not log: - return - - log_conf = json.decode_file(log) - common.json_schema_repo.validate(log_schema_id, log_conf) - logging.config.dictConfig(log_conf) @main.command() @@ -41,30 +29,35 @@ def main(log: typing.Optional[Path]): default=common.Method.FORWARD_GREEDY, type=common.Method, help="calculate method") -@click.option('--params', - default=None, - metavar='PATH', - type=Path, - help=f"calculate parameters file path ({params_schema_id})") -@click.option('--result', +@click.option('--output', default=None, metavar='PATH', type=Path, help=f"result file path ({result_schema_id})") +@click.argument('params', + required=False, + default=None, + metavar='PATH', + type=Path) def calculate(method: common.Method, - params: typing.Optional[Path], - result: typing.Optional[Path]): + output: typing.Optional[Path], + params: typing.Optional[Path]): """Calculate result""" - params = (json.decode_file(params) if params + params = (json.decode_file(params) if params and params != Path('-') else json.decode_stream(sys.stdin)) common.json_schema_repo.validate(params_schema_id, params) params = common.params_from_json(params) - res = opcut.csp.calculate(params, method) + try: + res = opcut.csp.calculate(params, method) + + except common.UnresolvableError: + sys.exit(42) + res = common.result_to_json(res) - if result: - json.encode_file(res, result) + if output and output != Path('-'): + json.encode_file(res, output) else: json.encode_stream(res, sys.stdout) @@ -77,32 +70,33 @@ def calculate(method: common.Method, @click.option('--panel', default=None, help="panel identifier") -@click.option('--result', - default=None, - metavar='PATH', - type=Path, - help=f"result file path ({result_schema_id})") @click.option('--output', default=None, metavar='PATH', type=Path, help="result file path") +@click.argument('result', + required=False, + default=None, + metavar='PATH', + type=Path) def generate_output(output_type: common.OutputType, panel: typing.Optional[str], - result: typing.Optional[Path], - output: typing.Optional[Path]): + output: typing.Optional[Path], + result: typing.Optional[Path]): """Generate output""" - result = (json.decode_file(result) if result + result = (json.decode_file(result) if result and result != Path('-') else json.decode_stream(sys.stdin)) common.json_schema_repo.validate(result_schema_id, result) result = common.result_from_json(result) out = opcut.output.generate_output(result, output_type, panel) - if output: - out.write_bytes(out) + if output and output != Path('-'): + output.write_bytes(out) else: - sys.stdout.detach().write(out) + stdout, sys.stdout = sys.stdout.detach(), None + stdout.write(out) @main.command() @@ -113,10 +107,29 @@ def generate_output(output_type: common.OutputType, default=8080, type=int, help="listening TCP port") +@click.option('--log-level', + default='INFO', + type=click.Choice(['CRITICAL', 'ERROR', 'WARNING', 'INFO', + 'DEBUG', 'NOTSET']), + help="log level") def server(host: str, - port: int): + port: int, + log_level: str): """Run server""" - loop = aio.init_asyncio() + logging.config.dictConfig({ + 'version': 1, + 'formatters': { + 'console': { + 'format': "[%(asctime)s %(levelname)s %(name)s] %(message)s"}}, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'console', + 'level': log_level}}, + 'root': { + 'level': log_level, + 'handlers': ['console']}, + 'disable_existing_loggers': False}) async def run(): server = await opcut.server.create(host, port) @@ -127,6 +140,7 @@ def server(host: str, finally: await aio.uncancellable(server.async_close()) + loop = aio.init_asyncio() with contextlib.suppress(asyncio.CancelledError): aio.run_asyncio(run(), loop=loop) diff --git a/src_py/opcut/server.py b/src_py/opcut/server.py index 92a4186..5e726ad 100644 --- a/src_py/opcut/server.py +++ b/src_py/opcut/server.py @@ -1,11 +1,13 @@ from pathlib import Path +import asyncio +import subprocess +import sys from hat import aio +from hat import json import aiohttp.web from opcut import common -import opcut.csp -import opcut.output static_dir: Path = common.package_path / 'ui' @@ -63,12 +65,13 @@ class Server(aio.Resource): return aiohttp.web.Response(status=400, text="Invalid request") + native = request.query.get('native') == 'true' method = common.Method(request.query['method']) params = common.params_from_json(data) try: - result = await self._executor(opcut.csp.calculate, params, method) - return aiohttp.web.json_response(common.result_to_json(result)) + result = await _calculate(native, method, params) + return aiohttp.web.json_response(result) except common.UnresolvableError: return aiohttp.web.Response(status=400, @@ -88,8 +91,7 @@ class Server(aio.Resource): panel = request.query.get('panel') result = common.result_from_json(data) - output = await self._executor(opcut.output.generate_output, result, - output_type, panel) + output = await _generate_output(output_type, panel, result) if output_type == common.OutputType.PDF: content_type = 'application/pdf' @@ -102,3 +104,53 @@ class Server(aio.Resource): return aiohttp.web.Response(body=output, content_type=content_type) + + +async def _calculate(native, method, params): + args = [*_get_calculate_cmd(native), '--method', method.value] + stdint_data = json.encode(common.params_to_json(params)).encode('utf-8') + + p = await asyncio.create_subprocess_exec(*args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout_data, stderr_data = await p.communicate(stdint_data) + + if p.returncode == 0: + return json.decode(stdout_data.decode('utf-8')) + + if p.returncode == 42: + raise common.UnresolvableError() + + raise Exception(stderr_data.decode('utf-8')) + + +async def _generate_output(output_type, panel, result): + args = [*_get_generate_output_cmd(), '--output-type', output_type.value, + *(['--panel', panel] if panel else [])] + stdint_data = json.encode(common.result_to_json(result)).encode('utf-8') + + p = await asyncio.create_subprocess_exec(*args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout_data, stderr_data = await p.communicate(stdint_data) + + if p.returncode == 0: + return stdout_data + + raise Exception(stderr_data.decode('utf-8')) + + +def _get_calculate_cmd(native): + if native and sys.platform == 'linux': + return [str(common.package_path / 'bin/linux-opcut-calculate')] + + elif native and sys.platform == 'win32': + return [str(common.package_path / 'bin/windows-opcut-calculate.exe')] + + return [sys.executable, '-m', 'opcut', 'calculate'] + + +def _get_generate_output_cmd(): + return [sys.executable, '-m', 'opcut', 'generate-output'] |
