From a7372f5c43c9e677b1cc79e15af3950257353474 Mon Sep 17 00:00:00 2001 From: Xinyi Joffre Date: Fri, 21 Mar 2025 15:15:19 -0700 Subject: [PATCH 1/4] Remove resource estimator files --- azure-quantum/azure/quantum/chemistry/__init__.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 azure-quantum/azure/quantum/chemistry/__init__.py diff --git a/azure-quantum/azure/quantum/chemistry/__init__.py b/azure-quantum/azure/quantum/chemistry/__init__.py deleted file mode 100644 index 714d2a07f..000000000 --- a/azure-quantum/azure/quantum/chemistry/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -## -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -## - -from urllib.request import urlopen - -def df_chemistry() -> bytes: - """ - Returns bitcode of a QIR program for the double-factorized chemistry - quantum algorithm. - """ - return urlopen("https://aka.ms/RE/df_chemistry").read() From dfe24ce07ae370354a9a150045f2a3df7fc1cd06 Mon Sep 17 00:00:00 2001 From: Xinyi Joffre Date: Fri, 21 Mar 2025 15:15:30 -0700 Subject: [PATCH 2/4] Remove resource estimator content --- .../azure/quantum/target/microsoft/target.py | 142 ---------- azure-quantum/azure/quantum/target/params.py | 253 ------------------ 2 files changed, 395 deletions(-) delete mode 100644 azure-quantum/azure/quantum/target/microsoft/target.py delete mode 100644 azure-quantum/azure/quantum/target/params.py diff --git a/azure-quantum/azure/quantum/target/microsoft/target.py b/azure-quantum/azure/quantum/target/microsoft/target.py deleted file mode 100644 index 194a667c5..000000000 --- a/azure-quantum/azure/quantum/target/microsoft/target.py +++ /dev/null @@ -1,142 +0,0 @@ -## -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -## -import re -import warnings -from dataclasses import dataclass, field -from typing import Any, Dict, Optional, Type, Union, List - -from ...job import Job -from ...job.base_job import ContentType -from ...workspace import Workspace -from ..params import InputParams, InputParamsItem, AutoValidatingParams, \ - validating_field -from ..target import Target - -def _check_error_rate(name, value): - if value <= 0.0 or value >= 1.0: - raise ValueError(f"{name} must be between 0 and 1") - -def _check_error_rate_or_process_and_readout(name, value): - if value is None: - return - - if isinstance(value, float): - _check_error_rate(name, value) - return - - if not isinstance(value, MeasurementErrorRate): - raise ValueError(f"{name} must be either a float or " - "MeasurementErrorRate with two fields: 'process' and 'readout'") - -def check_time(name, value): - pat = r"^(\+?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*(s|ms|μs|µs|us|ns)$" - if re.match(pat, value) is None: - raise ValueError(f"{name} is not a valid time string; use a " - "suffix s, ms, us, or ns") - -@dataclass -class MeasurementErrorRate(AutoValidatingParams): - process: float = field(metadata={"validate": _check_error_rate}) - readout: float = field(metadata={"validate": _check_error_rate}) - -@dataclass -class ProtocolSpecificDistillationUnitSpecification(AutoValidatingParams): - num_unit_qubits: Optional[int] = None - duration_in_qubit_cycle_time: Optional[int] = None - - def post_validation(self, result): - if self.num_unit_qubits is None: - raise LookupError("num_unit_qubits must be set") - - if self.duration_in_qubit_cycle_time is None: - raise LookupError("duration_in_qubit_cycle_time must be set") - - -@dataclass -class DistillationUnitSpecification(AutoValidatingParams): - name: Optional[str] = None - display_name: Optional[str] = None - num_input_ts: Optional[int] = None - num_output_ts: Optional[int] = None - failure_probability_formula: Optional[str] = None - output_error_rate_formula: Optional[str] = None - physical_qubit_specification: Optional[ProtocolSpecificDistillationUnitSpecification] = None - logical_qubit_specification: Optional[ProtocolSpecificDistillationUnitSpecification] = None - logical_qubit_specification_first_round_override: \ - Optional[ProtocolSpecificDistillationUnitSpecification] = None - - def has_custom_specification(self): - return \ - self.display_name is not None \ - or self.num_input_ts is not None \ - or self.num_output_ts is not None \ - or self.failure_probability_formula is not None \ - or self.output_error_rate_formula is not None \ - or self.physical_qubit_specification is not None \ - or self.logical_qubit_specification is not None \ - or self.logical_qubit_specification_first_round_override is not None - - def has_predefined_name(self): - return self.name is not None - - def post_validation(self, result): - if not self.has_custom_specification() and not self.has_predefined_name(): - raise LookupError("name must be set or custom specification must be provided") - - if self.has_custom_specification() and self.has_predefined_name(): - raise LookupError("If predefined name is provided, " - "custom specification is not allowed. " - "Either remove name or remove all other " - "specification of the distillation unit") - - if self.has_predefined_name(): - return # all other validation is on the server side - - if self.num_input_ts is None: - raise LookupError("num_input_ts must be set") - - if self.num_output_ts is None: - raise LookupError("num_output_ts must be set") - - if self.failure_probability_formula is None: - raise LookupError("failure_probability_formula must be set") - - if self.output_error_rate_formula is None: - raise LookupError("output_error_rate_formula must be set") - - if self.physical_qubit_specification is not None: - self.physical_qubit_specification.post_validation(result) - - if self.logical_qubit_specification is not None: - self.logical_qubit_specification.post_validation(result) - - if self.logical_qubit_specification_first_round_override is not None: - self.logical_qubit_specification_first_round_override.post_validation(result) - - def as_dict(self, validate=True) -> Dict[str, Any]: - specification_dict = super().as_dict(validate) - if len(specification_dict) != 0: - if self.physical_qubit_specification is not None: - physical_qubit_specification_dict = \ - self.physical_qubit_specification.as_dict(validate) - if len(physical_qubit_specification_dict) != 0: - specification_dict["physicalQubitSpecification"] = \ - physical_qubit_specification_dict - - if self.logical_qubit_specification is not None: - logical_qubit_specification_dict = \ - self.logical_qubit_specification.as_dict(validate) - if len(logical_qubit_specification_dict) != 0: - specification_dict["logicalQubitSpecification"] = \ - logical_qubit_specification_dict - - if self.logical_qubit_specification_first_round_override is not None: - logical_qubit_specification_first_round_override_dict = \ - self.logical_qubit_specification_first_round_override.as_dict(validate) - if len(logical_qubit_specification_first_round_override_dict) != 0: - specification_dict["logicalQubitSpecificationFirstRoundOverride"] = \ - logical_qubit_specification_first_round_override_dict - - return specification_dict diff --git a/azure-quantum/azure/quantum/target/params.py b/azure-quantum/azure/quantum/target/params.py deleted file mode 100644 index e493187b4..000000000 --- a/azure-quantum/azure/quantum/target/params.py +++ /dev/null @@ -1,253 +0,0 @@ -## -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -## -from dataclasses import dataclass, field -from re import sub -from typing import Any, Dict, List, Optional, Tuple, Type, Callable -from ..argument_types import EmptyArray, Pauli, Range, Result - - -__all__ = ["InputParams"] - - -class EntryPointArguments: - """ - Wrapper class to set QIR entry point arguments. - - This class is used to set QIR entry point arguments inside the - InputParamsItem class. It overrides the __setitem__ method to - automatically detect the entry point's argument from the passed value. - """ - - # Maps supported Python types to QIR entry point type names and a callable - # that extracts the serialized value from the original value. (list (= - # Array) is handled as a special case.) - type_map: Dict[type, Tuple[str, Callable]] = { - int: ("Int", lambda v: v), - float: ("Double", lambda v: v), - bool: ("Boolean", lambda v: v), - str: ("String", lambda v: v), - Pauli: ("Pauli", lambda v: v.value), - Result: ("Result", lambda v: v.value), - Range: ("Range", lambda v: v.value) - } - - def __init__(self): - self.entries = [] - - def __setitem__(self, name: str, value: Any): - """ - Creates entry point argument entry and automatically determines the - type from value. - """ - if type(value) == list: - if len(value) == 0: - raise ValueError("Use EmptyArray(type) to assign an empty " - "error") - else: - first = value[0] - first_value, first_type = self._extract_value_and_type(first) - values = [first_value] - for next in value[1:]: - next_value, next_type = self._extract_value_and_type(next) - if next_type != first_type: - raise TypeError("All elements in a list must have " - "the same type") - values.append(next_value) - self.entries.append( - {"name": name, - "value": values, - "type": "Array", - "elementType": first_type}) - elif type(value) == EmptyArray: - element_type = self._extract_type(value.element_type) - self.entries.append( - {"name": name, - "value": [], - "type": "Array", - "elementType": element_type}) - else: - entry_value, entry_type = self._extract_value_and_type(value) - self.entries.append( - {"name": name, "value": entry_value, "type": entry_type}) - - def _extract_type(self, type: Type) -> str: - """ - Convert Python type to QIR entry point argument type name. - """ - if type in self.type_map: - return self.type_map[type][0] - elif type == list: - raise TypeError(f"Nested lists are not supported") - else: - type_name = type.__name__ - raise TypeError(f"Unsupported type {type_name}") - - def _extract_value_and_type(self, value: Any) -> Tuple[Any, str]: - """ - Convert Python value to QIR entry point argument type name and - serialized value. - """ - if type(value) in self.type_map: - entry_type, entry_value_func = self.type_map[type(value)] - return entry_value_func(value), entry_type - elif type(value) == list: - raise TypeError(f"Nested lists are not supported") - else: - type_name = type(value).__name__ - raise TypeError(f"Unsupported type {type_name} for {value}") - - -@dataclass -class AutoValidatingParams: - """ - A helper class for target parameters. - - It has a function as_dict that automatically extracts a dictionary from - the class' fields. They are added to the result dictionary if their value - is not None, the key is automatically transformed from Python snake case - to camel case, and if validate is True and if the field has a validation - function, the field is validated beforehand. - """ - def as_dict(self, validate=True): - result = {} - - for name, field in self.__dataclass_fields__.items(): - field_value = self.__getattribute__(name) - if field_value is not None: - # validate field? - if validate and "validate" in field.metadata: - func = field.metadata["validate"] - # check for indirect call (like in @staticmethod) - if hasattr(func, "__func__"): - func = func.__func__ - func(name, field_value) - - # translate field name to camel case - s = sub(r"(_|-)+", " ", name).title().replace(" ", "") - attribute = ''.join([s[0].lower(), s[1:]]) - result[attribute] = field_value - - if validate: - self.post_validation(result) - - return result - - def post_validation(self, result): - """ - A function that is called after all individual fields have been - validated, but before the result is returned. - - Here result is the current dictionary. - """ - pass - - -def validating_field(validation_func, default=None): - """ - A helper method to declare field for an AutoValidatingParams data class. - """ - return field(default=default, metadata={"validate": validation_func}) - - -class InputParamsItem: - """ - Base class for input parameters. - - This class serves both as the base class for InputParams as well as for the - items in the InputParams. - """ - def __init__(self): - # all input param items may have an entry point name and a list of - # arguments - self.entry_point: Optional[str] = None - self.arguments = EntryPointArguments() - - def as_dict(self, validate=True) -> Dict[str, Any]: - """ - Returns input params as a dictionary. - """ - result = {} - - if self.entry_point is not None: - result['entryPoint'] = self.entry_point - - if len(self.arguments.entries) > 0: - result['arguments'] = self.arguments.entries - - return result - - -class InputParams(InputParamsItem): - """ - Class to define input parameters. - - This class allows to define input parameters for non-batching and batching - jobs. The instance represents a batching job, if and only if num_items is - set to some positive number less or equal to MAX_NUM_ITEMS. - - Both this class and the items in this class are based on InputParamsItem as - a template, which can be overriden for specializations created by a target. - This class should never be constructed directly but only through the - InputParams.make_params method. - """ - - MAX_NUM_ITEMS: int = 1000 - - def __init__( - self, - num_items: Optional[int] = None, - item_type: Type[InputParamsItem] = InputParamsItem): - """ - Constructs a InputParams instance. - - The item_type argument should be set by targets that override - InputParams and have a specialized InputParamsItem class. - """ - item_type.__init__(self) - - # fileURIs - self.file_uris = {} - - if num_items is not None: - self.has_items = True - if num_items <= 0 or num_items > self.MAX_NUM_ITEMS: - raise ValueError( - "num_items must be a positive value less or equal to " - f"{self.MAX_NUM_ITEMS}") - self._items = [item_type() for _ in range(num_items)] - else: - self.has_items = False - - self.item_type = item_type - - @property - def items(self) -> List: - if self.has_items: - return self._items - else: - raise Exception("Cannot access items in a non-batching job, call " - "make_params with num_items parameter") - - def as_dict(self, validate=True) -> Dict[str, Any]: - """ - Constructs a dictionary from the input params. - - For batching jobs, top-level entries are merged into item entries. - Item entries have priority in case they are specified. - """ - - # initialize result and set type hint - result: Dict[str, Any] = self.item_type.as_dict(self, validate) - - if self.has_items: - result["items"] = [item.as_dict(validate) for item in self._items] - # In case of batching, no need to stop if failing an item - result["resumeAfterFailedItem"] = True - - # add fileUris if existing - if len(self.file_uris) > 0: - result["fileUris"] = self.file_uris - - return result From a35412a9cc65ac8ab97e8de3447df2966649c489 Mon Sep 17 00:00:00 2001 From: Xinyi Joffre Date: Fri, 21 Mar 2025 15:20:04 -0700 Subject: [PATCH 3/4] Remove more resource estimator references --- azure-quantum/azure/quantum/target/target.py | 2 +- azure-quantum/tests.live/Run.ps1 | 3 --- azure-quantum/tests/unit/test_pagination.py | 12 ++++++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/azure-quantum/azure/quantum/target/target.py b/azure-quantum/azure/quantum/target/target.py index 6b5834407..992d8ce69 100644 --- a/azure-quantum/azure/quantum/target/target.py +++ b/azure-quantum/azure/quantum/target/target.py @@ -84,7 +84,7 @@ def __init__( :type output_data_format: str :param capability: QIR capability. Deprecated, use `target_profile` :type capability: str - :param provider_id: Id of provider (ex. "microsoft-qc") + :param provider_id: Id of provider :type provider_id: str :param content_type: "Content-Type" attribute value to set on input blob (ex. "application/json") :type content_type: azure.quantum.job.ContentType diff --git a/azure-quantum/tests.live/Run.ps1 b/azure-quantum/tests.live/Run.ps1 index c6a9e61b2..d7506f3d1 100644 --- a/azure-quantum/tests.live/Run.ps1 +++ b/azure-quantum/tests.live/Run.ps1 @@ -62,9 +62,6 @@ function PyTestMarkExpr() { if ($AzureQuantumCapabilities -notcontains "submit.quantinuum") { $MarkExpr += " and not quantinuum" } - if ($AzureQuantumCapabilities -notcontains "submit.microsoft-qc") { - $MarkExpr += " and not microsoft_qc" - } if ($AzureQuantumCapabilities -notcontains "submit.microsoft-elements") { $MarkExpr += " and not microsoft_elements_dft" } diff --git a/azure-quantum/tests/unit/test_pagination.py b/azure-quantum/tests/unit/test_pagination.py index e8b74aeae..ae93ed19b 100644 --- a/azure-quantum/tests/unit/test_pagination.py +++ b/azure-quantum/tests/unit/test_pagination.py @@ -109,11 +109,11 @@ def test_list_jobs_filtered_by_job_type(self): def test_list_jobs_filtered_by_provider(self): ws = self.create_workspace() - jobs = ws.list_jobs(provider = ["microsoft-qc", "ionq"]) + jobs = ws.list_jobs(provider = ["ionq"]) for job in jobs: self.assertEqual(job.item_type, "Job") - check_job_provider = job.details.provider_id == "microsoft-qc" or job.details.provider_id == "ionq" + check_job_provider = job.details.provider_id == "ionq" self.assertTrue( check_job_provider, job.details.provider_id) @pytest.mark.live_test @@ -215,11 +215,11 @@ def test_list_sessions(self): def test_list_sessions_filtered_by_provider(self): ws = self.create_workspace() - sessions = ws.list_sessions(provider = ["microsoft-qc", "ionq"]) + sessions = ws.list_sessions(provider = ["ionq"]) for session in sessions: self.assertEqual(session.item_type, "Session") - check_session_provider = session._details.provider_id == "microsoft-qc" or session._details.provider_id == "ionq" + check_session_provider = session._details.provider_id == "ionq" self.assertTrue( check_session_provider, session._details.provider_id) @pytest.mark.live_test @@ -489,13 +489,13 @@ def test_list_top_level_items_filtered_by_provider(self): resource_group = ws.resource_group workspace_name = ws.name - items = ws.list_top_level_items(provider = ["microsoft-qc", "ionq"]) + items = ws.list_top_level_items(provider = ["ionq"]) for item in items: self.assertEqual(item.workspace.subscription_id, subscription_id) self.assertEqual(item.workspace.resource_group, resource_group) self.assertEqual(item.workspace.name, workspace_name) - check_item_provider = item.details.provider_id == "microsoft-qc" or item.details.provider_id == "ionq" + check_item_provider = item.details.provider_id == "ionq" self.assertTrue( check_item_provider, item.details.provider_id) @pytest.mark.live_test From 503c5407373095b51427916a05c5339bd66b7550 Mon Sep 17 00:00:00 2001 From: Yousif Almulla Date: Fri, 28 Mar 2025 12:43:30 -0700 Subject: [PATCH 4/4] Restore `params.py` --- azure-quantum/azure/quantum/target/params.py | 253 +++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 azure-quantum/azure/quantum/target/params.py diff --git a/azure-quantum/azure/quantum/target/params.py b/azure-quantum/azure/quantum/target/params.py new file mode 100644 index 000000000..5ff4a928e --- /dev/null +++ b/azure-quantum/azure/quantum/target/params.py @@ -0,0 +1,253 @@ +## +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +## +from dataclasses import dataclass, field +from re import sub +from typing import Any, Dict, List, Optional, Tuple, Type, Callable +from ..argument_types import EmptyArray, Pauli, Range, Result + + +__all__ = ["InputParams"] + + +class EntryPointArguments: + """ + Wrapper class to set QIR entry point arguments. + + This class is used to set QIR entry point arguments inside the + InputParamsItem class. It overrides the __setitem__ method to + automatically detect the entry point's argument from the passed value. + """ + + # Maps supported Python types to QIR entry point type names and a callable + # that extracts the serialized value from the original value. (list (= + # Array) is handled as a special case.) + type_map: Dict[type, Tuple[str, Callable]] = { + int: ("Int", lambda v: v), + float: ("Double", lambda v: v), + bool: ("Boolean", lambda v: v), + str: ("String", lambda v: v), + Pauli: ("Pauli", lambda v: v.value), + Result: ("Result", lambda v: v.value), + Range: ("Range", lambda v: v.value) + } + + def __init__(self): + self.entries = [] + + def __setitem__(self, name: str, value: Any): + """ + Creates entry point argument entry and automatically determines the + type from value. + """ + if type(value) == list: + if len(value) == 0: + raise ValueError("Use EmptyArray(type) to assign an empty " + "error") + else: + first = value[0] + first_value, first_type = self._extract_value_and_type(first) + values = [first_value] + for next in value[1:]: + next_value, next_type = self._extract_value_and_type(next) + if next_type != first_type: + raise TypeError("All elements in a list must have " + "the same type") + values.append(next_value) + self.entries.append( + {"name": name, + "value": values, + "type": "Array", + "elementType": first_type}) + elif type(value) == EmptyArray: + element_type = self._extract_type(value.element_type) + self.entries.append( + {"name": name, + "value": [], + "type": "Array", + "elementType": element_type}) + else: + entry_value, entry_type = self._extract_value_and_type(value) + self.entries.append( + {"name": name, "value": entry_value, "type": entry_type}) + + def _extract_type(self, type: Type) -> str: + """ + Convert Python type to QIR entry point argument type name. + """ + if type in self.type_map: + return self.type_map[type][0] + elif type == list: + raise TypeError(f"Nested lists are not supported") + else: + type_name = type.__name__ + raise TypeError(f"Unsupported type {type_name}") + + def _extract_value_and_type(self, value: Any) -> Tuple[Any, str]: + """ + Convert Python value to QIR entry point argument type name and + serialized value. + """ + if type(value) in self.type_map: + entry_type, entry_value_func = self.type_map[type(value)] + return entry_value_func(value), entry_type + elif type(value) == list: + raise TypeError(f"Nested lists are not supported") + else: + type_name = type(value).__name__ + raise TypeError(f"Unsupported type {type_name} for {value}") + + +@dataclass +class AutoValidatingParams: + """ + A helper class for target parameters. + + It has a function as_dict that automatically extracts a dictionary from + the class' fields. They are added to the result dictionary if their value + is not None, the key is automatically transformed from Python snake case + to camel case, and if validate is True and if the field has a validation + function, the field is validated beforehand. + """ + def as_dict(self, validate=True): + result = {} + + for name, field in self.__dataclass_fields__.items(): + field_value = self.__getattribute__(name) + if field_value is not None: + # validate field? + if validate and "validate" in field.metadata: + func = field.metadata["validate"] + # check for indirect call (like in @staticmethod) + if hasattr(func, "__func__"): + func = func.__func__ + func(name, field_value) + + # translate field name to camel case + s = sub(r"(_|-)+", " ", name).title().replace(" ", "") + attribute = ''.join([s[0].lower(), s[1:]]) + result[attribute] = field_value + + if validate: + self.post_validation(result) + + return result + + def post_validation(self, result): + """ + A function that is called after all individual fields have been + validated, but before the result is returned. + + Here result is the current dictionary. + """ + pass + + +def validating_field(validation_func, default=None): + """ + A helper method to declare field for an AutoValidatingParams data class. + """ + return field(default=default, metadata={"validate": validation_func}) + + +class InputParamsItem: + """ + Base class for input parameters. + + This class serves both as the base class for InputParams as well as for the + items in the InputParams. + """ + def __init__(self): + # all input param items may have an entry point name and a list of + # arguments + self.entry_point: Optional[str] = None + self.arguments = EntryPointArguments() + + def as_dict(self, validate=True) -> Dict[str, Any]: + """ + Returns input params as a dictionary. + """ + result = {} + + if self.entry_point is not None: + result['entryPoint'] = self.entry_point + + if len(self.arguments.entries) > 0: + result['arguments'] = self.arguments.entries + + return result + + +class InputParams(InputParamsItem): + """ + Class to define input parameters. + + This class allows to define input parameters for non-batching and batching + jobs. The instance represents a batching job, if and only if num_items is + set to some positive number less or equal to MAX_NUM_ITEMS. + + Both this class and the items in this class are based on InputParamsItem as + a template, which can be overriden for specializations created by a target. + This class should never be constructed directly but only through the + InputParams.make_params method. + """ + + MAX_NUM_ITEMS: int = 1000 + + def __init__( + self, + num_items: Optional[int] = None, + item_type: Type[InputParamsItem] = InputParamsItem): + """ + Constructs a InputParams instance. + + The item_type argument should be set by targets that override + InputParams and have a specialized InputParamsItem class. + """ + item_type.__init__(self) + + # fileURIs + self.file_uris = {} + + if num_items is not None: + self.has_items = True + if num_items <= 0 or num_items > self.MAX_NUM_ITEMS: + raise ValueError( + "num_items must be a positive value less or equal to " + f"{self.MAX_NUM_ITEMS}") + self._items = [item_type() for _ in range(num_items)] + else: + self.has_items = False + + self.item_type = item_type + + @property + def items(self) -> List: + if self.has_items: + return self._items + else: + raise Exception("Cannot access items in a non-batching job, call " + "make_params with num_items parameter") + + def as_dict(self, validate=True) -> Dict[str, Any]: + """ + Constructs a dictionary from the input params. + + For batching jobs, top-level entries are merged into item entries. + Item entries have priority in case they are specified. + """ + + # initialize result and set type hint + result: Dict[str, Any] = self.item_type.as_dict(self, validate) + + if self.has_items: + result["items"] = [item.as_dict(validate) for item in self._items] + # In case of batching, no need to stop if failing an item + result["resumeAfterFailedItem"] = True + + # add fileUris if existing + if len(self.file_uris) > 0: + result["fileUris"] = self.file_uris + + return result \ No newline at end of file