diff --git a/src/quantum/azext_quantum/__init__.py b/src/quantum/azext_quantum/__init__.py index abc38ff7cac..1710931b51a 100644 --- a/src/quantum/azext_quantum/__init__.py +++ b/src/quantum/azext_quantum/__init__.py @@ -3,11 +3,11 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=import-outside-toplevel from azure.cli.core import AzCommandsLoader import azext_quantum._help # pylint: disable=unused-import + class QuantumCommandsLoader(AzCommandsLoader): def __init__(self, cli_ctx=None): diff --git a/src/quantum/azext_quantum/_client_factory.py b/src/quantum/azext_quantum/_client_factory.py index 261ac02015d..6ea7b65e8fc 100644 --- a/src/quantum/azext_quantum/_client_factory.py +++ b/src/quantum/azext_quantum/_client_factory.py @@ -3,13 +3,15 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=import-outside-toplevel,line-too-long +# pylint: disable=line-too-long import os + def is_env(name): return 'AZURE_QUANTUM_ENV' in os.environ and os.environ['AZURE_QUANTUM_ENV'] == name + def base_url(): if 'AZURE_QUANTUM_BASEURL' in os.environ: return os.environ['AZURE_QUANTUM_BASEURL'] @@ -17,27 +19,33 @@ def base_url(): return "https://app-jobs-canarysouthcentralus.azurewebsites.net/" return "https://app-jobscheduler-prod.azurewebsites.net/" + def _get_data_credentials(cli_ctx, subscription_id=None): from azure.cli.core._profile import Profile profile = Profile(cli_ctx=cli_ctx) creds, _, _ = profile.get_login_credentials(subscription_id=subscription_id, resource="https://quantum.microsoft.com") return creds + def cf_quantum(cli_ctx, subscription_id=None, resource_group_name=None, workspace_name=None): from .vendored_sdks.azure_quantum import QuantumClient creds = _get_data_credentials(cli_ctx, subscription_id) return QuantumClient(creds, subscription_id, resource_group_name, workspace_name, base_url=base_url()) + def cf_quantum_mgmt(cli_ctx, *_): from azure.cli.core.commands.client_factory import get_mgmt_service_client from .vendored_sdks.azure_mgmt_quantum import QuantumManagementClient return get_mgmt_service_client(cli_ctx, QuantumManagementClient) + def cf_workspaces(cli_ctx, *_): return cf_quantum_mgmt(cli_ctx).workspaces + def cf_providers(cli_ctx, subscription_id=None, resource_group_name=None, workspace_name=None): return cf_quantum(cli_ctx, subscription_id, resource_group_name, workspace_name).providers + def cf_jobs(cli_ctx, subscription_id=None, resource_group_name=None, workspace_name=None): return cf_quantum(cli_ctx, subscription_id, resource_group_name, workspace_name).jobs diff --git a/src/quantum/azext_quantum/_params.py b/src/quantum/azext_quantum/_params.py index d6362a6f05a..c6e3306177d 100644 --- a/src/quantum/azext_quantum/_params.py +++ b/src/quantum/azext_quantum/_params.py @@ -6,6 +6,7 @@ from knack.arguments import CLIArgumentType + def load_arguments(self, _): workspace_name_type = CLIArgumentType(options_list=['--workspace-name', '-w'], help='Name of the Quantum Workspace. You can configure the default workspace using `az quantum workspace set`.', id_part=None, required=False) program_args = CLIArgumentType(nargs='*', help='List of arguments expected by the Q# operation specified as --name=value after `--`.') diff --git a/src/quantum/azext_quantum/_validators.py b/src/quantum/azext_quantum/_validators.py index 9a3be025928..d4836392727 100644 --- a/src/quantum/azext_quantum/_validators.py +++ b/src/quantum/azext_quantum/_validators.py @@ -10,6 +10,7 @@ from .operations.workspace import WorkspaceInfo from .operations.target import TargetInfo + def validate_workspace_info(cmd, namespace): """ Makes sure all parameters for a workspace are available. @@ -46,6 +47,5 @@ def validate_workspace_and_target_info(cmd, namespace): # For the time being (Private Preview), we also need the AZURE_QUANTUM_STORAGE env variable populated # with the Azure Storage connection string to use to upload the program. - if not 'AZURE_QUANTUM_STORAGE' in os.environ: + if 'AZURE_QUANTUM_STORAGE' not in os.environ: raise ValueError(f"Please set the AZURE_QUANTUM_STORAGE environment variable with an Azure Storage's connection string.") - diff --git a/src/quantum/azext_quantum/commands.py b/src/quantum/azext_quantum/commands.py index ae7a6b73644..9ce3969d04e 100644 --- a/src/quantum/azext_quantum/commands.py +++ b/src/quantum/azext_quantum/commands.py @@ -25,6 +25,7 @@ def one(provider, target): for target in provider['targets'] ] + def transform_job(result): result = OrderedDict([ ('Id', result['id']), @@ -35,6 +36,7 @@ def transform_job(result): ]) return result + def transform_jobs(results): def creation(job): return job['creationTime'] @@ -61,7 +63,7 @@ def one(key, value): items = range(0, len(histogram), 2) for i in items: key = histogram[i] - value = histogram[i+1] + value = histogram[i + 1] table.append(one(key, value)) return table @@ -80,18 +82,16 @@ def load_command_table(self, _): with self.command_group('quantum workspace', workspace_ops) as w: w.command('list', 'list') - w.command('show', 'show', validator=validate_workspace_info) ## TODO: argument list/help + w.command('show', 'show', validator=validate_workspace_info) w.command('set', 'set', validator=validate_workspace_info) w.command('clear', 'clear') - with self.command_group('quantum target', target_ops) as w: w.command('list', 'list', validator=validate_workspace_info, table_transformer=transform_targets) w.command('show', 'show', validator=validate_target_info) w.command('set', 'set', validator=validate_target_info) w.command('clear', 'clear') - with self.command_group('quantum job', job_ops) as j: j.command('list', 'list', validator=validate_workspace_info, table_transformer=transform_jobs) j.command('show', 'show', validator=validate_workspace_info, table_transformer=transform_job) @@ -99,6 +99,5 @@ def load_command_table(self, _): j.command('wait', 'wait', validator=validate_workspace_info, table_transformer=transform_job) j.command('output', 'output', validator=validate_workspace_info, table_transformer=transform_output) - with self.command_group('quantum', job_ops, is_preview=True) as q: q.command('execute', 'execute', validator=validate_workspace_and_target_info, table_transformer=transform_output) diff --git a/src/quantum/azext_quantum/operations/job.py b/src/quantum/azext_quantum/operations/job.py index 2836d7b0b59..31e15650463 100644 --- a/src/quantum/azext_quantum/operations/job.py +++ b/src/quantum/azext_quantum/operations/job.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=import-outside-toplevel +# pylint: disable=redefined-builtin import logging @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) -# pylint: disable=redefined-builtin + def list(cmd, resource_group_name=None, workspace_name=None): """ Returns the list of jobs in a Quantum Workspace. @@ -112,6 +112,7 @@ def _generate_submit_args(program_args, ws, target, token, project, job_name, sh return args + def submit(cmd, program_args, resource_group_name=None, workspace_name=None, target_id=None, project=None, job_name=None, shots=None, no_build=False): """ @@ -158,6 +159,7 @@ def _parse_blob_url(url): "sas_token": sas_token } + def output(cmd, job_id, resource_group_name=None, workspace_name=None): """ Returns back the results of a Q# execution """ import tempfile @@ -168,9 +170,9 @@ def output(cmd, job_id, resource_group_name=None, workspace_name=None): path = os.path.join(tempfile.gettempdir(), job_id) if os.path.exists(path): - logger.debug(f"Using existing blob from {path}") + logger.debug("Using existing blob from %s", path) else: - logger.debug(f"Downloading job results blob into {path}") + logger.debug("Downloading job results blob into %s", path) info = WorkspaceInfo(cmd, resource_group_name, workspace_name) client = cf_jobs(cmd.cli_ctx, info.subscription, info.resource_group, info.name) @@ -217,6 +219,7 @@ def has_completed(job): return job + def execute(cmd, program_args, resource_group_name=None, workspace_name=None, target_id=None, project=None, job_name=None, shots=None, no_build=False): """ diff --git a/src/quantum/azext_quantum/operations/target.py b/src/quantum/azext_quantum/operations/target.py index eaa330bd6e2..00601550a12 100644 --- a/src/quantum/azext_quantum/operations/target.py +++ b/src/quantum/azext_quantum/operations/target.py @@ -3,23 +3,23 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=import-outside-toplevel,line-too-long,redefined-builtin +# pylint: disable=line-too-long,redefined-builtin from .._client_factory import cf_providers from .workspace import WorkspaceInfo + class TargetInfo(object): def __init__(self, cmd, target_id=None): def select_value(key, value): - if not value is None: + if value is not None: return value value = cmd.cli_ctx.config.get('quantum', key, None) - if not value is None: + if value is not None: return value value = cmd.cli_ctx.config.get(cmd.cli_ctx.config.defaults_section_name, key, None) - if not value is None: - return value + return value self.target_id = select_value('target_id', target_id) @@ -40,6 +40,7 @@ def show(cmd, target_id=None): info = TargetInfo(cmd, target_id) return info + def set(cmd, target_id=None): """ Selects the default target to use when submitting jobs to Azure Quantum. @@ -49,6 +50,7 @@ def set(cmd, target_id=None): info.save(cmd) return info + def list(cmd, resource_group_name=None, workspace_name=None): """ Returns the list of providers and their targets in a Quantum Workspace. @@ -57,6 +59,7 @@ def list(cmd, resource_group_name=None, workspace_name=None): client = cf_providers(cmd.cli_ctx, info.subscription, info.resource_group, info.name) return client.get_status() + def clear(cmd): """ Unsets the default target-id. diff --git a/src/quantum/azext_quantum/operations/workspace.py b/src/quantum/azext_quantum/operations/workspace.py index f14b60eef86..5f8bd0aee42 100644 --- a/src/quantum/azext_quantum/operations/workspace.py +++ b/src/quantum/azext_quantum/operations/workspace.py @@ -3,12 +3,13 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# pylint: disable=import-outside-toplevel,line-too-long,redefined-builtin +# pylint: disable=line-too-long,redefined-builtin from knack.util import CLIError from .._client_factory import cf_workspaces + class WorkspaceInfo(object): def __init__(self, cmd, resource_group_name=None, workspace_name=None): from azure.cli.core.commands.client_factory import get_subscription_id @@ -18,14 +19,13 @@ def __init__(self, cmd, resource_group_name=None, workspace_name=None): # then it checks if the key exists in the 'quantum' section in config, and uses that if available. # finally, it checks in the 'global' section in the config. def select_value(key, value): - if not value is None: + if value is not None: return value value = cmd.cli_ctx.config.get('quantum', key, None) - if not value is None: + if value is not None: return value value = cmd.cli_ctx.config.get(cmd.cli_ctx.config.defaults_section_name, key, None) - if not value is None: - return value + return value self.subscription = get_subscription_id(cmd.cli_ctx) self.resource_group = select_value('group', resource_group_name) @@ -51,6 +51,7 @@ def list(cmd, resource_group_name=None, tag=None, location=None): from azure.cli.command_modules.resource.custom import list_resources return list_resources(cmd, resource_group_name=resource_group_name, resource_type="Microsoft.Quantum/Workspaces", tag=tag, location=location) + def show(cmd, resource_group_name=None, workspace_name=None): """ Returns the details of the given (or current) Quantum Workspace. @@ -62,6 +63,7 @@ def show(cmd, resource_group_name=None, workspace_name=None): ws = client.get(info.resource_group, info.name) return ws + def set(cmd, workspace_name, resource_group_name=None): """ Sets the default Quantum Workspace. @@ -71,7 +73,8 @@ def set(cmd, workspace_name, resource_group_name=None): ws = client.get(info.resource_group, info.name) if ws: info.save(cmd) - return ws + return ws + def clear(cmd): """ diff --git a/src/quantum/azext_quantum/tests/latest/test_quantum_jobs.py b/src/quantum/azext_quantum/tests/latest/test_quantum_jobs.py index 79b8c767c42..a371359758e 100644 --- a/src/quantum/azext_quantum/tests/latest/test_quantum_jobs.py +++ b/src/quantum/azext_quantum/tests/latest/test_quantum_jobs.py @@ -76,7 +76,7 @@ def test_submit_args(self): self.assertIn("--job-name", args) self.assertIn("--shots", args) - def test_parse_blob_url(self): + def test_parse_blob_url(self): sas = "sv=2018-03-28&sr=c&sig=some-sig&sp=racwl" url = f"https://getest2.blob.core.windows.net/qio/rawOutputData?{sas}" args = _parse_blob_url(url) diff --git a/src/quantum/azext_quantum/tests/latest/test_quantum_targets.py b/src/quantum/azext_quantum/tests/latest/test_quantum_targets.py index dd0f3359996..0f9f0b9bcc7 100644 --- a/src/quantum/azext_quantum/tests/latest/test_quantum_targets.py +++ b/src/quantum/azext_quantum/tests/latest/test_quantum_targets.py @@ -15,7 +15,7 @@ class QuantumScenarioTest(ScenarioTest): - + def test_targets(self): # Since azure quantum is still in private preview, we require # these tests to run in a specific subscription (AzureQuantum-test) @@ -28,7 +28,7 @@ def test_targets(self): # clear current target self.cmd(f'az quantum target clear') - + # list targets = self.cmd('az quantum target list -o json').get_output_in_json() assert len(targets) > 0 @@ -51,4 +51,3 @@ def test_targets(self): # clear self.cmd(f'az quantum target clear') - diff --git a/src/quantum/azext_quantum/tests/latest/test_quantum_workspace.py b/src/quantum/azext_quantum/tests/latest/test_quantum_workspace.py index d040625f3b5..4d5e147803c 100644 --- a/src/quantum/azext_quantum/tests/latest/test_quantum_workspace.py +++ b/src/quantum/azext_quantum/tests/latest/test_quantum_workspace.py @@ -15,7 +15,7 @@ class QuantumScenarioTest(ScenarioTest): - + def test_workspace(self): # Since azure quantum is still in private preview, we require # these tests to run in a specific subscription (AzureQuantum-test) @@ -25,10 +25,10 @@ def test_workspace(self): # clear self.cmd(f'az quantum workspace clear') - + # list workspaces = self.cmd('az quantum workspace list -o json').get_output_in_json() - assert len(workspaces) > 0 + assert len(workspaces) > 0 self.cmd('az quantum workspace list -o json', checks=[ self.check(f"[?name=='{TEST_WORKSPACE}'].resourceGroup | [0]", TEST_RG) ]) @@ -45,4 +45,3 @@ def test_workspace(self): # clear self.cmd(f'az quantum workspace clear') - diff --git a/src/quantum/azext_quantum/tests/latest/utils.py b/src/quantum/azext_quantum/tests/latest/utils.py index f89c6024a32..39521904f12 100644 --- a/src/quantum/azext_quantum/tests/latest/utils.py +++ b/src/quantum/azext_quantum/tests/latest/utils.py @@ -7,6 +7,7 @@ TEST_RG = 'aqua-provider-validator' TEST_WORKSPACE = 'validator-workspace-westus' + def is_private_preview_subscription(scenario): """ Returns True if running in AzureQuantum-test """