class GemTableDataSource(Serializable['GemTableDataSource'], DataSource): """[ALPHA] A data source based on a GEM Table hosted on the data platform. Parameters ---------- table_id: UUID Unique identifier for the GEM Table table_version: Union[str,int] Version number for the GEM Table, which starts at 1 rather than 0. Strings are cast to ints. formulation_descriptor: Optional[FormulationDescriptor] Optional descriptor used to store formulations emitted by the data source. """ typ = properties.String('type', default='hosted_table_data_source', deserializable=False) table_id = properties.UUID("table_id") table_version = properties.Integer("table_version") formulation_descriptor = properties.Optional( properties.Object(FormulationDescriptor), "formulation_descriptor") def _attrs(self) -> List[str]: return ["table_id", "table_version", "typ"] def __init__( self, table_id: UUID, table_version: Union[int, str], formulation_descriptor: Optional[FormulationDescriptor] = None): self.table_id: UUID = table_id self.table_version: Union[int, str] = table_version self.formulation_descriptor: Optional[ FormulationDescriptor] = formulation_descriptor
class NthBiggestComponentQuantityColumn( Serializable["NthBiggestComponentQuantityColumn"], Column): """[ALPHA] Quantity of the Nth biggest component. If there are fewer than N components in the composition, then this column will be empty. Parameters ---------- data_source: str name of the variable to use when populating the column n: int index of the component quantity to extract, starting with 1 for the biggest normalize: bool whether to normalize the quantity by the sum of all component amounts. Default is false """ data_source = properties.String('data_source') n = properties.Integer("n") normalize = properties.Boolean("normalize") typ = properties.String('type', default="biggest_component_quantity_column", deserializable=False) def _attrs(self) -> List[str]: return ["data_source", "n", "normalize", "typ"] def __init__(self, *, data_source: str, n: int, normalize: bool = False): self.data_source = data_source self.n = n self.normalize = normalize
class NthBiggestComponentNameColumn( Serializable["NthBiggestComponentNameColumn"], Column): """[ALPHA] Name of the Nth biggest component. If there are fewer than N components in the composition, then this column will be empty. Parameters ---------- data_source: str name of the variable to use when populating the column n: int index of the component name to extract, starting with 1 for the biggest """ data_source = properties.String('data_source') n = properties.Integer("n") typ = properties.String('type', default="biggest_component_name_column", deserializable=False) def _attrs(self) -> List[str]: return ["data_source", "n", "typ"] def __init__(self, *, data_source: str, n: int): self.data_source = data_source self.n = n
class PredictedVsActualRealPoint(Serializable["PredictedVsActualRealPoint"]): """Predicted vs. actual data for a single real-valued data point.""" uuid = properties.UUID("uuid") """:UUID: Unique Citrine id given to the candidate""" identifiers = properties.Set(properties.String, "identifiers") """:Set[str]: Set of globally unique identifiers given to the candidate""" trial = properties.Integer("trial") """:int: 1-based index of the trial this candidate belonged to""" fold = properties.Integer("fold") """:int: 1-based index of the fold this candidate belonged to""" predicted = properties.Object(RealMetricValue, "predicted") """:RealMetricValue: Predicted value""" actual = properties.Object(RealMetricValue, "actual") """:RealMetricValue: Actual value""" def __init__(self): pass # pragma: no cover
class WorkflowExecution(Resource['WorkflowExecution']): """[ALPHA] A Citrine Workflow Execution. Parameters ---------- uid: str Unique identifier of the workflow execution project_id: str Unique identifier of the project that contains the workflow execution workflow_id: str Unique identifier of the workflow that was executed version_number: int Integer identifier that increases each time the workflow is executed. The first execution has version_number = 1. """ _response_key = 'WorkflowExecutions' uid = properties.UUID('id') project_id = properties.UUID('project_id', deserializable=False) workflow_id = properties.UUID('workflow_id', deserializable=False) version_number = properties.Integer("version_number") def __init__( self, uid: Optional[str] = None, project_id: Optional[str] = None, workflow_id: Optional[str] = None, session: Optional[Session] = None, version_number: Optional[int] = None, ): self.uid: str = uid self.project_id: str = project_id self.workflow_id: str = workflow_id self.session: Session = session self.version_number = version_number def __str__(self): return '<WorkflowExecution {!r}>'.format(str(self.uid)) def _path(self): return '/projects/{project_id}/workflows/{workflow_id}/executions/{execution_id}'.format( **{ "project_id": self.project_id, "workflow_id": self.workflow_id, "execution_id": self.uid }) def status(self): """Get the current status of this execution.""" response = self.session.get_resource(self._path() + "/status") return WorkflowExecutionStatus.build(response) def results(self): """Get the results of this execution.""" return self.session.get_resource(self._path() + "/results")
class DummyDescriptor(object): dummy_map = properties.Mapping(properties.Float(), properties.String) dummy_list = properties.List(properties.Float, properties.String) dummy_set = properties.Set(type(properties.Float())) link_or_else = properties.LinkOrElse() map_collection_key = properties.Mapping( properties.Optional(properties.String), properties.Integer) specified_mixed_list = properties.SpecifiedMixedList( [properties.Integer(default=100)])
class PredictedVsActualCategoricalPoint( Serializable["PredictedVsActualCategoricalPoint"]): """Predicted vs. actual data for a single categorical data point.""" uuid = properties.UUID("uuid") """:UUID: Unique Citrine id given to the candidate""" identifiers = properties.Set(properties.String, "identifiers") """:Set[str]: Set of globally unique identifiers given to the candidate""" trial = properties.Integer("trial") """:int: 1-based index of the trial this candidate belonged to""" fold = properties.Integer("fold") """:int: 1-based index of the fold this candidate belonged to""" predicted = properties.Mapping(properties.String, properties.Float, "predicted") """:Dict[str, float]: Predicted class probabilities defined as a map from each class name to its relative frequency""" actual = properties.Mapping(properties.String, properties.Float, "actual") """:Dict[str, float]: Actual class probabilities defined as a map from each class name to its relative frequency""" def __init__(self): pass # pragma: no cover
class IngredientCountConstraint(Serializable['IngredientCountConstraint'], Constraint): """Represents a constraint on the total number of ingredients in a formulation. Parameters ---------- formulation_descriptor: FormulationDescriptor descriptor to constrain min: int minimum ingredient count max: int maximum ingredient count label: Optional[str] Optional label to constrain. If specified, then only ingredients with the specified label will count towards the total. Default is ``None``; all ingredients count towards the total """ formulation_descriptor = properties.Object(FormulationDescriptor, 'formulation_descriptor') min = properties.Integer('min') max = properties.Integer('max') label = properties.Optional(properties.String, 'label') typ = properties.String('type', default='IngredientCountConstraint') def __init__(self, *, formulation_descriptor: FormulationDescriptor, min: int, max: int, label: Optional[str] = None, session: Optional[Session] = None): self.formulation_descriptor: FormulationDescriptor = formulation_descriptor self.min: int = min self.max: int = max self.label: Optional[str] = label self.session: Optional[Session] = session def __str__(self): return '<IngredientCountConstraint {!r}>'.format(self.formulation_descriptor.key)
class EnumeratedProcessor(Serializable['EnumeratedProcessor'], Processor): """Process a design space by enumerating up to `max_size` materials from the domain and processing each independently. Parameters ---------- name: str name of the processor description: str description of the processor max_size: int maximum number of samples that can be enumerated over """ uid = properties.Optional(properties.UUID, 'id', serializable=False) name = properties.String('config.name') description = properties.Optional(properties.String(), 'config.description') max_size = properties.Integer('config.max_size') typ = properties.String('config.type', default='Enumerated', deserializable=False) status = properties.String('status', serializable=False) status_info = properties.Optional(properties.List(properties.String()), 'status_info', serializable=False) # NOTE: These could go here or in _post_dump - it's unclear which is better right now module_type = properties.String('module_type', default='PROCESSOR') schema_id = properties.UUID( 'schema_id', default=UUID('307b88a2-fd50-4d27-ae91-b8d6282f68f7')) def __init__(self, name: str, description: str, max_size: Optional[int] = None, session: Optional[Session] = None): self.name: str = name self.description: str = description self.max_size: int = max_size or 2**31 - 1 # = 2147483647 (max 32-bit integer) self.session: Optional[Session] = session def _post_dump(self, data: dict) -> dict: data['display_name'] = data['config']['name'] return data def __str__(self): return '<EnumeratedProcessor {!r}>'.format(self.name)
class GemTableDataSource(Serializable['GemTableDataSource'], DataSource): """A data source based on a GEM Table hosted on the data platform. Parameters ---------- table_id: UUID Unique identifier for the GEM Table table_version: Union[str,int] Version number for the GEM Table. The first GEM table built from a configuration has version = 1. Strings are cast to ints. formulation_descriptor: Optional[FormulationDescriptor] Optional descriptor used to store formulations emitted by the data source. If the data source emits a formulation but this argument is not provided, then a default formulation descriptor will be generated. The formulations descriptor, and other descriptors, can be retrieved using :func:`~citrine.resources.descriptors.DescriptorMethods.descriptors_from_data_source`. """ typ = properties.String('type', default='hosted_table_data_source', deserializable=False) table_id = properties.UUID("table_id") table_version = properties.Integer("table_version") formulation_descriptor = properties.Optional( properties.Object(FormulationDescriptor), "formulation_descriptor" ) def _attrs(self) -> List[str]: return ["table_id", "table_version", "typ"] def __init__(self, table_id: UUID, table_version: Union[int, str], formulation_descriptor: Optional[FormulationDescriptor] = None): self.table_id: UUID = table_id self.table_version: Union[int, str] = table_version self.formulation_descriptor: Optional[FormulationDescriptor] = formulation_descriptor
class CrossValidationEvaluator(Serializable["CrossValidationEvaluator"], PredictorEvaluator): """Evaluate a predictor via cross validation. Performs cross-validation on requested predictor responses and computes the requested metrics on each response. For a discussion of how many folds and trials to use, please see the :ref:`documentation<Cross-validation evaluator>`. In addition to a name, set of responses to validate, trials, folds and metrics to compute, this evaluator defines a set of descriptor keys to ignore when grouping. Candidates with different values for ignored keys and identical values for all other predictor inputs will be placed in the same fold. For example, if you are baking cakes with different ingredients and different oven temperatures and want to group together the data by the ingredients, then you can set `ignore_when_grouping={"oven temperature"}`. That way, two recipes that differ only in their oven temperature will always end up in the same fold. Parameters ---------- name: str Name of the evaluator description: str Description of the evaluator responses: Set[str] Set of descriptor keys to evaluate n_folds: int Number of cross-validation folds n_trials: int Number of cross-validation trials, each contains ``n_folds`` folds metrics: Optional[Set[PredictorEvaluationMetric]] Optional set of metrics to compute for each response. Default is all metrics. ignore_when_grouping: Optional[Set[str]] Set of descriptor keys to group together. Candidates with different values for the given keys and identical values for all other descriptors will be in the same group. """ def _attrs(self) -> List[str]: return ["typ", "name", "description", "responses", "n_folds", "n_trials", "metrics", "ignore_when_grouping"] name = properties.String("name") description = properties.String("description") _responses = properties.Set(properties.String, "responses") n_folds = properties.Integer("n_folds") n_trials = properties.Integer("n_trials") _metrics = properties.Optional(properties.Set(properties.Object(PredictorEvaluationMetric)), "metrics") ignore_when_grouping = properties.Optional(properties.Set(properties.String), "ignore_when_grouping") typ = properties.String("type", default="CrossValidationEvaluator", deserializable=False) def __init__(self, *, name: str, description: str = "", responses: Set[str], n_folds: int = 5, n_trials: int = 3, metrics: Optional[Set[PredictorEvaluationMetric]] = None, ignore_when_grouping: Optional[Set[str]] = None): self.name: str = name self.description: str = description self._responses: Set[str] = responses self._metrics: Optional[Set[PredictorEvaluationMetric]] = metrics self.n_folds: int = n_folds self.n_trials: int = n_trials self.ignore_when_grouping: Optional[Set[str]] = ignore_when_grouping @property def responses(self) -> Set[str]: """Set of predictor responses cross-validated by the evaluator.""" return self._responses @property def metrics(self) -> Set[PredictorEvaluationMetric]: """Set of metrics computed during cross-validation.""" return self._metrics
class WorkflowExecution(Resource['WorkflowExecution'], Pageable): """[DEPRECATED] A Citrine Workflow Execution. Parameters ---------- uid: str Unique identifier of the workflow execution project_id: str Unique identifier of the project that contains the workflow execution workflow_id: str Unique identifier of the workflow that was executed version_number: int Integer identifier that increases each time the workflow is executed. The first execution has version_number = 1. """ _response_key = 'WorkflowExecutions' _paginator: Paginator = Paginator() _collection_key = 'response' uid = properties.UUID('id') project_id = properties.UUID('project_id', deserializable=False) workflow_id = properties.UUID('workflow_id', deserializable=False) version_number = properties.Integer("version_number") def __init__( self, uid: Optional[str] = None, project_id: Optional[str] = None, workflow_id: Optional[str] = None, session: Optional[Session] = None, version_number: Optional[int] = None, ): msg = "{this_class} is deprecated. Please use {dw_replacement} instead for " \ "Design Workflows and {pew_replacement} for Predictor Evaluation Workflows".format( this_class=self.__class__.__name__, dw_replacement=DesignExecution.__name__, pew_replacement=PredictorEvaluationExecution.__name__) warn(msg, category=DeprecationWarning) self.uid: str = uid self.project_id: str = project_id self.workflow_id: str = workflow_id self.session: Session = session self.version_number = version_number def __str__(self): return '<WorkflowExecution {!r}>'.format(str(self.uid)) def _path(self): return '/projects/{project_id}/workflows/{workflow_id}/executions/{execution_id}'.format( **{ "project_id": self.project_id, "workflow_id": self.workflow_id, "execution_id": self.uid }) def status(self): """Get the current status of this execution.""" response = self.session.get_resource(self._path() + "/status") return WorkflowExecutionStatus.build(response) def results(self): """Get the results of this execution.""" return self.session.get_resource(self._path() + "/results") @classmethod def _build_candidates( cls, subset_collection: Iterable[dict]) -> Iterable[DesignCandidate]: for candidate in subset_collection: yield DesignCandidate.build(candidate) def candidates( self, page: Optional[int] = None, per_page: int = 100, ) -> Iterable[DesignCandidate]: """Fetch the Design Candidates for the particular execution, paginated. Gets candidates from the new candidates API for a workflow executed by the old api. New candidates are paginated and have structured types. """ path = '/projects/{p_id}/design-workflows/{w_id}/executions/{e_id}/candidates'.format( p_id=self.project_id, w_id=self.workflow_id, e_id=self.uid) fetcher = partial(self._fetch_page, path=path) return self._paginator.paginate( page_fetcher=fetcher, collection_builder=self._build_candidates, page=page, per_page=per_page)
class MeanPropertyPredictor(Resource['MeanPropertyPredictor'], Predictor, AIResourceMetadata): """A predictor interface that computes mean component properties. .. seealso:: If you are using a deprecated generalized mean property predictor please see :class:`~citrine.informatics.predictors.generalized_mean_property_predictor.GeneralizedMeanPropertyPredictor` for details on how to migrate to the new format. Parameters ---------- name: str name of the configuration description: str description of the predictor input_descriptor: FormulationDescriptor descriptor that represents the input formulation properties: List[RealDescriptor] List of descriptors to featurize p: int Power of the `generalized mean <https://en.wikipedia.org/wiki/Generalized_mean>`_. Only integer powers are supported. impute_properties: bool Whether to impute missing ingredient properties. If ``False`` all ingredients must define values for all featurized properties. Otherwise, the row will not be featurized. If ``True`` and no ``default_properties`` are specified, then the average over the entire dataset is used. If ``True`` and a default is specified in ``default_properties``, then the specified default is used in place of missing values. label: Optional[str] Optional label training_data: Optional[List[DataSource]] Sources of training data. Each can be either a CSV or an GEM Table. Candidates from multiple data sources will be combined into a flattened list and de-duplicated by uid and identifiers. De-duplication is performed if a uid or identifier is shared between two or more rows. The content of a de-duplicated row will contain the union of data across all rows that share the same uid or at least 1 identifier. Training data is unnecessary if the predictor is part of a graph that includes all training data required by this predictor. default_properties: Optional[Mapping[str, float]] Default values to use for imputed properties. Defaults are specified as a map from descriptor key to its default value. If not specified and ``impute_properties == True`` the average over the entire dataset will be used to fill in missing values. Any specified defaults will be used in place of the average over the dataset. ``impute_properties`` must be ``True`` if ``default_properties`` are provided. """ _resource_type = ResourceTypeEnum.MODULE input_descriptor = _properties.Object(FormulationDescriptor, 'config.input') properties = _properties.List(_properties.Object(RealDescriptor), 'config.properties') p = _properties.Integer('config.p') training_data = _properties.List(_properties.Object(DataSource), 'config.training_data') impute_properties = _properties.Boolean('config.impute_properties') default_properties = _properties.Optional( _properties.Mapping(_properties.String, _properties.Float), 'config.default_properties') label = _properties.Optional(_properties.String, 'config.label') typ = _properties.String('config.type', default='MeanProperty', deserializable=False) module_type = _properties.String('module_type', default='PREDICTOR') def __init__(self, name: str, description: str, input_descriptor: FormulationDescriptor, properties: List[RealDescriptor], p: int, impute_properties: bool, default_properties: Optional[Mapping[str, float]] = None, label: Optional[str] = None, training_data: Optional[List[DataSource]] = None, archived: bool = False): self.name: str = name self.description: str = description self.input_descriptor: FormulationDescriptor = input_descriptor self.properties: List[RealDescriptor] = properties self.p: int = p self.training_data: List[DataSource] = self._wrap_training_data( training_data) self.impute_properties: bool = impute_properties self.default_properties: Optional[Mapping[str, float]] = default_properties self.label: Optional[str] = label self.archived: bool = archived def _post_dump(self, data: dict) -> dict: data['display_name'] = data['config']['name'] return data def __str__(self): return '<MeanPropertyPredictor {!r}>'.format(self.name)
class EnumeratedProcessor(Resource['EnumeratedProcessor'], Processor, AIResourceMetadata): """Process a design space by enumerating up to a fixed number of samples from the domain. Each sample is processed independently. Parameters ---------- name: str name of the processor description: str description of the processor max_candidates: int maximum number of samples that can be enumerated over (default: 1000) """ _resource_type = ResourceTypeEnum.MODULE max_candidates = properties.Integer('config.max_size') typ = properties.String('config.type', default='Enumerated', deserializable=False) module_type = properties.String('module_type', default='PROCESSOR') def _attrs(self) -> List[str]: return ["name", "description", "max_size", "typ"] def __init__(self, name: str, description: str, max_candidates: Optional[int] = None, max_size: Optional[int] = None, session: Optional[Session] = None): if max_candidates is not None and max_size is not None: raise ValueError( "Both max_candidates and max_size were specified. " "Please only specify max_candidates.") if max_size is not None: warn( "The max_size argument is deprecated. Please use max_candidates instead.", DeprecationWarning) self.name: str = name self.description: str = description self.max_candidates: int = max_candidates or max_size or 1000 self.session: Optional[Session] = session def _post_dump(self, data: dict) -> dict: data['display_name'] = data['config']['name'] return data def __str__(self): return '<EnumeratedProcessor {!r}>'.format(self.name) @property def max_size(self): """[DEPRECATED] Alias for max_candidates.""" warn( "EnumeratedProcessor.max_size is deprecated. Please use max_candidates instead", DeprecationWarning) return self.max_candidates
class CrossValidationAnalysisConfiguration( Serializable['CrossValidationAnalysisConfiguration']): """[DEPRECATED] Configuration settings for running cross-validation in a performance workflow. Parameters ---------- name : str Name of the analysis configuration description: str Description of the analysis configuration n_folds: int Number of folds n_trials: int Number of cross-validation trials to run, each with ``n_folds`` folds max_rows: int Maximum number of training candidates to use during cross-validation seed: int, optional Seed used to generate random test/train splits. If not provided, a random seed is used. group_by_keys: List[str], optional Set of keys used to group candidates. If present, candidates are grouped by the hash of ``(key, value)`` pairs computed on the given keys. If not provided, candidates are not grouped. responses: List[str], optional Set of descriptor keys to cross-validate. All requested responses must be present as an output of the predictor being analyzed. If not provided cross-validation metrics will be computed for all predictor responses. These cross-validated responses are removed from the data during the analysis, so which responses are requested can affect the performance metrics if the predictor contains latent variables. For example, if only the final output (leaf) responses are requested, latent variables are not removed during cross-validation. In this case the actual (and not predicted) values for latent variables are fed into the models used to compute leaf responses. Often this will manifest as a lower model error for the final response, compared to the model error computed when latent variables are requested and hence removed from the data. Note, if no responses are specified all leaf and latent variables are removed from the data during cross-validation. """ name = properties.String('name') description = properties.String('description') n_folds = properties.Integer('n_folds') n_trials = properties.Integer('n_trials') seed = properties.Optional(properties.Integer, 'seed') group_by_keys = properties.Optional(properties.List(properties.String), 'group_by_keys') responses = properties.Optional(properties.List(properties.String), 'responses') max_rows = properties.Integer('max_rows') typ = properties.String('type', default='CrossValidationAnalysis', deserializable=False) def __init__( self, name: str, description: str, n_folds: int, n_trials: int, max_rows: int, seed: Optional[int] = None, group_by_keys: Optional[List[str]] = None, responses: Optional[List[str]] = None, ): warn("{this_class} is deprecated. Please use {replacement} instead". format(this_class=self.__class__.name, replacement=CrossValidationEvaluator.__name__)) self.name = name self.description = description self.n_folds = n_folds self.n_trials = n_trials self.seed = seed self.group_by_keys = group_by_keys self.max_rows = max_rows self.responses = responses
class DesignExecution(Resource['DesignExecution'], Pageable, AsynchronousObject): """The execution of a DesignWorkflow. Possible statuses are INPROGRESS, SUCCEEDED, and FAILED. Design executions also have a ``status_description`` field with more information. Parameters ---------- project_id: str Unique identifier of the project that contains the workflow execution """ _paginator: Paginator = Paginator() _collection_key = 'response' uid: UUID = properties.UUID('id', serializable=False) """:UUID: Unique identifier of the workflow execution""" workflow_id = properties.UUID('workflow_id', serializable=False) """:UUID: Unique identifier of the workflow that was executed""" version_number = properties.Integer("version_number", serializable=False) """:int: Integer identifier that increases each time the workflow is executed. The first execution has version_number = 1.""" status = properties.Optional(properties.String(), 'status', serializable=False) """:Optional[str]: short description of the execution's status""" status_description = properties.Optional(properties.String(), 'status_description', serializable=False) """:Optional[str]: more detailed description of the execution's status""" status_info = properties.Optional(properties.List(properties.String()), 'status_info', serializable=False) """:Optional[List[str]]: human-readable explanations of the status""" experimental = properties.Boolean("experimental", serializable=False, default=True) """:bool: whether the execution is experimental (newer, less well-tested functionality)""" experimental_reasons = properties.Optional(properties.List( properties.String()), 'experimental_reasons', serializable=False) """:Optional[List[str]]: human-readable reasons why the execution is experimental""" created_by = properties.Optional(properties.UUID, 'created_by', serializable=False) """:Optional[UUID]: id of the user who created the resource""" updated_by = properties.Optional(properties.UUID, 'updated_by', serializable=False) """:Optional[UUID]: id of the user who most recently updated the resource, if it has been updated""" create_time = properties.Optional(properties.Datetime, 'create_time', serializable=False) """:Optional[datetime]: date and time at which the resource was created""" update_time = properties.Optional(properties.Datetime, 'update_time', serializable=False) """:Optional[datetime]: date and time at which the resource was most recently updated, if it has been updated""" score = properties.Object(Score, 'score') """:Score: score by which this execution was evaluated""" descriptors = properties.List(properties.Object(Descriptor), 'descriptors') """:List[Descriptor]: all of the descriptors in the candidates generated by this execution""" def __init__(self): """This shouldn't be called, but it defines members that are set elsewhere.""" self.project_id: Optional[UUID] = None # pragma: no cover self.session: Optional[Session] = None # pragma: no cover def __str__(self): return '<DesignExecution {!r}>'.format(str(self.uid)) def _path(self): return '/projects/{project_id}/design-workflows/{workflow_id}/executions/{execution_id}' \ .format(project_id=self.project_id, workflow_id=self.workflow_id, execution_id=self.uid) def in_progress(self) -> bool: """Whether design execution is in progress. Does not query state.""" return self.status == "INPROGRESS" def succeeded(self) -> bool: """Whether design execution has completed successfully. Does not query state.""" return self.status == "SUCCEEDED" def failed(self) -> bool: """Whether design execution has completed unsuccessfully. Does not query state.""" return self.status == "FAILED" @classmethod def _build_candidates( cls, subset_collection: Iterable[dict]) -> Iterable[DesignCandidate]: for candidate in subset_collection: yield DesignCandidate.build(candidate) def candidates( self, page: Optional[int] = None, per_page: int = 100, ) -> Iterable[DesignCandidate]: """Fetch the Design Candidates for the particular execution, paginated.""" path = self._path() + '/candidates' fetcher = partial(self._fetch_page, path=path) return self._paginator.paginate( page_fetcher=fetcher, collection_builder=self._build_candidates, page=page, per_page=per_page)