quapp-common 0.0.11.dev6__tar.gz → 0.0.11.dev7__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.
- {quapp_common-0.0.11.dev6/quapp_common.egg-info → quapp_common-0.0.11.dev7}/PKG-INFO +35 -19
- quapp_common-0.0.11.dev7/README.md +72 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/pyproject.toml +3 -2
- quapp_common-0.0.11.dev7/quapp_common/async_tasks/async_invocation_task.py +80 -0
- quapp_common-0.0.11.dev7/quapp_common/component/backend/job_manager.py +302 -0
- quapp_common-0.0.11.dev7/quapp_common/component/callback/update_job_metadata.py +77 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/request/request.py +4 -2
- quapp_common-0.0.11.dev7/quapp_common/model/invocation.py +54 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7/quapp_common.egg-info}/PKG-INFO +35 -19
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common.egg-info/SOURCES.txt +3 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common.egg-info/requires.txt +1 -0
- quapp_common-0.0.11.dev6/README.md +0 -57
- quapp_common-0.0.11.dev6/quapp_common/component/callback/update_job_metadata.py +0 -41
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/LICENSE +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/async_tasks/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/async_tasks/async_task.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/async_tasks/export_circuit_task.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/async_tasks/post_processing_task.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/invocation.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/job_fetcher.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/job_fetching.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/callback/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/device/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/device/device_selection.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/config/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/config/logging_config.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/config/thread_config.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/async_task/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/async_task/circuit_export/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/async_task/circuit_export/backend_holder.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/async_task/circuit_export/circuit_holder.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/backend/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/backend/backend_information.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/callback/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/callback/callback_url.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/device/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/device/circuit_running_option.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/promise/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/promise/post_processing_promise.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/promise/promise.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/request/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/request/invocation_request.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/request/job_fetching_request.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/authentication.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/custom_header.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/job_response.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/base_enum.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/http_header.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/invocation_step.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/media_type.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/processing_unit.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/provider_tag.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/sdk.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/status/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/status/job_status.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/status/status_code.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/token_type.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/device_factory.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/handler_factory.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/provider_factory.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/handler/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/handler/handler.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/device/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/device/custom_device.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/device/device.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/provider/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/provider/provider.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/util/__init__.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/util/file_utils.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/util/http_utils.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/util/json_parser_utils.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/util/response_utils.py +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common.egg-info/dependency_links.txt +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common.egg-info/top_level.txt +0 -0
- {quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quapp-common
|
|
3
|
-
Version: 0.0.11.
|
|
3
|
+
Version: 0.0.11.dev7
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.
|
|
7
|
+
version = "0.0.11.dev7"
|
|
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}")
|
|
@@ -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.
|
|
3
|
+
Version: 0.0.11.dev7
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/async_tasks/async_task.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/__init__.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/invocation.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/job_fetcher.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/backend/job_fetching.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/callback/__init__.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/component/device/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/async_task/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/callback/__init__.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/callback/callback_url.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/__init__.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/authentication.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/custom_header.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/data/response/job_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/status/job_status.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/enum/status/status_code.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/device_factory.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/handler_factory.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/factory/provider_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/device/custom_device.py
RENAMED
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/provider/__init__.py
RENAMED
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/model/provider/provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common/util/json_parser_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{quapp_common-0.0.11.dev6 → quapp_common-0.0.11.dev7}/quapp_common.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|