diff --git a/general-superstaq/general_superstaq/models.py b/general-superstaq/general_superstaq/models.py index 519d7af04..0a44ae275 100644 --- a/general-superstaq/general_superstaq/models.py +++ b/general-superstaq/general_superstaq/models.py @@ -366,9 +366,33 @@ class TargetStatus(str, Enum): """Target does not support job submission through Superstaq.""" -class TargetModel(DefaultPydanticModel): +class TargetInputType(str, Enum): + """The input type supported by a Superstaq target.""" + + CIRCUIT = "circuit" + QUBO = "qubo" + + +class TargetDescription(DefaultPydanticModel): """Model for the details of a target.""" + target: TargetStr + """The target name.""" + status: TargetStatus + """The status of this target.""" + supported_inputs: list[TargetInputType] + """The input types supported by this target (e.g. "circuit", "qubo").""" + accessible: bool = False + """Whether this target is accessible to the current user.""" + + @property + def simulator(self) -> bool: + return self.target.endswith("_simulator") + + +class TargetModel(DefaultPydanticModel): + """Legacy model for the details of a target.""" + target_name: TargetStr """The target name.""" supports_submit: bool @@ -386,6 +410,19 @@ class TargetModel(DefaultPydanticModel): accessible: bool """Target is accessible to user.""" + @classmethod + def from_target_description(cls, target_description: TargetDescription) -> TargetModel: + return cls( + target_name=target_description.target, + supports_submit=(target_description.status != TargetStatus.UNSUPPORTED), + supports_submit_qubo=(TargetInputType.QUBO in target_description.supported_inputs), + supports_compile=(TargetInputType.CIRCUIT in target_description.supported_inputs), + available=(target_description.status == TargetStatus.AVAILABLE), + retired=(target_description.status == TargetStatus.RETIRED), + simulator=target_description.simulator, + accessible=target_description.accessible, + ) + class GetTargetsFilterModel(DefaultPydanticModel): """Model for /get_target requests.""" diff --git a/general-superstaq/general_superstaq/models_test.py b/general-superstaq/general_superstaq/models_test.py index 0a03e6601..ecad4d21b 100644 --- a/general-superstaq/general_superstaq/models_test.py +++ b/general-superstaq/general_superstaq/models_test.py @@ -11,6 +11,65 @@ """ +def test_target_description() -> None: + target_description = gss.models.TargetDescription( + target="sqale_example_qpu", + status=gss.models.TargetStatus.RETIRED, + supported_inputs=[gss.models.TargetInputType.CIRCUIT], + ) + assert not target_description.accessible + assert not target_description.simulator + + target_model = gss.models.TargetModel.from_target_description(target_description) + assert target_model == gss.models.TargetModel( + target_name="sqale_example_qpu", + supports_submit=True, + supports_submit_qubo=False, + supports_compile=True, + available=False, + retired=True, + simulator=False, + accessible=False, + ) + + target_description = gss.models.TargetDescription( + target="ss_unconstrained_simulator", + status="available", + supported_inputs=("qubo", "circuit"), + accessible=True, + ) + assert target_description.accessible + assert target_description.simulator + + target_model = gss.models.TargetModel.from_target_description(target_description) + assert target_model == gss.models.TargetModel( + target_name="ss_unconstrained_simulator", + supports_submit=True, + supports_submit_qubo=True, + supports_compile=True, + available=True, + retired=False, + simulator=True, + accessible=True, + ) + + with pytest.raises(pydantic.ValidationError, match=r"valid target device type"): + _ = gss.models.TargetDescription( + target="sqale_example_system", + status="available", + supported_inputs=["circuit"], + accessible=True, + ) + + with pytest.raises(pydantic.ValidationError, match=r"valid string format"): + _ = gss.models.TargetDescription( + target="bad_target", + status="available", + supported_inputs=["circuit"], + accessible=True, + ) + + def test_user_token_response() -> None: gss.models.UserTokenResponse( email="valid.email@infleqtion.com",