quapp-common 0.0.11.dev9__tar.gz → 0.0.12.dev1__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 (86) hide show
  1. {quapp_common-0.0.11.dev9/quapp_common.egg-info → quapp_common-0.0.12.dev1}/PKG-INFO +33 -3
  2. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/README.md +30 -2
  3. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/pyproject.toml +4 -2
  4. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/async_tasks/async_invocation_task.py +8 -1
  5. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/backend/invocation.py +2 -2
  6. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/backend/job_fetcher.py +2 -2
  7. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/backend/job_fetching.py +1 -1
  8. quapp_common-0.0.12.dev1/quapp_common/component/bridge.py +94 -0
  9. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/callback/update_job_metadata.py +13 -5
  10. quapp_common-0.0.12.dev1/quapp_common/component/circuit_adapter.py +124 -0
  11. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/device/device_selection.py +1 -1
  12. quapp_common-0.0.12.dev1/quapp_common/component/dispatcher.py +79 -0
  13. quapp_common-0.0.12.dev1/quapp_common/component/result_serializer.py +22 -0
  14. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/request/invocation_request.py +1 -1
  15. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/request/job_fetching_request.py +6 -2
  16. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/request/request.py +1 -1
  17. quapp_common-0.0.12.dev1/quapp_common/enum/language.py +22 -0
  18. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/sdk.py +2 -0
  19. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/invocation.py +0 -1
  20. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/util/http_utils.py +1 -1
  21. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1/quapp_common.egg-info}/PKG-INFO +33 -3
  22. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common.egg-info/SOURCES.txt +5 -0
  23. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common.egg-info/requires.txt +2 -0
  24. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/LICENSE +0 -0
  25. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/__init__.py +0 -0
  26. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/async_tasks/__init__.py +0 -0
  27. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/async_tasks/async_task.py +0 -0
  28. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/async_tasks/export_circuit_task.py +0 -0
  29. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/async_tasks/post_processing_task.py +0 -0
  30. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/__init__.py +0 -0
  31. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/backend/__init__.py +0 -0
  32. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/backend/job_manager.py +0 -0
  33. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/callback/__init__.py +0 -0
  34. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/component/device/__init__.py +0 -0
  35. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/config/__init__.py +0 -0
  36. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/config/logging_config.py +0 -0
  37. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/config/thread_config.py +0 -0
  38. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/__init__.py +0 -0
  39. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/async_task/__init__.py +0 -0
  40. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/async_task/circuit_export/__init__.py +0 -0
  41. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/async_task/circuit_export/backend_holder.py +0 -0
  42. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/async_task/circuit_export/circuit_holder.py +0 -0
  43. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/backend/__init__.py +0 -0
  44. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/backend/backend_information.py +0 -0
  45. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/callback/__init__.py +0 -0
  46. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/callback/callback_url.py +0 -0
  47. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/device/__init__.py +0 -0
  48. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/device/circuit_running_option.py +0 -0
  49. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/promise/__init__.py +0 -0
  50. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/promise/post_processing_promise.py +0 -0
  51. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/promise/promise.py +0 -0
  52. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/request/__init__.py +0 -0
  53. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/response/__init__.py +0 -0
  54. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/response/authentication.py +0 -0
  55. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/response/custom_header.py +0 -0
  56. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/data/response/job_response.py +0 -0
  57. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/__init__.py +0 -0
  58. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/base_enum.py +0 -0
  59. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/http_header.py +0 -0
  60. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/invocation_step.py +0 -0
  61. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/media_type.py +0 -0
  62. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/processing_unit.py +0 -0
  63. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/provider_tag.py +0 -0
  64. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/status/__init__.py +0 -0
  65. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/status/job_status.py +0 -0
  66. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/status/status_code.py +0 -0
  67. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/enum/token_type.py +0 -0
  68. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/factory/__init__.py +0 -0
  69. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/factory/device_factory.py +0 -0
  70. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/factory/handler_factory.py +0 -0
  71. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/factory/provider_factory.py +0 -0
  72. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/handler/__init__.py +0 -0
  73. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/handler/handler.py +0 -0
  74. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/__init__.py +0 -0
  75. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/device/__init__.py +0 -0
  76. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/device/custom_device.py +0 -0
  77. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/device/device.py +0 -0
  78. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/provider/__init__.py +0 -0
  79. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/model/provider/provider.py +0 -0
  80. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/util/__init__.py +0 -0
  81. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/util/file_utils.py +0 -0
  82. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/util/json_parser_utils.py +0 -0
  83. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common/util/response_utils.py +0 -0
  84. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common.egg-info/dependency_links.txt +0 -0
  85. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/quapp_common.egg-info/top_level.txt +0 -0
  86. {quapp_common-0.0.11.dev9 → quapp_common-0.0.12.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.11.dev9
3
+ Version: 0.0.12.dev1
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)
@@ -25,6 +25,8 @@ Requires-Dist: numpy
25
25
  Requires-Dist: matplotlib
26
26
  Requires-Dist: pylatexenc
27
27
  Requires-Dist: starlette
28
+ Requires-Dist: qibo
29
+ Requires-Dist: dimod
28
30
  Provides-Extra: dev
29
31
  Requires-Dist: black; extra == "dev"
30
32
  Requires-Dist: bumpver; extra == "dev"
@@ -46,8 +48,10 @@ quantum providers and devices.
46
48
  Recent improvements add first-class asynchronous job processing with a
47
49
  cross-process JobManager,
48
50
  background task execution, and standardized result models for immediate client
49
- responses while work
50
- continues in the background.
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.
51
55
 
52
56
  ## Features
53
57
 
@@ -72,6 +76,16 @@ continues in the background.
72
76
  scheduler.
73
77
  - Safety: updates to jobs already DONE/FAILED are ignored to prevent
74
78
  post-completion mutations.
79
+ - Multi-language SDK support (v0.0.12.dev1):
80
+ - Language enum for validating and resolving Python/JavaScript runtimes.
81
+ - SubprocessDispatcher to delegate tasks to non-Python runtimes via
82
+ subprocess with timeout management and JSON payload processing.
83
+ - SubprocessBridge integrating dispatcher, circuit adapter, and result
84
+ serializer for seamless JS-Python handler interaction.
85
+ - CircuitAdapter for converting JS subprocess circuit outputs into native
86
+ SDK formats (OpenQASM, QUBO, JSON) across Qiskit, Braket, PennyLane, etc.
87
+ - ResultSerializer for converting complex job results into JSON-serializable
88
+ dictionaries for subprocess communication.
75
89
 
76
90
  ## Installation
77
91
 
@@ -85,9 +99,25 @@ Notes:
85
99
 
86
100
  - From version 0.0.11.dev7, `starlette` is a direct dependency to support
87
101
  background execution helpers.
102
+ - From version 0.0.12.dev1, `qibo` and `dimod` are added as dependencies to
103
+ support Qibo and D-Wave Ocean SDK integrations.
88
104
 
89
105
  ## Recently Changes Highlights
90
106
 
107
+ ### v0.0.12.dev1
108
+ - Version bump to 0.0.12.dev1 and dependency update (add `qibo`, `dimod`).
109
+ - Introduce multi-language SDK support primitives:
110
+ - `Language` enum for validating and resolving Python/JavaScript runtimes.
111
+ - `SubprocessDispatcher` for delegating tasks to non-Python runtimes via
112
+ subprocess with runner configuration and timeout management.
113
+ - `SubprocessBridge` integrating dispatcher, circuit adapter, and result
114
+ serializer for JS-Python handler interaction.
115
+ - `CircuitAdapter` for converting JS subprocess circuit outputs into native
116
+ SDK formats (OpenQASM, QUBO, JSON) across Qiskit, Braket, PennyLane, etc.
117
+ - `ResultSerializer` for converting complex job results (numpy, complex
118
+ numbers, Enums, datetime) into JSON-serializable dictionaries.
119
+
120
+ ### v0.0.11.dev7 – v0.0.11.dev10
91
121
  - Version bump to 0.0.11.dev7 and dependency update (add `starlette`).
92
122
  - Introduce asynchronous job processing primitives:
93
123
  - AsyncInvocationTask for background execution with immediate client
@@ -11,8 +11,10 @@ quantum providers and devices.
11
11
  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
- responses while work
15
- continues in the background.
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.
16
18
 
17
19
  ## Features
18
20
 
@@ -37,6 +39,16 @@ continues in the background.
37
39
  scheduler.
38
40
  - Safety: updates to jobs already DONE/FAILED are ignored to prevent
39
41
  post-completion mutations.
42
+ - Multi-language SDK support (v0.0.12.dev1):
43
+ - Language enum for validating and resolving Python/JavaScript runtimes.
44
+ - SubprocessDispatcher to delegate tasks to non-Python runtimes via
45
+ subprocess with timeout management and JSON payload processing.
46
+ - SubprocessBridge integrating dispatcher, circuit adapter, and result
47
+ serializer for seamless JS-Python handler interaction.
48
+ - CircuitAdapter for converting JS subprocess circuit outputs into native
49
+ SDK formats (OpenQASM, QUBO, JSON) across Qiskit, Braket, PennyLane, etc.
50
+ - ResultSerializer for converting complex job results into JSON-serializable
51
+ dictionaries for subprocess communication.
40
52
 
41
53
  ## Installation
42
54
 
@@ -50,9 +62,25 @@ Notes:
50
62
 
51
63
  - From version 0.0.11.dev7, `starlette` is a direct dependency to support
52
64
  background execution helpers.
65
+ - From version 0.0.12.dev1, `qibo` and `dimod` are added as dependencies to
66
+ support Qibo and D-Wave Ocean SDK integrations.
53
67
 
54
68
  ## Recently Changes Highlights
55
69
 
70
+ ### v0.0.12.dev1
71
+ - Version bump to 0.0.12.dev1 and dependency update (add `qibo`, `dimod`).
72
+ - Introduce multi-language SDK support primitives:
73
+ - `Language` enum for validating and resolving Python/JavaScript runtimes.
74
+ - `SubprocessDispatcher` for delegating tasks to non-Python runtimes via
75
+ subprocess with runner configuration and timeout management.
76
+ - `SubprocessBridge` integrating dispatcher, circuit adapter, and result
77
+ serializer for JS-Python handler interaction.
78
+ - `CircuitAdapter` for converting JS subprocess circuit outputs into native
79
+ SDK formats (OpenQASM, QUBO, JSON) across Qiskit, Braket, PennyLane, etc.
80
+ - `ResultSerializer` for converting complex job results (numpy, complex
81
+ numbers, Enums, datetime) into JSON-serializable dictionaries.
82
+
83
+ ### v0.0.11.dev7 – v0.0.11.dev10
56
84
  - Version bump to 0.0.11.dev7 and dependency update (add `starlette`).
57
85
  - Introduce asynchronous job processing primitives:
58
86
  - AsyncInvocationTask for background execution with immediate client
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quapp-common"
7
- version = "0.0.11.dev9"
7
+ version = "0.0.12.dev1"
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" }]
@@ -21,7 +21,9 @@ dependencies = [
21
21
  "numpy",
22
22
  "matplotlib",
23
23
  "pylatexenc",
24
- "starlette"
24
+ "starlette",
25
+ "qibo",
26
+ "dimod"
25
27
  ]
26
28
  requires-python = ">=3.7"
27
29
 
@@ -69,7 +69,14 @@ class AsyncInvocationTask(AsyncTask):
69
69
  self.post_processing_fn).handle()
70
70
 
71
71
  # Run in the background threadpool so this request can return immediately
72
- asyncio.create_task(run_in_threadpool(_run_handle))
72
+ task = asyncio.create_task(run_in_threadpool(_run_handle))
73
+
74
+ def _on_task_done(t: asyncio.Task):
75
+ if not t.cancelled() and t.exception():
76
+ logger.exception(
77
+ f"Background job task failed for job {job_id}: {t.exception()}")
78
+
79
+ task.add_done_callback(_on_task_done)
73
80
 
74
81
  # Respond immediately so clients can poll GET /jobs/{job_id}
75
82
  return JSONResponse(status_code=200, content=Success(
@@ -77,7 +77,7 @@ class Invocation(ABC):
77
77
  circuit = self.__prepare_circuit(circuit_preparation_fn)
78
78
 
79
79
  if circuit is None:
80
- self.logger.exception(
80
+ self.logger.error(
81
81
  "Circuit preparation failed, returning None from pre-execute")
82
82
  raise ValueError("Circuit preparation failed")
83
83
 
@@ -114,7 +114,7 @@ class Invocation(ABC):
114
114
 
115
115
  try:
116
116
  if self.backend_information is None:
117
- self.logger.exception(
117
+ self.logger.error(
118
118
  "Backend information is None before execution")
119
119
  raise ValueError("Backend is not found")
120
120
 
@@ -116,7 +116,7 @@ class JobFetcher(ABC):
116
116
  update_job_metadata(job_response, self.callback_urls[
117
117
  InvocationStep.EXECUTION].on_done)
118
118
  elif job_status == JobStatus.ERROR.value:
119
- self.logger.exception("Job resulted in error, handling failure")
119
+ self.logger.error("Job resulted in error, handling failure")
120
120
 
121
121
  self._handle_failed_job(original_job_result, job_response)
122
122
  update_job_metadata(job_response, self.callback_urls[
@@ -151,7 +151,7 @@ class JobFetcher(ABC):
151
151
  def _handle_failed_job(self, original_job_result,
152
152
  job_response: JobResponse):
153
153
  """Handles the job result when the job has encountered an error."""
154
- self.logger.exception("Parsing job result from failed job")
154
+ self.logger.error("Parsing job result from failed job")
155
155
  job_response.job_result = parse(original_job_result)
156
156
 
157
157
  def _process_job_result(self, original_job_result,
@@ -75,7 +75,7 @@ class JobFetching(ABC):
75
75
  InvocationStep.EXECUTION).on_done)
76
76
 
77
77
  elif JobStatus.ERROR.value.__eq__(job_response.job_status):
78
- self.logger.exception("Job resulted in error, handling failure")
78
+ self.logger.error("Job resulted in error, handling failure")
79
79
 
80
80
  job_response.job_result = parse(original_job_result)
81
81
 
@@ -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
+ 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
@@ -38,8 +38,16 @@ def update_job_metadata(job_response: JobResponse, callback_url: str):
38
38
 
39
39
  def _patch_scheduler() -> None:
40
40
  job_details = JobManager.get_job(job_id)
41
- scheduler_callback_url = job_details.get('meta').get(
41
+ if job_details is None:
42
+ logger.warning(
43
+ f"Job {job_id} not found in JobManager, skipping scheduler patch")
44
+ return
45
+ scheduler_callback_url = job_details.get('meta', {}).get(
42
46
  'scheduler_callback_url')
47
+ if not scheduler_callback_url:
48
+ logger.debug(
49
+ f"No scheduler callback URL for job {job_id}, skipping")
50
+ return
43
51
  scheduler_payload = {'job_id' : job_id,
44
52
  'status' : job_details.get('status'),
45
53
  'updated_at': job_details.get('updated_at')}
@@ -55,10 +63,6 @@ def update_job_metadata(job_response: JobResponse, callback_url: str):
55
63
  request_body = generate_response(job_response)
56
64
 
57
65
  try:
58
- # Update local job status and notify scheduler
59
- _update_local_status()
60
- _patch_scheduler()
61
-
62
66
  request_headers = create_application_json_header(
63
67
  token=job_response.user_token,
64
68
  project_header=job_response.project_header,
@@ -70,6 +74,10 @@ def update_job_metadata(job_response: JobResponse, callback_url: str):
70
74
  # Log backend response
71
75
  _log_http_result('backend', response)
72
76
 
77
+ # Update local job status and notify scheduler only after backend confirms
78
+ _update_local_status()
79
+ _patch_scheduler()
80
+
73
81
  except Exception as exception:
74
82
  logger.exception(f'Error occurred while calling backend: {exception}',
75
83
  exc_info=True)
@@ -0,0 +1,124 @@
1
+ """
2
+ QApp Platform Project circuit_adapter.py Copyright © CITYNOW Co. Ltd. All rights reserved.
3
+ """
4
+ # Quapp Platform Project
5
+ # circuit_adapter.py
6
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
7
+
8
+ import logging
9
+
10
+ from ..enum.sdk import Sdk
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CircuitAdapter:
16
+ """Convert JS subprocess circuit output to native SDK circuit objects.
17
+
18
+ Supported interchange formats:
19
+ - openqasm: OpenQASM 2.0 string -> SDK-specific QuantumCircuit
20
+ - qubo: QUBO dict -> dimod.BinaryQuadraticModel
21
+ - json: Arbitrary JSON pass-through
22
+ """
23
+
24
+ @staticmethod
25
+ def adapt(sdk: Sdk, result: dict):
26
+ """Route to the appropriate adapter based on the 'format' field."""
27
+ fmt = result.get("format")
28
+ if fmt == "openqasm":
29
+ circuit = result.get("circuit")
30
+ if circuit is None:
31
+ raise ValueError("Missing 'circuit' field in openqasm response")
32
+ return CircuitAdapter._adapt_openqasm(sdk, circuit)
33
+ elif fmt == "qubo":
34
+ qubo = result.get("qubo")
35
+ if qubo is None:
36
+ raise ValueError("Missing 'qubo' field in qubo response")
37
+ return CircuitAdapter._adapt_qubo(sdk, qubo)
38
+ elif fmt == "json":
39
+ return result.get("data")
40
+ raise ValueError(f"Unsupported circuit interchange format: {fmt}")
41
+
42
+ @staticmethod
43
+ def _adapt_openqasm(sdk, qasm_str):
44
+ """Convert an OpenQASM 2.0 string to a native SDK circuit object."""
45
+ logger.debug(f"Adapting OpenQASM circuit for SDK: {sdk}")
46
+
47
+ if sdk == Sdk.QISKIT:
48
+ from qiskit import QuantumCircuit
49
+ return QuantumCircuit.from_qasm_str(qasm_str)
50
+
51
+ elif sdk == Sdk.BRAKET:
52
+ from pytket.qasm import circuit_from_qasm_str
53
+ from pytket.extensions.braket import tk_to_braket
54
+ return tk_to_braket(circuit_from_qasm_str(qasm_str))
55
+
56
+ elif sdk == Sdk.PENNYLANE:
57
+ import pennylane as qml
58
+ # PennyLane >=0.37 treats QASM `measure` statements as mid-circuit
59
+ # measurements returning MeasurementValue, which fails QNode
60
+ # validation. Override with a terminal `qml.probs` over all qubits
61
+ # so the QNode produces valid output and downstream histogram
62
+ # logic (which already looks for ProbabilityMP) keeps working.
63
+ #
64
+ # NOTE: `qml.from_qasm(..., measurements=[...])` attaches those
65
+ # measurements via `make_qscript` internally, which means they are
66
+ # NOT queued into an active `QuantumTape()` context. Downstream
67
+ # code uses `with QuantumTape() as tape: circuit()` to discover
68
+ # the circuit's wires, which would then miss the measurement
69
+ # wires and cause `WireError` at device execution. We therefore
70
+ # return a wrapper that runs the QASM ops (measurements=[] to
71
+ # drop QASM's terminal measures) and then explicitly queue a
72
+ # fresh `qml.probs` over all qubits, so the wires are visible in
73
+ # both `QuantumTape` capture and full QNode execution.
74
+ num_wires = CircuitAdapter._count_qasm_qubits(qasm_str)
75
+ base_fn = qml.from_qasm(qasm_str, measurements=[])
76
+
77
+ def _circuit():
78
+ base_fn()
79
+ if num_wires > 0:
80
+ return qml.probs(wires=list(range(num_wires)))
81
+ return None
82
+
83
+ return _circuit
84
+
85
+ elif sdk == Sdk.PYQUIL:
86
+ from pytket.qasm import circuit_from_qasm_str
87
+ from pytket.extensions.pyquil import tk_to_pyquil
88
+ return tk_to_pyquil(circuit_from_qasm_str(qasm_str))
89
+
90
+ elif sdk == Sdk.QIBO:
91
+ from qibo.models import Circuit
92
+ return Circuit.from_qasm(qasm_str)
93
+
94
+ elif sdk in (Sdk.CUDA_QUANTUM, Sdk.CU_QUANTUM):
95
+ # CUDA-Q accepts OpenQASM directly via kernel builder
96
+ return qasm_str
97
+
98
+ raise ValueError(f"OpenQASM adaptation not supported for SDK: {sdk}")
99
+
100
+ @staticmethod
101
+ def _count_qasm_qubits(qasm_str):
102
+ """Sum qubit counts across all `qreg <name>[<n>];` declarations."""
103
+ import re
104
+ return sum(int(m) for m in re.findall(r'qreg\s+\w+\[(\d+)\]', qasm_str))
105
+
106
+ @staticmethod
107
+ def _adapt_qubo(sdk, qubo_raw):
108
+ """Convert a QUBO dict with string keys ('x0,x1') to a BinaryQuadraticModel.
109
+
110
+ JS returns keys like "x0,x1" as strings. We parse them into tuple keys
111
+ for dimod's from_qubo() method.
112
+ """
113
+ logger.debug(f"Adapting QUBO for SDK: {sdk}")
114
+
115
+ qubo_dict = {}
116
+ for key, val in qubo_raw.items():
117
+ if isinstance(key, str) and "," in key:
118
+ parts = tuple(k.strip() for k in key.split(",", 1))
119
+ qubo_dict[parts] = val
120
+ else:
121
+ qubo_dict[(key, key)] = val
122
+
123
+ from dimod import BinaryQuadraticModel
124
+ return BinaryQuadraticModel.from_qubo(qubo_dict)
@@ -60,7 +60,7 @@ class DeviceSelection:
60
60
  response = requests.get(self.url, params=request, headers=header)
61
61
 
62
62
  if response.status_code != 200:
63
- logger.exception(
63
+ logger.error(
64
64
  'Select device fail with status code: {0} and content: {1}'.format(
65
65
  response.status_code, response.content))
66
66
 
@@ -0,0 +1,79 @@
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', 'function/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
@@ -0,0 +1,22 @@
1
+ """
2
+ QApp Platform Project result_serializer.py Copyright © CITYNOW Co. Ltd. All rights reserved.
3
+ """
4
+ from ..util.json_parser_utils import parse
5
+
6
+
7
+ class ResultSerializer:
8
+ """Serialize Python job results into JSON-safe dicts for subprocess communication."""
9
+
10
+ @staticmethod
11
+ def serialize(job_result):
12
+ """
13
+ Convert a job result (which may contain numpy arrays, complex numbers,
14
+ Qiskit/Braket objects, etc.) into a plain JSON-serializable dict.
15
+
16
+ Reuses the existing parse() utility that handles numpy, complex, datetime,
17
+ Enum, and custom objects with to_dict()/__dict__.
18
+ """
19
+ try:
20
+ return parse(job_result)
21
+ except Exception as e:
22
+ raise RuntimeError(f"Failed to serialize job result: {e}") from e
@@ -54,7 +54,7 @@ class InvocationRequest(Request):
54
54
  # Validate authentication (if provided)
55
55
  authentication = request_data.get("authentication")
56
56
  if authentication is not None and not isinstance(authentication, dict):
57
- self.logger.exception(
57
+ self.logger.error(
58
58
  'authentication must be a dictionary if provided, got {0}'.format(
59
59
  type(authentication).__name__))
60
60
  raise ValueError(
@@ -1,14 +1,18 @@
1
1
  # Quapp Platform Project
2
2
  # job_fetching_request.py
3
3
  # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+ import logging
5
+
4
6
  from .request import Request
5
7
 
8
+ _logger = logging.getLogger(__name__)
9
+
6
10
 
7
11
  class JobFetchingRequest(Request):
8
12
  def __init__(self, request_data: dict | None):
9
13
 
10
14
  if not isinstance(request_data, dict):
11
- self.logger.exception(
15
+ _logger.error(
12
16
  f"request_data must be a dictionary, got {type(request_data).__name__}")
13
17
  raise ValueError(
14
18
  f"request_data must be a dictionary, got {type(request_data).__name__}")
@@ -17,7 +21,7 @@ class JobFetchingRequest(Request):
17
21
  provider_authentication = request_data.get("providerAuthentication")
18
22
  if provider_authentication is not None and not isinstance(
19
23
  provider_authentication, dict):
20
- self.logger.exception(
24
+ _logger.error(
21
25
  f"Invalid provider_authentication: {type(provider_authentication).__name__}")
22
26
  raise ValueError(
23
27
  f"provider_authentication must be a dictionary if provided, got {type(provider_authentication).__name__}")
@@ -27,7 +27,7 @@ class Request:
27
27
  self.workspace_header: CustomHeader = get_custom_header(request_data,
28
28
  'workspaceHeader')
29
29
  self.logger = job_logger(self.job_id)
30
- JobManager.add_job(job_id=self.job_id, status='RUNNING', meta={
30
+ JobManager.add_job(job_id=self.job_id, status='PENDING', meta={
31
31
  'scheduler_callback_url': request_data.get('schedulerCallBackURL')})
32
32
  self.logger.info(
33
33
  f"Initializing Request with job id: {self.job_id} and provider job id: {self.provider_job_id}")
@@ -0,0 +1,22 @@
1
+ """
2
+ QApp Platform Project language.py Copyright © CITYNOW Co. Ltd. All rights reserved.
3
+ """
4
+
5
+ # Quapp Platform Project
6
+ # language.py
7
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
8
+
9
+ from ..enum.base_enum import BaseEnum
10
+
11
+
12
+ class Language(BaseEnum):
13
+ PYTHON = 'python'
14
+ JAVASCRIPT = 'javascript'
15
+
16
+ @staticmethod
17
+ def resolve(language: str):
18
+ for element in Language:
19
+ if element.value == language:
20
+ return element
21
+
22
+ raise ValueError(f"Language '{language}' is not supported!")
@@ -14,6 +14,8 @@ class Sdk(BaseEnum):
14
14
  PENNYLANE = "pennylane"
15
15
  PYQUIL = "pyquil"
16
16
  QSHARP = "qsharp"
17
+ QIBO = "qibo"
18
+ QULACS = "qulacs"
17
19
 
18
20
  @staticmethod
19
21
  def resolve(sdk: str):
@@ -9,7 +9,6 @@ from datetime import datetime
9
9
 
10
10
  class Event:
11
11
  logger = logging.getLogger("model.Invocation.Event")
12
- logger.setLevel(logging.DEBUG)
13
12
 
14
13
  def __init__(self, request):
15
14
  self.request_body = {}
@@ -136,6 +136,6 @@ def get_job_id_from_url(url: str):
136
136
  except IndexError:
137
137
  logger.exception("No job id found after 'job'")
138
138
  else:
139
- logger.exception("Neither 'job' nor 'jobs' found in URL segments.")
139
+ logger.error("Neither 'job' nor 'jobs' found in URL segments.")
140
140
 
141
141
  return job_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.11.dev9
3
+ Version: 0.0.12.dev1
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)
@@ -25,6 +25,8 @@ Requires-Dist: numpy
25
25
  Requires-Dist: matplotlib
26
26
  Requires-Dist: pylatexenc
27
27
  Requires-Dist: starlette
28
+ Requires-Dist: qibo
29
+ Requires-Dist: dimod
28
30
  Provides-Extra: dev
29
31
  Requires-Dist: black; extra == "dev"
30
32
  Requires-Dist: bumpver; extra == "dev"
@@ -46,8 +48,10 @@ quantum providers and devices.
46
48
  Recent improvements add first-class asynchronous job processing with a
47
49
  cross-process JobManager,
48
50
  background task execution, and standardized result models for immediate client
49
- responses while work
50
- continues in the background.
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.
51
55
 
52
56
  ## Features
53
57
 
@@ -72,6 +76,16 @@ continues in the background.
72
76
  scheduler.
73
77
  - Safety: updates to jobs already DONE/FAILED are ignored to prevent
74
78
  post-completion mutations.
79
+ - Multi-language SDK support (v0.0.12.dev1):
80
+ - Language enum for validating and resolving Python/JavaScript runtimes.
81
+ - SubprocessDispatcher to delegate tasks to non-Python runtimes via
82
+ subprocess with timeout management and JSON payload processing.
83
+ - SubprocessBridge integrating dispatcher, circuit adapter, and result
84
+ serializer for seamless JS-Python handler interaction.
85
+ - CircuitAdapter for converting JS subprocess circuit outputs into native
86
+ SDK formats (OpenQASM, QUBO, JSON) across Qiskit, Braket, PennyLane, etc.
87
+ - ResultSerializer for converting complex job results into JSON-serializable
88
+ dictionaries for subprocess communication.
75
89
 
76
90
  ## Installation
77
91
 
@@ -85,9 +99,25 @@ Notes:
85
99
 
86
100
  - From version 0.0.11.dev7, `starlette` is a direct dependency to support
87
101
  background execution helpers.
102
+ - From version 0.0.12.dev1, `qibo` and `dimod` are added as dependencies to
103
+ support Qibo and D-Wave Ocean SDK integrations.
88
104
 
89
105
  ## Recently Changes Highlights
90
106
 
107
+ ### v0.0.12.dev1
108
+ - Version bump to 0.0.12.dev1 and dependency update (add `qibo`, `dimod`).
109
+ - Introduce multi-language SDK support primitives:
110
+ - `Language` enum for validating and resolving Python/JavaScript runtimes.
111
+ - `SubprocessDispatcher` for delegating tasks to non-Python runtimes via
112
+ subprocess with runner configuration and timeout management.
113
+ - `SubprocessBridge` integrating dispatcher, circuit adapter, and result
114
+ serializer for JS-Python handler interaction.
115
+ - `CircuitAdapter` for converting JS subprocess circuit outputs into native
116
+ SDK formats (OpenQASM, QUBO, JSON) across Qiskit, Braket, PennyLane, etc.
117
+ - `ResultSerializer` for converting complex job results (numpy, complex
118
+ numbers, Enums, datetime) into JSON-serializable dictionaries.
119
+
120
+ ### v0.0.11.dev7 – v0.0.11.dev10
91
121
  - Version bump to 0.0.11.dev7 and dependency update (add `starlette`).
92
122
  - Introduce asynchronous job processing primitives:
93
123
  - AsyncInvocationTask for background execution with immediate client
@@ -13,6 +13,10 @@ quapp_common/async_tasks/async_task.py
13
13
  quapp_common/async_tasks/export_circuit_task.py
14
14
  quapp_common/async_tasks/post_processing_task.py
15
15
  quapp_common/component/__init__.py
16
+ quapp_common/component/bridge.py
17
+ quapp_common/component/circuit_adapter.py
18
+ quapp_common/component/dispatcher.py
19
+ quapp_common/component/result_serializer.py
16
20
  quapp_common/component/backend/__init__.py
17
21
  quapp_common/component/backend/invocation.py
18
22
  quapp_common/component/backend/job_fetcher.py
@@ -51,6 +55,7 @@ quapp_common/enum/__init__.py
51
55
  quapp_common/enum/base_enum.py
52
56
  quapp_common/enum/http_header.py
53
57
  quapp_common/enum/invocation_step.py
58
+ quapp_common/enum/language.py
54
59
  quapp_common/enum/media_type.py
55
60
  quapp_common/enum/processing_unit.py
56
61
  quapp_common/enum/provider_tag.py
@@ -4,6 +4,8 @@ numpy
4
4
  matplotlib
5
5
  pylatexenc
6
6
  starlette
7
+ qibo
8
+ dimod
7
9
 
8
10
  [dev]
9
11
  black