quapp-common 0.0.14.dev4__tar.gz → 0.0.14.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 (107) hide show
  1. {quapp_common-0.0.14.dev4/quapp_common.egg-info → quapp_common-0.0.14.dev5}/PKG-INFO +59 -7
  2. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/README.md +55 -0
  3. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/pyproject.toml +13 -8
  4. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/circuit_adapter.py +79 -30
  5. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5/quapp_common.egg-info}/PKG-INFO +59 -7
  6. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common.egg-info/SOURCES.txt +1 -0
  7. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common.egg-info/requires.txt +4 -6
  8. quapp_common-0.0.14.dev5/tests/test_circuit_adapter.py +174 -0
  9. quapp_common-0.0.14.dev5/tests/test_no_sdk_dependency.py +142 -0
  10. quapp_common-0.0.14.dev4/tests/test_circuit_adapter.py +0 -120
  11. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/LICENSE +0 -0
  12. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/__init__.py +0 -0
  13. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/async_tasks/__init__.py +0 -0
  14. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/async_tasks/async_invocation_task.py +0 -0
  15. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/async_tasks/async_task.py +0 -0
  16. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/async_tasks/export_circuit_task.py +0 -0
  17. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/async_tasks/post_processing_task.py +0 -0
  18. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/__init__.py +0 -0
  19. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/backend/__init__.py +0 -0
  20. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/backend/invocation.py +0 -0
  21. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/backend/job_fetcher.py +0 -0
  22. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/backend/job_fetching.py +0 -0
  23. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/backend/job_heartbeat.py +0 -0
  24. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/backend/job_manager.py +0 -0
  25. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/bridge.py +0 -0
  26. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/callback/__init__.py +0 -0
  27. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/callback/update_job_metadata.py +0 -0
  28. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/device/__init__.py +0 -0
  29. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/device/device_selection.py +0 -0
  30. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/dispatcher.py +0 -0
  31. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/component/result_serializer.py +0 -0
  32. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/config/__init__.py +0 -0
  33. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/config/log_context.py +0 -0
  34. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/config/logging_config.py +0 -0
  35. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/config/thread_config.py +0 -0
  36. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/__init__.py +0 -0
  37. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/async_task/__init__.py +0 -0
  38. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/async_task/circuit_export/__init__.py +0 -0
  39. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/async_task/circuit_export/backend_holder.py +0 -0
  40. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/async_task/circuit_export/circuit_holder.py +0 -0
  41. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/backend/__init__.py +0 -0
  42. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/backend/backend_information.py +0 -0
  43. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/callback/__init__.py +0 -0
  44. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/callback/callback_url.py +0 -0
  45. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/device/__init__.py +0 -0
  46. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/device/circuit_running_option.py +0 -0
  47. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/promise/__init__.py +0 -0
  48. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/promise/post_processing_promise.py +0 -0
  49. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/promise/promise.py +0 -0
  50. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/request/__init__.py +0 -0
  51. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/request/invocation_request.py +0 -0
  52. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/request/job_fetching_request.py +0 -0
  53. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/request/request.py +0 -0
  54. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/response/__init__.py +0 -0
  55. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/response/authentication.py +0 -0
  56. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/response/custom_header.py +0 -0
  57. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/response/error_report.py +0 -0
  58. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/data/response/job_response.py +0 -0
  59. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/__init__.py +0 -0
  60. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/base_enum.py +0 -0
  61. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/error_category.py +0 -0
  62. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/http_header.py +0 -0
  63. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/invocation_step.py +0 -0
  64. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/language.py +0 -0
  65. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/media_type.py +0 -0
  66. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/processing_unit.py +0 -0
  67. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/provider_tag.py +0 -0
  68. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/runtime_phase.py +0 -0
  69. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/sdk.py +0 -0
  70. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/status/__init__.py +0 -0
  71. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/status/job_status.py +0 -0
  72. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/status/status_code.py +0 -0
  73. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/enum/token_type.py +0 -0
  74. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/factory/__init__.py +0 -0
  75. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/factory/device_factory.py +0 -0
  76. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/factory/handler_factory.py +0 -0
  77. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/factory/provider_factory.py +0 -0
  78. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/handler/__init__.py +0 -0
  79. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/handler/handler.py +0 -0
  80. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/__init__.py +0 -0
  81. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/device/__init__.py +0 -0
  82. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/device/custom_device.py +0 -0
  83. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/device/device.py +0 -0
  84. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/invocation.py +0 -0
  85. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/provider/__init__.py +0 -0
  86. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/model/provider/provider.py +0 -0
  87. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/__init__.py +0 -0
  88. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/error_classifier.py +0 -0
  89. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/file_utils.py +0 -0
  90. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/http_utils.py +0 -0
  91. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/invocation_failure.py +0 -0
  92. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/json_parser_utils.py +0 -0
  93. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common/util/response_utils.py +0 -0
  94. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common.egg-info/dependency_links.txt +0 -0
  95. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/quapp_common.egg-info/top_level.txt +0 -0
  96. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/setup.cfg +0 -0
  97. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_dispatcher_runner_error.py +0 -0
  98. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_error_classifier.py +0 -0
  99. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_error_log_dedupe.py +0 -0
  100. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_error_report.py +0 -0
  101. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_invocation_failure.py +0 -0
  102. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_job_heartbeat.py +0 -0
  103. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_json_parser_utils.py +0 -0
  104. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_language_dispatcher.py +0 -0
  105. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_log_secret_leak.py +0 -0
  106. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_logging_config.py +0 -0
  107. {quapp_common-0.0.14.dev4 → quapp_common-0.0.14.dev5}/tests/test_result_serializer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.14.dev4
3
+ Version: 0.0.14.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)
@@ -25,18 +25,15 @@ 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
30
- Requires-Dist: qulacs
31
- Requires-Dist: qiskit
32
- Requires-Dist: pytket
33
- Requires-Dist: pennylane
34
28
  Provides-Extra: dev
35
29
  Requires-Dist: black; extra == "dev"
36
30
  Requires-Dist: bumpver; extra == "dev"
37
31
  Requires-Dist: isort; extra == "dev"
38
32
  Requires-Dist: pip-tools; extra == "dev"
39
33
  Requires-Dist: pytest; extra == "dev"
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest; extra == "test"
36
+ Requires-Dist: qiskit; extra == "test"
40
37
  Dynamic: license-file
41
38
 
42
39
  # quapp-common
@@ -114,6 +111,61 @@ Notes:
114
111
 
115
112
  ## Recently Changes Highlights
116
113
 
114
+ ### v0.0.14.dev5 — Gỡ toàn bộ dependency SDK lượng tử
115
+
116
+ `quapp-common` không còn phụ thuộc SDK nào. SDK thuộc về lib con.
117
+
118
+ **Gỡ khỏi `dependencies`** — 6 package, chỉ được dùng trong `circuit_adapter.py`:
119
+ `qiskit`, `pennylane`, `qibo`, `qulacs`, `dimod`, `pytket`.
120
+
121
+ Còn lại: `loguru`, `requests`, `numpy`, `matplotlib`, `pylatexenc`, `starlette`
122
+ — đều được chính code của `quapp-common` dùng (`matplotlib`/`pylatexenc` cần cho
123
+ `CircuitExportTask.__convert()`, nơi gọi `circuit.draw(output='mpl')` rồi
124
+ `savefig()`).
125
+
126
+ **`circuit_adapter.py`: import SDK lazy.** Mỗi handler tự import SDK của nó ngay
127
+ trước khi dùng, qua helper `_require()`. Trước đây 7 SDK được import ở module
128
+ level, nên:
129
+
130
+ - `from quapp_common.component.bridge import SubprocessBridge` đòi có **đủ cả 6
131
+ SDK cùng lúc**, kể cả khi function chỉ chạy Qiskit. Điều này vô hiệu hoá thiết
132
+ kế lazy-import của `js-*/index.py` trong `quapp-sdk-templates` — comment trong
133
+ đó ghi rõ ý định *"so the server can start even if optional dependencies are
134
+ missing"*;
135
+ - buộc mọi function image tải cả 6 SDK, và cài chung chúng gây xung đột version
136
+ thật (`pytket`/`lark`, `pennylane-rigetti`/`pyquil` — xem
137
+ `revert/pennylane-to-0.0.4.dev6` trong `qapp-pennylane`).
138
+
139
+ `_require()` raise `ModuleNotFoundError` với thông điệp nêu tên package cần thêm
140
+ vào `requirements.txt`. Classifier map nó thành `DEPENDENCY_MISSING` nên user
141
+ nhận được hướng dẫn cụ thể thay vì traceback import.
142
+
143
+ **`tests/test_circuit_adapter.py`**: SDK được import trong từng test qua
144
+ `pytest.importorskip`. Trước đây file này import `qiskit` + `qibo` + `qulacs` ở
145
+ module level nên **cả file không collect được** nếu thiếu một SDK. Test
146
+ `test_qulacs_falls_back_to_string_on_parse_error` đổi target patch từ
147
+ `circuit_adapter.QiskitCircuit` (tên module-level đã bỏ) sang `_load_qasm_circuit`.
148
+
149
+ **`tests/test_no_sdk_dependency.py`** (mới) — khoá hồi quy bằng AST: quét từng
150
+ file, fail nếu có SDK nào được import ở module level; và đọc `pyproject.toml`,
151
+ fail nếu `dependencies` khai báo SDK.
152
+
153
+ Đã verify trong **venv sạch không có SDK nào**: 82/82 module import được, suite
154
+ 300 passed / 7 skipped. Với venv chỉ có `qiskit`: 302 passed / 5 skipped.
155
+
156
+ > **Lib con cần bổ sung khai báo.** Hai lib đang dựa vào `quapp-common` kéo hộ và
157
+ > sẽ vỡ đường OpenQASM sau bản này:
158
+ > - `qapp-pyquil` — thiếu `pytket` (cần cho `_openqasm_to_pyquil`);
159
+ > - `quapp-qulacs` — thiếu `qiskit` (cầu nối OpenQASM → Qulacs đi qua Qiskit).
160
+ >
161
+ > Ngoài ra `qapp-dwave-ocean` import `matplotlib` trực tiếp nhưng không khai báo;
162
+ > hiện vẫn chạy vì `quapp-common` còn giữ `matplotlib`, nên nên khai báo tường
163
+ > minh để không phụ thuộc may mắn.
164
+ >
165
+ > `pytket-braket` và `pytket-pyquil` chưa từng được khai báo ở đâu — code dùng
166
+ > `importlib.import_module()` cho chúng từ trước. Cần thêm vào `qapp-braket` và
167
+ > `qapp-pyquil`.
168
+
117
169
  ### v0.0.14.dev2 — Fix: log lỗi bị nhân đôi
118
170
 
119
171
  - **Fix**: mỗi lần lỗi phát **hai** dòng ERROR cùng `event=job_failed`, mỗi dòng
@@ -73,6 +73,61 @@ Notes:
73
73
 
74
74
  ## Recently Changes Highlights
75
75
 
76
+ ### v0.0.14.dev5 — Gỡ toàn bộ dependency SDK lượng tử
77
+
78
+ `quapp-common` không còn phụ thuộc SDK nào. SDK thuộc về lib con.
79
+
80
+ **Gỡ khỏi `dependencies`** — 6 package, chỉ được dùng trong `circuit_adapter.py`:
81
+ `qiskit`, `pennylane`, `qibo`, `qulacs`, `dimod`, `pytket`.
82
+
83
+ Còn lại: `loguru`, `requests`, `numpy`, `matplotlib`, `pylatexenc`, `starlette`
84
+ — đều được chính code của `quapp-common` dùng (`matplotlib`/`pylatexenc` cần cho
85
+ `CircuitExportTask.__convert()`, nơi gọi `circuit.draw(output='mpl')` rồi
86
+ `savefig()`).
87
+
88
+ **`circuit_adapter.py`: import SDK lazy.** Mỗi handler tự import SDK của nó ngay
89
+ trước khi dùng, qua helper `_require()`. Trước đây 7 SDK được import ở module
90
+ level, nên:
91
+
92
+ - `from quapp_common.component.bridge import SubprocessBridge` đòi có **đủ cả 6
93
+ SDK cùng lúc**, kể cả khi function chỉ chạy Qiskit. Điều này vô hiệu hoá thiết
94
+ kế lazy-import của `js-*/index.py` trong `quapp-sdk-templates` — comment trong
95
+ đó ghi rõ ý định *"so the server can start even if optional dependencies are
96
+ missing"*;
97
+ - buộc mọi function image tải cả 6 SDK, và cài chung chúng gây xung đột version
98
+ thật (`pytket`/`lark`, `pennylane-rigetti`/`pyquil` — xem
99
+ `revert/pennylane-to-0.0.4.dev6` trong `qapp-pennylane`).
100
+
101
+ `_require()` raise `ModuleNotFoundError` với thông điệp nêu tên package cần thêm
102
+ vào `requirements.txt`. Classifier map nó thành `DEPENDENCY_MISSING` nên user
103
+ nhận được hướng dẫn cụ thể thay vì traceback import.
104
+
105
+ **`tests/test_circuit_adapter.py`**: SDK được import trong từng test qua
106
+ `pytest.importorskip`. Trước đây file này import `qiskit` + `qibo` + `qulacs` ở
107
+ module level nên **cả file không collect được** nếu thiếu một SDK. Test
108
+ `test_qulacs_falls_back_to_string_on_parse_error` đổi target patch từ
109
+ `circuit_adapter.QiskitCircuit` (tên module-level đã bỏ) sang `_load_qasm_circuit`.
110
+
111
+ **`tests/test_no_sdk_dependency.py`** (mới) — khoá hồi quy bằng AST: quét từng
112
+ file, fail nếu có SDK nào được import ở module level; và đọc `pyproject.toml`,
113
+ fail nếu `dependencies` khai báo SDK.
114
+
115
+ Đã verify trong **venv sạch không có SDK nào**: 82/82 module import được, suite
116
+ 300 passed / 7 skipped. Với venv chỉ có `qiskit`: 302 passed / 5 skipped.
117
+
118
+ > **Lib con cần bổ sung khai báo.** Hai lib đang dựa vào `quapp-common` kéo hộ và
119
+ > sẽ vỡ đường OpenQASM sau bản này:
120
+ > - `qapp-pyquil` — thiếu `pytket` (cần cho `_openqasm_to_pyquil`);
121
+ > - `quapp-qulacs` — thiếu `qiskit` (cầu nối OpenQASM → Qulacs đi qua Qiskit).
122
+ >
123
+ > Ngoài ra `qapp-dwave-ocean` import `matplotlib` trực tiếp nhưng không khai báo;
124
+ > hiện vẫn chạy vì `quapp-common` còn giữ `matplotlib`, nên nên khai báo tường
125
+ > minh để không phụ thuộc may mắn.
126
+ >
127
+ > `pytket-braket` và `pytket-pyquil` chưa từng được khai báo ở đâu — code dùng
128
+ > `importlib.import_module()` cho chúng từ trước. Cần thêm vào `qapp-braket` và
129
+ > `qapp-pyquil`.
130
+
76
131
  ### v0.0.14.dev2 — Fix: log lỗi bị nhân đôi
77
132
 
78
133
  - **Fix**: mỗi lần lỗi phát **hai** dòng ERROR cùng `event=job_failed`, mỗi dòng
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quapp-common"
7
- version = "0.0.14.dev4"
7
+ version = "0.0.14.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" }]
@@ -15,24 +15,29 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  ]
17
17
  keywords = ["quapp", "quapp-common", "quantum"]
18
+ # KHÔNG khai báo SDK lượng tử ở đây. Mỗi lib con (quapp-qiskit, qapp-pennylane,
19
+ # qapp-braket, ...) tự khai báo SDK của nó, và một function image chỉ cài đúng
20
+ # SDK nó dùng. circuit_adapter.py import SDK lazy nên quapp-common không cần
21
+ # chúng để import được.
22
+ #
23
+ # Lý do: cài chung qiskit + pennylane + qibo + qulacs + dimod + pytket gây xung
24
+ # đột version thật (pytket/lark, pennylane-rigetti/pyquil), và bắt mọi function
25
+ # tải cả 6 SDK dù chỉ dùng một.
18
26
  dependencies = [
19
27
  "loguru",
20
28
  "requests",
21
29
  "numpy",
22
30
  "matplotlib",
23
31
  "pylatexenc",
24
- "starlette",
25
- "qibo",
26
- "dimod",
27
- "qulacs",
28
- "qiskit",
29
- "pytket",
30
- "pennylane"
32
+ "starlette"
31
33
  ]
32
34
  requires-python = ">=3.7"
33
35
 
34
36
  [project.optional-dependencies]
35
37
  dev = ["black", "bumpver", "isort", "pip-tools", "pytest"]
38
+ # SDK chỉ để chạy test của chính quapp-common (tests/test_circuit_adapter.py
39
+ # cần qiskit). KHÔNG phải runtime dependency.
40
+ test = ["pytest", "qiskit"]
36
41
 
37
42
  [project.urls]
38
43
  Homepage = "https://citynow.asia/"
@@ -2,38 +2,73 @@
2
2
  # circuit_adapter.py
3
3
  # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
4
 
5
+ """Chuyển circuit từ định dạng trung gian (OpenQASM / QUBO / JSON) sang object
6
+ native của từng SDK.
7
+
8
+ Mọi SDK được import LAZY, ngay bên trong handler cần nó. Lý do:
9
+
10
+ ``quapp-common`` không được phụ thuộc SDK nào. Mỗi lib con
11
+ (``quapp-qiskit``, ``qapp-pennylane``, ...) tự khai báo SDK của nó, và một
12
+ function image chỉ cài đúng SDK nó dùng. Nếu file này import ở module level thì:
13
+
14
+ - ``import quapp_common.component.bridge`` đòi có ĐỦ 6 SDK cùng lúc, kể cả khi
15
+ function chỉ chạy Qiskit -- điều này vô hiệu hoá thiết kế lazy-import của
16
+ ``js-*/index.py`` trong quapp-sdk-templates (comment trong đó ghi rõ ý định
17
+ "so the server can start even if optional dependencies are missing");
18
+ - cài chung 6 SDK gây xung đột version thật, đã xảy ra với ``pytket``/``lark``
19
+ và ``pennylane-rigetti``/``pyquil``.
20
+
21
+ Khi SDK thiếu, handler raise ``ModuleNotFoundError`` -- classifier map thành
22
+ ``DEPENDENCY_MISSING`` và user nhận được hướng dẫn thêm package vào
23
+ requirements.txt.
24
+ """
25
+
5
26
  import importlib
6
27
  import re
7
28
  from typing import Any, Dict
8
29
 
9
- import numpy as np
10
- import pennylane as qml
11
- import pennylane.measurements as _plm
12
- try:
13
- import qiskit.qasm2 as _qasm2
14
- except ImportError:
15
- _qasm2 = None
16
- from dimod import BinaryQuadraticModel
17
- from pytket.qasm import circuit_from_qasm_str
18
- from qibo.models import Circuit
19
- from qiskit import QuantumCircuit as QiskitCircuit
20
- from qulacs import QuantumCircuit as QulacsCircuit
21
- from qulacs.gate import DenseMatrix as DenseMatrixGate
22
-
23
30
  from ..config.logging_config import logger
24
31
  from ..enum.sdk import Sdk
25
32
 
26
33
  logger = logger.bind(context=__name__)
27
34
 
28
35
 
29
- def _load_qasm_circuit(qasm_str: str) -> QiskitCircuit:
30
- """Parse OpenQASM 2.0 string into a Qiskit QuantumCircuit.
36
+ def _require(module_name: str, package_hint: str = None):
37
+ """Import module của SDK, báo lỗi rõ ràng nếu thiếu.
38
+
39
+ @param module_name: tên module cần import, ví dụ 'qiskit.qasm2'.
40
+ @param package_hint: tên package trên PyPI để nhắc user, nếu khác module.
41
+ @raise ModuleNotFoundError: giữ đúng kiểu này để classifier phân loại thành
42
+ DEPENDENCY_MISSING.
43
+ """
44
+ try:
45
+ return importlib.import_module(module_name)
46
+ except ImportError as exception:
47
+ package = package_hint or module_name.split('.')[0]
48
+ raise ModuleNotFoundError(
49
+ f"SDK '{package}' chưa được cài trong function image nhưng "
50
+ f"circuit cần nó. Thêm '{package}' vào requirements.txt của "
51
+ f"function rồi deploy lại.") from exception
52
+
53
+
54
+ def _qiskit_quantum_circuit():
55
+ return _require('qiskit').QuantumCircuit
56
+
31
57
 
32
- Compatible with Qiskit >= 1.0 (qiskit.qasm2.loads) and < 1.0 (from_qasm_str).
58
+ def _load_qasm_circuit(qasm_str: str):
59
+ """Parse OpenQASM 2.0 string thành Qiskit QuantumCircuit.
60
+
61
+ Tương thích Qiskit >= 1.0 (``qiskit.qasm2.loads``) và < 1.0
62
+ (``QuantumCircuit.from_qasm_str``).
33
63
  """
34
- if _qasm2 is not None:
35
- return _qasm2.loads(qasm_str)
36
- return QiskitCircuit.from_qasm_str(qasm_str)
64
+ try:
65
+ qasm2 = importlib.import_module('qiskit.qasm2')
66
+ except ImportError:
67
+ qasm2 = None
68
+
69
+ if qasm2 is not None:
70
+ return qasm2.loads(qasm_str)
71
+ return _qiskit_quantum_circuit().from_qasm_str(qasm_str)
37
72
 
38
73
 
39
74
  def adapt(sdk: Sdk, result: dict):
@@ -91,7 +126,8 @@ def _adapt_qubo(sdk, qubo_raw: Dict[str, Any]):
91
126
  qubo_dict[(key, key)] = val
92
127
 
93
128
  logger.debug(f"QUBO parsed: {len(qubo_dict)} entries -> BinaryQuadraticModel")
94
- return BinaryQuadraticModel.from_qubo(qubo_dict)
129
+ binary_quadratic_model = _require('dimod').BinaryQuadraticModel
130
+ return binary_quadratic_model.from_qubo(qubo_dict)
95
131
 
96
132
 
97
133
  def _count_qasm_qubits(qasm_str):
@@ -101,7 +137,9 @@ def _count_qasm_qubits(qasm_str):
101
137
 
102
138
  def _openqasm_to_braket(qasm_str):
103
139
  logger.debug("Converting OpenQASM via pytket -> tk_to_braket")
104
- tk_to_braket = importlib.import_module("pytket.extensions.braket").tk_to_braket
140
+ circuit_from_qasm_str = _require('pytket.qasm', 'pytket').circuit_from_qasm_str
141
+ tk_to_braket = _require('pytket.extensions.braket',
142
+ 'pytket-braket').tk_to_braket
105
143
  result = tk_to_braket(circuit_from_qasm_str(qasm_str))
106
144
  # tk_to_braket returns (Circuit, n_shots) in pytket-braket >= 0.35
107
145
  is_tuple = isinstance(result, tuple)
@@ -122,10 +160,13 @@ def _openqasm_to_cuda_quantum(qasm_str):
122
160
 
123
161
  def _openqasm_to_pennylane(qasm_str):
124
162
  logger.debug("Converting OpenQASM via PennyLane qml.from_qasm wrapper")
163
+ qml = _require('pennylane')
164
+ measurements = _require('pennylane.measurements', 'pennylane')
165
+
125
166
  for _compat_name in ("Expectation", "Variance", "Sample", "Probability",
126
167
  "State", "MidMeasure"):
127
- if not hasattr(_plm, _compat_name):
128
- setattr(_plm, _compat_name, _compat_name)
168
+ if not hasattr(measurements, _compat_name):
169
+ setattr(measurements, _compat_name, _compat_name)
129
170
 
130
171
  num_wires = _count_qasm_qubits(qasm_str)
131
172
  logger.debug(f"PennyLane: detected {num_wires} qubits; wrapping with qml.probs over all wires")
@@ -150,13 +191,15 @@ def _openqasm_to_pennylane(qasm_str):
150
191
 
151
192
  def _openqasm_to_pyquil(qasm_str):
152
193
  logger.debug("Converting OpenQASM via pytket -> tk_to_pyquil")
153
- tk_to_pyquil = importlib.import_module("pytket.extensions.pyquil").tk_to_pyquil
194
+ circuit_from_qasm_str = _require('pytket.qasm', 'pytket').circuit_from_qasm_str
195
+ tk_to_pyquil = _require('pytket.extensions.pyquil',
196
+ 'pytket-pyquil').tk_to_pyquil
154
197
  return tk_to_pyquil(circuit_from_qasm_str(qasm_str))
155
198
 
156
199
 
157
200
  def _openqasm_to_qibo(qasm_str):
158
201
  logger.debug("Converting OpenQASM via Qibo Circuit.from_qasm")
159
- return Circuit.from_qasm(qasm_str)
202
+ return _require('qibo.models', 'qibo').Circuit.from_qasm(qasm_str)
160
203
 
161
204
 
162
205
  def _openqasm_to_qiskit(qasm_str):
@@ -184,6 +227,9 @@ def _qiskit_to_pennylane(qasm_str, num_wires):
184
227
  Fallback for environments where qml.from_qasm is unavailable because
185
228
  pennylane-qiskit is not installed (PennyLane < 0.36).
186
229
  """
230
+ qml = _require('pennylane')
231
+ numpy = _require('numpy')
232
+
187
233
  qiskit_circ = _load_qasm_circuit(qasm_str)
188
234
  qubit_map = {bit: idx for idx, bit in enumerate(qiskit_circ.qubits)}
189
235
  logger.debug(
@@ -205,9 +251,9 @@ def _qiskit_to_pennylane(qasm_str, num_wires):
205
251
  'y': lambda q, p: qml.PauliY(wires=q[0]),
206
252
  'z': lambda q, p: qml.PauliZ(wires=q[0]),
207
253
  's': lambda q, p: qml.S(wires=q[0]),
208
- 'sdg': lambda q, p: qml.PhaseShift(-np.pi / 2, wires=q[0]),
254
+ 'sdg': lambda q, p: qml.PhaseShift(-numpy.pi / 2, wires=q[0]),
209
255
  't': lambda q, p: qml.T(wires=q[0]),
210
- 'tdg': lambda q, p: qml.PhaseShift(-np.pi / 4, wires=q[0]),
256
+ 'tdg': lambda q, p: qml.PhaseShift(-numpy.pi / 4, wires=q[0]),
211
257
  'cx': lambda q, p: qml.CNOT(wires=q),
212
258
  'cz': lambda q, p: qml.CZ(wires=q),
213
259
  'swap': lambda q, p: qml.SWAP(wires=q),
@@ -244,9 +290,12 @@ def _qiskit_to_qulacs(qiskit_circ):
244
290
  unrecognized gates fall back to DenseMatrix (unitary passthrough).
245
291
  Measurement, barrier, reset, and identity instructions are skipped.
246
292
  """
293
+ qulacs_quantum_circuit = _require('qulacs').QuantumCircuit
294
+ dense_matrix_gate = _require('qulacs.gate', 'qulacs').DenseMatrix
295
+
247
296
  n = qiskit_circ.num_qubits
248
297
  logger.debug(f"_qiskit_to_qulacs: num_qubits={n} num_instructions={len(qiskit_circ.data)}")
249
- qulacs_circ = QulacsCircuit(n)
298
+ qulacs_circ = qulacs_quantum_circuit(n)
250
299
  qubit_map = {bit: idx for idx, bit in enumerate(qiskit_circ.qubits)}
251
300
 
252
301
  _SINGLE = {
@@ -280,7 +329,7 @@ def _qiskit_to_qulacs(qiskit_circ):
280
329
  # ambiguity; multi-qubit gates are passed with qulacs'
281
330
  # index order matching Qiskit's instruction qubit order.
282
331
  logger.debug(f"Gate '{name}' not in named map; converting via DenseMatrix on qubits={qubits}")
283
- qulacs_circ.add_gate(DenseMatrixGate(qubits, gate.to_matrix()))
332
+ qulacs_circ.add_gate(dense_matrix_gate(qubits, gate.to_matrix()))
284
333
 
285
334
  logger.debug(f"_qiskit_to_qulacs: conversion complete")
286
335
  return qulacs_circ
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.14.dev4
3
+ Version: 0.0.14.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)
@@ -25,18 +25,15 @@ 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
30
- Requires-Dist: qulacs
31
- Requires-Dist: qiskit
32
- Requires-Dist: pytket
33
- Requires-Dist: pennylane
34
28
  Provides-Extra: dev
35
29
  Requires-Dist: black; extra == "dev"
36
30
  Requires-Dist: bumpver; extra == "dev"
37
31
  Requires-Dist: isort; extra == "dev"
38
32
  Requires-Dist: pip-tools; extra == "dev"
39
33
  Requires-Dist: pytest; extra == "dev"
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest; extra == "test"
36
+ Requires-Dist: qiskit; extra == "test"
40
37
  Dynamic: license-file
41
38
 
42
39
  # quapp-common
@@ -114,6 +111,61 @@ Notes:
114
111
 
115
112
  ## Recently Changes Highlights
116
113
 
114
+ ### v0.0.14.dev5 — Gỡ toàn bộ dependency SDK lượng tử
115
+
116
+ `quapp-common` không còn phụ thuộc SDK nào. SDK thuộc về lib con.
117
+
118
+ **Gỡ khỏi `dependencies`** — 6 package, chỉ được dùng trong `circuit_adapter.py`:
119
+ `qiskit`, `pennylane`, `qibo`, `qulacs`, `dimod`, `pytket`.
120
+
121
+ Còn lại: `loguru`, `requests`, `numpy`, `matplotlib`, `pylatexenc`, `starlette`
122
+ — đều được chính code của `quapp-common` dùng (`matplotlib`/`pylatexenc` cần cho
123
+ `CircuitExportTask.__convert()`, nơi gọi `circuit.draw(output='mpl')` rồi
124
+ `savefig()`).
125
+
126
+ **`circuit_adapter.py`: import SDK lazy.** Mỗi handler tự import SDK của nó ngay
127
+ trước khi dùng, qua helper `_require()`. Trước đây 7 SDK được import ở module
128
+ level, nên:
129
+
130
+ - `from quapp_common.component.bridge import SubprocessBridge` đòi có **đủ cả 6
131
+ SDK cùng lúc**, kể cả khi function chỉ chạy Qiskit. Điều này vô hiệu hoá thiết
132
+ kế lazy-import của `js-*/index.py` trong `quapp-sdk-templates` — comment trong
133
+ đó ghi rõ ý định *"so the server can start even if optional dependencies are
134
+ missing"*;
135
+ - buộc mọi function image tải cả 6 SDK, và cài chung chúng gây xung đột version
136
+ thật (`pytket`/`lark`, `pennylane-rigetti`/`pyquil` — xem
137
+ `revert/pennylane-to-0.0.4.dev6` trong `qapp-pennylane`).
138
+
139
+ `_require()` raise `ModuleNotFoundError` với thông điệp nêu tên package cần thêm
140
+ vào `requirements.txt`. Classifier map nó thành `DEPENDENCY_MISSING` nên user
141
+ nhận được hướng dẫn cụ thể thay vì traceback import.
142
+
143
+ **`tests/test_circuit_adapter.py`**: SDK được import trong từng test qua
144
+ `pytest.importorskip`. Trước đây file này import `qiskit` + `qibo` + `qulacs` ở
145
+ module level nên **cả file không collect được** nếu thiếu một SDK. Test
146
+ `test_qulacs_falls_back_to_string_on_parse_error` đổi target patch từ
147
+ `circuit_adapter.QiskitCircuit` (tên module-level đã bỏ) sang `_load_qasm_circuit`.
148
+
149
+ **`tests/test_no_sdk_dependency.py`** (mới) — khoá hồi quy bằng AST: quét từng
150
+ file, fail nếu có SDK nào được import ở module level; và đọc `pyproject.toml`,
151
+ fail nếu `dependencies` khai báo SDK.
152
+
153
+ Đã verify trong **venv sạch không có SDK nào**: 82/82 module import được, suite
154
+ 300 passed / 7 skipped. Với venv chỉ có `qiskit`: 302 passed / 5 skipped.
155
+
156
+ > **Lib con cần bổ sung khai báo.** Hai lib đang dựa vào `quapp-common` kéo hộ và
157
+ > sẽ vỡ đường OpenQASM sau bản này:
158
+ > - `qapp-pyquil` — thiếu `pytket` (cần cho `_openqasm_to_pyquil`);
159
+ > - `quapp-qulacs` — thiếu `qiskit` (cầu nối OpenQASM → Qulacs đi qua Qiskit).
160
+ >
161
+ > Ngoài ra `qapp-dwave-ocean` import `matplotlib` trực tiếp nhưng không khai báo;
162
+ > hiện vẫn chạy vì `quapp-common` còn giữ `matplotlib`, nên nên khai báo tường
163
+ > minh để không phụ thuộc may mắn.
164
+ >
165
+ > `pytket-braket` và `pytket-pyquil` chưa từng được khai báo ở đâu — code dùng
166
+ > `importlib.import_module()` cho chúng từ trước. Cần thêm vào `qapp-braket` và
167
+ > `qapp-pyquil`.
168
+
117
169
  ### v0.0.14.dev2 — Fix: log lỗi bị nhân đôi
118
170
 
119
171
  - **Fix**: mỗi lần lỗi phát **hai** dòng ERROR cùng `event=job_failed`, mỗi dòng
@@ -100,4 +100,5 @@ tests/test_json_parser_utils.py
100
100
  tests/test_language_dispatcher.py
101
101
  tests/test_log_secret_leak.py
102
102
  tests/test_logging_config.py
103
+ tests/test_no_sdk_dependency.py
103
104
  tests/test_result_serializer.py
@@ -4,12 +4,6 @@ numpy
4
4
  matplotlib
5
5
  pylatexenc
6
6
  starlette
7
- qibo
8
- dimod
9
- qulacs
10
- qiskit
11
- pytket
12
- pennylane
13
7
 
14
8
  [dev]
15
9
  black
@@ -17,3 +11,7 @@ bumpver
17
11
  isort
18
12
  pip-tools
19
13
  pytest
14
+
15
+ [test]
16
+ pytest
17
+ qiskit