quapp-common 0.0.12.dev3__tar.gz → 0.0.12.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. {quapp_common-0.0.12.dev3/quapp_common.egg-info → quapp_common-0.0.12.dev5}/PKG-INFO +28 -6
  2. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/README.md +27 -5
  3. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/pyproject.toml +1 -1
  4. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/async_tasks/async_invocation_task.py +26 -2
  5. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/backend/invocation.py +1 -1
  6. quapp_common-0.0.12.dev5/quapp_common/component/bridge.py +94 -0
  7. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/circuit_adapter.py +2 -3
  8. quapp_common-0.0.12.dev5/quapp_common/component/dispatcher.py +80 -0
  9. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5/quapp_common.egg-info}/PKG-INFO +28 -6
  10. quapp_common-0.0.12.dev3/quapp_common/component/bridge.py +0 -94
  11. quapp_common-0.0.12.dev3/quapp_common/component/dispatcher.py +0 -79
  12. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/LICENSE +0 -0
  13. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/__init__.py +0 -0
  14. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/async_tasks/__init__.py +0 -0
  15. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/async_tasks/async_task.py +0 -0
  16. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/async_tasks/export_circuit_task.py +0 -0
  17. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/async_tasks/post_processing_task.py +0 -0
  18. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/__init__.py +0 -0
  19. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/backend/__init__.py +0 -0
  20. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/backend/job_fetcher.py +0 -0
  21. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/backend/job_fetching.py +0 -0
  22. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/backend/job_manager.py +0 -0
  23. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/callback/__init__.py +0 -0
  24. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/callback/update_job_metadata.py +0 -0
  25. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/device/__init__.py +0 -0
  26. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/device/device_selection.py +0 -0
  27. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/component/result_serializer.py +0 -0
  28. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/config/__init__.py +0 -0
  29. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/config/logging_config.py +0 -0
  30. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/config/thread_config.py +0 -0
  31. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/__init__.py +0 -0
  32. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/async_task/__init__.py +0 -0
  33. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/async_task/circuit_export/__init__.py +0 -0
  34. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/async_task/circuit_export/backend_holder.py +0 -0
  35. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/async_task/circuit_export/circuit_holder.py +0 -0
  36. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/backend/__init__.py +0 -0
  37. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/backend/backend_information.py +0 -0
  38. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/callback/__init__.py +0 -0
  39. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/callback/callback_url.py +0 -0
  40. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/device/__init__.py +0 -0
  41. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/device/circuit_running_option.py +0 -0
  42. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/promise/__init__.py +0 -0
  43. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/promise/post_processing_promise.py +0 -0
  44. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/promise/promise.py +0 -0
  45. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/request/__init__.py +0 -0
  46. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/request/invocation_request.py +0 -0
  47. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/request/job_fetching_request.py +0 -0
  48. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/request/request.py +0 -0
  49. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/response/__init__.py +0 -0
  50. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/response/authentication.py +0 -0
  51. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/response/custom_header.py +0 -0
  52. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/data/response/job_response.py +0 -0
  53. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/__init__.py +0 -0
  54. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/base_enum.py +0 -0
  55. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/http_header.py +0 -0
  56. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/invocation_step.py +0 -0
  57. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/language.py +0 -0
  58. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/media_type.py +0 -0
  59. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/processing_unit.py +0 -0
  60. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/provider_tag.py +0 -0
  61. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/sdk.py +0 -0
  62. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/status/__init__.py +0 -0
  63. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/status/job_status.py +0 -0
  64. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/status/status_code.py +0 -0
  65. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/enum/token_type.py +0 -0
  66. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/factory/__init__.py +0 -0
  67. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/factory/device_factory.py +0 -0
  68. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/factory/handler_factory.py +0 -0
  69. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/factory/provider_factory.py +0 -0
  70. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/handler/__init__.py +0 -0
  71. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/handler/handler.py +0 -0
  72. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/__init__.py +0 -0
  73. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/device/__init__.py +0 -0
  74. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/device/custom_device.py +0 -0
  75. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/device/device.py +0 -0
  76. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/invocation.py +0 -0
  77. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/provider/__init__.py +0 -0
  78. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/model/provider/provider.py +0 -0
  79. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/util/__init__.py +0 -0
  80. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/util/file_utils.py +0 -0
  81. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/util/http_utils.py +0 -0
  82. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/util/json_parser_utils.py +0 -0
  83. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common/util/response_utils.py +0 -0
  84. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common.egg-info/SOURCES.txt +0 -0
  85. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common.egg-info/dependency_links.txt +0 -0
  86. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common.egg-info/requires.txt +0 -0
  87. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/quapp_common.egg-info/top_level.txt +0 -0
  88. {quapp_common-0.0.12.dev3 → quapp_common-0.0.12.dev5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.12.dev3
3
+ Version: 0.0.12.dev5
4
4
  Summary: Quapp common library supporting Quapp Platform for Quantum Computing
5
5
  Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
6
6
  License: The MIT License (MIT)
@@ -49,11 +49,6 @@ Recent improvements add first-class asynchronous job processing with a
49
49
  cross-process JobManager,
50
50
  background task execution, and standardized result models for immediate client
51
51
  responses while work continues in the background.
52
- Version 0.0.12.dev1 introduces multi-language SDK support, enabling
53
- dispatching quantum tasks to non-Python runtimes (e.g., JavaScript) via
54
- subprocess with circuit adaptation and result serialization.
55
- Version 0.0.12.dev2 fixes the JavaScript handler runner path in
56
- `SubprocessDispatcher` so that the subprocess command resolves correctly.
57
52
 
58
53
  ## Features
59
54
 
@@ -106,6 +101,33 @@ Notes:
106
101
 
107
102
  ## Recently Changes Highlights
108
103
 
104
+ ### v0.0.12.dev5
105
+ - Version bump to 0.0.12.dev5.
106
+ - Refactor: update logger imports in `SubprocessBridge`, `CircuitAdapter`, and `SubprocessDispatcher`
107
+ to use relative paths (`from ..config.logging_config import logger`) for consistency across modules.
108
+
109
+ ### v0.0.12.dev4
110
+ - Version bumps to 0.0.12.dev4.
111
+ - Fix: `AsyncInvocationTask._on_task_done` now properly fires the step error
112
+ callback when a background task raises an unhandled exception — reads
113
+ `onErrorCallbackUrl` from the event's `preparation` step and calls
114
+ `update_job_metadata` with a structured error response.
115
+ - Fix: circuit preparation failure in `Invocation.__pre_execute` now returns
116
+ `None` instead of raising `ValueError`, allowing the caller to handle the
117
+ empty result gracefully without an unhandled exception propagating.
118
+ - Refactor: replace stdlib `logging` with the project’s loguru-based logger
119
+ (via `config.logging_config`) in `SubprocessBridge`, `CircuitAdapter`, and
120
+ `SubprocessDispatcher`; all three now call `logger.bind(context=__name__)`.
121
+ - Style: reformat `SubprocessBridge` and `SubprocessDispatcher` from 2-space
122
+ to 4-space indentation, consistent with the rest of the codebase.
123
+
124
+ ### v0.0.12.dev3
125
+ - Version bumps to 0.0.12.dev3.
126
+ - Add `QUANTUM_HPC` SDK enum value (display name `"quantum hpc"`) to support
127
+ the Quapp HPC backend.
128
+ - Make `circuitExportUrl` optional in `Invocation` for SDKs that do not
129
+ require a circuit-export step.
130
+
109
131
  ### v0.0.12.dev2
110
132
  - Version bumps to 0.0.12.dev2.
111
133
  - Fix: correct JavaScript handler runner path in `SubprocessDispatcher.RUNNER_MAP` —
@@ -12,11 +12,6 @@ Recent improvements add first-class asynchronous job processing with a
12
12
  cross-process JobManager,
13
13
  background task execution, and standardized result models for immediate client
14
14
  responses while work continues in the background.
15
- Version 0.0.12.dev1 introduces multi-language SDK support, enabling
16
- dispatching quantum tasks to non-Python runtimes (e.g., JavaScript) via
17
- subprocess with circuit adaptation and result serialization.
18
- Version 0.0.12.dev2 fixes the JavaScript handler runner path in
19
- `SubprocessDispatcher` so that the subprocess command resolves correctly.
20
15
 
21
16
  ## Features
22
17
 
@@ -69,6 +64,33 @@ Notes:
69
64
 
70
65
  ## Recently Changes Highlights
71
66
 
67
+ ### v0.0.12.dev5
68
+ - Version bump to 0.0.12.dev5.
69
+ - Refactor: update logger imports in `SubprocessBridge`, `CircuitAdapter`, and `SubprocessDispatcher`
70
+ to use relative paths (`from ..config.logging_config import logger`) for consistency across modules.
71
+
72
+ ### v0.0.12.dev4
73
+ - Version bumps to 0.0.12.dev4.
74
+ - Fix: `AsyncInvocationTask._on_task_done` now properly fires the step error
75
+ callback when a background task raises an unhandled exception — reads
76
+ `onErrorCallbackUrl` from the event's `preparation` step and calls
77
+ `update_job_metadata` with a structured error response.
78
+ - Fix: circuit preparation failure in `Invocation.__pre_execute` now returns
79
+ `None` instead of raising `ValueError`, allowing the caller to handle the
80
+ empty result gracefully without an unhandled exception propagating.
81
+ - Refactor: replace stdlib `logging` with the project’s loguru-based logger
82
+ (via `config.logging_config`) in `SubprocessBridge`, `CircuitAdapter`, and
83
+ `SubprocessDispatcher`; all three now call `logger.bind(context=__name__)`.
84
+ - Style: reformat `SubprocessBridge` and `SubprocessDispatcher` from 2-space
85
+ to 4-space indentation, consistent with the rest of the codebase.
86
+
87
+ ### v0.0.12.dev3
88
+ - Version bumps to 0.0.12.dev3.
89
+ - Add `QUANTUM_HPC` SDK enum value (display name `"quantum hpc"`) to support
90
+ the Quapp HPC backend.
91
+ - Make `circuitExportUrl` optional in `Invocation` for SDKs that do not
92
+ require a circuit-export step.
93
+
72
94
  ### v0.0.12.dev2
73
95
  - Version bumps to 0.0.12.dev2.
74
96
  - Fix: correct JavaScript handler runner path in `SubprocessDispatcher.RUNNER_MAP` —
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quapp-common"
7
- version = "0.0.12.dev3"
7
+ version = "0.0.12.dev5"
8
8
  description = "Quapp common library supporting Quapp Platform for Quantum Computing"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "CITYNOW Co. Ltd. ", email = "corp@citynow.vn" }]
@@ -9,9 +9,12 @@ from starlette.responses import JSONResponse
9
9
 
10
10
  from ..async_tasks.async_task import AsyncTask
11
11
  from ..component.backend.job_manager import JobManager
12
+ from ..component.callback.update_job_metadata import update_job_metadata
12
13
  from ..config.logging_config import logger
13
14
  from ..factory.handler_factory import HandlerFactory
14
15
  from ..model.invocation import Event, Error, Success
16
+ from ..util.http_utils import get_custom_header
17
+ from ..util.response_utils import build_error_job_response
15
18
 
16
19
 
17
20
  class AsyncInvocationTask(AsyncTask):
@@ -72,9 +75,30 @@ class AsyncInvocationTask(AsyncTask):
72
75
  task = asyncio.create_task(run_in_threadpool(_run_handle))
73
76
 
74
77
  def _on_task_done(t: asyncio.Task):
75
- if not t.cancelled() and t.exception():
78
+ if t.cancelled() or t.exception() is None:
79
+ return
80
+ exc = t.exception()
81
+ logger.exception(
82
+ f"Background job task failed for job {job_id}: {exc}")
83
+ try:
84
+ event_data = self.event.json()
85
+ on_error_url = (event_data.get('preparation') or {}).get(
86
+ 'onErrorCallbackUrl')
87
+ if on_error_url:
88
+ error_response = build_error_job_response(
89
+ exc,
90
+ message=f"Unhandled background task error: {exc}")
91
+ error_response.user_token = event_data.get('userToken')
92
+ error_response.user_identity = event_data.get(
93
+ 'userIdentity')
94
+ error_response.project_header = get_custom_header(
95
+ event_data, 'projectHeader')
96
+ error_response.workspace_header = get_custom_header(
97
+ event_data, 'workspaceHeader')
98
+ update_job_metadata(error_response, on_error_url)
99
+ except Exception as fallback_exc:
76
100
  logger.exception(
77
- f"Background job task failed for job {job_id}: {t.exception()}")
101
+ f"Failed to notify BE of error for job {job_id}: {fallback_exc}")
78
102
 
79
103
  task.add_done_callback(_on_task_done)
80
104
 
@@ -79,7 +79,7 @@ class Invocation(ABC):
79
79
  if circuit is None:
80
80
  self.logger.error(
81
81
  "Circuit preparation failed, returning None from pre-execute")
82
- raise ValueError("Circuit preparation failed")
82
+ return None
83
83
 
84
84
  try:
85
85
  self.logger.debug("Preparing backend data")
@@ -0,0 +1,94 @@
1
+ """
2
+ QApp Platform Project
3
+ bridge.py
4
+ Copyright © CITYNOW Co. Ltd. All rights reserved.
5
+ """
6
+ # Quapp Platform Project
7
+ # bridge.py
8
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
9
+
10
+ from .circuit_adapter import CircuitAdapter
11
+ from .dispatcher import SubprocessDispatcher
12
+ from .result_serializer import ResultSerializer
13
+ from ..config.logging_config import logger
14
+ from ..enum.language import Language
15
+ from ..enum.sdk import Sdk
16
+
17
+ logger = logger.bind(context=__name__)
18
+
19
+
20
+ class SubprocessBridge:
21
+ """Bridge between the Python invocation chain and JS subprocess handlers.
22
+
23
+ Provides processing() and post_processing() callables that can be passed
24
+ to AsyncInvocationTask in place of Python handler functions. Internally,
25
+ each call dispatches to the JS subprocess with the appropriate action,
26
+ then converts the result (circuit or post-processed data) back to
27
+ Python-native objects.
28
+ """
29
+
30
+ def __init__(self, language: Language, sdk: Sdk):
31
+ self.language = language
32
+ self.sdk = sdk
33
+
34
+ def processing(self, invocation_input: dict):
35
+ """Call JS handler's processing() and convert a result to a native circuit.
36
+
37
+ Args:
38
+ invocation_input: The input dict from the invocation request.
39
+
40
+ Returns:
41
+ A native SDK circuit object (QuantumCircuit, BQM, etc.)
42
+
43
+ Raises:
44
+ RuntimeError: If the JS subprocess returns an error status.
45
+ """
46
+ logger.info(
47
+ f"SubprocessBridge.processing: dispatching to {self.language.value}")
48
+
49
+ result = SubprocessDispatcher.dispatch(self.language, {
50
+ "action": "processing",
51
+ "input" : invocation_input
52
+ })
53
+
54
+ if result.get("status") == "error":
55
+ raise RuntimeError(f"JS processing failed: {result.get('error')}")
56
+
57
+ payload = result.get("result")
58
+ if payload is None:
59
+ raise RuntimeError("JS processing returned no 'result' field")
60
+ logger.debug(
61
+ f"SubprocessBridge.processing: JS returned format={payload.get('format')}")
62
+
63
+ return CircuitAdapter.adapt(self.sdk, payload)
64
+
65
+ def post_processing(self, job_result):
66
+ """Serialize a job result, send to JS handler's postProcessing(), return a result.
67
+
68
+ Args:
69
+ job_result: The raw job results from the quantum device execution.
70
+
71
+ Returns:
72
+ The post-processed result from the JS handler.
73
+
74
+ Raises:
75
+ RuntimeError: If the JS subprocess returns an error status.
76
+ """
77
+ logger.info(
78
+ f"SubprocessBridge.post_processing: dispatching to {self.language.value}")
79
+
80
+ serialized = ResultSerializer.serialize(job_result)
81
+
82
+ result = SubprocessDispatcher.dispatch(self.language, {
83
+ "action" : "post_processing",
84
+ "job_result": serialized
85
+ })
86
+
87
+ if result.get("status") == "error":
88
+ raise RuntimeError(
89
+ f"JS post_processing failed: {result.get('error')}")
90
+
91
+ payload = result.get("result")
92
+ if payload is None:
93
+ raise RuntimeError("JS post_processing returned no 'result' field")
94
+ return payload
@@ -5,11 +5,10 @@
5
5
  # circuit_adapter.py
6
6
  # Copyright © CITYNOW Co. Ltd. All rights reserved.
7
7
 
8
- import logging
9
-
8
+ from ..config.logging_config import logger
10
9
  from ..enum.sdk import Sdk
11
10
 
12
- logger = logging.getLogger(__name__)
11
+ logger = logger.bind(context=__name__)
13
12
 
14
13
 
15
14
  class CircuitAdapter:
@@ -0,0 +1,80 @@
1
+ """
2
+ QApp Platform Project dispatcher.py Copyright © CITYNOW Co. Ltd. All rights reserved.
3
+ """
4
+
5
+ # Quapp Platform Project
6
+ # dispatcher.py
7
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
8
+
9
+ import json
10
+ import os
11
+ import subprocess
12
+
13
+ from ..config.logging_config import logger
14
+ from ..enum.language import Language
15
+
16
+ logger = logger.bind(context=__name__)
17
+
18
+
19
+ class SubprocessDispatcher:
20
+ """Dispatch handler execution to non-Python runtimes via subprocess."""
21
+
22
+ # Override this at application startup to set a stable working directory
23
+ # for subprocess runners (e.g., the project root containing function/).
24
+ # Defaults to os.getcwd() at dispatch time if not set.
25
+ WORKING_DIR: str = None
26
+
27
+ RUNNER_MAP = {
28
+ Language.JAVASCRIPT: {
29
+ 'command': ['node', 'handler_runner.js'],
30
+ 'timeout': 300,
31
+ }
32
+ }
33
+
34
+ @staticmethod
35
+ def dispatch(language: Language, request_data: dict,
36
+ timeout: int = None) -> dict:
37
+ """Dispatch request_data to a subprocess runner.
38
+
39
+ Args:
40
+ language: The target language runtime.
41
+ request_data: Dict to serialize as JSON and pipe to stdin.
42
+ timeout: Optional timeout in seconds. Overrides the runner by default.
43
+
44
+ Returns:
45
+ Parsed JSON dict from the subprocess stdout.
46
+ """
47
+ runner = SubprocessDispatcher.RUNNER_MAP.get(language)
48
+ if runner is None:
49
+ raise ValueError(f"No runner configured for language: {language}")
50
+
51
+ effective_timeout = timeout if timeout is not None else runner[
52
+ 'timeout']
53
+ action = request_data.get('action', 'handle')
54
+ logger.info(f"Dispatching to {language.value} runner, action={action}, "
55
+ f"timeout={effective_timeout}s")
56
+
57
+ payload = json.dumps(request_data)
58
+ cwd = SubprocessDispatcher.WORKING_DIR or os.getcwd()
59
+ result = subprocess.run(runner['command'], input=payload,
60
+ capture_output=True, text=True,
61
+ timeout=effective_timeout, cwd=cwd)
62
+
63
+ if result.stderr:
64
+ logger.debug(f"Subprocess stderr: {result.stderr[:500]}")
65
+
66
+ if result.returncode != 0:
67
+ logger.error(
68
+ f"Handler runner failed (exit={result.returncode}): {result.stderr[:500]}")
69
+ raise RuntimeError(f"Handler runner failed: {result.stderr}")
70
+
71
+ logger.debug(f"Subprocess completed successfully, action={action}")
72
+ return json.loads(result.stdout)
73
+
74
+ @staticmethod
75
+ def is_subprocess_language(language_str: str) -> bool:
76
+ try:
77
+ lang = Language.resolve(language_str)
78
+ return lang != Language.PYTHON
79
+ except Exception:
80
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.12.dev3
3
+ Version: 0.0.12.dev5
4
4
  Summary: Quapp common library supporting Quapp Platform for Quantum Computing
5
5
  Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
6
6
  License: The MIT License (MIT)
@@ -49,11 +49,6 @@ Recent improvements add first-class asynchronous job processing with a
49
49
  cross-process JobManager,
50
50
  background task execution, and standardized result models for immediate client
51
51
  responses while work continues in the background.
52
- Version 0.0.12.dev1 introduces multi-language SDK support, enabling
53
- dispatching quantum tasks to non-Python runtimes (e.g., JavaScript) via
54
- subprocess with circuit adaptation and result serialization.
55
- Version 0.0.12.dev2 fixes the JavaScript handler runner path in
56
- `SubprocessDispatcher` so that the subprocess command resolves correctly.
57
52
 
58
53
  ## Features
59
54
 
@@ -106,6 +101,33 @@ Notes:
106
101
 
107
102
  ## Recently Changes Highlights
108
103
 
104
+ ### v0.0.12.dev5
105
+ - Version bump to 0.0.12.dev5.
106
+ - Refactor: update logger imports in `SubprocessBridge`, `CircuitAdapter`, and `SubprocessDispatcher`
107
+ to use relative paths (`from ..config.logging_config import logger`) for consistency across modules.
108
+
109
+ ### v0.0.12.dev4
110
+ - Version bumps to 0.0.12.dev4.
111
+ - Fix: `AsyncInvocationTask._on_task_done` now properly fires the step error
112
+ callback when a background task raises an unhandled exception — reads
113
+ `onErrorCallbackUrl` from the event's `preparation` step and calls
114
+ `update_job_metadata` with a structured error response.
115
+ - Fix: circuit preparation failure in `Invocation.__pre_execute` now returns
116
+ `None` instead of raising `ValueError`, allowing the caller to handle the
117
+ empty result gracefully without an unhandled exception propagating.
118
+ - Refactor: replace stdlib `logging` with the project’s loguru-based logger
119
+ (via `config.logging_config`) in `SubprocessBridge`, `CircuitAdapter`, and
120
+ `SubprocessDispatcher`; all three now call `logger.bind(context=__name__)`.
121
+ - Style: reformat `SubprocessBridge` and `SubprocessDispatcher` from 2-space
122
+ to 4-space indentation, consistent with the rest of the codebase.
123
+
124
+ ### v0.0.12.dev3
125
+ - Version bumps to 0.0.12.dev3.
126
+ - Add `QUANTUM_HPC` SDK enum value (display name `"quantum hpc"`) to support
127
+ the Quapp HPC backend.
128
+ - Make `circuitExportUrl` optional in `Invocation` for SDKs that do not
129
+ require a circuit-export step.
130
+
109
131
  ### v0.0.12.dev2
110
132
  - Version bumps to 0.0.12.dev2.
111
133
  - Fix: correct JavaScript handler runner path in `SubprocessDispatcher.RUNNER_MAP` —
@@ -1,94 +0,0 @@
1
- """
2
- QApp Platform Project
3
- bridge.py
4
- Copyright © CITYNOW Co. Ltd. All rights reserved.
5
- """
6
- # Quapp Platform Project
7
- # bridge.py
8
- # Copyright © CITYNOW Co. Ltd. All rights reserved.
9
-
10
- import logging
11
-
12
- from .circuit_adapter import CircuitAdapter
13
- from .dispatcher import SubprocessDispatcher
14
- from .result_serializer import ResultSerializer
15
- from ..enum.language import Language
16
- from ..enum.sdk import Sdk
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class SubprocessBridge:
22
- """Bridge between the Python invocation chain and JS subprocess handlers.
23
-
24
- Provides processing() and post_processing() callables that can be passed
25
- to AsyncInvocationTask in place of Python handler functions. Internally,
26
- each call dispatches to the JS subprocess with the appropriate action,
27
- then converts the result (circuit or post-processed data) back to
28
- Python-native objects.
29
- """
30
-
31
- def __init__(self, language: Language, sdk: Sdk):
32
- self.language = language
33
- self.sdk = sdk
34
-
35
- def processing(self, invocation_input: dict):
36
- """Call JS handler's processing() and convert a result to a native circuit.
37
-
38
- Args:
39
- invocation_input: The input dict from the invocation request.
40
-
41
- Returns:
42
- A native SDK circuit object (QuantumCircuit, BQM, etc.)
43
-
44
- Raises:
45
- RuntimeError: If the JS subprocess returns an error status.
46
- """
47
- logger.info(
48
- f"SubprocessBridge.processing: dispatching to {self.language.value}")
49
-
50
- result = SubprocessDispatcher.dispatch(self.language, {
51
- "action": "processing",
52
- "input" : invocation_input
53
- })
54
-
55
- if result.get("status") == "error":
56
- raise RuntimeError(f"JS processing failed: {result.get('error')}")
57
-
58
- payload = result.get("result")
59
- if payload is None:
60
- raise RuntimeError("JS processing returned no 'result' field")
61
- logger.debug(
62
- f"SubprocessBridge.processing: JS returned format={payload.get('format')}")
63
-
64
- return CircuitAdapter.adapt(self.sdk, payload)
65
-
66
- def post_processing(self, job_result):
67
- """Serialize a job result, send to JS handler's postProcessing(), return a result.
68
-
69
- Args:
70
- job_result: The raw job results from the quantum device execution.
71
-
72
- Returns:
73
- The post-processed result from the JS handler.
74
-
75
- Raises:
76
- RuntimeError: If the JS subprocess returns an error status.
77
- """
78
- logger.info(
79
- f"SubprocessBridge.post_processing: dispatching to {self.language.value}")
80
-
81
- serialized = ResultSerializer.serialize(job_result)
82
-
83
- result = SubprocessDispatcher.dispatch(self.language, {
84
- "action" : "post_processing",
85
- "job_result": serialized
86
- })
87
-
88
- if result.get("status") == "error":
89
- raise RuntimeError(f"JS post_processing failed: {result.get('error')}")
90
-
91
- payload = result.get("result")
92
- if payload is None:
93
- raise RuntimeError("JS post_processing returned no 'result' field")
94
- return payload
@@ -1,79 +0,0 @@
1
- """
2
- QApp Platform Project dispatcher.py Copyright © CITYNOW Co. Ltd. All rights reserved.
3
- """
4
-
5
- # Quapp Platform Project
6
- # dispatcher.py
7
- # Copyright © CITYNOW Co. Ltd. All rights reserved.
8
-
9
- import json
10
- import logging
11
- import os
12
- import subprocess
13
-
14
- from ..enum.language import Language
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- class SubprocessDispatcher:
20
- """Dispatch handler execution to non-Python runtimes via subprocess."""
21
-
22
- # Override this at application startup to set a stable working directory
23
- # for subprocess runners (e.g., the project root containing function/).
24
- # Defaults to os.getcwd() at dispatch time if not set.
25
- WORKING_DIR: str = None
26
-
27
- RUNNER_MAP = {
28
- Language.JAVASCRIPT: {
29
- 'command': ['node', 'handler_runner.js'],
30
- 'timeout': 300,
31
- }
32
- }
33
-
34
- @staticmethod
35
- def dispatch(language: Language, request_data: dict,
36
- timeout: int = None) -> dict:
37
- """Dispatch request_data to a subprocess runner.
38
-
39
- Args:
40
- language: The target language runtime.
41
- request_data: Dict to serialize as JSON and pipe to stdin.
42
- timeout: Optional timeout in seconds. Overrides the runner by default.
43
-
44
- Returns:
45
- Parsed JSON dict from the subprocess stdout.
46
- """
47
- runner = SubprocessDispatcher.RUNNER_MAP.get(language)
48
- if runner is None:
49
- raise ValueError(f"No runner configured for language: {language}")
50
-
51
- effective_timeout = timeout if timeout is not None else runner['timeout']
52
- action = request_data.get('action', 'handle')
53
- logger.info(f"Dispatching to {language.value} runner, action={action}, "
54
- f"timeout={effective_timeout}s")
55
-
56
- payload = json.dumps(request_data)
57
- cwd = SubprocessDispatcher.WORKING_DIR or os.getcwd()
58
- result = subprocess.run(runner['command'], input=payload,
59
- capture_output=True, text=True,
60
- timeout=effective_timeout, cwd=cwd)
61
-
62
- if result.stderr:
63
- logger.debug(f"Subprocess stderr: {result.stderr[:500]}")
64
-
65
- if result.returncode != 0:
66
- logger.error(
67
- f"Handler runner failed (exit={result.returncode}): {result.stderr[:500]}")
68
- raise RuntimeError(f"Handler runner failed: {result.stderr}")
69
-
70
- logger.debug(f"Subprocess completed successfully, action={action}")
71
- return json.loads(result.stdout)
72
-
73
- @staticmethod
74
- def is_subprocess_language(language_str: str) -> bool:
75
- try:
76
- lang = Language.resolve(language_str)
77
- return lang != Language.PYTHON
78
- except Exception:
79
- return False