quapp-common 0.0.11.dev6__tar.gz → 0.0.11.dev8__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 (83) hide show
  1. {quapp_common-0.0.11.dev6/quapp_common.egg-info → quapp_common-0.0.11.dev8}/PKG-INFO +35 -19
  2. quapp_common-0.0.11.dev8/README.md +72 -0
  3. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/pyproject.toml +3 -2
  4. quapp_common-0.0.11.dev8/quapp_common/async_tasks/async_invocation_task.py +80 -0
  5. quapp_common-0.0.11.dev8/quapp_common/component/backend/job_manager.py +302 -0
  6. quapp_common-0.0.11.dev8/quapp_common/component/callback/update_job_metadata.py +77 -0
  7. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/request/request.py +4 -2
  8. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/provider_tag.py +1 -0
  9. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/sdk.py +1 -0
  10. quapp_common-0.0.11.dev8/quapp_common/model/invocation.py +54 -0
  11. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8/quapp_common.egg-info}/PKG-INFO +35 -19
  12. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common.egg-info/SOURCES.txt +3 -0
  13. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common.egg-info/requires.txt +1 -0
  14. quapp_common-0.0.11.dev6/README.md +0 -57
  15. quapp_common-0.0.11.dev6/quapp_common/component/callback/update_job_metadata.py +0 -41
  16. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/LICENSE +0 -0
  17. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/__init__.py +0 -0
  18. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/async_tasks/__init__.py +0 -0
  19. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/async_tasks/async_task.py +0 -0
  20. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/async_tasks/export_circuit_task.py +0 -0
  21. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/async_tasks/post_processing_task.py +0 -0
  22. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/__init__.py +0 -0
  23. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/backend/__init__.py +0 -0
  24. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/backend/invocation.py +0 -0
  25. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/backend/job_fetcher.py +0 -0
  26. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/backend/job_fetching.py +0 -0
  27. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/callback/__init__.py +0 -0
  28. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/device/__init__.py +0 -0
  29. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/component/device/device_selection.py +0 -0
  30. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/config/__init__.py +0 -0
  31. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/config/logging_config.py +0 -0
  32. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/config/thread_config.py +0 -0
  33. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/__init__.py +0 -0
  34. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/async_task/__init__.py +0 -0
  35. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/async_task/circuit_export/__init__.py +0 -0
  36. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/async_task/circuit_export/backend_holder.py +0 -0
  37. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/async_task/circuit_export/circuit_holder.py +0 -0
  38. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/backend/__init__.py +0 -0
  39. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/backend/backend_information.py +0 -0
  40. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/callback/__init__.py +0 -0
  41. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/callback/callback_url.py +0 -0
  42. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/device/__init__.py +0 -0
  43. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/device/circuit_running_option.py +0 -0
  44. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/promise/__init__.py +0 -0
  45. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/promise/post_processing_promise.py +0 -0
  46. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/promise/promise.py +0 -0
  47. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/request/__init__.py +0 -0
  48. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/request/invocation_request.py +0 -0
  49. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/request/job_fetching_request.py +0 -0
  50. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/response/__init__.py +0 -0
  51. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/response/authentication.py +0 -0
  52. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/response/custom_header.py +0 -0
  53. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/data/response/job_response.py +0 -0
  54. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/__init__.py +0 -0
  55. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/base_enum.py +0 -0
  56. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/http_header.py +0 -0
  57. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/invocation_step.py +0 -0
  58. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/media_type.py +0 -0
  59. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/processing_unit.py +0 -0
  60. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/status/__init__.py +0 -0
  61. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/status/job_status.py +0 -0
  62. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/status/status_code.py +0 -0
  63. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/enum/token_type.py +0 -0
  64. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/factory/__init__.py +0 -0
  65. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/factory/device_factory.py +0 -0
  66. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/factory/handler_factory.py +0 -0
  67. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/factory/provider_factory.py +0 -0
  68. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/handler/__init__.py +0 -0
  69. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/handler/handler.py +0 -0
  70. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/model/__init__.py +0 -0
  71. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/model/device/__init__.py +0 -0
  72. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/model/device/custom_device.py +0 -0
  73. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/model/device/device.py +0 -0
  74. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/model/provider/__init__.py +0 -0
  75. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/model/provider/provider.py +0 -0
  76. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/util/__init__.py +0 -0
  77. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/util/file_utils.py +0 -0
  78. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/util/http_utils.py +0 -0
  79. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/util/json_parser_utils.py +0 -0
  80. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common/util/response_utils.py +0 -0
  81. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common.egg-info/dependency_links.txt +0 -0
  82. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/quapp_common.egg-info/top_level.txt +0 -0
  83. {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.11.dev6
3
+ Version: 0.0.11.dev8
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)
@@ -24,6 +24,7 @@ Requires-Dist: requests
24
24
  Requires-Dist: numpy
25
25
  Requires-Dist: matplotlib
26
26
  Requires-Dist: pylatexenc
27
+ Requires-Dist: starlette
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: black; extra == "dev"
29
30
  Requires-Dist: bumpver; extra == "dev"
@@ -42,12 +43,11 @@ Quapp common library supporting Quapp Platform for Quantum Computing.
42
43
  Quantum Computing by providing common utilities, configurations, and
43
44
  abstractions for working with
44
45
  quantum providers and devices.
45
-
46
- Recent improvements focus on cleaner and more consistent logging, enhanced error
47
- handling, and
48
- standardizing header management by adding workspace-specific headers instead of
49
- tenant-specific ones,
50
- improving maintainability and clarity.
46
+ Recent improvements add first-class asynchronous job processing with a
47
+ cross-process JobManager,
48
+ background task execution, and standardized result models for immediate client
49
+ responses while work
50
+ continues in the background.
51
51
 
52
52
  ## Features
53
53
 
@@ -61,6 +61,17 @@ improving maintainability and clarity.
61
61
  - Enhanced error handling and job metadata update mechanisms.
62
62
  - Simplified and cleaner HTTP request/response logging and URL parsing
63
63
  utilities.
64
+ - Asynchronous job processing:
65
+ - AsyncInvocationTask to run handlers in a background thread pool and return
66
+ immediate responses.
67
+ - JobManager for cross-process job registry with atomic add/update/get and
68
+ pub-sub updates.
69
+ - Standard Event/Result (Success, Error) models for consistent async
70
+ responses.
71
+ - Scheduler integration via callback URL; local updates are patched to the
72
+ scheduler.
73
+ - Safety: updates to jobs already DONE/FAILED are ignored to prevent
74
+ post-completion mutations.
64
75
 
65
76
  ## Installation
66
77
 
@@ -70,22 +81,27 @@ Install via pip:
70
81
  pip install quapp-common
71
82
  ```
72
83
 
84
+ Notes:
85
+
86
+ - From version 0.0.11.dev7, `starlette` is a direct dependency to support
87
+ background execution helpers.
88
+
73
89
  ## Recently Changes Highlights
74
90
 
75
- - Added workspace-specific classes for request, response, and promise,
76
- consolidating code to unify context handling.
77
- - Refactored logging throughout the codebase, removing redundant debug logs and
78
- adding context-rich log messages.
79
- - Improved URL parsing and job ID extraction, especially in HTTP utilities, to
80
- better track jobs.
81
- - Cleaned up logging configuration files to standardize log formats and levels.
82
- - Added robust error handling in job execution and analysis pipelines, ensuring
83
- detailed failure reporting.
84
- - Simplified header and request body logging, improving performance and
85
- readability.
91
+ - Version bump to 0.0.11.dev7 and dependency update (add `starlette`).
92
+ - Introduce asynchronous job processing primitives:
93
+ - AsyncInvocationTask for background execution with immediate client
94
+ response.
95
+ - Event and standardized Result models (Success, Error).
96
+ - Introduce JobManager for cross-process job management and update publication.
97
+ - Integrate JobManager into Request to register jobs and carry scheduler
98
+ callback URL.
99
+ - Modularize update_job_metadata with clearer helpers; patch scheduler on local
100
+ state changes.
101
+ - Fix: ignore updates for jobs that are already DONE or FAILED.
102
+ - Improve logging and error handling (use full tracebacks, cleaner logs).
86
103
 
87
104
  ---
88
105
 
89
106
  For detailed usage and API references, please refer to the in-code documentation
90
107
  or contact the maintainers.
91
-
@@ -0,0 +1,72 @@
1
+ # quapp-common
2
+
3
+ Quapp common library supporting Quapp Platform for Quantum Computing.
4
+
5
+ ## Overview
6
+
7
+ `quapp-common` is a Python library designed to support the Quapp Platform for
8
+ Quantum Computing by providing common utilities, configurations, and
9
+ abstractions for working with
10
+ quantum providers and devices.
11
+ Recent improvements add first-class asynchronous job processing with a
12
+ cross-process JobManager,
13
+ background task execution, and standardized result models for immediate client
14
+ responses while work
15
+ continues in the background.
16
+
17
+ ## Features
18
+
19
+ - Provider and device factory for quantum computing platforms.
20
+ - Logging and configuration utilities with improved and detailed log messages.
21
+ - Support for AWS Braket, OQC Cloud, Qiskit, PennyLane, DWave Ocean, and Quapp
22
+ quantum simulators.
23
+ - Refactored classes and utilities to remove tenant-specific request, response,
24
+ and promise classes.
25
+ - Standardized naming by renaming `ProjectHeader` to `CustomHeader`.
26
+ - Enhanced error handling and job metadata update mechanisms.
27
+ - Simplified and cleaner HTTP request/response logging and URL parsing
28
+ utilities.
29
+ - Asynchronous job processing:
30
+ - AsyncInvocationTask to run handlers in a background thread pool and return
31
+ immediate responses.
32
+ - JobManager for cross-process job registry with atomic add/update/get and
33
+ pub-sub updates.
34
+ - Standard Event/Result (Success, Error) models for consistent async
35
+ responses.
36
+ - Scheduler integration via callback URL; local updates are patched to the
37
+ scheduler.
38
+ - Safety: updates to jobs already DONE/FAILED are ignored to prevent
39
+ post-completion mutations.
40
+
41
+ ## Installation
42
+
43
+ Install via pip:
44
+
45
+ ```bash
46
+ pip install quapp-common
47
+ ```
48
+
49
+ Notes:
50
+
51
+ - From version 0.0.11.dev7, `starlette` is a direct dependency to support
52
+ background execution helpers.
53
+
54
+ ## Recently Changes Highlights
55
+
56
+ - Version bump to 0.0.11.dev7 and dependency update (add `starlette`).
57
+ - Introduce asynchronous job processing primitives:
58
+ - AsyncInvocationTask for background execution with immediate client
59
+ response.
60
+ - Event and standardized Result models (Success, Error).
61
+ - Introduce JobManager for cross-process job management and update publication.
62
+ - Integrate JobManager into Request to register jobs and carry scheduler
63
+ callback URL.
64
+ - Modularize update_job_metadata with clearer helpers; patch scheduler on local
65
+ state changes.
66
+ - Fix: ignore updates for jobs that are already DONE or FAILED.
67
+ - Improve logging and error handling (use full tracebacks, cleaner logs).
68
+
69
+ ---
70
+
71
+ For detailed usage and API references, please refer to the in-code documentation
72
+ or contact the maintainers.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quapp-common"
7
- version = "0.0.11.dev6"
7
+ version = "0.0.11.dev8"
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" }]
@@ -20,7 +20,8 @@ dependencies = [
20
20
  "requests",
21
21
  "numpy",
22
22
  "matplotlib",
23
- "pylatexenc"
23
+ "pylatexenc",
24
+ "starlette"
24
25
  ]
25
26
  requires-python = ">=3.7"
26
27
 
@@ -0,0 +1,80 @@
1
+ # Quapp Platform Project
2
+ # async_invocation_task.py
3
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+ import asyncio
5
+ import sys
6
+
7
+ from starlette.concurrency import run_in_threadpool
8
+ from starlette.responses import JSONResponse
9
+
10
+ from ..async_tasks.async_task import AsyncTask
11
+ from ..component.backend.job_manager import JobManager
12
+ from ..config.logging_config import logger
13
+ from ..factory.handler_factory import HandlerFactory
14
+ from ..model.invocation import Event, Error, Success
15
+
16
+
17
+ class AsyncInvocationTask(AsyncTask):
18
+ """
19
+ Handles asynchronous invocation tasks, designed to process events with
20
+ specific handlers and generate immediate responses.
21
+
22
+ The class is aimed at enabling tasks to be processed asynchronously, involving
23
+ a handler factory and functions for event processing and post-processing. It
24
+ executes operations in a background thread pool to ensure quick response to
25
+ clients, allowing them to poll for job status updates. Its primary purpose is
26
+ to facilitate efficient asynchronous processing of jobs without blocking the
27
+ main application flow.
28
+
29
+ Attributes:
30
+ handler_factory: Factory instance that produces job handlers.
31
+ event: Event to be processed by the created handler.
32
+ processing_fn: Function to be executed during the job processing stage.
33
+ post_processing_fn: Function to be executed post-job processing.
34
+ """
35
+
36
+ def __init__(self, handler_factory: HandlerFactory, event: Event,
37
+ processing_fn, post_processing_fn):
38
+ super().__init__()
39
+ self.handler_factory = handler_factory
40
+ self.event = event
41
+ self.processing_fn = processing_fn
42
+ self.post_processing_fn = post_processing_fn
43
+
44
+ def do(self) -> JSONResponse:
45
+ """
46
+ Handles executing a job asynchronously and provides an immediate response with the job's ID and
47
+ status. Logging is configured dynamically based on the provided job ID.
48
+
49
+ Returns:
50
+ JSONResponse: A response object with the status and job ID if the job submission succeeds,
51
+ or an error message if there is an issue.
52
+
53
+ Raises:
54
+ Exception: If any error occurs during job processing, it is logged, and an appropriate error
55
+ response is returned.
56
+ """
57
+ try:
58
+ job_id = self.event.json().get('jobId')
59
+ if job_id is None:
60
+ raise Exception("Job ID is missing from event")
61
+ logger.add(sink=sys.stderr,
62
+ format="[ConsoleJobLog][" + job_id + "] " + "{level} : {time} : {message}: {process}",
63
+ level='DEBUG')
64
+
65
+ # Define the blocking work
66
+ def _run_handle():
67
+ return self.handler_factory.create_handler(self.event,
68
+ self.processing_fn,
69
+ self.post_processing_fn).handle()
70
+
71
+ # Run in the background threadpool so this request can return immediately
72
+ asyncio.create_task(run_in_threadpool(_run_handle))
73
+
74
+ # Respond immediately so clients can poll GET /jobs/{job_id}
75
+ return JSONResponse(status_code=200, content=Success(
76
+ JobManager.get_job(job_id)).serialize())
77
+ except Exception as exception:
78
+ logger.exception(f"An error occurred: {exception}")
79
+ return JSONResponse(status_code=400,
80
+ content=Error(exception).serialize())
@@ -0,0 +1,302 @@
1
+ # Quapp Platform Project
2
+ # job_manager.py
3
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+
5
+ from __future__ import annotations
6
+
7
+ import atexit
8
+ import threading
9
+ import time
10
+ from datetime import timezone, datetime
11
+ from multiprocessing import Manager, current_process
12
+ from typing import Any, Callable, Dict, Iterable, Optional, Tuple
13
+
14
+ from ...config.logging_config import logger
15
+
16
+ SCHEDULER_CALLBACK_URL = 'scheduler_callback_url'
17
+
18
+ JOB_ID = 'job_id'
19
+ STATUS = 'status'
20
+ UPDATED_AT = 'updated_at'
21
+ META = 'meta'
22
+
23
+
24
+ class JobManager:
25
+ """
26
+ Cross-process job registry backed by multiprocessing.Manager.
27
+
28
+ - Stores job_id -> {"status": <str>, META: <dict>, "updated_at": <float>}
29
+ - Provides atomic add/update/get operations across processes.
30
+ - Publishes every change to a cross-process Queue so other processes/threads can react.
31
+ - Offers:
32
+ * subscribe(callback): run a background listener thread that invokes `callback(job_id, data)`
33
+ * watch_updates(timeout=None): generator to pull updates from the queue manually
34
+ """
35
+
36
+ # Process-wide singletons (one per process)
37
+ _manager: Optional[Manager] = None
38
+ _jobs: Any = None # proxy dict
39
+ _updates_queue: Any = None # proxy queue
40
+ _init_lock = threading.Lock()
41
+ _listener_threads: Dict[str, Tuple[threading.Thread, threading.Event]] = {}
42
+
43
+ # --------------- Lifecycle ---------------
44
+
45
+ @classmethod
46
+ def _ensure_initialized(cls) -> None:
47
+ """
48
+ Lazily initialize the Manager, shared dict, and update queue.
49
+ Safe to call multiple times across threads in the same process.
50
+ """
51
+ if cls._manager is not None:
52
+ return
53
+ with cls._init_lock:
54
+ if cls._manager is not None:
55
+ return
56
+ mgr = Manager()
57
+ cls._manager = mgr
58
+ cls._jobs = mgr.dict() # type: ignore[assignment]
59
+ cls._updates_queue = mgr.Queue() # type: ignore[assignment]
60
+
61
+ # Ensure a clean shutdown in this process
62
+ atexit.register(cls._shutdown)
63
+
64
+ @classmethod
65
+ def _shutdown(cls) -> None:
66
+ """
67
+ Stop any listener threads in this process and shutdown Manager.
68
+ """
69
+ # stop listeners
70
+ for key, (t, stop_ev) in list(cls._listener_threads.items()):
71
+ stop_ev.set()
72
+ # Don't join forever; avoid atexit hang
73
+ t.join(timeout=1.0)
74
+ cls._listener_threads.pop(key, None)
75
+
76
+ # close manager (safe to call multiple times)
77
+ if cls._manager is not None:
78
+ try:
79
+ cls._manager.shutdown()
80
+ except Exception as exception:
81
+ # Manager may already be down if the parent exited
82
+ logger.exception(f'Error shutting down manager: {exception}')
83
+ pass
84
+ cls._manager = None
85
+ cls._jobs = None
86
+ cls._updates_queue = None
87
+
88
+ # --------------- Core operations ---------------
89
+
90
+ @classmethod
91
+ def add_job(cls, job_id: str, status: str = 'PENDING',
92
+ meta: Optional[Dict[str, Any]] = None) -> None:
93
+ """
94
+ Add a new job. If a job exists, it will be overwritten.
95
+ Publishes an 'added' event.
96
+ """
97
+ cls._ensure_initialized()
98
+ data = {STATUS : status, META: meta or {},
99
+ UPDATED_AT: cls._get_current_utc_time()}
100
+ cls._jobs[job_id] = data # proxy is process-safe
101
+ cls._publish(job_id, {"event": "added", **data})
102
+
103
+ @classmethod
104
+ def update_status(cls, job_id: str, status: str,
105
+ meta_update: Optional[Dict[str, Any]] = None) -> None:
106
+ """
107
+ Update status (and optionally merge meta) for an existing job.
108
+ Publishes an 'updated' event.
109
+ """
110
+ cls._ensure_initialized()
111
+ if job_id not in cls._jobs:
112
+ # Create on the first update to be resilient
113
+ return cls.add_job(job_id, status=status, meta=meta_update or {})
114
+
115
+ current = dict(cls._jobs[job_id]) # materialize proxy value
116
+ if meta_update:
117
+ merged_meta = dict(current.get(META) or {})
118
+ merged_meta.update(meta_update)
119
+ else:
120
+ merged_meta = current.get(META) or {}
121
+
122
+ if current.get(STATUS) == 'DONE' or current.get(STATUS) == 'FAILED':
123
+ logger.warning(f'Job {job_id} already completed, ignoring update')
124
+ return None
125
+ new_data = {STATUS : status, META: merged_meta or {},
126
+ UPDATED_AT: cls._get_current_utc_time()}
127
+ cls._jobs[job_id] = new_data
128
+ cls._publish(job_id, {"event": "updated", **new_data})
129
+ return None
130
+
131
+ @classmethod
132
+ def remove_job(cls, job_id: str) -> None:
133
+ """
134
+ Remove a job if it exists. Publishes a 'removed' event.
135
+ """
136
+ cls._ensure_initialized()
137
+ existed = job_id in cls._jobs
138
+ if existed:
139
+ # Capture previous data for potential consumers
140
+ prev = dict(cls._jobs[job_id])
141
+ del cls._jobs[job_id]
142
+ cls._publish(job_id, {"event": "removed", **prev})
143
+
144
+ @classmethod
145
+ def get_status(cls, job_id: str) -> Optional[str]:
146
+ """
147
+ Get the current status string for a job, or None if not present.
148
+ """
149
+ cls._ensure_initialized()
150
+ data = cls._jobs.get(job_id)
151
+ return None if data is None else data.get(STATUS)
152
+
153
+ @classmethod
154
+ def get_job(cls, job_id: str) -> Optional[Dict[str, Any]]:
155
+ """
156
+ Get full job data: {"status", META, "updated_at"} or None.
157
+ """
158
+ cls._ensure_initialized()
159
+ data = cls._jobs.get(job_id)
160
+ return None if data is None else dict(data)
161
+
162
+ @classmethod
163
+ def list_jobs(cls) -> Dict[str, Dict[str, Any]]:
164
+ """
165
+ Snapshot of all jobs.
166
+ """
167
+ cls._ensure_initialized()
168
+ # Materialize to plain dict (avoid holding proxies)
169
+ return {k: dict(v) for k, v in dict(cls._jobs).items()}
170
+
171
+ @classmethod
172
+ def get_scheduler_callback_url(cls, job_id):
173
+ """
174
+ Get the scheduler callback URL of a job.
175
+ """
176
+ cls._ensure_initialized()
177
+ data = cls._jobs.get(job_id)
178
+ return None if data is None else data.get(META).get(
179
+ SCHEDULER_CALLBACK_URL)
180
+
181
+ # --------------- Change publication ---------------
182
+
183
+ @classmethod
184
+ def _publish(cls, job_id: str, payload: Dict[str, Any]) -> None:
185
+ """
186
+ Push an update notification to the shared queue.
187
+ """
188
+ cls._ensure_initialized()
189
+ try:
190
+ cls._updates_queue.put((job_id, payload))
191
+ logger.debug(f'Published job update: {payload}')
192
+ except Exception as exception:
193
+ # If queue is not available (e.g., after shutdown), ignore
194
+ logger.exception(f'Error publishing job update: {exception}')
195
+ pass
196
+
197
+ @classmethod
198
+ def watch_updates(cls, timeout: Optional[float] = None) -> Iterable[
199
+ Tuple[str, Dict[str, Any]]]:
200
+ """
201
+ Generator that yields (job_id, payload) for each change.
202
+ Blocks waiting for new items if the queue is empty.
203
+ - timeout=None: block indefinitely per item
204
+ - timeout=float: block up to timeout seconds, yielding nothing if no item
205
+ """
206
+ cls._ensure_initialized()
207
+ q = cls._updates_queue
208
+ while True:
209
+ try:
210
+ item = q.get(
211
+ timeout=timeout) if timeout is not None else q.get()
212
+ except Exception as exception:
213
+ logger.exception(
214
+ f'Queue empty in job update listener: {exception}')
215
+ # Timeout or queue failure
216
+ if timeout is not None:
217
+ logger.info(f'Watch timeout reached: {timeout}s')
218
+ return
219
+
220
+ logger.info('Watch terminated')
221
+ continue
222
+ yield item
223
+
224
+ @classmethod
225
+ def subscribe(cls, callback: Callable[[str, Dict[str, Any]], None],
226
+ name: Optional[str] = None, daemon: bool = True, ) -> str:
227
+ """
228
+ Start a background listener thread in the current process that consumes updates
229
+ and invokes `callback(job_id, payload)`.
230
+
231
+ Returns a subscription key that can be used to unsubscribe.
232
+ """
233
+ cls._ensure_initialized()
234
+ proc = current_process().name
235
+ sub_key = name or f'job_updates_listener@{proc}#{len(cls._listener_threads) + 1}'
236
+ if sub_key in cls._listener_threads:
237
+ # Stop existing and replace
238
+ cls.unsubscribe(sub_key)
239
+
240
+ stop_event = threading.Event()
241
+
242
+ def _run() -> None:
243
+ while not stop_event.is_set():
244
+ try:
245
+ job_id, payload = cls._updates_queue.get(timeout=0.2)
246
+ except Exception as exception:
247
+ logger.exception(
248
+ f'Queue empty in job update listener {sub_key}: {exception}')
249
+ continue
250
+ try:
251
+ callback(job_id, payload)
252
+ except Exception as exception:
253
+ # Keep the listener alive even if callback fails
254
+ logger.exception(f'Error in job update callback '
255
+ f'{callback}: {exception}')
256
+ pass
257
+
258
+ t = threading.Thread(target=_run, name=sub_key, daemon=daemon)
259
+ t.start()
260
+ cls._listener_threads[sub_key] = (t, stop_event)
261
+ return sub_key
262
+
263
+ @classmethod
264
+ def unsubscribe(cls, key: str) -> None:
265
+ """
266
+ Stop a previously started subscription listener thread.
267
+ """
268
+ pair = cls._listener_threads.get(key)
269
+ if not pair:
270
+ return
271
+ t, stop_event = pair
272
+ stop_event.set()
273
+ t.join(timeout=1.0)
274
+ cls._listener_threads.pop(key, None)
275
+
276
+ # --------------- Convenience helpers ---------------
277
+
278
+ @classmethod
279
+ def set_queued(cls, job_id: str,
280
+ meta_update: Optional[Dict[str, Any]] = None) -> None:
281
+ cls.update_status(job_id, 'QUEUED', meta_update)
282
+
283
+ @classmethod
284
+ def set_running(cls, job_id: str,
285
+ meta_update: Optional[Dict[str, Any]] = None) -> None:
286
+ cls.update_status(job_id, 'RUNNING', meta_update)
287
+
288
+ @classmethod
289
+ def set_done(cls, job_id: str,
290
+ meta_update: Optional[Dict[str, Any]] = None) -> None:
291
+ cls.update_status(job_id, 'DONE', meta_update)
292
+
293
+ @classmethod
294
+ def set_failed(cls, job_id: str,
295
+ meta_update: Optional[Dict[str, Any]] = None) -> None:
296
+ cls.update_status(job_id, 'FAILED', meta_update)
297
+
298
+ @classmethod
299
+ def _get_current_utc_time(cls) -> str:
300
+ now = time.time()
301
+ return datetime.fromtimestamp(now, tz=timezone.utc).isoformat().replace(
302
+ '+00:00', 'Z')
@@ -0,0 +1,77 @@
1
+ # Quapp Platform Project
2
+ # update_job_metadata.py
3
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+
5
+ import requests
6
+
7
+ from ..backend.job_manager import JobManager
8
+ from ...config.logging_config import job_logger
9
+ from ...data.response.job_response import JobResponse
10
+ from ...util.http_utils import create_application_json_header, \
11
+ get_job_id_from_url
12
+ from ...util.response_utils import generate_response
13
+
14
+
15
+ def update_job_metadata(job_response: JobResponse, callback_url: str):
16
+ # Helpers to improve readability and remove duplication
17
+ def _log_http_result(tag: str, resp):
18
+ logger.info(
19
+ f"Request to {tag} completed with status code: {resp.status_code}")
20
+ if not resp.ok:
21
+ logger.warning(
22
+ f"Unexpected status code received from {tag}: {resp.status_code}, response content: {resp.content}")
23
+
24
+ def _update_local_status() -> None:
25
+ """
26
+ Update local JobManager status based on callback URL semantics:
27
+ - contains 'FAILED' => FAILED
28
+ - contains 'FINALIZATION_PASSED' => DONE
29
+ - otherwise => RUNNING
30
+ """
31
+ url_upper = (callback_url or "").upper()
32
+ if "FAILED" in url_upper:
33
+ JobManager.set_failed(job_id=job_id)
34
+ elif "FINALIZATION_PASSED" in url_upper:
35
+ JobManager.set_done(job_id=job_id)
36
+ else:
37
+ JobManager.set_running(job_id=job_id)
38
+
39
+ def _patch_scheduler() -> None:
40
+ job_details = JobManager.get_job(job_id)
41
+ scheduler_callback_url = job_details.get('meta').get(
42
+ 'scheduler_callback_url')
43
+ scheduler_payload = {'job_id' : job_id,
44
+ 'status' : job_details.get('status'),
45
+ 'updated_at': job_details.get('updated_at')}
46
+ scheduler_response = requests.patch(url=scheduler_callback_url,
47
+ json=scheduler_payload)
48
+ _log_http_result('scheduler', scheduler_response)
49
+
50
+ job_id = get_job_id_from_url(callback_url)
51
+ logger = job_logger(job_id)
52
+ logger.info(
53
+ f'Calling backend to update job metadata at URL: {callback_url}')
54
+
55
+ request_body = generate_response(job_response)
56
+
57
+ try:
58
+ # Update local job status and notify scheduler
59
+ _update_local_status()
60
+ _patch_scheduler()
61
+
62
+ request_headers = create_application_json_header(
63
+ token=job_response.user_token,
64
+ project_header=job_response.project_header,
65
+ workspace_header=job_response.workspace_header)
66
+
67
+ response = requests.patch(url=callback_url, json={'data': request_body},
68
+ headers=request_headers)
69
+
70
+ # Log backend response
71
+ _log_http_result('backend', response)
72
+
73
+ except Exception as exception:
74
+ logger.exception(f'Error occurred while calling backend: {exception}',
75
+ exc_info=True)
76
+
77
+ raise exception
@@ -4,6 +4,7 @@
4
4
 
5
5
  from ..response.authentication import Authentication
6
6
  from ..response.custom_header import CustomHeader
7
+ from ...component.backend.job_manager import JobManager
7
8
  from ...config.logging_config import job_logger
8
9
  from ...data.callback.callback_url import CallbackUrl
9
10
  from ...util.http_utils import get_custom_header
@@ -26,6 +27,7 @@ class Request:
26
27
  self.workspace_header: CustomHeader = get_custom_header(request_data,
27
28
  'workspaceHeader')
28
29
  self.logger = job_logger(self.job_id)
30
+ JobManager.add_job(job_id=self.job_id, status='RUNNING', meta={
31
+ 'scheduler_callback_url': request_data.get('schedulerCallBackURL')})
29
32
  self.logger.info(
30
- f"Initializing Request with job id: {self.job_id} and provider job id: {self.provider_job_id}"
31
- )
33
+ f"Initializing Request with job id: {self.job_id} and provider job id: {self.provider_job_id}")
@@ -13,6 +13,7 @@ class ProviderTag(BaseEnum):
13
13
  D_WAVE = 'D_WAVE'
14
14
  OQC_CLOUD = 'OQC_CLOUD'
15
15
  RIGETTI = 'RIGETTI'
16
+ AZURE_QUANTUM = 'AZURE_QUANTUM'
16
17
 
17
18
  @staticmethod
18
19
  def resolve(provider_tag: str):
@@ -13,6 +13,7 @@ class Sdk(BaseEnum):
13
13
  CUDA_QUANTUM = "cuda quantum"
14
14
  PENNYLANE = "pennylane"
15
15
  PYQUIL = "pyquil"
16
+ QSHARP = "qsharp"
16
17
 
17
18
  @staticmethod
18
19
  def resolve(sdk: str):
@@ -0,0 +1,54 @@
1
+ # Quapp Platform Project
2
+ # invocation.py
3
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+ import logging
5
+ import traceback as tb
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+
9
+
10
+ class Event:
11
+ logger = logging.getLogger("model.Invocation.Event")
12
+ logger.setLevel(logging.DEBUG)
13
+
14
+ def __init__(self, request):
15
+ self.request_body = {}
16
+ self.request = request
17
+
18
+ def json(self):
19
+ return self.request_body
20
+
21
+
22
+ @dataclass
23
+ class Result:
24
+ def __init__(self):
25
+ self.timestamp: datetime = datetime.now()
26
+
27
+ def serialize(self):
28
+ return {
29
+ 'timestamp': self.timestamp.isoformat()
30
+ }
31
+
32
+
33
+ @dataclass
34
+ class Success(Result):
35
+ def __init__(self, data):
36
+ super().__init__()
37
+ self.data = data
38
+
39
+ def serialize(self):
40
+ serialize = super().serialize()
41
+ serialize['data'] = self.data
42
+ return serialize
43
+
44
+
45
+ @dataclass
46
+ class Error(Result):
47
+ def __init__(self, error: Exception):
48
+ super().__init__()
49
+ self.error = tb.format_exception(error)
50
+
51
+ def serialize(self):
52
+ serialize = super().serialize()
53
+ serialize['error'] = self.error
54
+ return serialize
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.11.dev6
3
+ Version: 0.0.11.dev8
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)
@@ -24,6 +24,7 @@ Requires-Dist: requests
24
24
  Requires-Dist: numpy
25
25
  Requires-Dist: matplotlib
26
26
  Requires-Dist: pylatexenc
27
+ Requires-Dist: starlette
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: black; extra == "dev"
29
30
  Requires-Dist: bumpver; extra == "dev"
@@ -42,12 +43,11 @@ Quapp common library supporting Quapp Platform for Quantum Computing.
42
43
  Quantum Computing by providing common utilities, configurations, and
43
44
  abstractions for working with
44
45
  quantum providers and devices.
45
-
46
- Recent improvements focus on cleaner and more consistent logging, enhanced error
47
- handling, and
48
- standardizing header management by adding workspace-specific headers instead of
49
- tenant-specific ones,
50
- improving maintainability and clarity.
46
+ Recent improvements add first-class asynchronous job processing with a
47
+ cross-process JobManager,
48
+ background task execution, and standardized result models for immediate client
49
+ responses while work
50
+ continues in the background.
51
51
 
52
52
  ## Features
53
53
 
@@ -61,6 +61,17 @@ improving maintainability and clarity.
61
61
  - Enhanced error handling and job metadata update mechanisms.
62
62
  - Simplified and cleaner HTTP request/response logging and URL parsing
63
63
  utilities.
64
+ - Asynchronous job processing:
65
+ - AsyncInvocationTask to run handlers in a background thread pool and return
66
+ immediate responses.
67
+ - JobManager for cross-process job registry with atomic add/update/get and
68
+ pub-sub updates.
69
+ - Standard Event/Result (Success, Error) models for consistent async
70
+ responses.
71
+ - Scheduler integration via callback URL; local updates are patched to the
72
+ scheduler.
73
+ - Safety: updates to jobs already DONE/FAILED are ignored to prevent
74
+ post-completion mutations.
64
75
 
65
76
  ## Installation
66
77
 
@@ -70,22 +81,27 @@ Install via pip:
70
81
  pip install quapp-common
71
82
  ```
72
83
 
84
+ Notes:
85
+
86
+ - From version 0.0.11.dev7, `starlette` is a direct dependency to support
87
+ background execution helpers.
88
+
73
89
  ## Recently Changes Highlights
74
90
 
75
- - Added workspace-specific classes for request, response, and promise,
76
- consolidating code to unify context handling.
77
- - Refactored logging throughout the codebase, removing redundant debug logs and
78
- adding context-rich log messages.
79
- - Improved URL parsing and job ID extraction, especially in HTTP utilities, to
80
- better track jobs.
81
- - Cleaned up logging configuration files to standardize log formats and levels.
82
- - Added robust error handling in job execution and analysis pipelines, ensuring
83
- detailed failure reporting.
84
- - Simplified header and request body logging, improving performance and
85
- readability.
91
+ - Version bump to 0.0.11.dev7 and dependency update (add `starlette`).
92
+ - Introduce asynchronous job processing primitives:
93
+ - AsyncInvocationTask for background execution with immediate client
94
+ response.
95
+ - Event and standardized Result models (Success, Error).
96
+ - Introduce JobManager for cross-process job management and update publication.
97
+ - Integrate JobManager into Request to register jobs and carry scheduler
98
+ callback URL.
99
+ - Modularize update_job_metadata with clearer helpers; patch scheduler on local
100
+ state changes.
101
+ - Fix: ignore updates for jobs that are already DONE or FAILED.
102
+ - Improve logging and error handling (use full tracebacks, cleaner logs).
86
103
 
87
104
  ---
88
105
 
89
106
  For detailed usage and API references, please refer to the in-code documentation
90
107
  or contact the maintainers.
91
-
@@ -8,6 +8,7 @@ quapp_common.egg-info/dependency_links.txt
8
8
  quapp_common.egg-info/requires.txt
9
9
  quapp_common.egg-info/top_level.txt
10
10
  quapp_common/async_tasks/__init__.py
11
+ quapp_common/async_tasks/async_invocation_task.py
11
12
  quapp_common/async_tasks/async_task.py
12
13
  quapp_common/async_tasks/export_circuit_task.py
13
14
  quapp_common/async_tasks/post_processing_task.py
@@ -16,6 +17,7 @@ quapp_common/component/backend/__init__.py
16
17
  quapp_common/component/backend/invocation.py
17
18
  quapp_common/component/backend/job_fetcher.py
18
19
  quapp_common/component/backend/job_fetching.py
20
+ quapp_common/component/backend/job_manager.py
19
21
  quapp_common/component/callback/__init__.py
20
22
  quapp_common/component/callback/update_job_metadata.py
21
23
  quapp_common/component/device/__init__.py
@@ -64,6 +66,7 @@ quapp_common/factory/provider_factory.py
64
66
  quapp_common/handler/__init__.py
65
67
  quapp_common/handler/handler.py
66
68
  quapp_common/model/__init__.py
69
+ quapp_common/model/invocation.py
67
70
  quapp_common/model/device/__init__.py
68
71
  quapp_common/model/device/custom_device.py
69
72
  quapp_common/model/device/device.py
@@ -3,6 +3,7 @@ requests
3
3
  numpy
4
4
  matplotlib
5
5
  pylatexenc
6
+ starlette
6
7
 
7
8
  [dev]
8
9
  black
@@ -1,57 +0,0 @@
1
- # quapp-common
2
-
3
- Quapp common library supporting Quapp Platform for Quantum Computing.
4
-
5
- ## Overview
6
-
7
- `quapp-common` is a Python library designed to support the Quapp Platform for
8
- Quantum Computing by providing common utilities, configurations, and
9
- abstractions for working with
10
- quantum providers and devices.
11
-
12
- Recent improvements focus on cleaner and more consistent logging, enhanced error
13
- handling, and
14
- standardizing header management by adding workspace-specific headers instead of
15
- tenant-specific ones,
16
- improving maintainability and clarity.
17
-
18
- ## Features
19
-
20
- - Provider and device factory for quantum computing platforms.
21
- - Logging and configuration utilities with improved and detailed log messages.
22
- - Support for AWS Braket, OQC Cloud, Qiskit, PennyLane, DWave Ocean, and Quapp
23
- quantum simulators.
24
- - Refactored classes and utilities to remove tenant-specific request, response,
25
- and promise classes.
26
- - Standardized naming by renaming `ProjectHeader` to `CustomHeader`.
27
- - Enhanced error handling and job metadata update mechanisms.
28
- - Simplified and cleaner HTTP request/response logging and URL parsing
29
- utilities.
30
-
31
- ## Installation
32
-
33
- Install via pip:
34
-
35
- ```bash
36
- pip install quapp-common
37
- ```
38
-
39
- ## Recently Changes Highlights
40
-
41
- - Added workspace-specific classes for request, response, and promise,
42
- consolidating code to unify context handling.
43
- - Refactored logging throughout the codebase, removing redundant debug logs and
44
- adding context-rich log messages.
45
- - Improved URL parsing and job ID extraction, especially in HTTP utilities, to
46
- better track jobs.
47
- - Cleaned up logging configuration files to standardize log formats and levels.
48
- - Added robust error handling in job execution and analysis pipelines, ensuring
49
- detailed failure reporting.
50
- - Simplified header and request body logging, improving performance and
51
- readability.
52
-
53
- ---
54
-
55
- For detailed usage and API references, please refer to the in-code documentation
56
- or contact the maintainers.
57
-
@@ -1,41 +0,0 @@
1
- # Quapp Platform Project
2
- # update_job_metadata.py
3
- # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
-
5
- import requests
6
-
7
- from ...config.logging_config import job_logger
8
- from ...data.response.job_response import JobResponse
9
- from ...util.http_utils import create_application_json_header, \
10
- get_job_id_from_url
11
- from ...util.response_utils import generate_response
12
-
13
-
14
- def update_job_metadata(job_response: JobResponse, callback_url: str):
15
- logger = job_logger(get_job_id_from_url(callback_url))
16
- logger.info(
17
- f"Calling backend to update job metadata at URL: {callback_url}")
18
-
19
- request_body = generate_response(job_response)
20
-
21
- try:
22
- request_headers = create_application_json_header(
23
- token=job_response.user_token,
24
- project_header=job_response.project_header,
25
- workspace_header=job_response.workspace_header)
26
-
27
- response = requests.patch(url=callback_url, json={'data': request_body},
28
- headers=request_headers)
29
-
30
- logger.info(
31
- f"Request to backend completed with status code: {response.status_code}")
32
- if not response.ok:
33
- logger.warning(
34
- f"Unexpected status code received: {response.status_code}, response content: {response.content}")
35
-
36
-
37
- except Exception as exception:
38
- logger.exception(f"Error occurred while calling backend: {exception}",
39
- exc_info=True)
40
-
41
- raise exception