quapp-pyquil 0.0.4.dev3__tar.gz → 0.0.4.dev5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/PKG-INFO +2 -2
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/pyproject.toml +2 -2
- quapp_pyquil-0.0.4.dev5/quapp_pyquil/__init__.py +5 -0
- quapp_pyquil-0.0.4.dev5/quapp_pyquil/error_rules.py +107 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/model/device/rigetti_pyquil_device.py +62 -38
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/model/provider/qapp_pyquil_provider.py +8 -3
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/model/provider/rigetti_pyquil_provider.py +8 -3
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/quapp_pyquil.egg-info/PKG-INFO +2 -2
- quapp_pyquil-0.0.4.dev5/quapp_pyquil.egg-info/SOURCES.txt +31 -0
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/quapp_pyquil.egg-info/requires.txt +1 -1
- quapp_pyquil-0.0.4.dev5/quapp_pyquil.egg-info/top_level.txt +1 -0
- quapp_pyquil-0.0.4.dev5/tests/test_error_logging.py +304 -0
- quapp_pyquil-0.0.4.dev3/qapp_pyquil/model/provider/__init__.py +0 -0
- quapp_pyquil-0.0.4.dev3/quapp_pyquil.egg-info/SOURCES.txt +0 -29
- quapp_pyquil-0.0.4.dev3/quapp_pyquil.egg-info/top_level.txt +0 -1
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/LICENSE +0 -0
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/README.md +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil/async_tasks}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/async_tasks/pyquil_circuit_export_task.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/async_tasks → quapp_pyquil-0.0.4.dev5/quapp_pyquil/component}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/component → quapp_pyquil-0.0.4.dev5/quapp_pyquil/component/backend}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/component/backend/pyquil_invocation.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/component/backend/pyquil_job_fetching.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/component/backend → quapp_pyquil-0.0.4.dev5/quapp_pyquil/factory}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/factory/pyquil_device_factory.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/factory/pyquil_handler_factory.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/factory/pyquil_provider_factory.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/factory → quapp_pyquil-0.0.4.dev5/quapp_pyquil/handler}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/handler/invocation_handler.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/handler/job_fetching_handler.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/handler → quapp_pyquil-0.0.4.dev5/quapp_pyquil/model}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/model → quapp_pyquil-0.0.4.dev5/quapp_pyquil/model/device}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil}/model/device/qapp_pyquil_device.py +0 -0
- {quapp_pyquil-0.0.4.dev3/qapp_pyquil/model/device → quapp_pyquil-0.0.4.dev5/quapp_pyquil/model/provider}/__init__.py +0 -0
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/quapp_pyquil.egg-info/dependency_links.txt +0 -0
- {quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quapp-pyquil
|
|
3
|
-
Version: 0.0.4.
|
|
3
|
+
Version: 0.0.4.dev5
|
|
4
4
|
Summary: QApp pyquil library supporting QApp Platform for Quantum Computing
|
|
5
5
|
Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
19
19
|
Requires-Python: <3.11,>=3.9
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: quapp-common==0.0.
|
|
22
|
+
Requires-Dist: quapp-common==0.0.14.dev10
|
|
23
23
|
Requires-Dist: pyquil==4.15.0
|
|
24
24
|
Provides-Extra: dev
|
|
25
25
|
Requires-Dist: black; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quapp-pyquil"
|
|
7
|
-
version = "0.0.4.
|
|
7
|
+
version = "0.0.4.dev5"
|
|
8
8
|
description = "QApp pyquil library supporting QApp Platform for Quantum Computing"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "CITYNOW Co. Ltd. ", email = "corp@citynow.vn" }]
|
|
@@ -16,7 +16,7 @@ classifiers = [
|
|
|
16
16
|
]
|
|
17
17
|
keywords = ["quapp", "qapp-pyquil", "quantum"]
|
|
18
18
|
dependencies = [
|
|
19
|
-
"quapp-common==0.0.
|
|
19
|
+
"quapp-common==0.0.14.dev10",
|
|
20
20
|
"pyquil==4.15.0"
|
|
21
21
|
]
|
|
22
22
|
requires-python = ">=3.9,<3.11"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Quapp Platform Project
|
|
2
|
+
# error_rules.py
|
|
3
|
+
# Copyright © CITYNOW Co. Ltd. All rights reserved.
|
|
4
|
+
|
|
5
|
+
"""pyQuil / Rigetti-specific error classification rules.
|
|
6
|
+
|
|
7
|
+
Registered into ``quapp_common.util.error_classifier`` when ``quapp_pyquil`` is
|
|
8
|
+
imported, so ``quapp-common`` never has to depend on pyQuil.
|
|
9
|
+
|
|
10
|
+
Matching is done on the class **NAME as a string** or on the message text, never
|
|
11
|
+
``isinstance``, and nothing from ``pyquil`` / ``qcs_sdk`` is imported. The reason:
|
|
12
|
+
the classifier has to work even when the SDK is absent.
|
|
13
|
+
|
|
14
|
+
The first group of rules keys off messages **this lib itself** raises -- checked
|
|
15
|
+
against the code, not guessed:
|
|
16
|
+
|
|
17
|
+
quapp_pyquil/model/provider/qapp_pyquil_provider.py:26
|
|
18
|
+
ValueError('[QAppPyquilProvider] Unsupported device')
|
|
19
|
+
quapp_pyquil/model/provider/rigetti_pyquil_provider.py:41
|
|
20
|
+
ValueError('[RigettiPyquilProvider] Unsupported device')
|
|
21
|
+
quapp_pyquil/factory/pyquil_device_factory.py:35
|
|
22
|
+
ValueError(f'Unsupported provider type: {provider_type}')
|
|
23
|
+
quapp_pyquil/factory/pyquil_provider_factory.py:25,36
|
|
24
|
+
ValueError(f'Unsupported SDK for provider type: {provider_type}')
|
|
25
|
+
|
|
26
|
+
The last rule matches exception names from pyQuil 4.15 / qcs_sdk (the versions
|
|
27
|
+
pinned in pyproject). If upstream renames them the rule becomes inert rather than
|
|
28
|
+
broken -- the classifier just falls back on the phase.
|
|
29
|
+
|
|
30
|
+
This lib is also the only one that opens ``RuntimePhase.POLLING``:
|
|
31
|
+
``RigettiPyquilDevice`` really does loop waiting for a result, so a timeout while
|
|
32
|
+
waiting on Rigetti stays separate from a failure at submit time.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from quapp_common.enum.error_category import ErrorCategory
|
|
36
|
+
from quapp_common.util.error_classifier import Rule, register_rules
|
|
37
|
+
|
|
38
|
+
DOC_DEVICE = 'errors/pyquil-device'
|
|
39
|
+
DOC_AUTH = 'errors/rigetti-auth'
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _message(exception) -> str:
|
|
43
|
+
return str(exception).lower()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _name(exception) -> str:
|
|
47
|
+
return type(exception).__name__
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
register_rules(
|
|
51
|
+
# ----------------- raised by quapp-pyquil itself (checked against code)
|
|
52
|
+
Rule(match=lambda e: 'unsupported device' in _message(e),
|
|
53
|
+
category=ErrorCategory.PROVIDER_DEVICE,
|
|
54
|
+
code='PYQUIL_DEVICE_UNSUPPORTED',
|
|
55
|
+
resolution="This device is not supported by Quapp's pyQuil lib "
|
|
56
|
+
'yet.',
|
|
57
|
+
steps=('Check the device selected for the job',
|
|
58
|
+
'On Rigetti the device name must be a valid QCS QPU or QVM'),
|
|
59
|
+
doc_slug=DOC_DEVICE),
|
|
60
|
+
|
|
61
|
+
Rule(match=lambda e: 'unsupported provider type' in _message(e)
|
|
62
|
+
or 'unsupported sdk for provider type' in _message(e),
|
|
63
|
+
category=ErrorCategory.PROVIDER_DEVICE,
|
|
64
|
+
code='PYQUIL_PROVIDER_UNSUPPORTED',
|
|
65
|
+
resolution='This provider/SDK pair is not supported by '
|
|
66
|
+
'quapp-pyquil yet.',
|
|
67
|
+
steps=('Check the providerTag sent with the invoke request',
|
|
68
|
+
'quapp-pyquil serves QUAO_QUANTUM_SIMULATOR and RIGETTI'),
|
|
69
|
+
doc_slug=DOC_DEVICE),
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------- Rigetti QCS credentials
|
|
72
|
+
# rigetti_pyquil_provider.py uses OAuthSession/RefreshToken; an expired
|
|
73
|
+
# or wrong token is something the user can fix, not a platform fault.
|
|
74
|
+
Rule(match=lambda e: 'refresh token' in _message(e)
|
|
75
|
+
or 'oauth' in _message(e)
|
|
76
|
+
or 'qcs credentials' in _message(e),
|
|
77
|
+
category=ErrorCategory.AUTHENTICATION,
|
|
78
|
+
code='RIGETTI_TOKEN_INVALID',
|
|
79
|
+
resolution="Rigetti's QCS token is invalid or has expired.",
|
|
80
|
+
steps=('Check the Rigetti credential in your workspace',
|
|
81
|
+
'Get a fresh refresh token from QCS and update it'),
|
|
82
|
+
doc_slug=DOC_AUTH,
|
|
83
|
+
retryable=False),
|
|
84
|
+
|
|
85
|
+
# ----------------------------------- pyQuil 4.15 / qcs_sdk exceptions
|
|
86
|
+
Rule(match=lambda e: _name(e) in {'QCSHTTPStatusError', 'QCSClientError',
|
|
87
|
+
'LoadClientConfigurationError'},
|
|
88
|
+
category=ErrorCategory.PROVIDER_UNAVAILABLE,
|
|
89
|
+
code='RIGETTI_API_UNAVAILABLE',
|
|
90
|
+
resolution="Rigetti's QCS API could not be reached.",
|
|
91
|
+
steps=('Try again in a few minutes',
|
|
92
|
+
"Check the status of Rigetti's QCS service"),
|
|
93
|
+
retryable=True,
|
|
94
|
+
retry_after=120),
|
|
95
|
+
|
|
96
|
+
# Placed after every rule above: matching is by NAME, so only these exact
|
|
97
|
+
# names land here and no subclass gets swallowed.
|
|
98
|
+
Rule(match=lambda e: _name(e) in {'QuilError', 'QuilSyntaxError',
|
|
99
|
+
'ProgramError', 'ApiError'},
|
|
100
|
+
category=ErrorCategory.SDK_RUNTIME,
|
|
101
|
+
code='PYQUIL_SDK_ERROR',
|
|
102
|
+
resolution='pyQuil failed while building or running the program. '
|
|
103
|
+
'See the stack trace below.',
|
|
104
|
+
steps=('Read the rootCause to see which step pyQuil rejected',
|
|
105
|
+
'Check the Quil program and the qubit count the device '
|
|
106
|
+
'supports')),
|
|
107
|
+
)
|
|
@@ -16,9 +16,11 @@ from quapp_common.data.response.job_response import JobResponse
|
|
|
16
16
|
|
|
17
17
|
from quapp_common.component.callback.update_job_metadata import update_job_metadata
|
|
18
18
|
from quapp_common.enum.status.job_status import JobStatus
|
|
19
|
-
from quapp_common.enum.media_type import MediaType
|
|
20
19
|
from quapp_common.enum.status.status_code import StatusCode
|
|
20
|
+
from quapp_common.config.log_context import phase
|
|
21
21
|
from quapp_common.data.callback.callback_url import CallbackUrl
|
|
22
|
+
from quapp_common.enum.runtime_phase import RuntimePhase
|
|
23
|
+
from quapp_common.util.response_utils import build_error_job_response
|
|
22
24
|
import pickle
|
|
23
25
|
import time
|
|
24
26
|
|
|
@@ -93,25 +95,36 @@ class RigettiPyquilDevice(Device):
|
|
|
93
95
|
update_job_metadata(job_response=job_response,
|
|
94
96
|
callback_url=execution_callback.on_start)
|
|
95
97
|
try:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
self.logger.debug("
|
|
114
|
-
|
|
98
|
+
# A copy of Device.run_circuit that lost every `with phase(...)`, so
|
|
99
|
+
# until now no rigetti log line carried a phase at all. Three stages are
|
|
100
|
+
# kept apart because they fail for different reasons: a wrong circuit
|
|
101
|
+
# type (COMPILATION), Rigetti refusing the job (EXECUTION), and a job
|
|
102
|
+
# that was accepted but never returns a result (POLLING).
|
|
103
|
+
with phase(RuntimePhase.COMPILATION):
|
|
104
|
+
job = self._create_job(circuit=circuit, options=options)
|
|
105
|
+
|
|
106
|
+
with phase(RuntimePhase.EXECUTION):
|
|
107
|
+
job_response.provider_job_id = self._get_provider_job_id(job)
|
|
108
|
+
job_response.job_status = self._get_job_status(job)
|
|
109
|
+
original_job_result = None
|
|
110
|
+
|
|
111
|
+
# This is the only lib with a real result-waiting loop, and therefore the
|
|
112
|
+
# only place where RuntimePhase.POLLING means anything.
|
|
113
|
+
with phase(RuntimePhase.POLLING):
|
|
114
|
+
while True:
|
|
115
|
+
self.logger.debug("fetching job result")
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
res = self._get_job_result(job)
|
|
119
|
+
self.logger.debug("Job result: {}".format(res))
|
|
120
|
+
if res.execution_duration_microseconds is not None:
|
|
121
|
+
job_response.job_status = JobStatus.DONE.value
|
|
122
|
+
original_job_result = res
|
|
123
|
+
break
|
|
124
|
+
except Exception as e:
|
|
125
|
+
self.logger.debug(
|
|
126
|
+
"Job result not ready yet: {}".format(str(e)))
|
|
127
|
+
time.sleep(5)
|
|
115
128
|
|
|
116
129
|
self.logger.debug("Job result: {}".format(job_response))
|
|
117
130
|
|
|
@@ -121,12 +134,17 @@ class RigettiPyquilDevice(Device):
|
|
|
121
134
|
return original_job_result, job_response
|
|
122
135
|
|
|
123
136
|
except Exception as exception:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
job_response
|
|
137
|
+
# This used to be `{"error": str(exception)}`: no category, no phase,
|
|
138
|
+
# no rootCause -- exactly what left the Job Log without a readable
|
|
139
|
+
# reason. build_error_job_response fills the whole envelope and logs
|
|
140
|
+
# once itself, so the logger.debug that used to sit here is gone too,
|
|
141
|
+
# to avoid logging the same error twice.
|
|
142
|
+
job_response = build_error_job_response(
|
|
143
|
+
exception, job_response,
|
|
144
|
+
message='Error when executing job',
|
|
145
|
+
stage='while running the circuit on the device',
|
|
146
|
+
phase=RuntimePhase.EXECUTION,
|
|
147
|
+
context=self._error_context())
|
|
130
148
|
|
|
131
149
|
update_job_metadata(job_response=job_response,
|
|
132
150
|
callback_url=execution_callback.on_error)
|
|
@@ -148,12 +166,16 @@ class RigettiPyquilDevice(Device):
|
|
|
148
166
|
callback_url=analysis_callback.on_start)
|
|
149
167
|
|
|
150
168
|
try:
|
|
151
|
-
|
|
169
|
+
with phase(RuntimePhase.ANALYSIS):
|
|
170
|
+
job_response.job_histogram = self._produce_histogram_data(
|
|
171
|
+
original_job_result)
|
|
172
|
+
|
|
173
|
+
self.execution_time = self._calculate_execution_time(
|
|
174
|
+
original_job_result)
|
|
152
175
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
self.logger.debug("Execution time: {}".format(job_response.execution_time))
|
|
176
|
+
job_response.execution_time = self.execution_time
|
|
177
|
+
self.logger.debug(
|
|
178
|
+
"Execution time: {}".format(job_response.execution_time))
|
|
157
179
|
|
|
158
180
|
update_job_metadata(
|
|
159
181
|
job_response=job_response,
|
|
@@ -162,13 +184,15 @@ class RigettiPyquilDevice(Device):
|
|
|
162
184
|
return job_response
|
|
163
185
|
|
|
164
186
|
except Exception as exception:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
job_response
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
# A logger.error() here would emit a second ERROR line for the same
|
|
188
|
+
# failure: build_error_job_response already logs the full envelope.
|
|
189
|
+
# Two lines skew every metric that counts job_failed.
|
|
190
|
+
job_response = build_error_job_response(
|
|
191
|
+
exception, job_response,
|
|
192
|
+
message='Error when analyzing job result',
|
|
193
|
+
stage='while analysing the job result',
|
|
194
|
+
phase=RuntimePhase.ANALYSIS,
|
|
195
|
+
context=self._error_context())
|
|
172
196
|
|
|
173
197
|
update_job_metadata(job_response=job_response,
|
|
174
198
|
callback_url=analysis_callback.on_error)
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
Copyright © CITYNOW Co. Ltd. All rights reserved.
|
|
5
5
|
"""
|
|
6
6
|
from quapp_common.config.logging_config import logger
|
|
7
|
+
|
|
8
|
+
# The component name belongs in the `context` field, not in the text
|
|
9
|
+
# of the message: left in the message, Grafana needs a regex to
|
|
10
|
+
# filter on it.
|
|
11
|
+
logger = logger.bind(context='QAppPyquilProvider')
|
|
7
12
|
from quapp_common.enum.provider_tag import ProviderTag
|
|
8
13
|
from quapp_common.model.provider.provider import Provider
|
|
9
14
|
|
|
@@ -12,11 +17,11 @@ from pyquil import get_qc
|
|
|
12
17
|
|
|
13
18
|
class QAppPyquilProvider(Provider):
|
|
14
19
|
def __init__(self, ):
|
|
15
|
-
logger.debug('
|
|
20
|
+
logger.debug('get_backend()')
|
|
16
21
|
super().__init__(ProviderTag.QUAO_QUANTUM_SIMULATOR)
|
|
17
22
|
|
|
18
23
|
def get_backend(self, device_specification):
|
|
19
|
-
logger.debug('
|
|
24
|
+
logger.debug('get_backend()')
|
|
20
25
|
|
|
21
26
|
try:
|
|
22
27
|
print(device_specification)
|
|
@@ -26,5 +31,5 @@ class QAppPyquilProvider(Provider):
|
|
|
26
31
|
raise ValueError('[QAppPyquilProvider] Unsupported device')
|
|
27
32
|
|
|
28
33
|
def collect_provider(self):
|
|
29
|
-
logger.debug('
|
|
34
|
+
logger.debug('collect_provider()')
|
|
30
35
|
return None
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
Copyright © CITYNOW Co. Ltd. All rights reserved.
|
|
5
5
|
"""
|
|
6
6
|
from quapp_common.config.logging_config import logger
|
|
7
|
+
|
|
8
|
+
# The component name belongs in the `context` field, not in the text
|
|
9
|
+
# of the message: left in the message, Grafana needs a regex to
|
|
10
|
+
# filter on it.
|
|
11
|
+
logger = logger.bind(context='RigettiPyquilProvider')
|
|
7
12
|
from quapp_common.enum.provider_tag import ProviderTag
|
|
8
13
|
from quapp_common.model.provider.provider import Provider
|
|
9
14
|
|
|
@@ -21,11 +26,11 @@ class RigettiPyquilProvider(Provider):
|
|
|
21
26
|
self.access_token = access_token
|
|
22
27
|
self.api_url = api_url
|
|
23
28
|
|
|
24
|
-
logger.debug('
|
|
29
|
+
logger.debug('get_backend()')
|
|
25
30
|
super().__init__(ProviderTag.RIGETTI)
|
|
26
31
|
|
|
27
32
|
def get_backend(self, device_specification) -> QuantumComputer:
|
|
28
|
-
logger.debug('
|
|
33
|
+
logger.debug('get_backend()')
|
|
29
34
|
|
|
30
35
|
try:
|
|
31
36
|
auth = AuthServer(client_id=self.client_id, issuer=self.issuer)
|
|
@@ -41,5 +46,5 @@ class RigettiPyquilProvider(Provider):
|
|
|
41
46
|
raise ValueError('[RigettiPyquilProvider] Unsupported device')
|
|
42
47
|
|
|
43
48
|
def collect_provider(self):
|
|
44
|
-
logger.debug('
|
|
49
|
+
logger.debug('collect_provider()')
|
|
45
50
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quapp-pyquil
|
|
3
|
-
Version: 0.0.4.
|
|
3
|
+
Version: 0.0.4.dev5
|
|
4
4
|
Summary: QApp pyquil library supporting QApp Platform for Quantum Computing
|
|
5
5
|
Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
19
19
|
Requires-Python: <3.11,>=3.9
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: quapp-common==0.0.
|
|
22
|
+
Requires-Dist: quapp-common==0.0.14.dev10
|
|
23
23
|
Requires-Dist: pyquil==4.15.0
|
|
24
24
|
Provides-Extra: dev
|
|
25
25
|
Requires-Dist: black; extra == "dev"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
quapp_pyquil/__init__.py
|
|
5
|
+
quapp_pyquil/error_rules.py
|
|
6
|
+
quapp_pyquil.egg-info/PKG-INFO
|
|
7
|
+
quapp_pyquil.egg-info/SOURCES.txt
|
|
8
|
+
quapp_pyquil.egg-info/dependency_links.txt
|
|
9
|
+
quapp_pyquil.egg-info/requires.txt
|
|
10
|
+
quapp_pyquil.egg-info/top_level.txt
|
|
11
|
+
quapp_pyquil/async_tasks/__init__.py
|
|
12
|
+
quapp_pyquil/async_tasks/pyquil_circuit_export_task.py
|
|
13
|
+
quapp_pyquil/component/__init__.py
|
|
14
|
+
quapp_pyquil/component/backend/__init__.py
|
|
15
|
+
quapp_pyquil/component/backend/pyquil_invocation.py
|
|
16
|
+
quapp_pyquil/component/backend/pyquil_job_fetching.py
|
|
17
|
+
quapp_pyquil/factory/__init__.py
|
|
18
|
+
quapp_pyquil/factory/pyquil_device_factory.py
|
|
19
|
+
quapp_pyquil/factory/pyquil_handler_factory.py
|
|
20
|
+
quapp_pyquil/factory/pyquil_provider_factory.py
|
|
21
|
+
quapp_pyquil/handler/__init__.py
|
|
22
|
+
quapp_pyquil/handler/invocation_handler.py
|
|
23
|
+
quapp_pyquil/handler/job_fetching_handler.py
|
|
24
|
+
quapp_pyquil/model/__init__.py
|
|
25
|
+
quapp_pyquil/model/device/__init__.py
|
|
26
|
+
quapp_pyquil/model/device/qapp_pyquil_device.py
|
|
27
|
+
quapp_pyquil/model/device/rigetti_pyquil_device.py
|
|
28
|
+
quapp_pyquil/model/provider/__init__.py
|
|
29
|
+
quapp_pyquil/model/provider/qapp_pyquil_provider.py
|
|
30
|
+
quapp_pyquil/model/provider/rigetti_pyquil_provider.py
|
|
31
|
+
tests/test_error_logging.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
quapp_pyquil
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Quapp Platform Project
|
|
2
|
+
# test_error_logging.py
|
|
3
|
+
# Copyright © CITYNOW Co. Ltd. All rights reserved.
|
|
4
|
+
|
|
5
|
+
"""Locks four classes of error-logging bug, all found in real provider-lib code.
|
|
6
|
+
|
|
7
|
+
1. **A copied hook lost its phase.** Some devices copied ``run_circuit`` /
|
|
8
|
+
``_on_execution`` / ``_on_analysis`` wholesale from ``Device`` but dropped the
|
|
9
|
+
``with phase(...)`` blocks, so that lib's log had no ``phase`` field at any
|
|
10
|
+
stage. Same class of bug as ``CustomDevice._on_analysis`` in quapp-common.
|
|
11
|
+
|
|
12
|
+
2. **The error was flattened into a string.** ``job_response.job_result =
|
|
13
|
+
{"error": str(exception)}`` leaves the Job Log with a bare string: no
|
|
14
|
+
category, no phase, no rootCause -- exactly what the ticket complained about.
|
|
15
|
+
|
|
16
|
+
3. **The error was logged twice.** ``logger.exception()`` immediately before
|
|
17
|
+
``build_error_job_response`` emits a second ERROR line for the same failure,
|
|
18
|
+
skewing every metric that counts job_failed.
|
|
19
|
+
|
|
20
|
+
4. **A fake job_id.** ``job_logger('SomeClass')`` -- the first argument of
|
|
21
|
+
``job_logger()`` is the **job_id**. Passing a class name there sets a fake
|
|
22
|
+
job_id and it BEATS the real one (``_inject_job_context`` only fills keys that
|
|
23
|
+
are still None), so filtering by jobId in Grafana loses every line that
|
|
24
|
+
component logged.
|
|
25
|
+
|
|
26
|
+
The tests discover files inside the package, so this file works for any lib
|
|
27
|
+
without hardcoding names.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import ast
|
|
31
|
+
import io
|
|
32
|
+
import pathlib
|
|
33
|
+
import re
|
|
34
|
+
import tokenize
|
|
35
|
+
|
|
36
|
+
import pytest
|
|
37
|
+
|
|
38
|
+
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
39
|
+
|
|
40
|
+
# The first release carrying a phase for analysis/post-processing and one traceId
|
|
41
|
+
# per job.
|
|
42
|
+
MINIMUM_COMMON = '0.0.14.dev6'
|
|
43
|
+
|
|
44
|
+
# Hooks already instrumented in quapp-common: copying one means copying its
|
|
45
|
+
# phases too. _on_execution creates the job and then talks to the provider, which
|
|
46
|
+
# is why it needs both -- a failed transpile and a rejected job are different
|
|
47
|
+
# errors.
|
|
48
|
+
INSTRUMENTED = {'_on_execution': {'COMPILATION', 'EXECUTION'},
|
|
49
|
+
'_on_analysis' : {'ANALYSIS'}}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def package_root() -> pathlib.Path:
|
|
53
|
+
"""The provider lib's package directory, inferred from the tree.
|
|
54
|
+
|
|
55
|
+
A few repos hold more than one package -- quapp-hpc also has
|
|
56
|
+
``quapp_slurm_hpc``, which only contains ``runner.py`` for the Slurm node and
|
|
57
|
+
is not the provider lib. Pick by structural evidence (it has ``model/`` or
|
|
58
|
+
``factory/``) rather than by name.
|
|
59
|
+
"""
|
|
60
|
+
candidates = [d for d in REPO_ROOT.iterdir()
|
|
61
|
+
if d.is_dir() and re.fullmatch(r'qu?app_\w+', d.name)
|
|
62
|
+
and (d / '__init__.py').is_file()]
|
|
63
|
+
assert candidates, 'found no package at all'
|
|
64
|
+
|
|
65
|
+
if len(candidates) > 1:
|
|
66
|
+
providers = [d for d in candidates
|
|
67
|
+
if (d / 'model').is_dir() or (d / 'factory').is_dir()]
|
|
68
|
+
assert len(providers) == 1, (
|
|
69
|
+
f'could not identify the provider package: '
|
|
70
|
+
f'{providers or candidates}')
|
|
71
|
+
return providers[0]
|
|
72
|
+
|
|
73
|
+
return candidates[0]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
PACKAGE = package_root()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def code_only(path) -> str:
|
|
80
|
+
"""Source with comments stripped out.
|
|
81
|
+
|
|
82
|
+
Needed because the explanatory comments *quote* the very code being banned
|
|
83
|
+
(``{"error": str(exception)}``, ``logger.exception(``). A raw scan would
|
|
84
|
+
report a false positive on the explanation itself.
|
|
85
|
+
"""
|
|
86
|
+
source = path.read_text(encoding='utf-8')
|
|
87
|
+
kept = [token for token in
|
|
88
|
+
tokenize.generate_tokens(io.StringIO(source).readline)
|
|
89
|
+
if token.type != tokenize.COMMENT]
|
|
90
|
+
return tokenize.untokenize(kept)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def phases_opened_in(source: str) -> set:
|
|
94
|
+
"""The RuntimePhases opened with ``with phase(...)`` in a piece of code."""
|
|
95
|
+
tree = ast.parse(source.lstrip())
|
|
96
|
+
return {
|
|
97
|
+
argument.attr
|
|
98
|
+
for node in ast.walk(tree) if isinstance(node, ast.With)
|
|
99
|
+
for item in node.items if isinstance(item.context_expr, ast.Call)
|
|
100
|
+
if (getattr(item.context_expr.func, 'id', None)
|
|
101
|
+
or getattr(item.context_expr.func, 'attr', None)) == 'phase'
|
|
102
|
+
for argument in item.context_expr.args
|
|
103
|
+
if isinstance(argument, ast.Attribute)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def python_files():
|
|
108
|
+
return sorted(PACKAGE.rglob('*.py'))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------- (1) a copied hook keeps its phases
|
|
112
|
+
|
|
113
|
+
def test_copied_hooks_keep_their_phases():
|
|
114
|
+
"""Overriding an instrumented hook means copying its `with phase(...)` too."""
|
|
115
|
+
offenders = []
|
|
116
|
+
for path in python_files():
|
|
117
|
+
source = path.read_text(encoding='utf-8')
|
|
118
|
+
tree = ast.parse(source, filename=str(path))
|
|
119
|
+
for node in ast.walk(tree):
|
|
120
|
+
if not isinstance(node, ast.FunctionDef) \
|
|
121
|
+
or node.name not in INSTRUMENTED:
|
|
122
|
+
continue
|
|
123
|
+
opened = phases_opened_in(ast.get_source_segment(source, node))
|
|
124
|
+
missing = INSTRUMENTED[node.name] - opened
|
|
125
|
+
if missing:
|
|
126
|
+
offenders.append(
|
|
127
|
+
f'{path.name}::{node.name} is missing {sorted(missing)}')
|
|
128
|
+
|
|
129
|
+
assert not offenders, (
|
|
130
|
+
f'overridden hooks lost their phase: {offenders}. The original in '
|
|
131
|
+
f'quapp-common has `with phase(...)`; copying it without that leaves the '
|
|
132
|
+
f'log with no phase field.')
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_at_least_one_phase_is_opened_somewhere():
|
|
136
|
+
"""A lib that overrides no hook needs none; one that does needs a phase."""
|
|
137
|
+
overrides = any(
|
|
138
|
+
isinstance(node, ast.FunctionDef) and node.name in INSTRUMENTED
|
|
139
|
+
for path in python_files()
|
|
140
|
+
for node in ast.walk(ast.parse(path.read_text(encoding='utf-8')))
|
|
141
|
+
)
|
|
142
|
+
if not overrides:
|
|
143
|
+
pytest.skip('lib overrides no hook; phases come from quapp-common')
|
|
144
|
+
|
|
145
|
+
opened = set()
|
|
146
|
+
for path in python_files():
|
|
147
|
+
opened |= phases_opened_in(path.read_text(encoding='utf-8'))
|
|
148
|
+
assert opened, 'hooks are overridden but no phase is opened'
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# --------------------------------- (2) no hand-rolled error string
|
|
152
|
+
|
|
153
|
+
@pytest.mark.parametrize('path', python_files(), ids=lambda p: p.name)
|
|
154
|
+
def test_no_hand_rolled_error_dict(path):
|
|
155
|
+
"""`{"error": str(exception)}` bypasses the entire error envelope."""
|
|
156
|
+
assert '{"error": str(exception)}' not in code_only(path), (
|
|
157
|
+
f'{path.name}: flattens the error into a string, losing '
|
|
158
|
+
f'category/phase/rootCause. Use '
|
|
159
|
+
f'build_error_job_response(..., phase=..., context=...)')
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_error_envelope_carries_phase_and_context():
|
|
163
|
+
"""Every build_error_job_response in the device needs phase= and context=."""
|
|
164
|
+
for path in python_files():
|
|
165
|
+
source = code_only(path)
|
|
166
|
+
calls = source.count('build_error_job_response(')
|
|
167
|
+
if not calls:
|
|
168
|
+
continue
|
|
169
|
+
assert source.count('phase=RuntimePhase.') >= calls, (
|
|
170
|
+
f'{path.name}: has {calls} error envelopes but is missing phase=, '
|
|
171
|
+
f'so the ErrorReport would come out with phase=None')
|
|
172
|
+
assert source.count('context=self._error_context()') >= calls, (
|
|
173
|
+
f'{path.name}: error envelope is missing context=, leaving support '
|
|
174
|
+
f'without the device/provider')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ------------------------------------------ (3) one error is logged once
|
|
178
|
+
|
|
179
|
+
def test_no_duplicate_error_log_before_envelope():
|
|
180
|
+
"""logger.exception()/logger.error() right before the envelope = 2 ERRORs."""
|
|
181
|
+
offenders = []
|
|
182
|
+
for path in python_files():
|
|
183
|
+
source = code_only(path)
|
|
184
|
+
for match in re.finditer(r'logger\.(exception|error)\(', source):
|
|
185
|
+
if 'build_error_job_response(' in source[match.end():match.end() + 400]:
|
|
186
|
+
line = source[:match.start()].count('\n') + 1
|
|
187
|
+
offenders.append(f'{path.name}:{line}')
|
|
188
|
+
|
|
189
|
+
assert not offenders, (
|
|
190
|
+
f'duplicate ERROR log before build_error_job_response at {offenders}; '
|
|
191
|
+
f'the envelope already logs once')
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ------------------------------------------------- (4) no fake job_id
|
|
195
|
+
|
|
196
|
+
@pytest.mark.parametrize('path', python_files(), ids=lambda p: p.name)
|
|
197
|
+
def test_no_fake_job_logger_argument(path):
|
|
198
|
+
"""`job_logger()` takes a real job_id; a component name belongs in `context`.
|
|
199
|
+
|
|
200
|
+
Two shapes of fake id are caught: a string literal (`job_logger('Provider')`)
|
|
201
|
+
and `__name__`, which quietly makes the job_id the dotted module path and
|
|
202
|
+
blows up jobId cardinality in Grafana as well as losing the real job.
|
|
203
|
+
"""
|
|
204
|
+
tree = ast.parse(path.read_text(encoding='utf-8'), filename=str(path))
|
|
205
|
+
|
|
206
|
+
offenders = []
|
|
207
|
+
for node in ast.walk(tree):
|
|
208
|
+
if not isinstance(node, ast.Call) or not node.args:
|
|
209
|
+
continue
|
|
210
|
+
if (getattr(node.func, 'id', None)
|
|
211
|
+
or getattr(node.func, 'attr', None)) != 'job_logger':
|
|
212
|
+
continue
|
|
213
|
+
first = node.args[0]
|
|
214
|
+
if isinstance(first, ast.Constant) and isinstance(first.value, str):
|
|
215
|
+
offenders.append(repr(first.value))
|
|
216
|
+
elif isinstance(first, ast.Name) and first.id == '__name__':
|
|
217
|
+
offenders.append('__name__')
|
|
218
|
+
|
|
219
|
+
assert not offenders, (
|
|
220
|
+
f'{path.name}: job_logger() got {offenders} -- those are fake job_ids. '
|
|
221
|
+
f"Use logger.bind(context='...') for the component name")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_providers_bind_their_name_to_context():
|
|
225
|
+
"""A provider that logs must bind its own name, so lines say where they came
|
|
226
|
+
from."""
|
|
227
|
+
providers = sorted((PACKAGE / 'model' / 'provider').glob('*.py'))
|
|
228
|
+
if not providers:
|
|
229
|
+
pytest.skip('lib has no model/provider directory')
|
|
230
|
+
|
|
231
|
+
offenders = []
|
|
232
|
+
for path in providers:
|
|
233
|
+
if path.name == '__init__.py':
|
|
234
|
+
continue
|
|
235
|
+
# code_only: several providers keep their old logging commented out, and
|
|
236
|
+
# a raw scan would flag those as if they still logged.
|
|
237
|
+
source = code_only(path)
|
|
238
|
+
if 'logger' not in source:
|
|
239
|
+
continue
|
|
240
|
+
if not re.search(r"logger\.bind\(context='[A-Za-z]+'\)", source):
|
|
241
|
+
offenders.append(path.name)
|
|
242
|
+
|
|
243
|
+
assert not offenders, (
|
|
244
|
+
f'providers that log without binding context: {offenders}. The component '
|
|
245
|
+
f'name must be a field, not text inside the message, or Grafana cannot '
|
|
246
|
+
f'filter on it')
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------ rules + the quapp-common pin
|
|
250
|
+
|
|
251
|
+
def test_error_rules_module_exists():
|
|
252
|
+
assert (PACKAGE / 'error_rules.py').is_file(), \
|
|
253
|
+
"no error_rules.py, so this SDK's errors never get classified"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_error_rules_are_registered_on_import():
|
|
257
|
+
init = (PACKAGE / '__init__.py').read_text(encoding='utf-8')
|
|
258
|
+
assert 'error_rules' in init, \
|
|
259
|
+
'__init__.py does not import error_rules, so the rules never register'
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_error_rules_imports_no_vendor_sdk():
|
|
263
|
+
"""The classifier has to run even with the SDK absent, hence name matching."""
|
|
264
|
+
tree = ast.parse((PACKAGE / 'error_rules.py').read_text(encoding='utf-8'))
|
|
265
|
+
|
|
266
|
+
roots = set()
|
|
267
|
+
for node in ast.walk(tree):
|
|
268
|
+
if isinstance(node, ast.Import):
|
|
269
|
+
roots.update(alias.name.split('.')[0] for alias in node.names)
|
|
270
|
+
elif isinstance(node, ast.ImportFrom) and node.module and not node.level:
|
|
271
|
+
roots.add(node.module.split('.')[0])
|
|
272
|
+
|
|
273
|
+
assert roots <= {'quapp_common'}, \
|
|
274
|
+
f'error_rules.py may only import quapp_common, but imports: {sorted(roots)}'
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_common_pin_is_exact_and_current():
|
|
278
|
+
version = pytest.importorskip('packaging.version')
|
|
279
|
+
|
|
280
|
+
text = (REPO_ROOT / 'pyproject.toml').read_text(encoding='utf-8')
|
|
281
|
+
assert not re.findall(r'"quapp-common\s*(>=|>|~=)', text), (
|
|
282
|
+
'quapp-common must be pinned with `==`: `>=` lets pip pick a higher '
|
|
283
|
+
'stable release that is missing the feature (which already happened '
|
|
284
|
+
'with quapp-qiskit 1.0.14)')
|
|
285
|
+
|
|
286
|
+
match = re.search(r'"quapp-common\s*==\s*([^"]+)"', text)
|
|
287
|
+
assert match, 'no quapp-common pin found'
|
|
288
|
+
assert version.Version(match.group(1)) >= version.Version(MINIMUM_COMMON), (
|
|
289
|
+
f'pin {match.group(1)} < {MINIMUM_COMMON}: that release has no phase for '
|
|
290
|
+
f'analysis/post-processing and still splits one job into two traceIds')
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_registered_rules_are_reachable():
|
|
294
|
+
"""Runs for real: import the lib and read the registry. Skips without the SDK."""
|
|
295
|
+
package_name = PACKAGE.name
|
|
296
|
+
pytest.importorskip(package_name)
|
|
297
|
+
__import__(package_name)
|
|
298
|
+
from quapp_common.util.error_classifier import registered_rules
|
|
299
|
+
|
|
300
|
+
codes = {rule.code for rule in registered_rules()}
|
|
301
|
+
own = {code for code in codes if not code.startswith(
|
|
302
|
+
('INPUT_', 'DEPENDENCY_', 'PROVIDER_', 'HANDLER_', 'PLATFORM_',
|
|
303
|
+
'COMPILATION_', 'EXECUTION_', 'ANALYSIS_', 'POST_'))}
|
|
304
|
+
assert own, f'no lib-specific rule registered, only generic ones: {sorted(codes)}'
|
|
File without changes
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
qapp_pyquil/__init__.py
|
|
5
|
-
qapp_pyquil/async_tasks/__init__.py
|
|
6
|
-
qapp_pyquil/async_tasks/pyquil_circuit_export_task.py
|
|
7
|
-
qapp_pyquil/component/__init__.py
|
|
8
|
-
qapp_pyquil/component/backend/__init__.py
|
|
9
|
-
qapp_pyquil/component/backend/pyquil_invocation.py
|
|
10
|
-
qapp_pyquil/component/backend/pyquil_job_fetching.py
|
|
11
|
-
qapp_pyquil/factory/__init__.py
|
|
12
|
-
qapp_pyquil/factory/pyquil_device_factory.py
|
|
13
|
-
qapp_pyquil/factory/pyquil_handler_factory.py
|
|
14
|
-
qapp_pyquil/factory/pyquil_provider_factory.py
|
|
15
|
-
qapp_pyquil/handler/__init__.py
|
|
16
|
-
qapp_pyquil/handler/invocation_handler.py
|
|
17
|
-
qapp_pyquil/handler/job_fetching_handler.py
|
|
18
|
-
qapp_pyquil/model/__init__.py
|
|
19
|
-
qapp_pyquil/model/device/__init__.py
|
|
20
|
-
qapp_pyquil/model/device/qapp_pyquil_device.py
|
|
21
|
-
qapp_pyquil/model/device/rigetti_pyquil_device.py
|
|
22
|
-
qapp_pyquil/model/provider/__init__.py
|
|
23
|
-
qapp_pyquil/model/provider/qapp_pyquil_provider.py
|
|
24
|
-
qapp_pyquil/model/provider/rigetti_pyquil_provider.py
|
|
25
|
-
quapp_pyquil.egg-info/PKG-INFO
|
|
26
|
-
quapp_pyquil.egg-info/SOURCES.txt
|
|
27
|
-
quapp_pyquil.egg-info/dependency_links.txt
|
|
28
|
-
quapp_pyquil.egg-info/requires.txt
|
|
29
|
-
quapp_pyquil.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
qapp_pyquil
|
|
File without changes
|
|
File without changes
|
{quapp_pyquil-0.0.4.dev3/qapp_pyquil → quapp_pyquil-0.0.4.dev5/quapp_pyquil/async_tasks}/__init__.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_pyquil-0.0.4.dev3 → quapp_pyquil-0.0.4.dev5}/quapp_pyquil.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|